Add Chromium-only Blender WebEngine parity work
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* MATCHING GPUTextureType. */
|
||||
#define GPU_TEXTURE_1D (1 << 0)
|
||||
#define GPU_TEXTURE_2D (1 << 1)
|
||||
#define GPU_TEXTURE_3D (1 << 2)
|
||||
#define GPU_TEXTURE_CUBE (1 << 3)
|
||||
#define GPU_TEXTURE_ARRAY (1 << 4)
|
||||
#define GPU_TEXTURE_BUFFER (1 << 5)
|
||||
#define GPU_TEXTURE_1D_ARRAY (GPU_TEXTURE_1D | GPU_TEXTURE_ARRAY)
|
||||
#define GPU_TEXTURE_2D_ARRAY (GPU_TEXTURE_2D | GPU_TEXTURE_ARRAY)
|
||||
#define GPU_TEXTURE_CUBE_ARRAY (GPU_TEXTURE_CUBE | GPU_TEXTURE_ARRAY)
|
||||
|
||||
/* Determine input texture type. */
|
||||
#if IS_DEPTH_FORMAT == 1
|
||||
# define TEX_NAME_BASE depth
|
||||
#else
|
||||
# define TEX_NAME_BASE texture
|
||||
#endif
|
||||
|
||||
#define JOIN(x, y) x##y
|
||||
#define FUNC_NAME(x, y) JOIN(x, y)
|
||||
|
||||
/* Assign parameters based on texture type. */
|
||||
#if TEX_TYPE == GPU_TEXTURE_1D
|
||||
# define TEX_TYPE_NAME FUNC_NAME(TEX_NAME_BASE, 1d)
|
||||
# define DIMS 1
|
||||
#elif TEX_TYPE == GPU_TEXTURE_2D
|
||||
# define TEX_TYPE_NAME FUNC_NAME(TEX_NAME_BASE, 2d)
|
||||
# define DIMS 2
|
||||
#elif TEX_TYPE == GPU_TEXTURE_3D
|
||||
# define TEX_TYPE_NAME FUNC_NAME(TEX_NAME_BASE, 3d)
|
||||
# define DIMS 3
|
||||
#elif TEX_TYPE == GPU_TEXTURE_1D_ARRAY
|
||||
# define TEX_TYPE_NAME FUNC_NAME(TEX_NAME_BASE, 1d_array)
|
||||
# define DIMS 2
|
||||
#elif TEX_TYPE == GPU_TEXTURE_2D_ARRAY
|
||||
# define TEX_TYPE_NAME FUNC_NAME(TEX_NAME_BASE, 2d_array)
|
||||
# define DIMS 3
|
||||
#endif
|
||||
|
||||
/* Position dimensionality for thread-group. */
|
||||
#if DIMS == 1
|
||||
# define POSITION_TYPE uint
|
||||
#elif DIMS == 2
|
||||
# define POSITION_TYPE uint2
|
||||
#elif DIMS == 3
|
||||
# define POSITION_TYPE uint3
|
||||
#endif
|
||||
|
||||
using namespace metal;
|
||||
|
||||
template<typename T> T denormalize(float val)
|
||||
{
|
||||
return T(float(DEPTH_SCALE_FACTOR) * val);
|
||||
};
|
||||
|
||||
template<> int denormalize<int>(float val)
|
||||
{
|
||||
return int((float(DEPTH_SCALE_FACTOR) * val - 1.0f) / 2.0f);
|
||||
}
|
||||
template<> uint denormalize<uint>(float val)
|
||||
{
|
||||
return uint(float(DEPTH_SCALE_FACTOR) * val);
|
||||
}
|
||||
|
||||
/* `float` to other type case. */
|
||||
template<typename T> T convert_type(float type)
|
||||
{
|
||||
return T(type);
|
||||
}
|
||||
|
||||
/* `uint` to other types. */
|
||||
template<typename T> T convert_type(uint type)
|
||||
{
|
||||
return T(type);
|
||||
}
|
||||
|
||||
/* `int` to other types. */
|
||||
template<typename T> T convert_type(int type)
|
||||
{
|
||||
return T(type);
|
||||
}
|
||||
|
||||
template<> uchar convert_type<uchar>(float val)
|
||||
{
|
||||
return uchar(val * float(0xFF));
|
||||
}
|
||||
|
||||
template<> uint convert_type<uint>(float val)
|
||||
{
|
||||
return uint(val * float(0xFFFFFFFFu));
|
||||
}
|
||||
|
||||
template<typename D, typename S> uint pack_depth_stencil(D depth, S stencil)
|
||||
{
|
||||
return (((uint(depth)) << 8) & (~(0xFFu - 1))) | (uint(stencil) & (0xFFu - 1));
|
||||
}
|
||||
|
||||
struct TextureReadParams {
|
||||
int mip_index;
|
||||
int extent[3];
|
||||
int offset[3];
|
||||
};
|
||||
|
||||
#if IS_DEPTH_FORMAT == 1
|
||||
constexpr sampler pixelSampler = sampler(coord::pixel, address::clamp_to_edge, filter::nearest);
|
||||
#endif
|
||||
|
||||
kernel void compute_texture_read(constant TextureReadParams ¶ms [[buffer(0)]],
|
||||
device OUTPUT_DATA_TYPE *output_data [[buffer(1)]],
|
||||
#if IS_DEPTH_FORMAT == 1
|
||||
TEX_TYPE_NAME<float, access::sample> read_tex [[texture(0)]],
|
||||
#else
|
||||
TEX_TYPE_NAME<INPUT_DATA_TYPE, access::read> read_tex
|
||||
[[texture(0)]],
|
||||
#endif
|
||||
POSITION_TYPE position [[thread_position_in_grid]])
|
||||
{
|
||||
/* Read colour. */
|
||||
vec<INPUT_DATA_TYPE, 4> read_colour;
|
||||
|
||||
/* 1D TEXTURE */
|
||||
#if TEX_TYPE == GPU_TEXTURE_1D
|
||||
|
||||
/* xx, yy, layer determined by kernel invocation pattern */
|
||||
uint xx = position[0];
|
||||
int index = (xx)*COMPONENT_COUNT_OUTPUT;
|
||||
read_colour = read_tex.read(uint(params.offset[0]) + uint(xx));
|
||||
|
||||
/* 2D TEXTURE */
|
||||
#elif TEX_TYPE == GPU_TEXTURE_2D
|
||||
|
||||
/* xx, yy, layer determined by kernel invocation pattern */
|
||||
uint xx = position[0];
|
||||
uint yy = position[1];
|
||||
int index = (yy * params.extent[0] + xx) * COMPONENT_COUNT_OUTPUT;
|
||||
|
||||
/* Read data */
|
||||
# if IS_DEPTH_FORMAT == 1
|
||||
OUTPUT_DATA_TYPE value = denormalize<OUTPUT_DATA_TYPE>(
|
||||
read_tex.sample(pixelSampler, float2(params.offset[0], params.offset[1]) + float2(xx, yy)));
|
||||
# if IS_DEPTHSTENCIL_24_8 == 1
|
||||
/* Shift depth value into 24 bit region and mask out 8 bit stencil.
|
||||
* NOTE: Stencil currently not read as no use cases exist for this. */
|
||||
value = pack_depth_stencil(value, 0);
|
||||
# endif
|
||||
output_data[index] = value;
|
||||
# else
|
||||
read_colour = read_tex.read(uint2(params.offset[0], params.offset[1]) + uint2(xx, yy));
|
||||
# endif
|
||||
|
||||
/* 3D TEXTURE */
|
||||
#elif TEX_TYPE == GPU_TEXTURE_3D
|
||||
/* xx, yy, layer determined by kernel invocation pattern */
|
||||
uint xx = position[0];
|
||||
uint yy = position[1];
|
||||
uint zz = position[2];
|
||||
int index = (zz * (params.extent[0] * params.extent[1]) + yy * params.extent[0] + xx) *
|
||||
COMPONENT_COUNT_OUTPUT;
|
||||
read_colour = read_tex.read(uint3(params.offset[0], params.offset[1], params.offset[2]) +
|
||||
uint3(xx, yy, zz));
|
||||
|
||||
/* 1D ARRAY TEXTURE */
|
||||
#elif TEX_TYPE == GPU_TEXTURE_1D_ARRAY
|
||||
|
||||
/* xx, yy, layer determined by kernel invocation pattern */
|
||||
uint xx = position[0];
|
||||
uint layer = position[1];
|
||||
int index = (layer * params.extent[0] + xx) * COMPONENT_COUNT_OUTPUT;
|
||||
read_colour = read_tex.read(uint(params.offset[0]) + uint(xx), uint(params.offset[1]) + layer);
|
||||
|
||||
/* 2D ARRAY TEXTURE */
|
||||
#elif TEX_TYPE == GPU_TEXTURE_2D_ARRAY
|
||||
|
||||
/* xx, yy, layer determined by kernel invocation pattern */
|
||||
uint xx = position[0];
|
||||
uint yy = position[1];
|
||||
uint layer = position[2];
|
||||
int index = (layer * (params.extent[0] * params.extent[1]) + yy * params.extent[0] + xx) *
|
||||
COMPONENT_COUNT_OUTPUT;
|
||||
|
||||
/* Read data */
|
||||
# if IS_DEPTH_FORMAT == 1
|
||||
OUTPUT_DATA_TYPE value = denormalize<OUTPUT_DATA_TYPE>(
|
||||
read_tex.sample(pixelSampler,
|
||||
float2(params.offset[0], params.offset[1]) + float2(xx, yy),
|
||||
uint(params.offset[2] + layer)));
|
||||
# if IS_DEPTHSTENCIL_24_8 == 1
|
||||
/* Shift depth value into 24 bit region and mask out 8 bit stencil.
|
||||
* NOTE: Stencil currently not read as no use cases exist for this. */
|
||||
value = pack_depth_stencil(value, 0);
|
||||
# endif
|
||||
output_data[index] = value;
|
||||
# else
|
||||
read_colour = read_tex.read(uint2(params.offset[0], params.offset[1]) + uint2(xx, yy),
|
||||
uint(params.offset[2] + layer));
|
||||
# endif
|
||||
|
||||
#endif
|
||||
|
||||
/* Output per-component colour data. */
|
||||
#if IS_DEPTH_FORMAT != 1
|
||||
/* Write data to block */
|
||||
for (int i = 0; i < WRITE_COMPONENT_COUNT; i++) {
|
||||
output_data[index + i] = convert_type<OUTPUT_DATA_TYPE>(read_colour[i]);
|
||||
}
|
||||
|
||||
/* Fill in empty cells if more components are being read than exist */
|
||||
for (int i = COMPONENT_COUNT_INPUT; i < COMPONENT_COUNT_OUTPUT; i++) {
|
||||
output_data[index + i] = convert_type<OUTPUT_DATA_TYPE>(0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
using namespace metal;
|
||||
|
||||
/* MATCHING GPUTextureType. */
|
||||
#define GPU_TEXTURE_1D (1 << 0)
|
||||
#define GPU_TEXTURE_2D (1 << 1)
|
||||
#define GPU_TEXTURE_3D (1 << 2)
|
||||
#define GPU_TEXTURE_CUBE (1 << 3)
|
||||
#define GPU_TEXTURE_ARRAY (1 << 4)
|
||||
#define GPU_TEXTURE_BUFFER (1 << 5)
|
||||
#define GPU_TEXTURE_1D_ARRAY (GPU_TEXTURE_1D | GPU_TEXTURE_ARRAY)
|
||||
#define GPU_TEXTURE_2D_ARRAY (GPU_TEXTURE_2D | GPU_TEXTURE_ARRAY)
|
||||
#define GPU_TEXTURE_CUBE_ARRAY (GPU_TEXTURE_CUBE | GPU_TEXTURE_ARRAY)
|
||||
|
||||
/* Assign parameters based on texture type. */
|
||||
#if TEX_TYPE == GPU_TEXTURE_1D
|
||||
# define TEX_TYPE_NAME texture1d
|
||||
# define DIMS 1
|
||||
#elif TEX_TYPE == GPU_TEXTURE_2D
|
||||
# define TEX_TYPE_NAME texture2d
|
||||
# define DIMS 2
|
||||
#elif TEX_TYPE == GPU_TEXTURE_3D
|
||||
# define TEX_TYPE_NAME texture3d
|
||||
# define DIMS 3
|
||||
#elif TEX_TYPE == GPU_TEXTURE_1D_ARRAY
|
||||
# define TEX_TYPE_NAME texture1d_array
|
||||
# define DIMS 2
|
||||
#elif TEX_TYPE == GPU_TEXTURE_2D_ARRAY
|
||||
# define TEX_TYPE_NAME texture2d_array
|
||||
# define DIMS 3
|
||||
#endif
|
||||
|
||||
/* `base_offset` refers to the base read offset in bytes of the input data buffer.
|
||||
* Component offset adds an additional offset to read components. When clearing a buffer
|
||||
* and only one source color data element is provided, the base_offset is always zero.*/
|
||||
#if IS_TEXTURE_CLEAR == 1
|
||||
# define READ_INPUT_DATA(base_offset, component_offset) (input_data[0 + component_offset])
|
||||
#else
|
||||
# define READ_INPUT_DATA(base_offset, component_offset) \
|
||||
(input_data[base_offset + component_offset])
|
||||
#endif
|
||||
|
||||
/* Position dimensionality for thread-group. */
|
||||
#if DIMS == 1
|
||||
# define POSITION_TYPE uint
|
||||
#elif DIMS == 2
|
||||
# define POSITION_TYPE uint2
|
||||
#elif DIMS == 3
|
||||
# define POSITION_TYPE uint3
|
||||
#endif
|
||||
|
||||
struct TextureUpdateParams {
|
||||
int mip_index;
|
||||
int extent[3];
|
||||
int offset[3];
|
||||
uint unpack_row_length;
|
||||
};
|
||||
|
||||
kernel void compute_texture_update(constant TextureUpdateParams ¶ms [[buffer(0)]],
|
||||
constant INPUT_DATA_TYPE *input_data [[buffer(1)]],
|
||||
TEX_TYPE_NAME<OUTPUT_DATA_TYPE, access::write> update_tex
|
||||
[[texture(0)]],
|
||||
POSITION_TYPE position [[thread_position_in_grid]])
|
||||
{
|
||||
|
||||
/* 1D TEXTURE */
|
||||
#if TEX_TYPE == GPU_TEXTURE_1D
|
||||
|
||||
/* xx, yy, layer determined by kernel invocation pattern */
|
||||
uint xx = position;
|
||||
int index = xx * COMPONENT_COUNT_INPUT;
|
||||
|
||||
vec<OUTPUT_DATA_TYPE, /*COMPONENT_COUNT_OUTPUT*/ 4> output;
|
||||
for (int i = 0; i < COMPONENT_COUNT_INPUT; i++) {
|
||||
output[i] = OUTPUT_DATA_TYPE(READ_INPUT_DATA(index, i));
|
||||
}
|
||||
for (int i = COMPONENT_COUNT_INPUT; i < COMPONENT_COUNT_OUTPUT; i++) {
|
||||
output[i] = OUTPUT_DATA_TYPE(i == 3 ? 1 : 0);
|
||||
}
|
||||
update_tex.write(output, uint(params.offset[0]) + uint(xx));
|
||||
|
||||
/* 2D TEXTURE */
|
||||
#elif TEX_TYPE == GPU_TEXTURE_2D
|
||||
|
||||
/* xx, yy, layer determined by kernel invocation pattern */
|
||||
uint xx = position[0];
|
||||
uint yy = position[1];
|
||||
int index = (yy * params.unpack_row_length + xx) * COMPONENT_COUNT_INPUT;
|
||||
|
||||
vec<OUTPUT_DATA_TYPE, /*COMPONENT_COUNT_OUTPUT*/ 4> output;
|
||||
for (int i = 0; i < COMPONENT_COUNT_INPUT; i++) {
|
||||
output[i] = OUTPUT_DATA_TYPE(READ_INPUT_DATA(index, i));
|
||||
}
|
||||
for (int i = COMPONENT_COUNT_INPUT; i < COMPONENT_COUNT_OUTPUT; i++) {
|
||||
output[i] = OUTPUT_DATA_TYPE(i == 3 ? 1 : 0);
|
||||
}
|
||||
update_tex.write(output, uint2(params.offset[0], params.offset[1]) + uint2(xx, yy));
|
||||
|
||||
/* 3D TEXTURE */
|
||||
#elif TEX_TYPE == GPU_TEXTURE_3D
|
||||
|
||||
/* xx, yy, zz determined by kernel invocation pattern */
|
||||
uint xx = position[0];
|
||||
uint yy = position[1];
|
||||
uint zz = position[2];
|
||||
int index = (zz * (params.unpack_row_length * params.extent[1]) + yy * params.unpack_row_length +
|
||||
xx) *
|
||||
COMPONENT_COUNT_INPUT;
|
||||
|
||||
vec<OUTPUT_DATA_TYPE, /*COMPONENT_COUNT_OUTPUT*/ 4> output;
|
||||
for (int i = 0; i < COMPONENT_COUNT_INPUT; i++) {
|
||||
output[i] = OUTPUT_DATA_TYPE(READ_INPUT_DATA(index, i));
|
||||
}
|
||||
for (int i = COMPONENT_COUNT_INPUT; i < COMPONENT_COUNT_OUTPUT; i++) {
|
||||
output[i] = OUTPUT_DATA_TYPE(i == 3 ? 1 : 0);
|
||||
}
|
||||
update_tex.write(
|
||||
output, uint3(params.offset[0], params.offset[1], params.offset[2]) + uint3(xx, yy, zz));
|
||||
|
||||
/* 1D ARRAY TEXTURE */
|
||||
#elif TEX_TYPE == GPU_TEXTURE_1D_ARRAY
|
||||
|
||||
/* xx, yy, layer determined by kernel invocation pattern */
|
||||
uint xx = position[0];
|
||||
uint layer = position[1];
|
||||
int index = (layer * params.unpack_row_length + xx) * COMPONENT_COUNT_INPUT;
|
||||
|
||||
vec<OUTPUT_DATA_TYPE, /*COMPONENT_COUNT_OUTPUT*/ 4> output;
|
||||
for (int i = 0; i < COMPONENT_COUNT_INPUT; i++) {
|
||||
output[i] = OUTPUT_DATA_TYPE(READ_INPUT_DATA(index, i));
|
||||
}
|
||||
for (int i = COMPONENT_COUNT_INPUT; i < COMPONENT_COUNT_OUTPUT; i++) {
|
||||
output[i] = OUTPUT_DATA_TYPE(i == 3 ? 1 : 0);
|
||||
}
|
||||
update_tex.write(
|
||||
output, uint(params.offset[0]) + uint(xx), uint(params.offset[1]) + uint(layer));
|
||||
|
||||
/* 2D ARRAY TEXTURE */
|
||||
#elif TEX_TYPE == GPU_TEXTURE_2D_ARRAY
|
||||
|
||||
/* xx, yy, layer determined by kernel invocation pattern */
|
||||
uint xx = position[0];
|
||||
uint yy = position[1];
|
||||
uint layer = position[2];
|
||||
int index = (layer * (params.unpack_row_length * params.extent[1]) +
|
||||
yy * params.unpack_row_length + xx) *
|
||||
COMPONENT_COUNT_INPUT;
|
||||
|
||||
vec<OUTPUT_DATA_TYPE, /*COMPONENT_COUNT_OUTPUT*/ 4> output;
|
||||
for (int i = 0; i < COMPONENT_COUNT_INPUT; i++) {
|
||||
output[i] = OUTPUT_DATA_TYPE(READ_INPUT_DATA(index, i));
|
||||
}
|
||||
for (int i = COMPONENT_COUNT_INPUT; i < COMPONENT_COUNT_OUTPUT; i++) {
|
||||
output[i] = OUTPUT_DATA_TYPE(i == 3 ? 1 : 0);
|
||||
}
|
||||
update_tex.write(
|
||||
output, uint2(params.offset[0], params.offset[1]) + uint2(xx, yy), params.offset[2] + layer);
|
||||
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragDepth = textureLod(source_data, texCoord_interp, mip).r;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#ifdef GPU_SHADER
|
||||
# pragma once
|
||||
#endif
|
||||
|
||||
#include "gpu_shader_create_info.hh"
|
||||
|
||||
GPU_SHADER_INTERFACE_INFO(depth_2d_update_iface)
|
||||
SMOOTH(float2, texCoord_interp)
|
||||
GPU_SHADER_INTERFACE_END()
|
||||
|
||||
GPU_SHADER_CREATE_INFO(depth_2d_update_info_base)
|
||||
VERTEX_IN(0, float2, pos)
|
||||
VERTEX_OUT(depth_2d_update_iface)
|
||||
FRAGMENT_OUT(0, float4, fragColor)
|
||||
PUSH_CONSTANT(float2, extent)
|
||||
PUSH_CONSTANT(float2, offset)
|
||||
PUSH_CONSTANT(float2, size)
|
||||
PUSH_CONSTANT(int, mip)
|
||||
DEPTH_WRITE(DepthWrite::ANY)
|
||||
VERTEX_SOURCE("depth_2d_update_vert.glsl");
|
||||
GPU_SHADER_CREATE_END()
|
||||
|
||||
GPU_SHADER_CREATE_INFO(depth_2d_update_float)
|
||||
METAL_BACKEND_ONLY()
|
||||
FRAGMENT_SOURCE("depth_2d_update_float_frag.glsl")
|
||||
SAMPLER(0, sampler2D, source_data)
|
||||
ADDITIONAL_INFO(depth_2d_update_info_base)
|
||||
DO_STATIC_COMPILATION()
|
||||
DEPTH_WRITE(DepthWrite::ANY);
|
||||
GPU_SHADER_CREATE_END()
|
||||
|
||||
GPU_SHADER_CREATE_INFO(depth_2d_update_int24)
|
||||
METAL_BACKEND_ONLY()
|
||||
FRAGMENT_SOURCE("depth_2d_update_int24_frag.glsl")
|
||||
ADDITIONAL_INFO(depth_2d_update_info_base)
|
||||
SAMPLER(0, isampler2D, source_data)
|
||||
DO_STATIC_COMPILATION()
|
||||
DEPTH_WRITE(DepthWrite::ANY);
|
||||
GPU_SHADER_CREATE_END()
|
||||
|
||||
GPU_SHADER_CREATE_INFO(depth_2d_update_int32)
|
||||
METAL_BACKEND_ONLY()
|
||||
FRAGMENT_SOURCE("depth_2d_update_int32_frag.glsl")
|
||||
ADDITIONAL_INFO(depth_2d_update_info_base)
|
||||
SAMPLER(0, isampler2D, source_data)
|
||||
DO_STATIC_COMPILATION()
|
||||
DEPTH_WRITE(DepthWrite::ANY);
|
||||
GPU_SHADER_CREATE_END()
|
||||
@@ -0,0 +1,11 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
void main()
|
||||
{
|
||||
uint val = textureLod(source_data, texCoord_interp, mip).r;
|
||||
uint stencil = (val >> 24) & 0xFFu;
|
||||
uint depth = (val) & 0xFFFFFFu;
|
||||
gl_FragDepth = float(depth) / float(0xFFFFFFu);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
void main()
|
||||
{
|
||||
uint val = textureLod(source_data, texCoord_interp, mip).r;
|
||||
uint depth = (val) & (0xFFFFFFFFu);
|
||||
gl_FragDepth = float(depth) / float(0xFFFFFFFFu);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
void main()
|
||||
{
|
||||
float4 rect = float4(offset.x, offset.y, offset.x + extent.x, offset.y + extent.y);
|
||||
rect /= float4(size, size);
|
||||
float4 tex = rect;
|
||||
rect = rect * 2.0f - 1.0f;
|
||||
|
||||
/* QUAD */
|
||||
if (pos.x == 0.0f && pos.y == 0.0f) {
|
||||
rect.xy = rect.xy;
|
||||
texCoord_interp = tex.xy;
|
||||
}
|
||||
else if (pos.x == 0.0f && pos.y == 1.0f) {
|
||||
rect.xy = rect.xw;
|
||||
texCoord_interp = tex.xw;
|
||||
}
|
||||
else if (pos.x == 1.0f && pos.y == 1.0f) {
|
||||
rect.xy = rect.zw;
|
||||
texCoord_interp = tex.zw;
|
||||
}
|
||||
else {
|
||||
rect.xy = rect.zy;
|
||||
texCoord_interp = tex.zy;
|
||||
}
|
||||
gl_Position = float4(rect.xy, 0.0f, 1.0f);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
void main()
|
||||
{
|
||||
float4 tex_color = textureLod(imageTexture, screen_uv, mip);
|
||||
fragColor = tex_color;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gpu_shader_create_info.hh"
|
||||
|
||||
GPU_SHADER_INTERFACE_INFO(fullscreen_blit_iface)
|
||||
SMOOTH(float2, screen_uv)
|
||||
GPU_SHADER_INTERFACE_END()
|
||||
|
||||
GPU_SHADER_CREATE_INFO(fullscreen_blit)
|
||||
VERTEX_IN(0, float2, pos)
|
||||
VERTEX_OUT(fullscreen_blit_iface)
|
||||
FRAGMENT_OUT(0, float4, fragColor)
|
||||
PUSH_CONSTANT(float2, fullscreen)
|
||||
PUSH_CONSTANT(float2, size)
|
||||
PUSH_CONSTANT(float2, dst_offset)
|
||||
PUSH_CONSTANT(float2, src_offset)
|
||||
PUSH_CONSTANT(int, mip)
|
||||
SAMPLER(0, sampler2D, imageTexture)
|
||||
VERTEX_SOURCE("gpu_shader_fullscreen_blit_vert.glsl")
|
||||
FRAGMENT_SOURCE("gpu_shader_fullscreen_blit_frag.glsl")
|
||||
DO_STATIC_COMPILATION();
|
||||
GPU_SHADER_CREATE_END()
|
||||
@@ -0,0 +1,13 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
void main()
|
||||
{
|
||||
/* The position represents a 0-1 square, we first scale it by the size we want to have it on
|
||||
* screen next we divide by the full-screen size, this will bring everything in range [0,1].
|
||||
* Next we scale to NDC range [-1,1]. */
|
||||
gl_Position = float4((((pos * size + dst_offset) / fullscreen) * 2.0f - 1.0f), 1.0f, 1.0f);
|
||||
float2 uvoff = (src_offset / fullscreen);
|
||||
screen_uv = float2(pos + uvoff);
|
||||
}
|
||||
87
blender-5.2.0/source/blender/gpu/metal/mtl_backend.hh
Normal file
87
blender-5.2.0/source/blender/gpu/metal/mtl_backend.hh
Normal file
@@ -0,0 +1,87 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "gpu_backend.hh"
|
||||
#include "gpu_shader_private.hh"
|
||||
#include "mtl_capabilities.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
class Batch;
|
||||
class FrameBuffer;
|
||||
class QueryPool;
|
||||
class Shader;
|
||||
class UniformBuf;
|
||||
class VertBuf;
|
||||
class MTLContext;
|
||||
|
||||
class MTLBackend : public GPUBackend {
|
||||
friend class MTLContext;
|
||||
|
||||
public:
|
||||
/* Capabilities. */
|
||||
static MTLCapabilities capabilities;
|
||||
|
||||
static MTLCapabilities &get_capabilities()
|
||||
{
|
||||
return MTLBackend::capabilities;
|
||||
}
|
||||
|
||||
~MTLBackend() override
|
||||
{
|
||||
MTLBackend::platform_exit();
|
||||
}
|
||||
|
||||
void init_resources() override;
|
||||
|
||||
void delete_resources() override;
|
||||
|
||||
static bool metal_is_supported();
|
||||
static MTLBackend *get()
|
||||
{
|
||||
return static_cast<MTLBackend *>(GPUBackend::get());
|
||||
}
|
||||
|
||||
void compute_dispatch(int groups_x_len, int groups_y_len, int groups_z_len) override;
|
||||
void compute_dispatch_indirect(StorageBuf *indirect_buf) override;
|
||||
|
||||
/* MTL Allocators need to be implemented in separate `.mm` files,
|
||||
* due to allocation of Objective-C objects. */
|
||||
Context *context_alloc(GHOST_IWindow *ghost_window, GHOST_IContext *ghost_context) override;
|
||||
Batch *batch_alloc() override;
|
||||
Fence *fence_alloc() override;
|
||||
FrameBuffer *framebuffer_alloc(const char *name) override;
|
||||
IndexBuf *indexbuf_alloc() override;
|
||||
PixelBuffer *pixelbuf_alloc(size_t size) override;
|
||||
QueryPool *querypool_alloc() override;
|
||||
Shader *shader_alloc(const char *name) override;
|
||||
Texture *texture_alloc(const char *name) override;
|
||||
TexturePool *texturepool_alloc() override;
|
||||
UniformBuf *uniformbuf_alloc(size_t size, const char *name) override;
|
||||
StorageBuf *storagebuf_alloc(size_t size, GPUUsageType usage, const char *name) override;
|
||||
VertBuf *vertbuf_alloc() override;
|
||||
void shader_cache_dir_clear_old() override {}
|
||||
|
||||
/* Render Frame Coordination. */
|
||||
void render_begin() override;
|
||||
void render_end() override;
|
||||
void render_step(bool force_resource_release = false) override;
|
||||
bool is_inside_render_boundary();
|
||||
|
||||
private:
|
||||
static void platform_init(MTLContext *ctx);
|
||||
static void platform_exit();
|
||||
|
||||
static void capabilities_init(MTLContext *ctx);
|
||||
};
|
||||
|
||||
} // namespace blender::gpu
|
||||
619
blender-5.2.0/source/blender/gpu/metal/mtl_backend.mm
Normal file
619
blender-5.2.0/source/blender/gpu/metal/mtl_backend.mm
Normal file
@@ -0,0 +1,619 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "BLI_threads.h"
|
||||
|
||||
#include "BKE_global.hh"
|
||||
|
||||
#include "gpu_backend.hh"
|
||||
#include "mtl_backend.hh"
|
||||
#include "mtl_batch.hh"
|
||||
#include "mtl_context.hh"
|
||||
#include "mtl_framebuffer.hh"
|
||||
#include "mtl_immediate.hh"
|
||||
#include "mtl_index_buffer.hh"
|
||||
#include "mtl_query.hh"
|
||||
#include "mtl_shader.hh"
|
||||
#include "mtl_storage_buffer.hh"
|
||||
#include "mtl_texture_pool.hh"
|
||||
#include "mtl_uniform_buffer.hh"
|
||||
#include "mtl_vertex_buffer.hh"
|
||||
|
||||
#include "gpu_capabilities_private.hh"
|
||||
#include "gpu_platform_private.hh"
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <Metal/Metal.h>
|
||||
#include <QuartzCore/QuartzCore.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/* Global per-thread AutoReleasePool. */
|
||||
thread_local NSAutoreleasePool *g_autoreleasepool = nil;
|
||||
thread_local int g_autoreleasepool_depth = 0;
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Metal Backend
|
||||
* \{ */
|
||||
|
||||
void MTLBackend::init_resources()
|
||||
{
|
||||
compiler_ = MEM_new<MTLShaderCompiler>(__func__);
|
||||
}
|
||||
|
||||
void MTLBackend::delete_resources()
|
||||
{
|
||||
MEM_delete(compiler_);
|
||||
}
|
||||
|
||||
Context *MTLBackend::context_alloc(GHOST_IWindow *ghost_window, GHOST_IContext *ghost_context)
|
||||
{
|
||||
return new MTLContext(ghost_window, ghost_context);
|
||||
};
|
||||
|
||||
Batch *MTLBackend::batch_alloc()
|
||||
{
|
||||
return new MTLBatch();
|
||||
};
|
||||
|
||||
Fence *MTLBackend::fence_alloc()
|
||||
{
|
||||
return new MTLFence();
|
||||
};
|
||||
|
||||
FrameBuffer *MTLBackend::framebuffer_alloc(const char *name)
|
||||
{
|
||||
return new MTLFrameBuffer(MTLContext::get(), name);
|
||||
};
|
||||
|
||||
IndexBuf *MTLBackend::indexbuf_alloc()
|
||||
{
|
||||
return new MTLIndexBuf();
|
||||
};
|
||||
|
||||
PixelBuffer *MTLBackend::pixelbuf_alloc(size_t size)
|
||||
{
|
||||
return new MTLPixelBuffer(size);
|
||||
};
|
||||
|
||||
QueryPool *MTLBackend::querypool_alloc()
|
||||
{
|
||||
return new MTLQueryPool();
|
||||
};
|
||||
|
||||
Shader *MTLBackend::shader_alloc(const char *name)
|
||||
{
|
||||
return new MTLShader(MTLContext::get(), name);
|
||||
};
|
||||
|
||||
Texture *MTLBackend::texture_alloc(const char *name)
|
||||
{
|
||||
return new gpu::MTLTexture(name);
|
||||
}
|
||||
|
||||
TexturePool *MTLBackend::texturepool_alloc()
|
||||
{
|
||||
if (GCaps.texture_pool_workaround) {
|
||||
return new TexturePoolImpl();
|
||||
}
|
||||
return new MTLTexturePool();
|
||||
}
|
||||
|
||||
UniformBuf *MTLBackend::uniformbuf_alloc(size_t size, const char *name)
|
||||
{
|
||||
return new MTLUniformBuf(size, name);
|
||||
};
|
||||
|
||||
StorageBuf *MTLBackend::storagebuf_alloc(size_t size, GPUUsageType usage, const char *name)
|
||||
{
|
||||
return new MTLStorageBuf(size, usage, name);
|
||||
}
|
||||
|
||||
VertBuf *MTLBackend::vertbuf_alloc()
|
||||
{
|
||||
return new MTLVertBuf();
|
||||
}
|
||||
|
||||
void MTLBackend::render_begin()
|
||||
{
|
||||
/* All Rendering must occur within a render boundary */
|
||||
/* Track a call-count for nested calls, used to ensure we are inside an
|
||||
* autoreleasepool from all rendering path. */
|
||||
BLI_assert(g_autoreleasepool_depth >= 0);
|
||||
|
||||
if (g_autoreleasepool == nil) {
|
||||
g_autoreleasepool = [[NSAutoreleasePool alloc] init];
|
||||
}
|
||||
g_autoreleasepool_depth++;
|
||||
BLI_assert(g_autoreleasepool_depth > 0);
|
||||
}
|
||||
|
||||
void MTLBackend::render_end()
|
||||
{
|
||||
/* If call-count reaches zero, drain auto release pool.
|
||||
* Ensures temporary objects are freed within a frame's lifetime. */
|
||||
BLI_assert(g_autoreleasepool != nil);
|
||||
g_autoreleasepool_depth--;
|
||||
BLI_assert(g_autoreleasepool_depth >= 0);
|
||||
|
||||
if (g_autoreleasepool_depth == 0) {
|
||||
[g_autoreleasepool drain];
|
||||
g_autoreleasepool = nil;
|
||||
}
|
||||
}
|
||||
|
||||
void MTLBackend::render_step(bool force_resource_release)
|
||||
{
|
||||
/* NOTE(Metal): Primarily called from main thread, but below data-structures
|
||||
* and operations are thread-safe, and GPUContext rendering coordination
|
||||
* is also thread-safe. */
|
||||
|
||||
/* Flush any MTLSafeFreeLists which have previously been released by any MTLContext. */
|
||||
MTLContext::get_global_memory_manager()->update_memory_pools();
|
||||
|
||||
/* End existing MTLSafeFreeList and begin new list --
|
||||
* Buffers wont `free` until all associated in-flight command buffers have completed.
|
||||
* Decrement final reference count for ensuring the previous list is certainly
|
||||
* released. */
|
||||
MTLSafeFreeList *cmd_free_buffer_list =
|
||||
MTLContext::get_global_memory_manager()->get_current_safe_list();
|
||||
if (cmd_free_buffer_list->should_flush()) {
|
||||
MTLContext::get_global_memory_manager()->begin_new_safe_list();
|
||||
}
|
||||
|
||||
if (force_resource_release && g_autoreleasepool) {
|
||||
[g_autoreleasepool drain];
|
||||
g_autoreleasepool = [[NSAutoreleasePool alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
bool MTLBackend::is_inside_render_boundary()
|
||||
{
|
||||
return (g_autoreleasepool != nil);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Platform
|
||||
* \{ */
|
||||
|
||||
/* For Metal, platform_init needs to be called after MTLContext initialization. */
|
||||
void MTLBackend::platform_init(MTLContext *ctx)
|
||||
{
|
||||
if (GPG.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
GPUDeviceType device = GPU_DEVICE_UNKNOWN;
|
||||
GPUOSType os = GPU_OS_MAC;
|
||||
GPUDriverType driver = GPU_DRIVER_ANY;
|
||||
GPUSupportLevel support_level = GPU_SUPPORT_LEVEL_SUPPORTED;
|
||||
|
||||
BLI_assert(ctx);
|
||||
id<MTLDevice> mtl_device = ctx->device;
|
||||
BLI_assert(device);
|
||||
|
||||
NSString *gpu_name = [mtl_device name];
|
||||
const char *vendor = [gpu_name UTF8String];
|
||||
const char *renderer = "Metal API";
|
||||
const char *version = "1.2";
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
printf("METAL API - DETECTED GPU: %s\n", vendor);
|
||||
}
|
||||
|
||||
/* macOS is the only supported platform, but check to ensure we are not building with Metal
|
||||
* enablement on another platform. */
|
||||
BLI_assert_msg(os == GPU_OS_MAC, "Platform must be macOS");
|
||||
|
||||
/* Determine Vendor from name. */
|
||||
if (strstr(vendor, "ATI") || strstr(vendor, "AMD")) {
|
||||
device = GPU_DEVICE_ATI;
|
||||
driver = GPU_DRIVER_OFFICIAL;
|
||||
}
|
||||
else if (strstr(vendor, "NVIDIA")) {
|
||||
device = GPU_DEVICE_NVIDIA;
|
||||
driver = GPU_DRIVER_OFFICIAL;
|
||||
}
|
||||
else if (strstr(vendor, "Intel")) {
|
||||
device = GPU_DEVICE_INTEL;
|
||||
driver = GPU_DRIVER_OFFICIAL;
|
||||
support_level = GPU_SUPPORT_LEVEL_LIMITED;
|
||||
}
|
||||
else if (strstr(vendor, "Apple") || strstr(vendor, "APPLE")) {
|
||||
/* Apple Silicon. */
|
||||
device = GPU_DEVICE_APPLE;
|
||||
driver = GPU_DRIVER_OFFICIAL;
|
||||
}
|
||||
else if (strstr(renderer, "Apple Software Renderer")) {
|
||||
device = GPU_DEVICE_SOFTWARE;
|
||||
driver = GPU_DRIVER_SOFTWARE;
|
||||
}
|
||||
else if (strstr(renderer, "llvmpipe") || strstr(renderer, "softpipe")) {
|
||||
device = GPU_DEVICE_SOFTWARE;
|
||||
driver = GPU_DRIVER_SOFTWARE;
|
||||
}
|
||||
else if (G.debug & G_DEBUG_GPU) {
|
||||
printf("Warning: Could not find a matching GPU name. Things may not behave as expected.\n");
|
||||
printf("Detected configuration:\n");
|
||||
printf("Vendor: %s\n", vendor);
|
||||
printf("Renderer: %s\n", renderer);
|
||||
}
|
||||
|
||||
GPUArchitectureType architecture_type = (mtl_device.hasUnifiedMemory &&
|
||||
device == GPU_DEVICE_APPLE) ?
|
||||
GPU_ARCHITECTURE_TBDR :
|
||||
GPU_ARCHITECTURE_IMR;
|
||||
|
||||
GPG.init(device,
|
||||
os,
|
||||
driver,
|
||||
support_level,
|
||||
GPU_BACKEND_METAL,
|
||||
vendor,
|
||||
renderer,
|
||||
version,
|
||||
architecture_type);
|
||||
|
||||
GPG.devices.append(
|
||||
{.identifier = "METAL", .index = 0, .vendor_id = 0, .device_id = 0, .name = renderer});
|
||||
|
||||
/* UUID is not supported on Metal. */
|
||||
GPG.device_uuid.reinitialize(0);
|
||||
|
||||
/* LUID is registryID on Metal, or at least this is what libraries like OIDN expects. */
|
||||
const uint64_t luid = mtl_device.registryID;
|
||||
GPG.device_luid.reinitialize(sizeof(luid));
|
||||
std::memcpy(GPG.device_luid.data(), &luid, sizeof(luid));
|
||||
|
||||
/* Metal only has one device per LUID, so only the first bit will always be active.. */
|
||||
GPG.device_luid_node_mask = 1;
|
||||
}
|
||||
|
||||
void MTLBackend::platform_exit()
|
||||
{
|
||||
BLI_assert(GPG.initialized);
|
||||
GPG.clear();
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Capabilities
|
||||
* \{ */
|
||||
MTLCapabilities MTLBackend::capabilities = {};
|
||||
|
||||
static const char *mtl_extensions_get_null(int /*i*/)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool supports_barycentric_whitelist(id<MTLDevice> device)
|
||||
{
|
||||
NSString *gpu_name = [device name];
|
||||
BLI_assert([gpu_name length]);
|
||||
const char *vendor = [gpu_name UTF8String];
|
||||
|
||||
/* Verify GPU support. */
|
||||
bool supported_gpu = [device supportsFamily:MTLGPUFamilyMac2];
|
||||
bool should_support_barycentrics = false;
|
||||
|
||||
/* Known good configs. */
|
||||
if (strstr(vendor, "AMD") || strstr(vendor, "Apple") || strstr(vendor, "APPLE")) {
|
||||
should_support_barycentrics = true;
|
||||
}
|
||||
|
||||
/* Explicit support for Intel-based platforms. */
|
||||
if ((strstr(vendor, "Intel") || strstr(vendor, "INTEL"))) {
|
||||
should_support_barycentrics = true;
|
||||
}
|
||||
return supported_gpu && should_support_barycentrics;
|
||||
}
|
||||
|
||||
bool is_apple_sillicon(id<MTLDevice> device)
|
||||
{
|
||||
NSString *gpu_name = [device name];
|
||||
BLI_assert([gpu_name length]);
|
||||
|
||||
const char *vendor = [gpu_name UTF8String];
|
||||
|
||||
/* Known good configs. */
|
||||
return (strstr(vendor, "Apple") || strstr(vendor, "APPLE"));
|
||||
}
|
||||
|
||||
static int get_num_performance_cpu_cores(id<MTLDevice> device)
|
||||
{
|
||||
const int SYSCTL_BUF_LENGTH = 16;
|
||||
int num_performance_cores = -1;
|
||||
unsigned char sysctl_buffer[SYSCTL_BUF_LENGTH];
|
||||
size_t sysctl_buffer_length = SYSCTL_BUF_LENGTH;
|
||||
|
||||
if (is_apple_sillicon(device)) {
|
||||
/* On Apple Silicon query the number of performance cores */
|
||||
if (sysctlbyname(
|
||||
"hw.perflevel0.logicalcpu", &sysctl_buffer, &sysctl_buffer_length, nullptr, 0) == 0)
|
||||
{
|
||||
num_performance_cores = sysctl_buffer[0];
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* On Intel just return the logical core count */
|
||||
if (sysctlbyname("hw.logicalcpu", &sysctl_buffer, &sysctl_buffer_length, nullptr, 0) == 0) {
|
||||
num_performance_cores = sysctl_buffer[0];
|
||||
}
|
||||
}
|
||||
BLI_assert(num_performance_cores != -1);
|
||||
return num_performance_cores;
|
||||
}
|
||||
|
||||
static int get_num_efficiency_cpu_cores(id<MTLDevice> device)
|
||||
{
|
||||
if (is_apple_sillicon(device)) {
|
||||
/* On Apple Silicon query the number of efficiency cores */
|
||||
const int SYSCTL_BUF_LENGTH = 16;
|
||||
int num_efficiency_cores = -1;
|
||||
unsigned char sysctl_buffer[SYSCTL_BUF_LENGTH];
|
||||
size_t sysctl_buffer_length = SYSCTL_BUF_LENGTH;
|
||||
if (sysctlbyname(
|
||||
"hw.perflevel1.logicalcpu", &sysctl_buffer, &sysctl_buffer_length, nullptr, 0) == 0)
|
||||
{
|
||||
num_efficiency_cores = sysctl_buffer[0];
|
||||
}
|
||||
|
||||
BLI_assert(num_efficiency_cores != -1);
|
||||
return num_efficiency_cores;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool MTLBackend::metal_is_supported()
|
||||
{
|
||||
/* Device compatibility information using Metal Feature-set tables.
|
||||
* See: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */
|
||||
|
||||
NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion];
|
||||
|
||||
/* Metal Viewport requires macOS Version 10.15 onward. */
|
||||
bool supported_os_version = version.majorVersion >= 11 ||
|
||||
(version.majorVersion == 10 ? version.minorVersion >= 15 : false);
|
||||
if (!supported_os_version) {
|
||||
printf(
|
||||
"OS Version too low to run minimum required metal version. Required at least 10.15, got "
|
||||
"%ld.%ld \n",
|
||||
(long)version.majorVersion,
|
||||
(long)version.minorVersion);
|
||||
return false;
|
||||
}
|
||||
|
||||
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
|
||||
|
||||
/* Debug: Enable low power GPU with Environment Var: METAL_FORCE_INTEL. */
|
||||
static const char *forceIntelStr = getenv("METAL_FORCE_INTEL");
|
||||
bool forceIntel = forceIntelStr ? (atoi(forceIntelStr) != 0) : false;
|
||||
|
||||
if (forceIntel) {
|
||||
NSArray<id<MTLDevice>> *allDevices = MTLCopyAllDevices();
|
||||
for (id<MTLDevice> _device in allDevices) {
|
||||
if (_device.lowPower) {
|
||||
device = _device;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Metal Viewport requires argument buffer tier-2 support and Barycentric Coordinates.
|
||||
* These are available on most hardware configurations supporting Metal 2.2. */
|
||||
bool supports_argument_buffers_tier2 = ([device argumentBuffersSupport] ==
|
||||
MTLArgumentBuffersTier2);
|
||||
bool supports_barycentrics = [device supportsShaderBarycentricCoordinates] ||
|
||||
supports_barycentric_whitelist(device);
|
||||
bool supported_metal_version = [device supportsFamily:MTLGPUFamilyMac2];
|
||||
|
||||
bool result = supports_argument_buffers_tier2 && supports_barycentrics && supported_os_version &&
|
||||
supported_metal_version;
|
||||
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
if (!supports_argument_buffers_tier2) {
|
||||
printf("[Metal] Device does not support argument buffers tier 2\n");
|
||||
}
|
||||
if (!supports_barycentrics) {
|
||||
printf("[Metal] Device does not support barycentrics coordinates\n");
|
||||
}
|
||||
if (!supported_metal_version) {
|
||||
printf("[Metal] Device does not support metal 2.2 or higher\n");
|
||||
}
|
||||
|
||||
if (result) {
|
||||
printf("Device with name %s supports metal minimum requirements\n",
|
||||
[[device name] UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void MTLBackend::capabilities_init(MTLContext *ctx)
|
||||
{
|
||||
BLI_assert(ctx);
|
||||
id<MTLDevice> device = ctx->device;
|
||||
BLI_assert(device);
|
||||
|
||||
/* Initialize Capabilities. */
|
||||
MTLBackend::capabilities.supports_argument_buffers_tier2 = ([device argumentBuffersSupport] ==
|
||||
MTLArgumentBuffersTier2);
|
||||
MTLBackend::capabilities.supports_family_mac1 = [device supportsFamily:MTLGPUFamilyMac1];
|
||||
MTLBackend::capabilities.supports_family_mac2 = [device supportsFamily:MTLGPUFamilyMac2];
|
||||
MTLBackend::capabilities.supports_family_mac_catalyst1 = [device
|
||||
supportsFamily:MTLGPUFamilyMacCatalyst1];
|
||||
MTLBackend::capabilities.supports_family_mac_catalyst2 = [device
|
||||
supportsFamily:MTLGPUFamilyMacCatalyst2];
|
||||
/* NOTE(Metal): Texture gather is supported on AMD, but results are non consistent
|
||||
* with Apple Silicon GPUs. Disabling for now to avoid erroneous rendering. */
|
||||
MTLBackend::capabilities.supports_texture_gather = [device hasUnifiedMemory];
|
||||
|
||||
/* GPU Type. */
|
||||
const char *gpu_name = [device.name UTF8String];
|
||||
if (strstr(gpu_name, "M1")) {
|
||||
MTLBackend::capabilities.gpu = APPLE_GPU_M1;
|
||||
}
|
||||
else if (strstr(gpu_name, "M2")) {
|
||||
MTLBackend::capabilities.gpu = APPLE_GPU_M2;
|
||||
}
|
||||
else if (strstr(gpu_name, "M3")) {
|
||||
MTLBackend::capabilities.gpu = APPLE_GPU_M3;
|
||||
}
|
||||
else {
|
||||
MTLBackend::capabilities.gpu = APPLE_GPU_UNKNOWN;
|
||||
}
|
||||
|
||||
/* Texture atomics supported in Metal 3.1. */
|
||||
MTLBackend::capabilities.supports_texture_atomics = false;
|
||||
#if defined(MAC_OS_VERSION_14_0)
|
||||
if (@available(macOS 14.0, *)) {
|
||||
MTLBackend::capabilities.supports_texture_atomics = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Identify support for tile inputs. */
|
||||
const bool is_tile_based_arch = (GPU_platform_architecture() == GPU_ARCHITECTURE_TBDR);
|
||||
if (is_tile_based_arch) {
|
||||
MTLBackend::capabilities.supports_native_tile_inputs = true;
|
||||
}
|
||||
else {
|
||||
/* NOTE: If emulating tile input reads, we must ensure we also expose position data. */
|
||||
MTLBackend::capabilities.supports_native_tile_inputs = false;
|
||||
}
|
||||
|
||||
/* CPU Info */
|
||||
MTLBackend::capabilities.num_performance_cores = get_num_performance_cpu_cores(ctx->device);
|
||||
MTLBackend::capabilities.num_efficiency_cores = get_num_efficiency_cpu_cores(ctx->device);
|
||||
|
||||
/* Common Global Capabilities. */
|
||||
GCaps.max_texture_size = ([device supportsFamily:MTLGPUFamilyApple3] ||
|
||||
MTLBackend::capabilities.supports_family_mac1) ?
|
||||
16384 :
|
||||
8192;
|
||||
GCaps.max_texture_3d_size = 2048;
|
||||
GCaps.max_buffer_texture_size = UINT_MAX;
|
||||
GCaps.max_texture_layers = 2048;
|
||||
GCaps.max_textures = (MTLBackend::capabilities.supports_family_mac1) ?
|
||||
128 :
|
||||
(([device supportsFamily:MTLGPUFamilyApple4]) ? 96 : 31);
|
||||
/* Hardcoded limit due to ShaderInterface::enabled_tex_mask_. */
|
||||
GCaps.max_textures = std::min(GCaps.max_textures, 64);
|
||||
|
||||
GCaps.max_images = GCaps.max_textures;
|
||||
|
||||
/* Conservative uniform data limit is 4KB per-stage -- This is the limit of setBytes.
|
||||
* MTLBuffer path is also supported but not as efficient. */
|
||||
GCaps.max_uniforms_vert = 1024;
|
||||
GCaps.max_uniforms_frag = 1024;
|
||||
|
||||
GCaps.max_batch_indices = 1 << 31;
|
||||
GCaps.max_batch_vertices = 1 << 31;
|
||||
GCaps.max_vertex_attribs = 31;
|
||||
GCaps.max_varying_floats = 60;
|
||||
|
||||
/* Feature support */
|
||||
GCaps.mem_stats_support = false;
|
||||
GCaps.hdr_viewport_support = true;
|
||||
|
||||
GCaps.geometry_shader_support = false;
|
||||
|
||||
/* Compile shaders on performance cores but leave one free so UI is still responsive.
|
||||
* Also respect command line option to reduce number of threads. */
|
||||
GCaps.max_parallel_compilations = std::min(BLI_system_thread_count(),
|
||||
MTLBackend::capabilities.num_performance_cores - 1);
|
||||
|
||||
/* Maximum buffer bindings: 31. Consider required slot for uniforms/UBOs/Vertex attributes.
|
||||
* Can use argument buffers if a higher limit is required. */
|
||||
GCaps.max_shader_storage_buffer_bindings = 14;
|
||||
GCaps.max_compute_shader_storage_blocks = 14;
|
||||
GCaps.max_storage_buffer_size = size_t(ctx->device.maxBufferLength);
|
||||
GCaps.max_uniform_buffer_size = size_t(ctx->device.maxBufferLength);
|
||||
GCaps.storage_buffer_alignment = 256; /* TODO(fclem): But also unused. */
|
||||
|
||||
GCaps.max_work_group_count[0] = 65535;
|
||||
GCaps.max_work_group_count[1] = 65535;
|
||||
GCaps.max_work_group_count[2] = 65535;
|
||||
/* In Metal, total_thread_count is 512 or 1024, such that
|
||||
* threadgroup `width*height*depth <= total_thread_count` */
|
||||
uint max_threads_per_threadgroup_per_dim = ([device supportsFamily:MTLGPUFamilyApple4] ||
|
||||
MTLBackend::capabilities.supports_family_mac1) ?
|
||||
1024 :
|
||||
512;
|
||||
GCaps.max_work_group_size[0] = max_threads_per_threadgroup_per_dim;
|
||||
GCaps.max_work_group_size[1] = max_threads_per_threadgroup_per_dim;
|
||||
GCaps.max_work_group_size[2] = max_threads_per_threadgroup_per_dim;
|
||||
|
||||
GCaps.stencil_export_support = true;
|
||||
|
||||
/* OPENGL Related workarounds -- none needed for Metal. */
|
||||
GCaps.extensions_len = 0;
|
||||
GCaps.extension_get = mtl_extensions_get_null;
|
||||
GCaps.depth_blitting_workaround = false;
|
||||
GCaps.use_main_context_workaround = false;
|
||||
|
||||
/* Metal related workarounds. */
|
||||
/* Minimum per-vertex stride is 4 bytes in Metal.
|
||||
* A bound vertex buffer must contribute at least 4 bytes per vertex. */
|
||||
GCaps.minimum_per_vertex_stride = 4;
|
||||
|
||||
/* Force workarounds when starting blender with `--debug-gpu-force-workarounds`.
|
||||
*
|
||||
* Not all workarounds are listed here as some capabilities are currently assumed to be present
|
||||
* on all devices. */
|
||||
if (G.debug & G_DEBUG_GPU_FORCE_WORKAROUNDS) {
|
||||
/* Texture gather is supported on AMD, but results are non consistent with Apple Silicon GPUs
|
||||
* and can be disabled. */
|
||||
MTLBackend::capabilities.supports_texture_gather = false;
|
||||
MTLBackend::capabilities.supports_texture_atomics = false;
|
||||
MTLBackend::capabilities.supports_native_tile_inputs = false;
|
||||
GCaps.texture_pool_workaround = true;
|
||||
}
|
||||
|
||||
if (G.debug & G_DEBUG_GPU_NO_TEXTURE_POOL) {
|
||||
GCaps.texture_pool_workaround = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Compute dispatch.
|
||||
* \{ */
|
||||
|
||||
void MTLBackend::compute_dispatch(int groups_x_len, int groups_y_len, int groups_z_len)
|
||||
{
|
||||
/* Fetch Context.
|
||||
* With Metal, workload submission and resource management occurs within the context.
|
||||
* Call compute dispatch on valid context. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx != nullptr);
|
||||
if (ctx) {
|
||||
ctx->compute_dispatch(groups_x_len, groups_y_len, groups_z_len);
|
||||
}
|
||||
}
|
||||
|
||||
void MTLBackend::compute_dispatch_indirect(StorageBuf *indirect_buf)
|
||||
{
|
||||
/* Fetch Context.
|
||||
* With Metal, workload submission and resource management occurs within the context.
|
||||
* Call compute dispatch on valid context. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx != nullptr);
|
||||
if (ctx) {
|
||||
ctx->compute_dispatch_indirect(indirect_buf);
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::gpu
|
||||
140
blender-5.2.0/source/blender/gpu/metal/mtl_batch.hh
Normal file
140
blender-5.2.0/source/blender/gpu/metal/mtl_batch.hh
Normal file
@@ -0,0 +1,140 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*
|
||||
* GPU geometry batch
|
||||
* Contains VAOs + VBOs + Shader representing a drawable entity.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GPU_batch.hh"
|
||||
#include "MEM_guardedalloc.h"
|
||||
#include "mtl_index_buffer.hh"
|
||||
#include "mtl_primitive.hh"
|
||||
#include "mtl_shader.hh"
|
||||
#include "mtl_vertex_buffer.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
class MTLContext;
|
||||
class MTLShaderInterface;
|
||||
|
||||
#define GPU_VAO_STATIC_LEN 64
|
||||
|
||||
struct VertBufBinding {
|
||||
/* Slot in the buffer layout descriptor. -1 if buffer is not needed. */
|
||||
int desc_id;
|
||||
/* Binding point this buffer needs to be bound at. */
|
||||
int slot_id;
|
||||
};
|
||||
|
||||
/* TODO(fclem): This needs to be revisited as the complexity of this code is off the chart.
|
||||
* There are multiple copies of the same information in multiple place. */
|
||||
struct VertexBufferID {
|
||||
uint32_t id : 16;
|
||||
uint32_t used : 1;
|
||||
uint32_t slot : 15;
|
||||
};
|
||||
|
||||
class MTLBatch : public Batch {
|
||||
|
||||
/* Vertex Bind-state Caching for a given shader interface used with the Batch. */
|
||||
struct VertexDescriptorShaderInterfacePair {
|
||||
MTLVertexDescriptor vertex_descriptor{};
|
||||
const ShaderInterface *interface = nullptr;
|
||||
uint16_t attr_mask = 0;
|
||||
int num_buffers = 0;
|
||||
VertexBufferID bufferIds[GPU_BATCH_VBO_MAX_LEN] = {};
|
||||
/* Cache life index compares a cache entry with the active MTLBatch state.
|
||||
* This is initially set to the cache life index of MTLBatch. If the batch has been modified,
|
||||
* this index is incremented to cheaply invalidate existing cache entries. */
|
||||
uint32_t cache_life_index = 0;
|
||||
};
|
||||
|
||||
class MTLVertexDescriptorCache {
|
||||
|
||||
private:
|
||||
MTLBatch *batch_;
|
||||
|
||||
VertexDescriptorShaderInterfacePair cache_[GPU_VAO_STATIC_LEN] = {};
|
||||
MTLContext *cache_context_ = nullptr;
|
||||
uint32_t cache_life_index_ = 0;
|
||||
|
||||
public:
|
||||
MTLVertexDescriptorCache(MTLBatch *batch) : batch_(batch) {};
|
||||
VertexDescriptorShaderInterfacePair *find(const ShaderInterface *interface);
|
||||
bool insert(VertexDescriptorShaderInterfacePair &data);
|
||||
|
||||
private:
|
||||
void vertex_descriptor_cache_init(MTLContext *ctx);
|
||||
void vertex_descriptor_cache_clear();
|
||||
void vertex_descriptor_cache_ensure();
|
||||
};
|
||||
|
||||
private:
|
||||
MTLShader *active_shader_ = nullptr;
|
||||
MTLVertexDescriptorCache vao_cache = {this};
|
||||
|
||||
/* Topology emulation. */
|
||||
gpu::MTLBuffer *emulated_topology_buffer_ = nullptr;
|
||||
GPUPrimType emulated_topology_type_;
|
||||
uint32_t topology_buffer_input_v_count_ = 0;
|
||||
uint32_t topology_buffer_output_v_count_ = 0;
|
||||
|
||||
public:
|
||||
MTLBatch() = default;
|
||||
~MTLBatch() override = default;
|
||||
|
||||
void draw(int v_first, int v_count, int i_first, int i_count) override;
|
||||
void draw_indirect(StorageBuf *indirect_buf, intptr_t offset) override;
|
||||
void multi_draw_indirect(StorageBuf * /*indirect_buf*/,
|
||||
int /*count*/,
|
||||
intptr_t /*offset*/,
|
||||
intptr_t /*stride*/) override
|
||||
{
|
||||
/* TODO(Metal): Support indirect draw commands. */
|
||||
}
|
||||
|
||||
/* Returns an initialized RenderComandEncoder for drawing if all is good.
|
||||
* Otherwise, nil. */
|
||||
id<MTLRenderCommandEncoder> bind();
|
||||
void unbind(id<MTLRenderCommandEncoder> rec);
|
||||
|
||||
/* Convenience getters. */
|
||||
MTLIndexBuf *elem_() const
|
||||
{
|
||||
return static_cast<MTLIndexBuf *>(elem);
|
||||
}
|
||||
MTLVertBuf *verts_(const int index) const
|
||||
{
|
||||
return static_cast<MTLVertBuf *>(verts[index]);
|
||||
}
|
||||
MTLShader *active_shader_get() const
|
||||
{
|
||||
return active_shader_;
|
||||
}
|
||||
|
||||
private:
|
||||
void draw_advanced(int v_first, int v_count, int i_first, int i_count);
|
||||
void draw_advanced_indirect(StorageBuf *indirect_buf, intptr_t offset);
|
||||
|
||||
VertBufBinding prepare_vertex_binding(MTLVertBuf *verts,
|
||||
MTLRenderPipelineStateDescriptor &desc,
|
||||
const MTLShaderInterface &interface,
|
||||
uint16_t &attr_mask,
|
||||
uint32_t &buffer_mask);
|
||||
|
||||
id<MTLBuffer> get_emulated_toplogy_buffer(GPUPrimType &in_out_prim_type, uint32_t &v_count);
|
||||
|
||||
void prepare_vertex_descriptor_and_bindings(MutableSpan<MTLVertBuf *> buffers,
|
||||
MutableSpan<int> buffer_slots,
|
||||
int &num_buffers);
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLBatch");
|
||||
};
|
||||
|
||||
} // namespace blender::gpu
|
||||
768
blender-5.2.0/source/blender/gpu/metal/mtl_batch.mm
Normal file
768
blender-5.2.0/source/blender/gpu/metal/mtl_batch.mm
Normal file
@@ -0,0 +1,768 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*
|
||||
* Metal implementation of gpu::Batch.
|
||||
*/
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_span.hh"
|
||||
|
||||
#include "BKE_global.hh"
|
||||
|
||||
#include "GPU_batch.hh"
|
||||
#include "GPU_common.hh"
|
||||
#include "gpu_shader_private.hh"
|
||||
#include "gpu_vertex_format_private.hh"
|
||||
|
||||
#include "mtl_batch.hh"
|
||||
#include "mtl_context.hh"
|
||||
#include "mtl_debug.hh"
|
||||
#include "mtl_index_buffer.hh"
|
||||
#include "mtl_shader.hh"
|
||||
#include "mtl_storage_buffer.hh"
|
||||
#include "mtl_vertex_buffer.hh"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Creation & Deletion
|
||||
* \{ */
|
||||
void MTLBatch::draw(int v_first, int v_count, int i_first, int i_count)
|
||||
{
|
||||
this->draw_advanced(v_first, v_count, i_first, i_count);
|
||||
}
|
||||
|
||||
void MTLBatch::draw_indirect(StorageBuf *indirect_buf, intptr_t offset)
|
||||
{
|
||||
this->draw_advanced_indirect(indirect_buf, offset);
|
||||
}
|
||||
|
||||
void MTLBatch::MTLVertexDescriptorCache::vertex_descriptor_cache_init(MTLContext *ctx)
|
||||
{
|
||||
BLI_assert(ctx != nullptr);
|
||||
this->vertex_descriptor_cache_clear();
|
||||
cache_context_ = ctx;
|
||||
}
|
||||
|
||||
void MTLBatch::MTLVertexDescriptorCache::vertex_descriptor_cache_clear()
|
||||
{
|
||||
cache_life_index_++;
|
||||
cache_context_ = nullptr;
|
||||
}
|
||||
|
||||
void MTLBatch::MTLVertexDescriptorCache::vertex_descriptor_cache_ensure()
|
||||
{
|
||||
if (this->cache_context_ != nullptr) {
|
||||
|
||||
/* Invalidate vertex descriptor bindings cache if batch has changed. */
|
||||
if (batch_->flag & GPU_BATCH_DIRTY) {
|
||||
batch_->flag &= ~GPU_BATCH_DIRTY;
|
||||
this->vertex_descriptor_cache_clear();
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize cache if not ready. */
|
||||
if (cache_context_ == nullptr) {
|
||||
this->vertex_descriptor_cache_init(MTLContext::get());
|
||||
}
|
||||
}
|
||||
|
||||
MTLBatch::VertexDescriptorShaderInterfacePair *MTLBatch::MTLVertexDescriptorCache::find(
|
||||
const ShaderInterface *interface)
|
||||
{
|
||||
this->vertex_descriptor_cache_ensure();
|
||||
for (int i = 0; i < GPU_VAO_STATIC_LEN; ++i) {
|
||||
if (cache_[i].interface == interface && cache_[i].cache_life_index == cache_life_index_) {
|
||||
return &cache_[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool MTLBatch::MTLVertexDescriptorCache::insert(
|
||||
MTLBatch::VertexDescriptorShaderInterfacePair &data)
|
||||
{
|
||||
vertex_descriptor_cache_ensure();
|
||||
for (int i = 0; i < GPU_VAO_STATIC_LEN; ++i) {
|
||||
if (cache_[i].interface == nullptr || cache_[i].cache_life_index != cache_life_index_) {
|
||||
cache_[i] = data;
|
||||
cache_[i].cache_life_index = cache_life_index_;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Return index inside the vertex descriptor. */
|
||||
VertBufBinding MTLBatch::prepare_vertex_binding(MTLVertBuf *verts,
|
||||
MTLRenderPipelineStateDescriptor &desc,
|
||||
const MTLShaderInterface &interface,
|
||||
uint16_t &attr_mask,
|
||||
uint32_t &buffer_mask)
|
||||
{
|
||||
|
||||
const GPUVertFormat *format = &verts->format;
|
||||
/* Per-vertex stride of current vertex buffer. */
|
||||
int buffer_stride = format->stride;
|
||||
/* Buffer binding index of the vertex buffer once added to the buffer layout descriptor. */
|
||||
VertBufBinding binding{-1, -1};
|
||||
int attribute_offset = 0;
|
||||
|
||||
/* Iterate over VertBuf vertex format and find attributes matching those in the active
|
||||
* shader's interface. */
|
||||
for (uint32_t a_idx = 0; a_idx < format->attr_len; a_idx++) {
|
||||
const GPUVertAttr *a = &format->attrs[a_idx];
|
||||
|
||||
if (format->deinterleaved) {
|
||||
attribute_offset += ((a_idx == 0) ? 0 : format->attrs[a_idx - 1].type.size()) *
|
||||
verts->vertex_len;
|
||||
buffer_stride = a->type.size();
|
||||
}
|
||||
else {
|
||||
attribute_offset = a->offset;
|
||||
}
|
||||
|
||||
/* Find attribute with the matching name. Attributes may have multiple compatible
|
||||
* name aliases. */
|
||||
for (uint32_t n_idx = 0; n_idx < a->name_len; n_idx++) {
|
||||
const char *name = GPU_vertformat_attr_name_get(format, a, n_idx);
|
||||
const ShaderInput *input = interface.attr_get(name);
|
||||
|
||||
if (input == nullptr || input->location == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check if attribute is already present in the given slot. */
|
||||
if ((~attr_mask) & (1 << input->location)) {
|
||||
MTL_LOG_DEBUG(
|
||||
" -- [Batch] Skipping attribute with input location %d (As one is already bound)",
|
||||
input->location);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Update attribute used-slot mask. */
|
||||
attr_mask &= ~(1 << input->location);
|
||||
|
||||
/* Add buffer layout entry in descriptor if it has not yet been added
|
||||
* for current vertex buffer. */
|
||||
if (binding.desc_id == -1) {
|
||||
if (buffer_mask == 0) {
|
||||
MTL_LOG_DEBUG(" -- [Batch] Error: Not enough available slot to bind vertex buffer");
|
||||
return binding;
|
||||
}
|
||||
|
||||
binding.desc_id = desc.vertex_descriptor.num_vert_buffers++;
|
||||
binding.slot_id = bitscan_forward_clear_uint(&buffer_mask);
|
||||
|
||||
auto &buffer_layout = desc.vertex_descriptor.buffer_layouts[binding.desc_id];
|
||||
buffer_layout.step_function = MTLVertexStepFunctionPerVertex;
|
||||
buffer_layout.step_rate = 1;
|
||||
buffer_layout.stride = buffer_stride;
|
||||
buffer_layout.buffer_slot = binding.slot_id;
|
||||
|
||||
MTL_LOG_DEBUG(" -- [Batch] Adding source vertex buffer (Index: %d, Stride: %d)",
|
||||
binding.desc_id,
|
||||
buffer_stride);
|
||||
}
|
||||
else {
|
||||
/* Ensure stride is correct for de-interleaved attributes. */
|
||||
desc.vertex_descriptor.buffer_layouts[binding.desc_id].stride = buffer_stride;
|
||||
}
|
||||
|
||||
{
|
||||
MTLVertexAttributeDescriptorPSO &pso_attr =
|
||||
desc.vertex_descriptor.attributes[input->location];
|
||||
|
||||
pso_attr.format = gpu_vertex_format_to_metal(a->type.format);
|
||||
pso_attr.offset = attribute_offset;
|
||||
pso_attr.buffer_index = binding.slot_id;
|
||||
pso_attr.format_conversion_mode = is_fetch_float(a->type.format) ?
|
||||
GPUVertFetchMode(GPU_FETCH_FLOAT) :
|
||||
GPUVertFetchMode(GPU_FETCH_INT);
|
||||
|
||||
desc.vertex_descriptor.max_attribute_value =
|
||||
((input->location) > desc.vertex_descriptor.max_attribute_value) ?
|
||||
(input->location) :
|
||||
desc.vertex_descriptor.max_attribute_value;
|
||||
desc.vertex_descriptor.total_attributes++;
|
||||
|
||||
/* NOTE: We are setting max_attribute_value to be up to the maximum found index, because
|
||||
* of this, it is possible that we may skip over certain attributes if they were not in
|
||||
* the source GPUVertFormat. */
|
||||
MTL_LOG_DEBUG(
|
||||
" -- Batch Attribute(%d): ORIG Vert format: %d, Vert "
|
||||
"components: %d, Fetch Mode %d --> FINAL FORMAT: %d",
|
||||
input->location,
|
||||
(int)a->type.comp_type(),
|
||||
(int)a->type.comp_len(),
|
||||
(int)a->type.fetch_mode(),
|
||||
(int)pso_attr.format);
|
||||
|
||||
MTL_LOG_DEBUG(
|
||||
" -- [Batch] matching vertex attribute '%s' (Attribute Index: %d, Buffer index: "
|
||||
"%d, "
|
||||
"offset: %d)",
|
||||
name,
|
||||
input->location,
|
||||
binding.desc_id,
|
||||
attribute_offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
return binding;
|
||||
}
|
||||
|
||||
id<MTLRenderCommandEncoder> MTLBatch::bind()
|
||||
{
|
||||
/* Setup draw call and render pipeline state here. Called by every draw, but setup here so that
|
||||
* MTLDrawList only needs to perform setup a single time. */
|
||||
BLI_assert(this);
|
||||
|
||||
/* Fetch Metal device. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
if (!ctx) {
|
||||
BLI_assert_msg(false, "No context available for rendering.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
/* Fetch bound shader from context. */
|
||||
active_shader_ = static_cast<MTLShader *>(ctx->shader);
|
||||
|
||||
if (active_shader_ == nullptr || !active_shader_->is_valid()) {
|
||||
/* Skip drawing if there is no valid Metal shader.
|
||||
* This will occur if the path through which the shader is prepared
|
||||
* is invalid (e.g. Python without create-info), or, the source shader uses a geometry pass. */
|
||||
BLI_assert_msg(false, "No valid Metal shader!");
|
||||
return nil;
|
||||
}
|
||||
|
||||
/* Prepare Vertex Descriptor and extract VertexBuffers to bind. */
|
||||
MTLVertBuf *buffers[GPU_BATCH_VBO_MAX_LEN] = {nullptr};
|
||||
int binding_slot[GPU_BATCH_VBO_MAX_LEN] = {0};
|
||||
int num_buffers = 0;
|
||||
|
||||
/* Ensure Index Buffer is ready. */
|
||||
MTLIndexBuf *mtl_elem = static_cast<MTLIndexBuf *>(this->elem);
|
||||
if (mtl_elem != nullptr) {
|
||||
mtl_elem->upload_data();
|
||||
}
|
||||
|
||||
/* Populate vertex descriptor with attribute binding information.
|
||||
* The vertex descriptor and buffer layout descriptors describe
|
||||
* how vertex data from bound vertex buffers maps to the
|
||||
* shader's input.
|
||||
* A unique vertex descriptor will result in a new PipelineStateObject
|
||||
* being generated for the currently bound shader. */
|
||||
prepare_vertex_descriptor_and_bindings(
|
||||
{buffers, GPU_BATCH_VBO_MAX_LEN}, {binding_slot, GPU_BATCH_VBO_MAX_LEN}, num_buffers);
|
||||
|
||||
/* Prepare Vertex Buffers - Run before RenderCommandEncoder in case BlitCommandEncoder buffer
|
||||
* data operations are required. */
|
||||
for (int i = 0; i < num_buffers; i++) {
|
||||
buffers[i]->bind();
|
||||
}
|
||||
|
||||
/* Ensure render pass is active and fetch active RenderCommandEncoder. */
|
||||
id<MTLRenderCommandEncoder> rec = ctx->ensure_begin_render_pass();
|
||||
|
||||
/* Fetch RenderPassState to enable resource binding for active pass. */
|
||||
MTLRenderPassState &rps = ctx->main_command_buffer.get_render_pass_state();
|
||||
|
||||
/* Debug Check: Ensure Frame-buffer instance is not dirty. */
|
||||
BLI_assert(!ctx->main_command_buffer.get_active_framebuffer()->get_dirty());
|
||||
|
||||
/* GPU debug markers. */
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
ctx->main_command_buffer.unfold_pending_debug_groups();
|
||||
[rec pushDebugGroup:[NSString stringWithFormat:@"Batch(Shader:%s)",
|
||||
active_shader_->name_get().c_str()]];
|
||||
}
|
||||
|
||||
/*** Bind Vertex Buffers and Index Buffers **/
|
||||
|
||||
/* Ensure Context Render Pipeline State is fully setup and ready to execute the draw.
|
||||
* This should happen after all other final rendering setup is complete. */
|
||||
MTLPrimitiveType mtl_prim_type = gpu_prim_type_to_metal(this->prim_type);
|
||||
if (!ctx->ensure_render_pipeline_state(mtl_prim_type)) {
|
||||
MTL_LOG_ERROR("Failed to prepare and apply render pipeline state.");
|
||||
BLI_assert(false);
|
||||
return nil;
|
||||
}
|
||||
|
||||
/* Bind Vertex Buffers. */
|
||||
for (int i = 0; i < num_buffers; i++) {
|
||||
MTLVertBuf *buf_at_index = buffers[i];
|
||||
/* Buffer handle. */
|
||||
MTLVertBuf *mtlvbo = static_cast<MTLVertBuf *>(reinterpret_cast<VertBuf *>(buf_at_index));
|
||||
mtlvbo->flag_used();
|
||||
|
||||
/* Fetch buffer from MTLVertexBuffer and bind. */
|
||||
id<MTLBuffer> mtl_buffer = mtlvbo->get_metal_buffer();
|
||||
|
||||
BLI_assert(mtl_buffer != nil);
|
||||
rps.bind_vertex_buffer(mtl_buffer, 0, binding_slot[i]);
|
||||
}
|
||||
|
||||
/* Return Render Command Encoder used with setup. */
|
||||
return rec;
|
||||
}
|
||||
|
||||
void MTLBatch::unbind(id<MTLRenderCommandEncoder> rec)
|
||||
{
|
||||
/* Pop bind debug group. */
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
[rec popDebugGroup];
|
||||
}
|
||||
}
|
||||
|
||||
void MTLBatch::prepare_vertex_descriptor_and_bindings(MutableSpan<MTLVertBuf *> buffers,
|
||||
MutableSpan<int> buffer_slots,
|
||||
int &num_buffers)
|
||||
{
|
||||
|
||||
/* Here we populate the MTLContext vertex descriptor and resolve which buffers need to be bound.
|
||||
*/
|
||||
MTLStateManager *state_manager = static_cast<MTLStateManager *>(
|
||||
MTLContext::get()->state_manager);
|
||||
MTLRenderPipelineStateDescriptor &desc = state_manager->get_pipeline_descriptor();
|
||||
const MTLShaderInterface &interface = active_shader_->get_interface();
|
||||
uint16_t attr_mask = interface.enabled_attr_mask_;
|
||||
uint32_t buffer_mask = interface.vertex_buffer_mask();
|
||||
|
||||
/* Reset vertex descriptor to default state. */
|
||||
desc.reset_vertex_descriptor();
|
||||
|
||||
/* Fetch Vertex Buffers. */
|
||||
Span<VertBuf *> mtl_verts(this->verts, GPU_BATCH_VBO_MAX_LEN);
|
||||
|
||||
/* Resolve Metal vertex buffer bindings. */
|
||||
/* Vertex Descriptors
|
||||
* ------------------
|
||||
* Vertex Descriptors are required to generate a pipeline state, based on the current Batch's
|
||||
* buffer bindings. These bindings are a unique matching, depending on what input attributes a
|
||||
* batch has in its buffers, and those which are supported by the shader interface.
|
||||
*
|
||||
* We iterate through the buffers and resolve which attributes satisfy the requirements of the
|
||||
* currently bound shader. We cache this data, for a given Batch<->ShderInterface pairing in a
|
||||
* VAO cache to avoid the need to recalculate this data. */
|
||||
|
||||
VertexDescriptorShaderInterfacePair *pair = this->vao_cache.find(&interface);
|
||||
if (pair) {
|
||||
desc.vertex_descriptor = pair->vertex_descriptor;
|
||||
attr_mask = pair->attr_mask;
|
||||
num_buffers = pair->num_buffers;
|
||||
|
||||
for (int bid = 0; bid < GPU_BATCH_VBO_MAX_LEN; ++bid) {
|
||||
if (pair->bufferIds[bid].used) {
|
||||
buffers[bid] = static_cast<MTLVertBuf *>(this->verts[pair->bufferIds[bid].id]);
|
||||
buffer_slots[bid] = pair->bufferIds[bid].slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
VertexDescriptorShaderInterfacePair pair{};
|
||||
pair.interface = &interface;
|
||||
|
||||
for (int i = 0; i < GPU_BATCH_VBO_MAX_LEN; ++i) {
|
||||
pair.bufferIds[i].id = -1;
|
||||
pair.bufferIds[i].used = 0;
|
||||
}
|
||||
/* NOTE: Attribute extraction order from buffer is the reverse of the OpenGL as we flag once an
|
||||
* attribute is found, rather than pre-setting the mask. */
|
||||
|
||||
/* Extract Vertex attributes (First-bound vertex buffer takes priority). */
|
||||
for (int v = 0; v < GPU_BATCH_VBO_MAX_LEN; v++) {
|
||||
if (mtl_verts[v] != nullptr) {
|
||||
MTL_LOG_DEBUG(" -- [Batch] Checking bindings for bound vertex buffer %p", mtl_verts[v]);
|
||||
VertBufBinding bindings = this->prepare_vertex_binding(
|
||||
static_cast<MTLVertBuf *>(this->verts[v]), desc, interface, attr_mask, buffer_mask);
|
||||
if (bindings.desc_id >= 0) {
|
||||
buffers[bindings.desc_id] = static_cast<MTLVertBuf *>(this->verts[v]);
|
||||
buffer_slots[bindings.desc_id] = bindings.slot_id;
|
||||
pair.bufferIds[bindings.desc_id].id = v;
|
||||
pair.bufferIds[bindings.desc_id].used = 1;
|
||||
pair.bufferIds[bindings.desc_id].slot = bindings.slot_id;
|
||||
num_buffers = max_ii(bindings.desc_id + 1, num_buffers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Add to VertexDescriptor cache */
|
||||
pair.attr_mask = attr_mask;
|
||||
pair.vertex_descriptor = desc.vertex_descriptor;
|
||||
pair.num_buffers = num_buffers;
|
||||
if (!this->vao_cache.insert(pair)) {
|
||||
printf(
|
||||
"[Performance Warning] cache is full (Size: %d), vertex descriptor will not be cached\n",
|
||||
GPU_VAO_STATIC_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
/* DEBUG: verify if our attribute bindings have been fully provided as expected. */
|
||||
#if MTL_DEBUG_SHADER_ATTRIBUTES == 1
|
||||
if (attr_mask != 0) {
|
||||
/* Attributes are not necessarily contiguous. */
|
||||
for (int i = 0; i < active_shader_->get_interface()->get_total_attributes(); i++) {
|
||||
const MTLShaderInputAttribute &attr = active_shader_->get_interface()->get_attribute(i);
|
||||
if (attr_mask & (1 << attr.location)) {
|
||||
MTL_LOG_WARNING(
|
||||
"Warning: Missing expected attribute '%s' with location: %u in shader %s (attr "
|
||||
"number: %u)",
|
||||
active_shader_->get_interface()->name_at_offset(attr.name_offset),
|
||||
attr.location,
|
||||
active_shader_->name_get(),
|
||||
i);
|
||||
|
||||
/* If an attribute is not included, then format in vertex descriptor should be invalid due
|
||||
* to nil assignment. */
|
||||
BLI_assert(desc.vertex_descriptor.attributes[attr.location].format ==
|
||||
MTLVertexFormatInvalid);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MTLBatch::draw_advanced(int v_first, int v_count, int i_first, int i_count)
|
||||
{
|
||||
BLI_assert(v_count > 0 && i_count > 0);
|
||||
|
||||
/* Setup RenderPipelineState for batch. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
id<MTLRenderCommandEncoder> rec = this->bind();
|
||||
if (rec == nil) {
|
||||
/* End of draw. */
|
||||
this->unbind(rec);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Fetch IndexBuffer and resolve primitive type. */
|
||||
MTLIndexBuf *mtl_elem = static_cast<MTLIndexBuf *>(reinterpret_cast<IndexBuf *>(this->elem));
|
||||
MTLPrimitiveType mtl_prim_type = gpu_prim_type_to_metal(this->prim_type);
|
||||
|
||||
/* Perform regular draw. */
|
||||
if (mtl_elem == nullptr) {
|
||||
/* Primitive Type topology emulation. */
|
||||
if (mtl_needs_topology_emulation(this->prim_type)) {
|
||||
/* Generate index buffer for primitive types requiring emulation. */
|
||||
GPUPrimType emulated_prim_type = this->prim_type;
|
||||
uint32_t emulated_v_count = v_count;
|
||||
id<MTLBuffer> generated_index_buffer = this->get_emulated_toplogy_buffer(emulated_prim_type,
|
||||
emulated_v_count);
|
||||
BLI_assert(generated_index_buffer != nil);
|
||||
|
||||
MTLPrimitiveType emulated_mtl_prim_type = gpu_prim_type_to_metal(emulated_prim_type);
|
||||
|
||||
/* Temp: Disable culling for emulated primitive types.
|
||||
* TODO(Metal): Support face winding in topology buffer. */
|
||||
[rec setCullMode:MTLCullModeNone];
|
||||
|
||||
if (generated_index_buffer != nil) {
|
||||
BLI_assert(emulated_mtl_prim_type == MTLPrimitiveTypeTriangle ||
|
||||
emulated_mtl_prim_type == MTLPrimitiveTypeLine);
|
||||
if (emulated_mtl_prim_type == MTLPrimitiveTypeTriangle) {
|
||||
BLI_assert(emulated_v_count % 3 == 0);
|
||||
}
|
||||
if (emulated_mtl_prim_type == MTLPrimitiveTypeLine) {
|
||||
BLI_assert(emulated_v_count % 2 == 0);
|
||||
}
|
||||
|
||||
/* Set depth stencil state (TODO: Move it back upstream, is legacy of depth bias). */
|
||||
ctx->ensure_depth_stencil_state();
|
||||
|
||||
[rec drawIndexedPrimitives:emulated_mtl_prim_type
|
||||
indexCount:emulated_v_count
|
||||
indexType:MTLIndexTypeUInt32
|
||||
indexBuffer:generated_index_buffer
|
||||
indexBufferOffset:0
|
||||
instanceCount:i_count
|
||||
baseVertex:v_first
|
||||
baseInstance:i_first];
|
||||
}
|
||||
else {
|
||||
printf("[Note] Cannot draw batch -- Emulated Topology mode: %u not yet supported\n",
|
||||
this->prim_type);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Set depth stencil state (TODO: Move it back upstream, is legacy of depth bias). */
|
||||
ctx->ensure_depth_stencil_state();
|
||||
|
||||
/* Issue draw call. */
|
||||
[rec drawPrimitives:mtl_prim_type
|
||||
vertexStart:v_first
|
||||
vertexCount:v_count
|
||||
instanceCount:i_count
|
||||
baseInstance:i_first];
|
||||
}
|
||||
ctx->main_command_buffer.register_draw_counters(v_count * i_count);
|
||||
}
|
||||
/* Perform indexed draw. */
|
||||
else {
|
||||
|
||||
MTLIndexType index_type = MTLIndexBuf::gpu_index_type_to_metal(mtl_elem->index_type_);
|
||||
uint32_t base_index = mtl_elem->index_base_;
|
||||
uint32_t index_size = (mtl_elem->index_type_ == GPU_INDEX_U16) ? 2 : 4;
|
||||
uint32_t v_first_ofs = ((v_first + mtl_elem->index_start_) * index_size);
|
||||
BLI_assert_msg((v_first_ofs % index_size) == 0,
|
||||
"Index offset is not 2/4-byte aligned as per METAL spec");
|
||||
|
||||
/* Fetch index buffer. May return an index buffer of a differing format,
|
||||
* if index buffer optimization is used. In these cases, final_prim_type and
|
||||
* index_count get updated with the new properties. */
|
||||
GPUPrimType final_prim_type = this->prim_type;
|
||||
uint index_count = v_count;
|
||||
|
||||
id<MTLBuffer> index_buffer = mtl_elem->get_index_buffer(final_prim_type, index_count);
|
||||
mtl_prim_type = gpu_prim_type_to_metal(final_prim_type);
|
||||
BLI_assert(index_buffer != nil);
|
||||
|
||||
if (index_buffer != nil) {
|
||||
|
||||
/* Set depth stencil state (TODO: Move it back upstream, is legacy of depth bias). */
|
||||
ctx->ensure_depth_stencil_state();
|
||||
|
||||
/* Issue draw call. */
|
||||
[rec drawIndexedPrimitives:mtl_prim_type
|
||||
indexCount:index_count
|
||||
indexType:index_type
|
||||
indexBuffer:index_buffer
|
||||
indexBufferOffset:v_first_ofs
|
||||
instanceCount:i_count
|
||||
baseVertex:base_index
|
||||
baseInstance:i_first];
|
||||
ctx->main_command_buffer.register_draw_counters(index_count * i_count);
|
||||
}
|
||||
else {
|
||||
BLI_assert_msg(false, "Index buffer does not have backing Metal buffer");
|
||||
}
|
||||
}
|
||||
|
||||
/* End of draw. */
|
||||
this->unbind(rec);
|
||||
}
|
||||
|
||||
void MTLBatch::draw_advanced_indirect(StorageBuf *indirect_buf, intptr_t offset)
|
||||
{
|
||||
/* Setup RenderPipelineState for batch. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
id<MTLRenderCommandEncoder> rec = this->bind();
|
||||
if (rec == nil) {
|
||||
printf("Failed to open Render Command encoder for DRAW INDIRECT\n");
|
||||
|
||||
/* End of draw. */
|
||||
this->unbind(rec);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Fetch indirect buffer Metal handle. */
|
||||
MTLStorageBuf *mtlssbo = static_cast<MTLStorageBuf *>(indirect_buf);
|
||||
id<MTLBuffer> mtl_indirect_buf = mtlssbo->get_metal_buffer();
|
||||
BLI_assert(mtl_indirect_buf != nil);
|
||||
if (mtl_indirect_buf == nil) {
|
||||
MTL_LOG_WARNING("Metal Indirect Draw Storage Buffer is nil.");
|
||||
|
||||
/* End of draw. */
|
||||
this->unbind(rec);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Unsupported primitive type check. */
|
||||
BLI_assert_msg(this->prim_type != GPU_PRIM_TRI_FAN,
|
||||
"TriangleFan is not supported in Metal for Indirect draws.");
|
||||
|
||||
/* Fetch IndexBuffer and resolve primitive type. */
|
||||
MTLIndexBuf *mtl_elem = static_cast<MTLIndexBuf *>(reinterpret_cast<IndexBuf *>(this->elem));
|
||||
MTLPrimitiveType mtl_prim_type = gpu_prim_type_to_metal(this->prim_type);
|
||||
|
||||
if (mtl_needs_topology_emulation(this->prim_type)) {
|
||||
BLI_assert_msg(false, "Metal Topology emulation unsupported for draw indirect.\n");
|
||||
|
||||
/* End of draw. */
|
||||
this->unbind(rec);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mtl_elem == nullptr) {
|
||||
/* Set depth stencil state (TODO: Move it back upstream, is legacy of depth bias). */
|
||||
ctx->ensure_depth_stencil_state();
|
||||
|
||||
/* Issue draw call. */
|
||||
[rec drawPrimitives:mtl_prim_type indirectBuffer:mtl_indirect_buf indirectBufferOffset:offset];
|
||||
ctx->main_command_buffer.register_draw_counters(1);
|
||||
}
|
||||
else {
|
||||
/* Fetch index buffer. May return an index buffer of a differing format,
|
||||
* if index buffer optimization is used. In these cases, final_prim_type and
|
||||
* index_count get updated with the new properties. */
|
||||
MTLIndexType index_type = MTLIndexBuf::gpu_index_type_to_metal(mtl_elem->index_type_);
|
||||
GPUPrimType final_prim_type = this->prim_type;
|
||||
uint index_count = 0;
|
||||
|
||||
/* Disable index optimization for indirect draws. */
|
||||
mtl_elem->flag_can_optimize(false);
|
||||
|
||||
id<MTLBuffer> index_buffer = mtl_elem->get_index_buffer(final_prim_type, index_count);
|
||||
mtl_prim_type = gpu_prim_type_to_metal(final_prim_type);
|
||||
BLI_assert(index_buffer != nil);
|
||||
|
||||
if (index_buffer != nil) {
|
||||
|
||||
/* Set depth stencil state (TODO: Move it back upstream, is legacy of depth bias). */
|
||||
ctx->ensure_depth_stencil_state();
|
||||
|
||||
/* Issue draw call. */
|
||||
[rec drawIndexedPrimitives:mtl_prim_type
|
||||
indexType:index_type
|
||||
indexBuffer:index_buffer
|
||||
indexBufferOffset:0
|
||||
indirectBuffer:mtl_indirect_buf
|
||||
indirectBufferOffset:offset];
|
||||
ctx->main_command_buffer.register_draw_counters(1);
|
||||
}
|
||||
else {
|
||||
BLI_assert_msg(false, "Index buffer does not have backing Metal buffer");
|
||||
}
|
||||
}
|
||||
|
||||
/* End of draw. */
|
||||
this->unbind(rec);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Topology emulation and optimization
|
||||
* \{ */
|
||||
|
||||
id<MTLBuffer> MTLBatch::get_emulated_toplogy_buffer(GPUPrimType &in_out_prim_type,
|
||||
uint32_t &in_out_v_count)
|
||||
{
|
||||
|
||||
BLI_assert(in_out_v_count > 0);
|
||||
/* Determine emulated primitive types. */
|
||||
GPUPrimType input_prim_type = in_out_prim_type;
|
||||
uint32_t v_count = in_out_v_count;
|
||||
GPUPrimType output_prim_type;
|
||||
switch (input_prim_type) {
|
||||
case GPU_PRIM_POINTS:
|
||||
case GPU_PRIM_LINES:
|
||||
case GPU_PRIM_TRIS:
|
||||
BLI_assert_msg(false, "Optimal primitive types should not reach here.");
|
||||
return nil;
|
||||
break;
|
||||
case GPU_PRIM_LINES_ADJ:
|
||||
case GPU_PRIM_TRIS_ADJ:
|
||||
BLI_assert_msg(false, "Adjacency primitive types should not reach here.");
|
||||
return nil;
|
||||
break;
|
||||
case GPU_PRIM_LINE_STRIP:
|
||||
case GPU_PRIM_LINE_LOOP:
|
||||
case GPU_PRIM_LINE_STRIP_ADJ:
|
||||
output_prim_type = GPU_PRIM_LINES;
|
||||
break;
|
||||
case GPU_PRIM_TRI_STRIP:
|
||||
case GPU_PRIM_TRI_FAN:
|
||||
output_prim_type = GPU_PRIM_TRIS;
|
||||
break;
|
||||
default:
|
||||
BLI_assert_msg(false, "Invalid primitive type.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
/* Check if topology buffer exists and is valid. */
|
||||
if (this->emulated_topology_buffer_ != nullptr &&
|
||||
(emulated_topology_type_ != input_prim_type || topology_buffer_input_v_count_ != v_count))
|
||||
{
|
||||
|
||||
/* Release existing topology buffer. */
|
||||
emulated_topology_buffer_->free();
|
||||
emulated_topology_buffer_ = nullptr;
|
||||
}
|
||||
|
||||
/* Generate new topology index buffer. */
|
||||
if (this->emulated_topology_buffer_ == nullptr) {
|
||||
/* Calculate IB len. */
|
||||
uint32_t output_prim_count = 0;
|
||||
switch (input_prim_type) {
|
||||
case GPU_PRIM_LINE_STRIP:
|
||||
case GPU_PRIM_LINE_STRIP_ADJ:
|
||||
output_prim_count = v_count - 1;
|
||||
break;
|
||||
case GPU_PRIM_LINE_LOOP:
|
||||
output_prim_count = v_count;
|
||||
break;
|
||||
case GPU_PRIM_TRI_STRIP:
|
||||
case GPU_PRIM_TRI_FAN:
|
||||
output_prim_count = v_count - 2;
|
||||
break;
|
||||
default:
|
||||
BLI_assert_msg(false, "Cannot generate optimized topology buffer for other types.");
|
||||
break;
|
||||
}
|
||||
uint32_t output_IB_elems = output_prim_count * ((output_prim_type == GPU_PRIM_TRIS) ? 3 : 2);
|
||||
|
||||
/* Allocate buffer. */
|
||||
uint32_t buffer_bytes = output_IB_elems * 4;
|
||||
BLI_assert(buffer_bytes > 0);
|
||||
this->emulated_topology_buffer_ = MTLContext::get_global_memory_manager()->allocate(
|
||||
buffer_bytes, true);
|
||||
|
||||
/* Populate. */
|
||||
uint32_t *data = (uint32_t *)this->emulated_topology_buffer_->get_host_ptr();
|
||||
BLI_assert(data != nullptr);
|
||||
|
||||
/* TODO(Metal): Support inverse winding modes. */
|
||||
bool winding_clockwise = false;
|
||||
UNUSED_VARS(winding_clockwise);
|
||||
|
||||
switch (input_prim_type) {
|
||||
/* Line Loop. */
|
||||
case GPU_PRIM_LINE_LOOP: {
|
||||
int line = 0;
|
||||
for (line = 0; line < output_prim_count - 1; line++) {
|
||||
data[line * 2 + 0] = line + 0;
|
||||
data[line * 2 + 1] = line + 1;
|
||||
}
|
||||
/* Closing line. */
|
||||
data[line * 2 + 0] = line + 0;
|
||||
data[line * 2 + 1] = 0;
|
||||
} break;
|
||||
|
||||
/* Triangle Fan. */
|
||||
case GPU_PRIM_TRI_FAN: {
|
||||
for (int triangle = 0; triangle < output_prim_count; triangle++) {
|
||||
data[triangle * 3 + 0] = 0; /* Always 0 */
|
||||
data[triangle * 3 + 1] = triangle + 1;
|
||||
data[triangle * 3 + 2] = triangle + 2;
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
BLI_assert_msg(false, "Other primitive types do not require emulation.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
/* Flush. */
|
||||
this->emulated_topology_buffer_->flush();
|
||||
/* Assign members relating to current cached IB. */
|
||||
topology_buffer_input_v_count_ = v_count;
|
||||
topology_buffer_output_v_count_ = output_IB_elems;
|
||||
emulated_topology_type_ = input_prim_type;
|
||||
}
|
||||
|
||||
/* Return. */
|
||||
in_out_v_count = topology_buffer_output_v_count_;
|
||||
in_out_prim_type = output_prim_type;
|
||||
return (emulated_topology_buffer_) ? emulated_topology_buffer_->get_metal_buffer() : nil;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::gpu
|
||||
70
blender-5.2.0/source/blender/gpu/metal/mtl_capabilities.hh
Normal file
70
blender-5.2.0/source/blender/gpu/metal/mtl_capabilities.hh
Normal file
@@ -0,0 +1,70 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/*** Derived from: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf ***/
|
||||
/** Upper Bound/Fixed Limits **/
|
||||
|
||||
/** Must match because of #StateManager::image_formats. */
|
||||
#define MTL_MAX_IMAGE_SLOTS GPU_MAX_IMAGE
|
||||
#define MTL_MAX_SAMPLER_SLOTS 64
|
||||
/* Theoretical limit is 128 no target hardware. */
|
||||
#define MTL_MAX_TEXTURE_SLOTS (MTL_MAX_SAMPLER_SLOTS + MTL_MAX_IMAGE_SLOTS)
|
||||
/* Max limit without using bind-less for samplers. */
|
||||
#define MTL_MAX_DEFAULT_SAMPLERS 16
|
||||
/* Total maximum buffers which can be bound to an encoder, for use within a shader.
|
||||
* Uniform buffers and storage buffers share the set of available bind buffers.
|
||||
* The total number of buffer bindings must be <= MTL_MAX_BUFFER_BINDINGS
|
||||
* We also require an additional 2 core buffers for:
|
||||
* - Argument buffer for bindless resources (e.g. samplers)
|
||||
* - Default push constant block
|
||||
* Along with up to 6+1 buffers for vertex data, and index data. */
|
||||
#define MTL_MAX_BUFFER_BINDINGS 31
|
||||
#define MTL_MAX_VERTEX_INPUT_ATTRIBUTES 31
|
||||
#define MTL_MAX_UNIFORMS_PER_BLOCK 64
|
||||
|
||||
#define MTL_MAX_SET_BYTES_SIZE 4096
|
||||
|
||||
enum AppleGPUType { APPLE_GPU_UNKNOWN = 0, APPLE_GPU_M1 = 1, APPLE_GPU_M2 = 2, APPLE_GPU_M3 = 3 };
|
||||
|
||||
/* Context-specific limits -- populated in 'MTLBackend::platform_init' */
|
||||
struct MTLCapabilities {
|
||||
|
||||
/* Variable Limits & feature sets. */
|
||||
int max_color_render_targets = 4; /* Minimum = 4 */
|
||||
int buffer_alignment_for_textures = 256; /* Upper bound = 256 bytes */
|
||||
int minimum_buffer_offset_alignment = 256; /* Upper bound = 256 bytes */
|
||||
|
||||
/* Capabilities */
|
||||
bool supports_vertex_amplification = false;
|
||||
bool supports_texture_swizzle = true;
|
||||
bool supports_cubemaps = true;
|
||||
bool supports_layered_rendering = true;
|
||||
bool supports_memory_barriers = false;
|
||||
bool supports_sampler_border_color = false;
|
||||
bool supports_argument_buffers_tier2 = false;
|
||||
bool supports_texture_gather = false;
|
||||
bool supports_texture_atomics = false;
|
||||
bool supports_native_tile_inputs = false;
|
||||
|
||||
/* GPU Family */
|
||||
bool supports_family_mac1 = false;
|
||||
bool supports_family_mac2 = false;
|
||||
bool supports_family_mac_catalyst1 = false;
|
||||
bool supports_family_mac_catalyst2 = false;
|
||||
AppleGPUType gpu = APPLE_GPU_UNKNOWN;
|
||||
|
||||
/* CPU Info */
|
||||
int num_performance_cores = -1;
|
||||
int num_efficiency_cores = -1;
|
||||
};
|
||||
|
||||
} // namespace blender::gpu
|
||||
261
blender-5.2.0/source/blender/gpu/metal/mtl_command_buffer.hh
Normal file
261
blender-5.2.0/source/blender/gpu/metal/mtl_command_buffer.hh
Normal file
@@ -0,0 +1,261 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mtl_capabilities.hh"
|
||||
#include "mtl_common.hh"
|
||||
#include "mtl_debug.hh"
|
||||
#include "mtl_memory.hh"
|
||||
#include "mtl_texture.hh"
|
||||
|
||||
/* Metal profiling tools complain about redundant bindings. Using our own tracking mechanism we can
|
||||
* avoid these redundant binds. Set to 0 to turn off this feature. */
|
||||
#define MTL_ENABLE_REDUNDANT_BINDING_OPTIMIZATION 1
|
||||
/* Avoid using the offset only update and force rebind even if buffer is the same. */
|
||||
#define MTL_FORCE_BUFFER_REBIND 0
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/* Combined sampler state configuration for Argument Buffer caching. */
|
||||
struct MTLSamplerArray {
|
||||
uint num_samplers;
|
||||
/* MTLSamplerState permutations between 0..256 - slightly more than a byte. */
|
||||
MTLSamplerState mtl_sampler_flags[MTL_MAX_TEXTURE_SLOTS];
|
||||
id<MTLSamplerState> mtl_sampler[MTL_MAX_TEXTURE_SLOTS];
|
||||
|
||||
bool operator==(const MTLSamplerArray &other) const
|
||||
{
|
||||
if (this->num_samplers != other.num_samplers) {
|
||||
return false;
|
||||
}
|
||||
return (memcmp(this->mtl_sampler_flags,
|
||||
other.mtl_sampler_flags,
|
||||
sizeof(MTLSamplerState) * this->num_samplers) == 0);
|
||||
}
|
||||
|
||||
uint32_t hash() const
|
||||
{
|
||||
uint32_t hash = this->num_samplers;
|
||||
for (int i = 0; i < this->num_samplers; i++) {
|
||||
hash ^= uint32_t(this->mtl_sampler_flags[i]) << (i % 3);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
/* Structs containing information on current binding state for textures and samplers. */
|
||||
struct MTLTextureBinding {
|
||||
gpu::MTLTexture *texture_resource = nullptr;
|
||||
};
|
||||
|
||||
struct MTLSamplerBinding {
|
||||
gpu::MTLSamplerState state = DEFAULT_SAMPLER_STATE;
|
||||
|
||||
bool operator==(MTLSamplerBinding const &other) const
|
||||
{
|
||||
return state == other.state;
|
||||
}
|
||||
|
||||
id<MTLSamplerState> get_mtl_sampler(MTLContext &ctx);
|
||||
};
|
||||
|
||||
/* Caching of CommandEncoder Vertex/Fragment buffer bindings. */
|
||||
struct MTLBufferBindingCached {
|
||||
id<MTLBuffer> metal_buffer = nil;
|
||||
uint64_t offset = -1;
|
||||
};
|
||||
|
||||
/* Caching of CommandEncoder textures bindings. */
|
||||
struct MTLTextureBindingCached {
|
||||
id<MTLTexture> metal_texture = nil;
|
||||
};
|
||||
|
||||
/* Cached of CommandEncoder sampler states. */
|
||||
struct MTLSamplerStateBindingCached {
|
||||
gpu::MTLSamplerState binding_state = DEFAULT_SAMPLER_STATE;
|
||||
id<MTLSamplerState> sampler_state = nil;
|
||||
bool is_arg_buffer_binding = false;
|
||||
};
|
||||
|
||||
/* Thin wrappers over to allow overloading of objective-C methods that have different names per
|
||||
* encoder. */
|
||||
|
||||
struct MTLComputeCommandEncoder {
|
||||
id<MTLComputeCommandEncoder> enc;
|
||||
|
||||
MTLComputeCommandEncoder(id<MTLComputeCommandEncoder> encoder) : enc(encoder) {}
|
||||
|
||||
void set_buffer_offset(size_t offset, int index);
|
||||
void set_buffer(id<MTLBuffer> buf, size_t offset, int index);
|
||||
void set_bytes(const void *bytes, size_t length, int index);
|
||||
void set_texture(id<MTLTexture> tex, int index);
|
||||
void set_sampler(id<MTLSamplerState> sampler_state, int index);
|
||||
};
|
||||
|
||||
struct MTLVertexCommandEncoder {
|
||||
id<MTLRenderCommandEncoder> enc;
|
||||
|
||||
MTLVertexCommandEncoder(id<MTLRenderCommandEncoder> encoder) : enc(encoder) {}
|
||||
|
||||
void set_buffer_offset(size_t offset, int index);
|
||||
void set_buffer(id<MTLBuffer> buf, size_t offset, int index);
|
||||
void set_bytes(const void *bytes, size_t length, int index);
|
||||
void set_texture(id<MTLTexture> tex, int index);
|
||||
void set_sampler(id<MTLSamplerState> sampler_state, int index);
|
||||
};
|
||||
|
||||
struct MTLFragmentCommandEncoder {
|
||||
id<MTLRenderCommandEncoder> enc;
|
||||
|
||||
MTLFragmentCommandEncoder(id<MTLRenderCommandEncoder> encoder) : enc(encoder) {}
|
||||
|
||||
void set_buffer_offset(size_t offset, int index);
|
||||
void set_buffer(id<MTLBuffer> buf, size_t offset, int index);
|
||||
void set_bytes(const void *bytes, size_t length, int index);
|
||||
void set_texture(id<MTLTexture> tex, int index);
|
||||
void set_sampler(id<MTLSamplerState> sampler_state, int index);
|
||||
};
|
||||
|
||||
/* Class to remove redundant resource bindings. */
|
||||
template<typename CommandEncoderT> struct MTLBindingCache {
|
||||
/* Indexed by final backend bindings, not by shader interface bindings. */
|
||||
std::array<MTLBufferBindingCached, MTL_MAX_BUFFER_BINDINGS> buffer_bindings = {};
|
||||
std::array<MTLTextureBindingCached, MTL_MAX_TEXTURE_SLOTS> texture_bindings = {};
|
||||
std::array<MTLSamplerStateBindingCached, MTL_MAX_TEXTURE_SLOTS> sampler_state_bindings = {};
|
||||
|
||||
void bind_buffer(CommandEncoderT enc, id<MTLBuffer> buf, size_t offset, uint index);
|
||||
void bind_bytes(CommandEncoderT enc,
|
||||
MTLScratchBufferManager &scratch_buffer,
|
||||
const void *bytes,
|
||||
size_t length,
|
||||
uint index);
|
||||
void bind_texture(CommandEncoderT enc, id<MTLTexture> tex, uint index);
|
||||
void bind_sampler(CommandEncoderT enc,
|
||||
gpu::MTLSamplerArray &sampler_array,
|
||||
id<MTLSamplerState> sampler_state,
|
||||
gpu::MTLSamplerState binding_state,
|
||||
bool use_samplers_argument_buffer,
|
||||
uint index);
|
||||
};
|
||||
|
||||
template<typename CommandEncoderT>
|
||||
void MTLBindingCache<CommandEncoderT>::bind_buffer(CommandEncoderT enc,
|
||||
id<MTLBuffer> buf,
|
||||
size_t offset,
|
||||
uint index)
|
||||
{
|
||||
BLI_assert(buf != nil);
|
||||
BLI_assert(index >= 0);
|
||||
BLI_assert(index < MTL_MAX_BUFFER_BINDINGS);
|
||||
MTLBufferBindingCached &binding = this->buffer_bindings[index];
|
||||
|
||||
#if MTL_ENABLE_REDUNDANT_BINDING_OPTIMIZATION
|
||||
if (binding.metal_buffer == buf && binding.offset == offset) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (binding.metal_buffer == buf && MTL_FORCE_BUFFER_REBIND == 0) {
|
||||
enc.set_buffer_offset(offset, index);
|
||||
}
|
||||
else {
|
||||
enc.set_buffer(buf, offset, index);
|
||||
}
|
||||
|
||||
binding.metal_buffer = buf;
|
||||
binding.offset = offset;
|
||||
}
|
||||
|
||||
template<typename CommandEncoderT>
|
||||
void MTLBindingCache<CommandEncoderT>::bind_bytes(CommandEncoderT enc,
|
||||
MTLScratchBufferManager &scratch_buffer,
|
||||
const void *bytes,
|
||||
size_t length,
|
||||
uint index)
|
||||
{
|
||||
/* Bytes are always updated as source data may have changed. */
|
||||
BLI_assert(index >= 0 && index < MTL_MAX_BUFFER_BINDINGS);
|
||||
BLI_assert(length > 0);
|
||||
BLI_assert(bytes != nullptr);
|
||||
MTLBufferBindingCached &binding = this->buffer_bindings[index];
|
||||
|
||||
if (length >= MTL_MAX_SET_BYTES_SIZE) {
|
||||
/* We have run over the setBytes limit, bind buffer instead. */
|
||||
MTLTemporaryBuffer range = scratch_buffer.scratch_buffer_allocate_range_aligned(length, 256);
|
||||
memcpy(range.data, bytes, length);
|
||||
this->bind_buffer(enc, range.metal_buffer, range.buffer_offset, index);
|
||||
return;
|
||||
}
|
||||
|
||||
enc.set_bytes(bytes, length, index);
|
||||
|
||||
binding.metal_buffer = nil;
|
||||
binding.offset = -1;
|
||||
}
|
||||
|
||||
template<typename CommandEncoderT>
|
||||
void MTLBindingCache<CommandEncoderT>::bind_texture(CommandEncoderT enc,
|
||||
id<MTLTexture> tex,
|
||||
uint index)
|
||||
{
|
||||
BLI_assert(tex != nil);
|
||||
BLI_assert(index >= 0);
|
||||
BLI_assert(index < MTL_MAX_TEXTURE_SLOTS);
|
||||
MTLTextureBindingCached &binding = this->texture_bindings[index];
|
||||
|
||||
#if MTL_ENABLE_REDUNDANT_BINDING_OPTIMIZATION
|
||||
if (binding.metal_texture == tex) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
enc.set_texture(tex, index);
|
||||
|
||||
binding.metal_texture = tex;
|
||||
}
|
||||
|
||||
template<typename CommandEncoderT>
|
||||
void MTLBindingCache<CommandEncoderT>::bind_sampler(CommandEncoderT enc,
|
||||
gpu::MTLSamplerArray &sampler_array,
|
||||
id<MTLSamplerState> sampler_state,
|
||||
gpu::MTLSamplerState binding_state,
|
||||
bool use_samplers_argument_buffer,
|
||||
uint index)
|
||||
{
|
||||
BLI_assert(index >= 0);
|
||||
BLI_assert(index < MTL_MAX_TEXTURE_SLOTS);
|
||||
MTLSamplerStateBindingCached &binding = this->sampler_state_bindings[index];
|
||||
|
||||
#if MTL_ENABLE_REDUNDANT_BINDING_OPTIMIZATION
|
||||
/* If sampler state has not changed for the given slot, we do not need to fetch. */
|
||||
if (binding.sampler_state != nil && binding.binding_state == binding_state &&
|
||||
!use_samplers_argument_buffer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Flag last binding type. */
|
||||
binding.is_arg_buffer_binding = use_samplers_argument_buffer;
|
||||
|
||||
/* Always assign to argument buffer samplers binding array - Efficiently ensures the value in
|
||||
* the samplers array is always up to date. */
|
||||
sampler_array.mtl_sampler[index] = sampler_state;
|
||||
sampler_array.mtl_sampler_flags[index] = binding_state;
|
||||
|
||||
if (use_samplers_argument_buffer) {
|
||||
/* TODO(fclem): Always updating the argument buffer is something that was done before the huge
|
||||
* Metal backend refactor. Maybe we can move all the update inside this if clause. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Update binding and cached state. */
|
||||
enc.set_sampler(sampler_state, index);
|
||||
|
||||
binding.binding_state = binding_state;
|
||||
binding.sampler_state = sampler_state;
|
||||
}
|
||||
|
||||
} // namespace blender::gpu
|
||||
940
blender-5.2.0/source/blender/gpu/metal/mtl_command_buffer.mm
Normal file
940
blender-5.2.0/source/blender/gpu/metal/mtl_command_buffer.mm
Normal file
@@ -0,0 +1,940 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "DNA_userdef_types.h"
|
||||
|
||||
#include "GPU_debug.hh"
|
||||
|
||||
#include "intern/GHOST_ContextMTL.hh"
|
||||
|
||||
#include "mtl_backend.hh"
|
||||
#include "mtl_command_buffer.hh"
|
||||
#include "mtl_common.hh"
|
||||
#include "mtl_context.hh"
|
||||
#include "mtl_debug.hh"
|
||||
#include "mtl_framebuffer.hh"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
using namespace blender::gpu;
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/* Counter for active command buffers. */
|
||||
volatile std::atomic<int> MTLCommandBufferManager::num_active_cmd_bufs_in_system = 0;
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name MTLCommandBuffer initialization and render coordination.
|
||||
* \{ */
|
||||
|
||||
void MTLCommandBufferManager::prepare(bool /*supports_render*/)
|
||||
{
|
||||
render_pass_state_.reset_state();
|
||||
compute_state_.reset_state();
|
||||
}
|
||||
|
||||
void MTLCommandBufferManager::register_encoder_counters()
|
||||
{
|
||||
encoder_count_++;
|
||||
empty_ = false;
|
||||
}
|
||||
|
||||
id<MTLCommandBuffer> MTLCommandBufferManager::ensure_begin()
|
||||
{
|
||||
if (active_command_buffer_ == nil) {
|
||||
|
||||
/* Verify number of active command buffers is below limit.
|
||||
* Exceeding this limit will mean we either have a command buffer leak/GPU hang
|
||||
* or we should increase the command buffer limit during MTLQueue creation.
|
||||
* Excessive command buffers can also be caused by frequent GPUContext switches, which cause
|
||||
* the GPU pipeline to flush. This is common during indirect light baking operations.
|
||||
*
|
||||
* NOTE: We currently stall until completion of GPU work upon ::submit if we have reached the
|
||||
* in-flight command buffer limit. */
|
||||
BLI_assert(MTLCommandBufferManager::num_active_cmd_bufs_in_system <
|
||||
GHOST_ContextMTL::max_command_buffer_count);
|
||||
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
/* Debug: Enable Advanced Errors for GPU work execution. */
|
||||
MTLCommandBufferDescriptor *desc = [[MTLCommandBufferDescriptor alloc] init];
|
||||
desc.errorOptions = MTLCommandBufferErrorOptionEncoderExecutionStatus;
|
||||
desc.retainedReferences = YES;
|
||||
BLI_assert(context_.queue != nil);
|
||||
active_command_buffer_ = [context_.queue commandBufferWithDescriptor:desc];
|
||||
|
||||
std::string group_name = "CmdBuf: " + GPU_debug_get_groups_names({0, 1});
|
||||
[active_command_buffer_ setLabel:@(group_name.c_str())];
|
||||
[desc release];
|
||||
}
|
||||
else {
|
||||
active_command_buffer_ = [context_.queue commandBuffer];
|
||||
}
|
||||
|
||||
[active_command_buffer_ retain];
|
||||
context_.main_command_buffer.inc_active_command_buffer_count();
|
||||
|
||||
/* Ensure we begin new Scratch Buffer if we are on a new frame. */
|
||||
MTLScratchBufferManager &mem = context_.memory_manager;
|
||||
mem.ensure_increment_scratch_buffer();
|
||||
|
||||
/* Reset Command buffer heuristics. */
|
||||
this->reset_counters();
|
||||
}
|
||||
BLI_assert(active_command_buffer_ != nil);
|
||||
return active_command_buffer_;
|
||||
}
|
||||
|
||||
/* If wait is true, CPU will stall until GPU work has completed. */
|
||||
bool MTLCommandBufferManager::submit(bool wait)
|
||||
{
|
||||
/* If we have to wait, we must ensure to either finish the active command encoder or to wait
|
||||
* until all previous one have completed. */
|
||||
if (empty_ && !wait) {
|
||||
/* Skip submission if command buffer is empty. */
|
||||
return false;
|
||||
}
|
||||
|
||||
if (active_command_buffer_ == nil) {
|
||||
if (wait) {
|
||||
/* Wait for any previously submitted work on this context to complete.
|
||||
* (The wait function will yield so may need reworking if this hits a
|
||||
* performance critical path which is sensitive to CPU<->GPU latency) */
|
||||
wait_until_active_command_buffers_complete();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Ensure current encoders are finished. */
|
||||
this->end_active_command_encoder();
|
||||
BLI_assert(active_command_encoder_type_ == MTL_NO_COMMAND_ENCODER);
|
||||
|
||||
/* Flush active ScratchBuffer associated with parent MTLContext. */
|
||||
context_.memory_manager.flush_active_scratch_buffer();
|
||||
|
||||
/*** Submit Command Buffer. ***/
|
||||
/* Command buffer lifetime tracking. */
|
||||
/* Increment current MTLSafeFreeList reference counter to flag MTLBuffers freed within
|
||||
* the current command buffer lifetime as used.
|
||||
* This ensures that in-use resources are not prematurely de-referenced and returned to the
|
||||
* available buffer pool while they are in-use by the GPU. */
|
||||
MTLSafeFreeList *cmd_free_buffer_list =
|
||||
MTLContext::get_global_memory_manager()->get_current_safe_list();
|
||||
BLI_assert(cmd_free_buffer_list);
|
||||
cmd_free_buffer_list->increment_reference();
|
||||
|
||||
id<MTLCommandBuffer> cmd_buffer_ref = active_command_buffer_;
|
||||
[cmd_buffer_ref retain];
|
||||
|
||||
[cmd_buffer_ref addCompletedHandler:^(id<MTLCommandBuffer> /*cb*/) {
|
||||
/* Upon command buffer completion, decrement MTLSafeFreeList reference count
|
||||
* to allow buffers no longer in use by this CommandBuffer to be freed. */
|
||||
cmd_free_buffer_list->decrement_reference();
|
||||
|
||||
/* Release command buffer after completion callback handled. */
|
||||
[cmd_buffer_ref release];
|
||||
|
||||
/* Decrement count. */
|
||||
context_.main_command_buffer.dec_active_command_buffer_count();
|
||||
}];
|
||||
|
||||
/* Submit command buffer to GPU. */
|
||||
[active_command_buffer_ commit];
|
||||
|
||||
/* If we have too many active command buffers in flight, wait until completed to avoid running
|
||||
* out. We can increase */
|
||||
if (MTLCommandBufferManager::num_active_cmd_bufs_in_system >=
|
||||
(GHOST_ContextMTL::max_command_buffer_count - 1))
|
||||
{
|
||||
wait = true;
|
||||
MTL_LOG_WARNING(
|
||||
"Maximum number of command buffers in flight. Host will wait until GPU work has "
|
||||
"completed. Consider increasing GHOST_ContextMTL::max_command_buffer_count or reducing "
|
||||
"work fragmentation to better utilize system hardware. Command buffers are flushed upon "
|
||||
"GPUContext switches, this is the most common cause of excessive command buffer "
|
||||
"generation.");
|
||||
}
|
||||
|
||||
if (wait || (G.debug & G_DEBUG_GPU)) {
|
||||
/* Wait until current GPU work has finished executing. */
|
||||
[active_command_buffer_ waitUntilCompleted];
|
||||
|
||||
/* Command buffer execution debugging can return an error message if
|
||||
* execution has failed or encountered GPU-side errors. */
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
|
||||
NSError *error = [active_command_buffer_ error];
|
||||
if (error != nil) {
|
||||
NSLog(@"%@", error);
|
||||
BLI_assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Release previous frames command buffer and reset active cmd buffer. */
|
||||
if (last_submitted_command_buffer_ != nil) {
|
||||
|
||||
BLI_assert(MTLBackend::get()->is_inside_render_boundary());
|
||||
[last_submitted_command_buffer_ autorelease];
|
||||
last_submitted_command_buffer_ = nil;
|
||||
}
|
||||
last_submitted_command_buffer_ = active_command_buffer_;
|
||||
active_command_buffer_ = nil;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Render Command Encoder Utility and management functions.
|
||||
* \{ */
|
||||
|
||||
/* Fetch/query current encoder. */
|
||||
bool MTLCommandBufferManager::is_inside_render_pass()
|
||||
{
|
||||
return (active_command_encoder_type_ == MTL_RENDER_COMMAND_ENCODER);
|
||||
}
|
||||
|
||||
bool MTLCommandBufferManager::is_inside_blit()
|
||||
{
|
||||
return (active_command_encoder_type_ == MTL_BLIT_COMMAND_ENCODER);
|
||||
}
|
||||
|
||||
bool MTLCommandBufferManager::is_inside_compute()
|
||||
{
|
||||
return (active_command_encoder_type_ == MTL_COMPUTE_COMMAND_ENCODER);
|
||||
}
|
||||
|
||||
id<MTLRenderCommandEncoder> MTLCommandBufferManager::get_active_render_command_encoder()
|
||||
{
|
||||
/* Calling code should check if inside render pass. Otherwise nil. */
|
||||
return active_render_command_encoder_;
|
||||
}
|
||||
|
||||
id<MTLBlitCommandEncoder> MTLCommandBufferManager::get_active_blit_command_encoder()
|
||||
{
|
||||
/* Calling code should check if inside render pass. Otherwise nil. */
|
||||
return active_blit_command_encoder_;
|
||||
}
|
||||
|
||||
id<MTLComputeCommandEncoder> MTLCommandBufferManager::get_active_compute_command_encoder()
|
||||
{
|
||||
/* Calling code should check if inside render pass. Otherwise nil. */
|
||||
return active_compute_command_encoder_;
|
||||
}
|
||||
|
||||
MTLFrameBuffer *MTLCommandBufferManager::get_active_framebuffer()
|
||||
{
|
||||
/* If outside of RenderPass, nullptr will be returned. */
|
||||
if (this->is_inside_render_pass()) {
|
||||
return active_frame_buffer_;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Encoder and Pass management. */
|
||||
/* End currently active MTLCommandEncoder. */
|
||||
bool MTLCommandBufferManager::end_active_command_encoder(bool retain_framebuffers)
|
||||
{
|
||||
/* End active encoder if one is active. */
|
||||
if (active_command_encoder_type_ == MTL_NO_COMMAND_ENCODER) {
|
||||
/* MTL_NO_COMMAND_ENCODER. */
|
||||
BLI_assert(active_render_command_encoder_ == nil);
|
||||
BLI_assert(active_blit_command_encoder_ == nil);
|
||||
BLI_assert(active_compute_command_encoder_ == nil);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
fold_remaining_debug_groups();
|
||||
}
|
||||
|
||||
switch (active_command_encoder_type_) {
|
||||
case MTL_RENDER_COMMAND_ENCODER:
|
||||
BLI_assert(active_render_command_encoder_ != nil);
|
||||
[active_render_command_encoder_ endEncoding];
|
||||
[active_render_command_encoder_ release];
|
||||
active_render_command_encoder_ = nil;
|
||||
/* Reset associated frame-buffer flag. */
|
||||
if (!retain_framebuffers) {
|
||||
active_frame_buffer_ = nullptr;
|
||||
active_pass_descriptor_ = nullptr;
|
||||
}
|
||||
break;
|
||||
case MTL_BLIT_COMMAND_ENCODER:
|
||||
BLI_assert(active_blit_command_encoder_ != nil);
|
||||
[active_blit_command_encoder_ endEncoding];
|
||||
[active_blit_command_encoder_ release];
|
||||
active_blit_command_encoder_ = nil;
|
||||
break;
|
||||
case MTL_COMPUTE_COMMAND_ENCODER:
|
||||
BLI_assert(active_compute_command_encoder_ != nil);
|
||||
[active_compute_command_encoder_ endEncoding];
|
||||
[active_compute_command_encoder_ release];
|
||||
active_compute_command_encoder_ = nil;
|
||||
break;
|
||||
default: {
|
||||
BLI_assert(false && "Invalid command encoder type");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
active_command_encoder_type_ = MTL_NO_COMMAND_ENCODER;
|
||||
return true;
|
||||
}
|
||||
|
||||
id<MTLRenderCommandEncoder> MTLCommandBufferManager::ensure_begin_render_command_encoder(
|
||||
MTLFrameBuffer *ctx_framebuffer, bool force_begin, bool *r_new_pass)
|
||||
{
|
||||
/* Ensure valid frame-buffer. */
|
||||
BLI_assert(ctx_framebuffer != nullptr);
|
||||
|
||||
/* Ensure active command buffer. */
|
||||
id<MTLCommandBuffer> cmd_buf = this->ensure_begin();
|
||||
BLI_assert(cmd_buf);
|
||||
|
||||
/* Begin new command encoder if the currently active one is
|
||||
* incompatible or requires updating. */
|
||||
if (active_command_encoder_type_ != MTL_RENDER_COMMAND_ENCODER ||
|
||||
active_frame_buffer_ != ctx_framebuffer || force_begin)
|
||||
{
|
||||
this->end_active_command_encoder();
|
||||
|
||||
/* Determine if this is a re-bind of the same frame-buffer. */
|
||||
bool is_rebind = (active_frame_buffer_ == ctx_framebuffer);
|
||||
|
||||
/* Generate RenderPassDescriptor from bound frame-buffer. */
|
||||
BLI_assert(ctx_framebuffer);
|
||||
active_frame_buffer_ = ctx_framebuffer;
|
||||
active_pass_descriptor_ = active_frame_buffer_->bake_render_pass_descriptor(
|
||||
is_rebind && (!active_frame_buffer_->get_pending_clear()));
|
||||
|
||||
/* Determine if there is a visibility buffer assigned to the context. */
|
||||
gpu::MTLBuffer *visibility_buffer = context_.get_visibility_buffer();
|
||||
this->active_pass_descriptor_.visibilityResultBuffer =
|
||||
(visibility_buffer) ? visibility_buffer->get_metal_buffer() : nil;
|
||||
context_.clear_visibility_dirty();
|
||||
|
||||
/* Ensure we have already cleaned up our previous render command encoder. */
|
||||
BLI_assert(active_render_command_encoder_ == nil);
|
||||
|
||||
/* Create new RenderCommandEncoder based on descriptor (and begin encoding). */
|
||||
active_render_command_encoder_ = [cmd_buf
|
||||
renderCommandEncoderWithDescriptor:active_pass_descriptor_];
|
||||
[active_render_command_encoder_ retain];
|
||||
active_command_encoder_type_ = MTL_RENDER_COMMAND_ENCODER;
|
||||
|
||||
/* Add debug label. */
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
std::string debug_name = "FrameBuf: " + std::string(active_frame_buffer_->name_get());
|
||||
[active_render_command_encoder_ setLabel:@(debug_name.c_str())];
|
||||
}
|
||||
|
||||
/* Unroll pending debug groups. */
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
unfold_pending_debug_groups();
|
||||
}
|
||||
|
||||
/* Update command buffer encoder heuristics. */
|
||||
this->register_encoder_counters();
|
||||
|
||||
/* Apply initial state. */
|
||||
/* Update Viewport and Scissor State */
|
||||
active_frame_buffer_->apply_state();
|
||||
|
||||
/* FLAG FRAMEBUFFER AS CLEARED -- A clear only lasts as long as one has been specified.
|
||||
* After this, resets to Load attachments to parallel GL behavior. */
|
||||
active_frame_buffer_->mark_cleared();
|
||||
|
||||
/* Reset RenderPassState to ensure resource bindings are re-applied. */
|
||||
render_pass_state_.reset_state();
|
||||
|
||||
/* Return true as new pass started. */
|
||||
*r_new_pass = true;
|
||||
}
|
||||
else {
|
||||
/* No new pass. */
|
||||
*r_new_pass = false;
|
||||
}
|
||||
|
||||
BLI_assert(active_render_command_encoder_ != nil);
|
||||
return active_render_command_encoder_;
|
||||
}
|
||||
|
||||
id<MTLBlitCommandEncoder> MTLCommandBufferManager::ensure_begin_blit_encoder()
|
||||
{
|
||||
/* Ensure active command buffer. */
|
||||
id<MTLCommandBuffer> cmd_buf = this->ensure_begin();
|
||||
BLI_assert(cmd_buf);
|
||||
|
||||
/* Ensure no existing command encoder of a different type is active. */
|
||||
if (active_command_encoder_type_ != MTL_BLIT_COMMAND_ENCODER) {
|
||||
this->end_active_command_encoder();
|
||||
}
|
||||
|
||||
/* Begin new Blit Encoder. */
|
||||
if (active_blit_command_encoder_ == nil) {
|
||||
|
||||
active_blit_command_encoder_ = [cmd_buf blitCommandEncoder];
|
||||
BLI_assert(active_blit_command_encoder_ != nil);
|
||||
[active_blit_command_encoder_ retain];
|
||||
active_command_encoder_type_ = MTL_BLIT_COMMAND_ENCODER;
|
||||
|
||||
/* Add debug label. */
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
std::string debug_name = GPU_debug_get_groups_names({1, 1});
|
||||
[active_blit_command_encoder_ setLabel:@(debug_name.c_str())];
|
||||
}
|
||||
/* Unroll pending debug groups. */
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
unfold_pending_debug_groups();
|
||||
}
|
||||
|
||||
/* Update command buffer encoder heuristics. */
|
||||
this->register_encoder_counters();
|
||||
}
|
||||
BLI_assert(active_blit_command_encoder_ != nil);
|
||||
return active_blit_command_encoder_;
|
||||
}
|
||||
|
||||
id<MTLComputeCommandEncoder> MTLCommandBufferManager::ensure_begin_compute_encoder()
|
||||
{
|
||||
/* Ensure active command buffer. */
|
||||
id<MTLCommandBuffer> cmd_buf = this->ensure_begin();
|
||||
BLI_assert(cmd_buf);
|
||||
|
||||
/* Ensure no existing command encoder of a different type is active. */
|
||||
if (active_command_encoder_type_ != MTL_COMPUTE_COMMAND_ENCODER) {
|
||||
this->end_active_command_encoder();
|
||||
}
|
||||
|
||||
/* Begin new Compute Encoder. */
|
||||
if (active_compute_command_encoder_ == nil) {
|
||||
active_compute_command_encoder_ = [cmd_buf computeCommandEncoder];
|
||||
BLI_assert(active_compute_command_encoder_ != nil);
|
||||
[active_compute_command_encoder_ retain];
|
||||
active_command_encoder_type_ = MTL_COMPUTE_COMMAND_ENCODER;
|
||||
|
||||
/* Add debug label. */
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
std::string debug_name = GPU_debug_get_groups_names({1, 1});
|
||||
[active_compute_command_encoder_ setLabel:@(debug_name.c_str())];
|
||||
}
|
||||
|
||||
/* Unroll pending debug groups. */
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
unfold_pending_debug_groups();
|
||||
}
|
||||
|
||||
/* Update command buffer encoder heuristics. */
|
||||
this->register_encoder_counters();
|
||||
|
||||
/* Reset RenderPassState to ensure resource bindings are re-applied. */
|
||||
compute_state_.reset_state();
|
||||
}
|
||||
BLI_assert(active_compute_command_encoder_ != nil);
|
||||
return active_compute_command_encoder_;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Command buffer heuristics.
|
||||
* \{ */
|
||||
|
||||
/* Rendering Heuristics. */
|
||||
void MTLCommandBufferManager::register_draw_counters(int vertex_submission)
|
||||
{
|
||||
current_draw_call_count_++;
|
||||
vertex_submitted_count_ += vertex_submission;
|
||||
empty_ = false;
|
||||
}
|
||||
|
||||
/* Reset workload counters. */
|
||||
void MTLCommandBufferManager::reset_counters()
|
||||
{
|
||||
empty_ = true;
|
||||
current_draw_call_count_ = 0;
|
||||
encoder_count_ = 0;
|
||||
vertex_submitted_count_ = 0;
|
||||
}
|
||||
|
||||
/* Workload evaluation. */
|
||||
bool MTLCommandBufferManager::do_break_submission()
|
||||
{
|
||||
/* Skip if no active command buffer. */
|
||||
if (active_command_buffer_ == nil) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Use optimized heuristic to split heavy command buffer submissions to better saturate the
|
||||
* hardware and also reduce stalling from individual large submissions. */
|
||||
if (GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_ANY, GPU_DRIVER_ANY) ||
|
||||
GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_ANY))
|
||||
{
|
||||
return ((current_draw_call_count_ > 30000) || (vertex_submitted_count_ > 100000000) ||
|
||||
(encoder_count_ > 25));
|
||||
}
|
||||
/* Apple Silicon is less efficient if splitting submissions. */
|
||||
return false;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Command buffer debugging.
|
||||
* \{ */
|
||||
|
||||
/* Debug. */
|
||||
void MTLCommandBufferManager::push_debug_group(const char *name, int /*index*/)
|
||||
{
|
||||
/* Only perform this operation if capturing. */
|
||||
MTLCaptureManager *capture_manager = [MTLCaptureManager sharedCaptureManager];
|
||||
if (![capture_manager isCapturing]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (active_command_buffer_ == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
id<MTLCommandBuffer> cmd = this->ensure_begin();
|
||||
if (cmd != nil) {
|
||||
switch (active_command_encoder_type_) {
|
||||
case MTL_RENDER_COMMAND_ENCODER:
|
||||
[active_render_command_encoder_ pushDebugGroup:@(name)];
|
||||
break;
|
||||
case MTL_BLIT_COMMAND_ENCODER:
|
||||
[active_blit_command_encoder_ pushDebugGroup:@(name)];
|
||||
break;
|
||||
case MTL_COMPUTE_COMMAND_ENCODER:
|
||||
[active_compute_command_encoder_ pushDebugGroup:@(name)];
|
||||
break;
|
||||
default:
|
||||
// [active_command_buffer_ pushDebugGroup:@(name)];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MTLCommandBufferManager::pop_debug_group()
|
||||
{
|
||||
/* Only perform this operation if capturing. */
|
||||
MTLCaptureManager *capture_manager = [MTLCaptureManager sharedCaptureManager];
|
||||
if (![capture_manager isCapturing]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (active_command_buffer_ == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
id<MTLCommandBuffer> cmd = this->ensure_begin();
|
||||
if (cmd != nil) {
|
||||
switch (active_command_encoder_type_) {
|
||||
case MTL_RENDER_COMMAND_ENCODER:
|
||||
[active_render_command_encoder_ popDebugGroup];
|
||||
break;
|
||||
case MTL_BLIT_COMMAND_ENCODER:
|
||||
[active_blit_command_encoder_ popDebugGroup];
|
||||
break;
|
||||
case MTL_COMPUTE_COMMAND_ENCODER:
|
||||
[active_compute_command_encoder_ popDebugGroup];
|
||||
break;
|
||||
default:
|
||||
// [active_command_buffer_ popDebugGroup];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MTLCommandBufferManager::unfold_pending_debug_groups()
|
||||
{
|
||||
/* Only perform this operation if capturing. */
|
||||
MTLCaptureManager *capture_manager = [MTLCaptureManager sharedCaptureManager];
|
||||
if (![capture_manager isCapturing]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (active_command_buffer_ != nil) {
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
const DebugStack &gpu_stack = ctx->debug_stack;
|
||||
DebugStack &mtl_stack = mtl_debug_stack_;
|
||||
/* Bottom level group is the label of the command buffer. */
|
||||
Span<StringRef> groups = gpu_stack.as_span().drop_front(1);
|
||||
|
||||
/* Close closed groups. */
|
||||
for ([[maybe_unused]] int i : mtl_stack.index_range().drop_front(groups.size())) {
|
||||
pop_debug_group();
|
||||
mtl_stack.pop_last();
|
||||
}
|
||||
BLI_assert(mtl_stack.size() <= groups.size());
|
||||
/* Verify all open groups are the same on both stack. */
|
||||
int first_mismatch = 0;
|
||||
for (int i : mtl_stack.index_range()) {
|
||||
if (mtl_stack[i] != groups[i]) {
|
||||
break;
|
||||
}
|
||||
first_mismatch++;
|
||||
}
|
||||
/* Discard the ones that are not. */
|
||||
for ([[maybe_unused]] int i : mtl_stack.index_range().drop_front(first_mismatch)) {
|
||||
pop_debug_group();
|
||||
mtl_stack.pop_last();
|
||||
}
|
||||
BLI_assert(mtl_stack.size() <= groups.size());
|
||||
/* Add new groups not present in the metal stack. */
|
||||
for (const StringRef &name : groups.drop_front(mtl_stack.size())) {
|
||||
std::string s_name = name;
|
||||
push_debug_group(s_name.c_str(), 0);
|
||||
mtl_stack.append(name);
|
||||
}
|
||||
BLI_assert(mtl_stack.size() == groups.size());
|
||||
}
|
||||
}
|
||||
|
||||
void MTLCommandBufferManager::fold_remaining_debug_groups()
|
||||
{
|
||||
/* Only perform this operation if capturing. */
|
||||
MTLCaptureManager *capture_manager = [MTLCaptureManager sharedCaptureManager];
|
||||
if (![capture_manager isCapturing]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (active_command_buffer_ != nil) {
|
||||
DebugStack &mtl_stack = mtl_debug_stack_;
|
||||
for ([[maybe_unused]] int i : mtl_stack.index_range()) {
|
||||
pop_debug_group();
|
||||
mtl_stack.pop_last();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Workload Synchronization. */
|
||||
bool MTLCommandBufferManager::insert_memory_barrier(GPUBarrier barrier_bits,
|
||||
GPUStageBarrierBits before_stages,
|
||||
GPUStageBarrierBits after_stages)
|
||||
{
|
||||
/* Apple Silicon does not support memory barriers for RenderCommandEncoder's.
|
||||
* We do not currently need these due to implicit API guarantees. However, render->render
|
||||
* resource dependencies are only evaluated at RenderCommandEncoder boundaries due to work
|
||||
* execution on TBDR architecture.
|
||||
*
|
||||
* NOTE: Render barriers are therefore inherently expensive. Where possible, opt for local
|
||||
* synchronization using raster order groups, or, prefer compute to avoid subsequent passes
|
||||
* re-loading pass attachments which are not needed. */
|
||||
const bool is_tile_based_arch = (GPU_platform_architecture() == GPU_ARCHITECTURE_TBDR);
|
||||
if (is_tile_based_arch) {
|
||||
if (active_command_encoder_type_ == MTL_RENDER_COMMAND_ENCODER) {
|
||||
/* Break render pass to ensure final pass results are visible to subsequent calls. */
|
||||
end_active_command_encoder();
|
||||
return true;
|
||||
}
|
||||
/* Skip all barriers for compute and blit passes as Metal will resolve these dependencies. */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Resolve scope. */
|
||||
MTLBarrierScope scope = 0;
|
||||
if (barrier_bits & GPU_BARRIER_SHADER_IMAGE_ACCESS || barrier_bits & GPU_BARRIER_TEXTURE_FETCH) {
|
||||
bool is_compute = (active_command_encoder_type_ != MTL_RENDER_COMMAND_ENCODER);
|
||||
scope |= (is_compute ? 0 : MTLBarrierScopeRenderTargets) | MTLBarrierScopeTextures;
|
||||
}
|
||||
if (barrier_bits & GPU_BARRIER_SHADER_STORAGE ||
|
||||
barrier_bits & GPU_BARRIER_VERTEX_ATTRIB_ARRAY || barrier_bits & GPU_BARRIER_ELEMENT_ARRAY ||
|
||||
barrier_bits & GPU_BARRIER_UNIFORM || barrier_bits & GPU_BARRIER_BUFFER_UPDATE)
|
||||
{
|
||||
scope = scope | MTLBarrierScopeBuffers;
|
||||
}
|
||||
|
||||
if (scope != 0) {
|
||||
/* Issue barrier based on encoder. */
|
||||
switch (active_command_encoder_type_) {
|
||||
case MTL_NO_COMMAND_ENCODER:
|
||||
case MTL_BLIT_COMMAND_ENCODER: {
|
||||
/* No barrier to be inserted. */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Rendering. */
|
||||
case MTL_RENDER_COMMAND_ENCODER: {
|
||||
/* Currently flagging both stages -- can use bits above to filter on stage type --
|
||||
* though full barrier is safe for now. */
|
||||
MTLRenderStages before_stage_flags = 0;
|
||||
MTLRenderStages after_stage_flags = 0;
|
||||
if (before_stages & GPU_BARRIER_STAGE_VERTEX &&
|
||||
!(before_stages & GPU_BARRIER_STAGE_FRAGMENT))
|
||||
{
|
||||
before_stage_flags = before_stage_flags | MTLRenderStageVertex;
|
||||
}
|
||||
if (before_stages & GPU_BARRIER_STAGE_FRAGMENT) {
|
||||
before_stage_flags = before_stage_flags | MTLRenderStageFragment;
|
||||
}
|
||||
if (after_stages & GPU_BARRIER_STAGE_VERTEX) {
|
||||
after_stage_flags = after_stage_flags | MTLRenderStageVertex;
|
||||
}
|
||||
if (after_stages & GPU_BARRIER_STAGE_FRAGMENT) {
|
||||
after_stage_flags = MTLRenderStageFragment;
|
||||
}
|
||||
|
||||
id<MTLRenderCommandEncoder> rec = this->get_active_render_command_encoder();
|
||||
BLI_assert(rec != nil);
|
||||
[rec memoryBarrierWithScope:scope
|
||||
afterStages:after_stage_flags
|
||||
beforeStages:before_stage_flags];
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Compute. */
|
||||
case MTL_COMPUTE_COMMAND_ENCODER: {
|
||||
id<MTLComputeCommandEncoder> rec = this->get_active_compute_command_encoder();
|
||||
BLI_assert(rec != nil);
|
||||
[rec memoryBarrierWithScope:scope];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* No barrier support. */
|
||||
return false;
|
||||
}
|
||||
|
||||
void MTLCommandBufferManager::encode_signal_event(id<MTLEvent> event, uint64_t signal_value)
|
||||
{
|
||||
/* Ensure active command buffer. */
|
||||
id<MTLCommandBuffer> cmd_buf = this->ensure_begin();
|
||||
BLI_assert(cmd_buf);
|
||||
this->end_active_command_encoder();
|
||||
[cmd_buf encodeSignalEvent:event value:signal_value];
|
||||
register_encoder_counters();
|
||||
}
|
||||
|
||||
void MTLCommandBufferManager::encode_wait_for_event(id<MTLEvent> event, uint64_t signal_value)
|
||||
{
|
||||
/* Ensure active command buffer. */
|
||||
id<MTLCommandBuffer> cmd_buf = this->ensure_begin();
|
||||
BLI_assert(cmd_buf);
|
||||
this->end_active_command_encoder();
|
||||
[cmd_buf encodeWaitForEvent:event value:signal_value];
|
||||
register_encoder_counters();
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Render Pass State for active RenderCommandEncoder
|
||||
* \{ */
|
||||
|
||||
/* Reset binding state when a new RenderCommandEncoder is bound, to ensure
|
||||
* pipeline resources are re-applied to the new Encoder.
|
||||
* NOTE: In Metal, state is only persistent within an MTLCommandEncoder,
|
||||
* not globally. */
|
||||
void MTLRenderPassState::reset_state()
|
||||
{
|
||||
/* Reset Cached pipeline state. */
|
||||
this->bound_pso = nil;
|
||||
this->bound_ds_state = nil;
|
||||
|
||||
/* Clear shader binding. */
|
||||
this->last_bound_shader_state = {nullptr, 0};
|
||||
|
||||
/* Other states. */
|
||||
MTLFrameBuffer *fb = this->cmd.get_active_framebuffer();
|
||||
this->last_used_stencil_ref_value = 0;
|
||||
this->last_scissor_rect = {0,
|
||||
0,
|
||||
(uint)((fb != nullptr) ? fb->get_width() : 0),
|
||||
(uint)((fb != nullptr) ? fb->get_height() : 0)};
|
||||
|
||||
this->vertex_bindings = {};
|
||||
this->fragment_bindings = {};
|
||||
}
|
||||
|
||||
void MTLComputeState::reset_state()
|
||||
{
|
||||
/* Reset Cached pipeline state. */
|
||||
this->bound_pso = nil;
|
||||
|
||||
this->compute_bindings = {};
|
||||
}
|
||||
|
||||
void MTLComputeState::bind_pso(id<MTLComputePipelineState> pso)
|
||||
{
|
||||
if (this->bound_pso != pso) {
|
||||
id<MTLComputeCommandEncoder> rec = this->cmd.get_active_compute_command_encoder();
|
||||
[rec setComputePipelineState:pso];
|
||||
this->bound_pso = pso;
|
||||
}
|
||||
}
|
||||
|
||||
void MTLComputeState::bind_compute_texture(id<MTLTexture> tex, uint slot)
|
||||
{
|
||||
this->compute_bindings.bind_texture(this->cmd.get_active_compute_command_encoder(), tex, slot);
|
||||
}
|
||||
|
||||
void MTLComputeState::bind_compute_sampler(MTLSamplerBinding &sampler_binding,
|
||||
bool use_samplers_argument_buffer,
|
||||
uint slot)
|
||||
{
|
||||
this->compute_bindings.bind_sampler(this->cmd.get_active_compute_command_encoder(),
|
||||
this->ctx.get_sampler_array(),
|
||||
sampler_binding.get_mtl_sampler(this->ctx),
|
||||
sampler_binding.state,
|
||||
use_samplers_argument_buffer,
|
||||
slot);
|
||||
}
|
||||
|
||||
void MTLComputeState::bind_compute_buffer(id<MTLBuffer> buffer, uint64_t buffer_offset, uint index)
|
||||
{
|
||||
this->compute_bindings.bind_buffer(
|
||||
this->cmd.get_active_compute_command_encoder(), buffer, buffer_offset, index);
|
||||
}
|
||||
|
||||
void MTLComputeState::bind_compute_bytes(const void *bytes, uint64_t length, uint index)
|
||||
{
|
||||
this->compute_bindings.bind_bytes(this->cmd.get_active_compute_command_encoder(),
|
||||
this->ctx.get_scratch_buffer_manager(),
|
||||
bytes,
|
||||
length,
|
||||
index);
|
||||
}
|
||||
|
||||
void MTLRenderPassState::bind_vertex_texture(id<MTLTexture> tex, uint slot)
|
||||
{
|
||||
this->vertex_bindings.bind_texture(this->cmd.get_active_render_command_encoder(), tex, slot);
|
||||
}
|
||||
|
||||
void MTLRenderPassState::bind_vertex_sampler(MTLSamplerBinding &sampler_binding,
|
||||
bool use_samplers_argument_buffer,
|
||||
uint slot)
|
||||
{
|
||||
this->vertex_bindings.bind_sampler(this->cmd.get_active_render_command_encoder(),
|
||||
this->ctx.get_sampler_array(),
|
||||
sampler_binding.get_mtl_sampler(this->ctx),
|
||||
sampler_binding.state,
|
||||
use_samplers_argument_buffer,
|
||||
slot);
|
||||
}
|
||||
|
||||
void MTLRenderPassState::bind_vertex_buffer(id<MTLBuffer> buffer,
|
||||
uint64_t buffer_offset,
|
||||
uint index)
|
||||
{
|
||||
this->vertex_bindings.bind_buffer(
|
||||
this->cmd.get_active_render_command_encoder(), buffer, buffer_offset, index);
|
||||
}
|
||||
|
||||
void MTLRenderPassState::bind_vertex_bytes(const void *bytes, uint64_t length, uint index)
|
||||
{
|
||||
this->vertex_bindings.bind_bytes(this->cmd.get_active_render_command_encoder(),
|
||||
this->ctx.get_scratch_buffer_manager(),
|
||||
bytes,
|
||||
length,
|
||||
index);
|
||||
}
|
||||
|
||||
void MTLRenderPassState::bind_fragment_texture(id<MTLTexture> tex, uint slot)
|
||||
{
|
||||
this->fragment_bindings.bind_texture(this->cmd.get_active_render_command_encoder(), tex, slot);
|
||||
}
|
||||
|
||||
void MTLRenderPassState::bind_fragment_sampler(MTLSamplerBinding &sampler_binding,
|
||||
bool use_samplers_argument_buffer,
|
||||
uint slot)
|
||||
{
|
||||
this->fragment_bindings.bind_sampler(this->cmd.get_active_render_command_encoder(),
|
||||
this->ctx.get_sampler_array(),
|
||||
sampler_binding.get_mtl_sampler(this->ctx),
|
||||
sampler_binding.state,
|
||||
use_samplers_argument_buffer,
|
||||
slot);
|
||||
}
|
||||
|
||||
void MTLRenderPassState::bind_fragment_buffer(id<MTLBuffer> buffer,
|
||||
uint64_t buffer_offset,
|
||||
uint index)
|
||||
{
|
||||
this->fragment_bindings.bind_buffer(
|
||||
this->cmd.get_active_render_command_encoder(), buffer, buffer_offset, index);
|
||||
}
|
||||
|
||||
void MTLRenderPassState::bind_fragment_bytes(const void *bytes, uint64_t length, uint index)
|
||||
{
|
||||
this->fragment_bindings.bind_bytes(this->cmd.get_active_render_command_encoder(),
|
||||
this->ctx.get_scratch_buffer_manager(),
|
||||
bytes,
|
||||
length,
|
||||
index);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
id<MTLSamplerState> MTLSamplerBinding::get_mtl_sampler(MTLContext &ctx)
|
||||
{
|
||||
return (this->state == DEFAULT_SAMPLER_STATE) ? ctx.get_default_sampler_state() :
|
||||
ctx.get_sampler_from_state(this->state);
|
||||
}
|
||||
|
||||
void MTLComputeCommandEncoder::set_buffer_offset(size_t offset, int index)
|
||||
{
|
||||
[enc setBufferOffset:offset atIndex:index];
|
||||
}
|
||||
void MTLComputeCommandEncoder::set_buffer(id<MTLBuffer> buf, size_t offset, int index)
|
||||
{
|
||||
[enc setBuffer:buf offset:offset atIndex:index];
|
||||
}
|
||||
void MTLComputeCommandEncoder::set_bytes(const void *bytes, size_t length, int index)
|
||||
{
|
||||
[enc setBytes:bytes length:length atIndex:index];
|
||||
}
|
||||
void MTLComputeCommandEncoder::set_texture(id<MTLTexture> tex, int index)
|
||||
{
|
||||
[enc setTexture:tex atIndex:index];
|
||||
}
|
||||
void MTLComputeCommandEncoder::set_sampler(id<MTLSamplerState> sampler_state, int index)
|
||||
{
|
||||
[enc setSamplerState:sampler_state atIndex:index];
|
||||
}
|
||||
|
||||
void MTLVertexCommandEncoder::set_buffer_offset(size_t offset, int index)
|
||||
{
|
||||
[enc setVertexBufferOffset:offset atIndex:index];
|
||||
}
|
||||
void MTLVertexCommandEncoder::set_buffer(id<MTLBuffer> buf, size_t offset, int index)
|
||||
{
|
||||
[enc setVertexBuffer:buf offset:offset atIndex:index];
|
||||
}
|
||||
void MTLVertexCommandEncoder::set_bytes(const void *bytes, size_t length, int index)
|
||||
{
|
||||
[enc setVertexBytes:bytes length:length atIndex:index];
|
||||
}
|
||||
void MTLVertexCommandEncoder::set_texture(id<MTLTexture> tex, int index)
|
||||
{
|
||||
[enc setVertexTexture:tex atIndex:index];
|
||||
}
|
||||
void MTLVertexCommandEncoder::set_sampler(id<MTLSamplerState> sampler_state, int index)
|
||||
{
|
||||
[enc setVertexSamplerState:sampler_state atIndex:index];
|
||||
}
|
||||
|
||||
void MTLFragmentCommandEncoder::set_buffer_offset(size_t offset, int index)
|
||||
{
|
||||
[enc setFragmentBufferOffset:offset atIndex:index];
|
||||
}
|
||||
void MTLFragmentCommandEncoder::set_buffer(id<MTLBuffer> buf, size_t offset, int index)
|
||||
{
|
||||
[enc setFragmentBuffer:buf offset:offset atIndex:index];
|
||||
}
|
||||
void MTLFragmentCommandEncoder::set_bytes(const void *bytes, size_t length, int index)
|
||||
{
|
||||
[enc setFragmentBytes:bytes length:length atIndex:index];
|
||||
}
|
||||
void MTLFragmentCommandEncoder::set_texture(id<MTLTexture> tex, int index)
|
||||
{
|
||||
[enc setFragmentTexture:tex atIndex:index];
|
||||
}
|
||||
void MTLFragmentCommandEncoder::set_sampler(id<MTLSamplerState> sampler_state, int index)
|
||||
{
|
||||
[enc setFragmentSamplerState:sampler_state atIndex:index];
|
||||
}
|
||||
|
||||
} // namespace blender::gpu
|
||||
18
blender-5.2.0/source/blender/gpu/metal/mtl_common.hh
Normal file
18
blender-5.2.0/source/blender/gpu/metal/mtl_common.hh
Normal file
@@ -0,0 +1,18 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** -- Renderer Options -- */
|
||||
/* Number of frames over which rolling averages are taken. */
|
||||
#define MTL_FRAME_AVERAGE_COUNT 15
|
||||
#define MTL_MAX_DRAWABLES 3
|
||||
#define MTL_FORCE_WAIT_IDLE 0
|
||||
|
||||
/* Number of frames for which we retain in-flight resources such as scratch buffers.
|
||||
* Set as number of GPU frames in flight, plus an additional value for extra possible CPU frame. */
|
||||
#define MTL_NUM_SAFE_FRAMES (MTL_MAX_DRAWABLES + 1)
|
||||
|
||||
/* Display debug information about missing attributes and incorrect vertex formats. */
|
||||
#define MTL_DEBUG_SHADER_ATTRIBUTES 0
|
||||
931
blender-5.2.0/source/blender/gpu/metal/mtl_context.hh
Normal file
931
blender-5.2.0/source/blender/gpu/metal/mtl_context.hh
Normal file
@@ -0,0 +1,931 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "gpu_context_private.hh"
|
||||
|
||||
#include "GPU_common_types.hh"
|
||||
|
||||
/* Don't generate OpenGL deprecation warning. This is a known thing, and is not something easily
|
||||
* solvable in a short term. */
|
||||
#ifdef __clang__
|
||||
# pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
|
||||
#include "GHOST_Types.hh"
|
||||
#include "intern/GHOST_Context.hh"
|
||||
#include "intern/GHOST_ContextMTL.hh"
|
||||
#include "intern/GHOST_Window.hh"
|
||||
|
||||
#include "mtl_backend.hh"
|
||||
#include "mtl_capabilities.hh"
|
||||
#include "mtl_command_buffer.hh"
|
||||
#include "mtl_common.hh"
|
||||
#include "mtl_framebuffer.hh"
|
||||
#include "mtl_memory.hh"
|
||||
#include "mtl_shader.hh"
|
||||
#include "mtl_shader_generate.hh"
|
||||
#include "mtl_shader_interface.hh"
|
||||
#include "mtl_texture.hh"
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <Metal/Metal.h>
|
||||
#include <QuartzCore/QuartzCore.h>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
|
||||
@class CAMetalLayer;
|
||||
@class MTLCommandQueue;
|
||||
@class MTLRenderPipelineState;
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/* Forward Declarations */
|
||||
class MTLContext;
|
||||
class MTLCommandBufferManager;
|
||||
class MTLUniformBuf;
|
||||
class MTLStorageBuf;
|
||||
|
||||
/* Caching of resource bindings for active MTLRenderCommandEncoder.
|
||||
* In Metal, resource bindings are local to the MTLCommandEncoder,
|
||||
* not globally to the whole pipeline/cmd buffer. */
|
||||
struct MTLBoundShaderState {
|
||||
MTLShader *shader = nullptr;
|
||||
uint pso_index = 0;
|
||||
|
||||
bool operator==(MTLBoundShaderState const &other) const
|
||||
{
|
||||
return (shader == other.shader && pso_index == other.pso_index);
|
||||
}
|
||||
bool operator!=(MTLBoundShaderState const &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
/* Metal Context Render Pass State -- Used to track active RenderCommandEncoder state based on
|
||||
* bound MTLFrameBuffer's.Owned by MTLContext. */
|
||||
class MTLRenderPassState {
|
||||
friend class MTLContext;
|
||||
|
||||
public:
|
||||
MTLRenderPassState(MTLContext &context, MTLCommandBufferManager &command_buffer_manager)
|
||||
: ctx(context), cmd(command_buffer_manager) {};
|
||||
|
||||
/* Given a RenderPassState is associated with a live RenderCommandEncoder,
|
||||
* this state sits within the MTLCommandBufferManager. */
|
||||
MTLContext &ctx;
|
||||
MTLCommandBufferManager &cmd;
|
||||
|
||||
MTLBoundShaderState last_bound_shader_state = {};
|
||||
id<MTLRenderPipelineState> bound_pso = nil;
|
||||
id<MTLDepthStencilState> bound_ds_state = nil;
|
||||
uint last_used_stencil_ref_value = 0;
|
||||
MTLScissorRect last_scissor_rect;
|
||||
|
||||
MTLBindingCache<gpu::MTLVertexCommandEncoder> vertex_bindings;
|
||||
MTLBindingCache<gpu::MTLFragmentCommandEncoder> fragment_bindings;
|
||||
|
||||
/* Reset RenderCommandEncoder binding state. */
|
||||
void reset_state();
|
||||
|
||||
/* Texture Binding (RenderCommandEncoder). */
|
||||
void bind_vertex_texture(id<MTLTexture> tex, uint slot);
|
||||
void bind_fragment_texture(id<MTLTexture> tex, uint slot);
|
||||
|
||||
/* Sampler Binding (RenderCommandEncoder). */
|
||||
void bind_vertex_sampler(MTLSamplerBinding &sampler_binding,
|
||||
bool use_samplers_argument_buffer,
|
||||
uint slot);
|
||||
void bind_fragment_sampler(MTLSamplerBinding &sampler_binding,
|
||||
bool use_samplers_argument_buffer,
|
||||
uint slot);
|
||||
|
||||
/* Buffer binding (RenderCommandEncoder). */
|
||||
void bind_vertex_buffer(id<MTLBuffer> buffer, uint64_t buffer_offset, uint index);
|
||||
void bind_fragment_buffer(id<MTLBuffer> buffer, uint64_t buffer_offset, uint index);
|
||||
void bind_vertex_bytes(const void *bytes, uint64_t length, uint index);
|
||||
void bind_fragment_bytes(const void *bytes, uint64_t length, uint index);
|
||||
};
|
||||
|
||||
/* Metal Context Compute Pass State -- Used to track active ComputeCommandEncoder state. */
|
||||
class MTLComputeState {
|
||||
friend class MTLContext;
|
||||
|
||||
public:
|
||||
MTLComputeState(MTLContext &context, MTLCommandBufferManager &command_buffer_manager)
|
||||
: ctx(context), cmd(command_buffer_manager) {};
|
||||
|
||||
/* Given a ComputePassState is associated with a live ComputeCommandEncoder,
|
||||
* this state sits within the MTLCommandBufferManager. */
|
||||
MTLContext &ctx;
|
||||
MTLCommandBufferManager &cmd;
|
||||
|
||||
id<MTLComputePipelineState> bound_pso = nil;
|
||||
|
||||
MTLBindingCache<gpu::MTLComputeCommandEncoder> compute_bindings;
|
||||
|
||||
/* Reset ComputeCommandEncoder binding state. */
|
||||
void reset_state();
|
||||
|
||||
/* PSO Binding. */
|
||||
void bind_pso(id<MTLComputePipelineState> pso);
|
||||
|
||||
void bind_compute_texture(id<MTLTexture> tex, uint slot);
|
||||
void bind_compute_sampler(MTLSamplerBinding &sampler_binding,
|
||||
bool use_samplers_argument_buffer,
|
||||
uint slot);
|
||||
void bind_compute_buffer(id<MTLBuffer> buffer, uint64_t buffer_offset, uint index);
|
||||
void bind_compute_bytes(const void *bytes, uint64_t length, uint index);
|
||||
};
|
||||
|
||||
/* Depth Stencil State */
|
||||
struct MTLContextDepthStencilState {
|
||||
|
||||
/* Depth State. */
|
||||
bool depth_write_enable;
|
||||
bool depth_test_enabled;
|
||||
MTLCompareFunction depth_function;
|
||||
|
||||
/* Stencil State. */
|
||||
bool stencil_test_enabled;
|
||||
uint stencil_read_mask;
|
||||
uint stencil_write_mask;
|
||||
uint stencil_ref;
|
||||
MTLCompareFunction stencil_func;
|
||||
|
||||
MTLStencilOperation stencil_op_front_stencil_fail;
|
||||
MTLStencilOperation stencil_op_front_depth_fail;
|
||||
MTLStencilOperation stencil_op_front_depthstencil_pass;
|
||||
|
||||
MTLStencilOperation stencil_op_back_stencil_fail;
|
||||
MTLStencilOperation stencil_op_back_depth_fail;
|
||||
MTLStencilOperation stencil_op_back_depthstencil_pass;
|
||||
|
||||
/* Frame-buffer State -- We need to mark this, in case stencil state remains unchanged,
|
||||
* but attachment state has changed. */
|
||||
bool has_depth_target;
|
||||
bool has_stencil_target;
|
||||
|
||||
/* TODO(Metal): Consider optimizing this function using `memcmp`.
|
||||
* Un-used, but differing, stencil state leads to over-generation
|
||||
* of state objects when doing trivial compare. */
|
||||
bool operator==(const MTLContextDepthStencilState &other) const
|
||||
{
|
||||
bool depth_state_equality = (has_depth_target == other.has_depth_target &&
|
||||
depth_write_enable == other.depth_write_enable &&
|
||||
depth_test_enabled == other.depth_test_enabled &&
|
||||
depth_function == other.depth_function);
|
||||
|
||||
bool stencil_state_equality = true;
|
||||
if (has_stencil_target) {
|
||||
stencil_state_equality =
|
||||
(has_stencil_target == other.has_stencil_target &&
|
||||
stencil_test_enabled == other.stencil_test_enabled &&
|
||||
stencil_op_front_stencil_fail == other.stencil_op_front_stencil_fail &&
|
||||
stencil_op_front_depth_fail == other.stencil_op_front_depth_fail &&
|
||||
stencil_op_front_depthstencil_pass == other.stencil_op_front_depthstencil_pass &&
|
||||
stencil_op_back_stencil_fail == other.stencil_op_back_stencil_fail &&
|
||||
stencil_op_back_depth_fail == other.stencil_op_back_depth_fail &&
|
||||
stencil_op_back_depthstencil_pass == other.stencil_op_back_depthstencil_pass &&
|
||||
stencil_func == other.stencil_func && stencil_read_mask == other.stencil_read_mask &&
|
||||
stencil_write_mask == other.stencil_write_mask);
|
||||
}
|
||||
|
||||
return depth_state_equality && stencil_state_equality;
|
||||
}
|
||||
|
||||
/* Depth stencil state will get hashed in order to prepare
|
||||
* MTLDepthStencilState objects. The hash should comprise of
|
||||
* all elements which fill the MTLDepthStencilDescriptor.
|
||||
* These are bound when [rec setDepthStencilState:...] is called.
|
||||
* Depth bias and stencil reference value are set dynamically on the RenderCommandEncoder:
|
||||
* - setStencilReferenceValue:
|
||||
* - setDepthBias:slopeScale:clamp:
|
||||
*/
|
||||
std::size_t hash() const
|
||||
{
|
||||
std::size_t boolean_bitmask = (this->depth_write_enable ? 1 : 0) |
|
||||
((this->depth_test_enabled ? 1 : 0) << 1) |
|
||||
((this->stencil_test_enabled ? 1 : 0) << 2) |
|
||||
((this->has_depth_target ? 1 : 0) << 3) |
|
||||
((this->has_stencil_target ? 1 : 0) << 4);
|
||||
|
||||
std::size_t stencilop_bitmask = (std::size_t(this->stencil_op_front_stencil_fail)) |
|
||||
(std::size_t(this->stencil_op_front_depth_fail) << 3) |
|
||||
(std::size_t(this->stencil_op_front_depthstencil_pass) << 6) |
|
||||
(std::size_t(this->stencil_op_back_stencil_fail) << 9) |
|
||||
(std::size_t(this->stencil_op_back_depth_fail) << 12) |
|
||||
(std::size_t(this->stencil_op_back_depthstencil_pass) << 15);
|
||||
|
||||
std::size_t main_hash = std::size_t(this->depth_function);
|
||||
if (this->has_stencil_target) {
|
||||
main_hash += std::size_t(this->stencil_read_mask & 0xFF) << 8;
|
||||
main_hash += std::size_t(this->stencil_write_mask & 0xFF) << 16;
|
||||
}
|
||||
main_hash ^= std::size_t(this->stencil_func) << 16;
|
||||
main_hash ^= stencilop_bitmask;
|
||||
|
||||
std::size_t final_hash = (main_hash << 8) | boolean_bitmask;
|
||||
return final_hash;
|
||||
}
|
||||
};
|
||||
|
||||
struct MTLContextTextureUtils {
|
||||
|
||||
/* Depth Update Utilities */
|
||||
/* Depth texture updates are not directly supported with Blit operations, similarly, we cannot
|
||||
* use a compute shader to write to depth, so we must instead render to a depth target.
|
||||
* These processes use vertex/fragment shaders to render texture data from an intermediate
|
||||
* source, in order to prime the depth buffer. */
|
||||
Map<DepthTextureUpdateRoutineSpecialisation, gpu::Shader *> depth_2d_update_shaders;
|
||||
gpu::Shader *fullscreen_blit_shader = nullptr;
|
||||
|
||||
/* Texture Read/Update routines */
|
||||
Map<TextureReadRoutineSpecialisation, id<MTLComputePipelineState>> texture_1d_read_compute_psos;
|
||||
Map<TextureReadRoutineSpecialisation, id<MTLComputePipelineState>>
|
||||
texture_1d_array_read_compute_psos;
|
||||
Map<TextureReadRoutineSpecialisation, id<MTLComputePipelineState>> texture_2d_read_compute_psos;
|
||||
Map<TextureReadRoutineSpecialisation, id<MTLComputePipelineState>>
|
||||
texture_2d_array_read_compute_psos;
|
||||
Map<TextureReadRoutineSpecialisation, id<MTLComputePipelineState>> texture_3d_read_compute_psos;
|
||||
Map<TextureReadRoutineSpecialisation, id<MTLComputePipelineState>>
|
||||
texture_cube_read_compute_psos;
|
||||
Map<TextureReadRoutineSpecialisation, id<MTLComputePipelineState>>
|
||||
texture_cube_array_read_compute_psos;
|
||||
Map<TextureReadRoutineSpecialisation, id<MTLComputePipelineState>>
|
||||
texture_buffer_read_compute_psos;
|
||||
|
||||
Map<TextureUpdateRoutineSpecialisation, id<MTLComputePipelineState>>
|
||||
texture_1d_update_compute_psos;
|
||||
Map<TextureUpdateRoutineSpecialisation, id<MTLComputePipelineState>>
|
||||
texture_1d_array_update_compute_psos;
|
||||
Map<TextureUpdateRoutineSpecialisation, id<MTLComputePipelineState>>
|
||||
texture_2d_update_compute_psos;
|
||||
Map<TextureUpdateRoutineSpecialisation, id<MTLComputePipelineState>>
|
||||
texture_2d_array_update_compute_psos;
|
||||
Map<TextureUpdateRoutineSpecialisation, id<MTLComputePipelineState>>
|
||||
texture_3d_update_compute_psos;
|
||||
Map<TextureUpdateRoutineSpecialisation, id<MTLComputePipelineState>>
|
||||
texture_cube_update_compute_psos;
|
||||
Map<TextureUpdateRoutineSpecialisation, id<MTLComputePipelineState>>
|
||||
texture_cube_array_update_compute_psos;
|
||||
Map<TextureUpdateRoutineSpecialisation, id<MTLComputePipelineState>>
|
||||
texture_buffer_update_compute_psos;
|
||||
|
||||
template<typename T> void free_cached_pso_map(Map<T, id<MTLComputePipelineState>> &map)
|
||||
{
|
||||
for (typename blender::MutableMapItem<T, id<MTLComputePipelineState>> item : map.items()) {
|
||||
[item.value release];
|
||||
}
|
||||
map.clear();
|
||||
}
|
||||
|
||||
void init()
|
||||
{
|
||||
fullscreen_blit_shader = nullptr;
|
||||
}
|
||||
|
||||
void cleanup()
|
||||
{
|
||||
if (fullscreen_blit_shader) {
|
||||
GPU_shader_free(fullscreen_blit_shader);
|
||||
}
|
||||
|
||||
/* Free depth 2D Update shaders */
|
||||
for (auto item : depth_2d_update_shaders.items()) {
|
||||
GPU_shader_free(item.value);
|
||||
}
|
||||
depth_2d_update_shaders.clear();
|
||||
|
||||
/* Free Read shader maps */
|
||||
free_cached_pso_map(texture_1d_read_compute_psos);
|
||||
free_cached_pso_map(texture_1d_read_compute_psos);
|
||||
free_cached_pso_map(texture_1d_array_read_compute_psos);
|
||||
free_cached_pso_map(texture_2d_read_compute_psos);
|
||||
free_cached_pso_map(texture_2d_array_read_compute_psos);
|
||||
free_cached_pso_map(texture_3d_read_compute_psos);
|
||||
free_cached_pso_map(texture_cube_read_compute_psos);
|
||||
free_cached_pso_map(texture_cube_array_read_compute_psos);
|
||||
free_cached_pso_map(texture_buffer_read_compute_psos);
|
||||
free_cached_pso_map(texture_1d_update_compute_psos);
|
||||
free_cached_pso_map(texture_1d_array_update_compute_psos);
|
||||
free_cached_pso_map(texture_2d_update_compute_psos);
|
||||
free_cached_pso_map(texture_2d_array_update_compute_psos);
|
||||
free_cached_pso_map(texture_3d_update_compute_psos);
|
||||
free_cached_pso_map(texture_cube_update_compute_psos);
|
||||
free_cached_pso_map(texture_cube_array_update_compute_psos);
|
||||
free_cached_pso_map(texture_buffer_update_compute_psos);
|
||||
}
|
||||
};
|
||||
|
||||
class MTLContextComputeUtils {
|
||||
private:
|
||||
id<MTLComputePipelineState> buffer_clear_pso_ = nil;
|
||||
|
||||
public:
|
||||
id<MTLComputePipelineState> get_buffer_clear_pso();
|
||||
void cleanup()
|
||||
{
|
||||
if (buffer_clear_pso_) {
|
||||
[buffer_clear_pso_ release];
|
||||
buffer_clear_pso_ = nil;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
enum MTLPipelineStateDirtyFlag {
|
||||
MTL_PIPELINE_STATE_NULL_FLAG = 0,
|
||||
/* Whether we need to call setViewport. */
|
||||
MTL_PIPELINE_STATE_VIEWPORT_FLAG = (1 << 0),
|
||||
/* Whether we need to call setScissor. */
|
||||
MTL_PIPELINE_STATE_SCISSOR_FLAG = (1 << 1),
|
||||
/* Whether we need to update/rebind active depth stencil state. */
|
||||
MTL_PIPELINE_STATE_DEPTHSTENCIL_FLAG = (1 << 2),
|
||||
/* Whether we need to update/rebind active PSO. */
|
||||
MTL_PIPELINE_STATE_PSO_FLAG = (1 << 3),
|
||||
/* Whether we need to update the frontFacingWinding state. */
|
||||
MTL_PIPELINE_STATE_FRONT_FACING_FLAG = (1 << 4),
|
||||
/* Whether we need to update the culling state. */
|
||||
MTL_PIPELINE_STATE_CULLMODE_FLAG = (1 << 5),
|
||||
/* Full pipeline state needs applying. Occurs when beginning a new render pass. */
|
||||
MTL_PIPELINE_STATE_ALL_FLAG =
|
||||
(MTL_PIPELINE_STATE_VIEWPORT_FLAG | MTL_PIPELINE_STATE_SCISSOR_FLAG |
|
||||
MTL_PIPELINE_STATE_DEPTHSTENCIL_FLAG | MTL_PIPELINE_STATE_PSO_FLAG |
|
||||
MTL_PIPELINE_STATE_FRONT_FACING_FLAG | MTL_PIPELINE_STATE_CULLMODE_FLAG)
|
||||
};
|
||||
|
||||
ENUM_OPERATORS(MTLPipelineStateDirtyFlag);
|
||||
|
||||
struct MTLUniformBufferBinding {
|
||||
bool bound = false;
|
||||
MTLUniformBuf *ubo = nullptr;
|
||||
};
|
||||
|
||||
struct MTLStorageBufferBinding {
|
||||
bool bound = false;
|
||||
MTLStorageBuf *ssbo = nullptr;
|
||||
};
|
||||
|
||||
struct MTLContextGlobalShaderPipelineState {
|
||||
/* Whether the pipeline state has been modified since application.
|
||||
* `dirty_flags` is a bitmask of the types of state which have been updated.
|
||||
* This is in order to optimize calls and only re-apply state as needed.
|
||||
* Some state parameters are dynamically applied on the RenderCommandEncoder,
|
||||
* others may be encapsulated in GPU-resident state objects such as
|
||||
* MTLDepthStencilState or MTLRenderPipelineState. */
|
||||
bool dirty = true;
|
||||
MTLPipelineStateDirtyFlag dirty_flags = MTL_PIPELINE_STATE_NULL_FLAG;
|
||||
|
||||
/* Active Shader State. */
|
||||
MTLShader *active_shader = nullptr;
|
||||
|
||||
/* Global Uniform Buffers. */
|
||||
std::array<MTLUniformBufferBinding, MTL_MAX_UBO> ubo_bindings = {};
|
||||
/* Storage buffer. */
|
||||
std::array<MTLStorageBufferBinding, MTL_MAX_SSBO> ssbo_bindings = {};
|
||||
/* Context Texture bindings. */
|
||||
std::array<MTLTextureBinding, MTL_MAX_SAMPLER_SLOTS> texture_bindings = {};
|
||||
std::array<MTLSamplerBinding, MTL_MAX_SAMPLER_SLOTS> sampler_bindings = {};
|
||||
/* Image bindings. */
|
||||
std::array<MTLTextureBinding, MTL_MAX_IMAGE_SLOTS> image_bindings = {};
|
||||
|
||||
/*** --- Render Pipeline State --- ***/
|
||||
/* Track global render pipeline state for the current context. The functions in GPU_state.hh
|
||||
* modify these parameters. Certain values, tagged [PSO], are parameters which are required to be
|
||||
* passed into PSO creation, rather than dynamic state functions on the RenderCommandEncoder.
|
||||
*/
|
||||
|
||||
/* Blending State. */
|
||||
MTLColorWriteMask color_write_mask; /* [PSO] */
|
||||
bool blending_enabled; /* [PSO] */
|
||||
MTLBlendOperation alpha_blend_op; /* [PSO] */
|
||||
MTLBlendOperation rgb_blend_op; /* [PSO] */
|
||||
MTLBlendFactor dest_alpha_blend_factor; /* [PSO] */
|
||||
MTLBlendFactor dest_rgb_blend_factor; /* [PSO] */
|
||||
MTLBlendFactor src_alpha_blend_factor; /* [PSO] */
|
||||
MTLBlendFactor src_rgb_blend_factor; /* [PSO] */
|
||||
|
||||
/* Culling State. */
|
||||
bool culling_enabled;
|
||||
GPUFaceCullTest cull_mode;
|
||||
GPUFrontFace front_face;
|
||||
|
||||
/* Depth State. */
|
||||
MTLContextDepthStencilState depth_stencil_state;
|
||||
|
||||
/* Viewport/Scissor Region. */
|
||||
int num_active_viewports = 1;
|
||||
int viewport_offset_x[GPU_MAX_VIEWPORTS];
|
||||
int viewport_offset_y[GPU_MAX_VIEWPORTS];
|
||||
int viewport_width[GPU_MAX_VIEWPORTS];
|
||||
int viewport_height[GPU_MAX_VIEWPORTS];
|
||||
bool scissor_enabled;
|
||||
int scissor_x;
|
||||
int scissor_y;
|
||||
int scissor_width;
|
||||
int scissor_height;
|
||||
|
||||
/* Render parameters. */
|
||||
float point_size = 1.0f;
|
||||
float line_width = 1.0f;
|
||||
|
||||
/* Clipping plane enablement. */
|
||||
bool clip_distance_enabled[6] = {false};
|
||||
};
|
||||
|
||||
/* Command Buffer Manager - Owned by MTLContext.
|
||||
* The MTLCommandBufferManager represents all work associated with
|
||||
* a command buffer of a given identity. This manager is a fixed-state
|
||||
* on the context, which coordinates the lifetime of command buffers
|
||||
* for particular categories of work.
|
||||
*
|
||||
* This ensures operations on command buffers, and the state associated,
|
||||
* is correctly tracked and managed. Workload submission and MTLCommandEncoder
|
||||
* coordination is managed from here.
|
||||
*
|
||||
* There is currently only one MTLCommandBufferManager for managing submission
|
||||
* of the "main" rendering commands. A secondary upload command buffer track,
|
||||
* or asynchronous compute command buffer track may be added in the future. */
|
||||
class MTLCommandBufferManager {
|
||||
friend class MTLContext;
|
||||
|
||||
public:
|
||||
/* Counter for all active command buffers. */
|
||||
static volatile std::atomic<int> num_active_cmd_bufs_in_system;
|
||||
|
||||
private:
|
||||
/* Associated Context and properties. */
|
||||
MTLContext &context_;
|
||||
bool supports_render_ = false;
|
||||
|
||||
/* CommandBuffer tracking. */
|
||||
id<MTLCommandBuffer> active_command_buffer_ = nil;
|
||||
id<MTLCommandBuffer> last_submitted_command_buffer_ = nil;
|
||||
volatile std::atomic<int> num_active_cmd_bufs = 0;
|
||||
|
||||
/* Active MTLCommandEncoders. */
|
||||
enum {
|
||||
MTL_NO_COMMAND_ENCODER = 0,
|
||||
MTL_RENDER_COMMAND_ENCODER = 1,
|
||||
MTL_BLIT_COMMAND_ENCODER = 2,
|
||||
MTL_COMPUTE_COMMAND_ENCODER = 3
|
||||
} active_command_encoder_type_ = MTL_NO_COMMAND_ENCODER;
|
||||
|
||||
id<MTLRenderCommandEncoder> active_render_command_encoder_ = nil;
|
||||
id<MTLBlitCommandEncoder> active_blit_command_encoder_ = nil;
|
||||
id<MTLComputeCommandEncoder> active_compute_command_encoder_ = nil;
|
||||
|
||||
/* State associated with active RenderCommandEncoder. */
|
||||
MTLRenderPassState render_pass_state_;
|
||||
MTLFrameBuffer *active_frame_buffer_ = nullptr;
|
||||
MTLRenderPassDescriptor *active_pass_descriptor_ = nullptr;
|
||||
|
||||
/* State associated with active ComputeCommandEncoder. */
|
||||
MTLComputeState compute_state_;
|
||||
|
||||
/* Workload heuristics - We may need to split command buffers to optimize workload and balancing.
|
||||
*/
|
||||
int current_draw_call_count_ = 0;
|
||||
int encoder_count_ = 0;
|
||||
int vertex_submitted_count_ = 0;
|
||||
bool empty_ = true;
|
||||
|
||||
/** Debug groups. */
|
||||
/* Copy of the debug stack to keep track of which group have been pushed to the debug layers.
|
||||
* This is needed because we do JIT push and pop the debug groups to better accommodate the
|
||||
* Metal API structure. */
|
||||
DebugStack mtl_debug_stack_;
|
||||
|
||||
public:
|
||||
MTLCommandBufferManager(MTLContext &context)
|
||||
: context_(context), render_pass_state_(context, *this), compute_state_(context, *this) {};
|
||||
void prepare(bool supports_render = true);
|
||||
|
||||
/* If wait is true, CPU will stall until GPU work has completed. */
|
||||
bool submit(bool wait);
|
||||
|
||||
/* Fetch/query current encoder. */
|
||||
bool is_inside_render_pass();
|
||||
bool is_inside_blit();
|
||||
bool is_inside_compute();
|
||||
id<MTLRenderCommandEncoder> get_active_render_command_encoder();
|
||||
id<MTLBlitCommandEncoder> get_active_blit_command_encoder();
|
||||
id<MTLComputeCommandEncoder> get_active_compute_command_encoder();
|
||||
MTLFrameBuffer *get_active_framebuffer();
|
||||
|
||||
/* RenderPassState for RenderCommandEncoder. */
|
||||
MTLRenderPassState &get_render_pass_state()
|
||||
{
|
||||
/* Render pass state should only be valid if we are inside a render pass. */
|
||||
BLI_assert(this->is_inside_render_pass());
|
||||
return render_pass_state_;
|
||||
}
|
||||
|
||||
/* RenderPassState for RenderCommandEncoder. */
|
||||
MTLComputeState &get_compute_state()
|
||||
{
|
||||
/* Render pass state should only be valid if we are inside a compute encoder. */
|
||||
BLI_assert(this->is_inside_compute());
|
||||
return compute_state_;
|
||||
}
|
||||
|
||||
/* Rendering Heuristics. */
|
||||
void register_draw_counters(int vertex_submission);
|
||||
void reset_counters();
|
||||
bool do_break_submission();
|
||||
|
||||
/* Encoder and Pass management. */
|
||||
/* End currently active MTLCommandEncoder. */
|
||||
bool end_active_command_encoder(bool retain_framebuffers = false);
|
||||
id<MTLRenderCommandEncoder> ensure_begin_render_command_encoder(MTLFrameBuffer *ctx_framebuffer,
|
||||
bool force_begin,
|
||||
bool *r_new_pass);
|
||||
id<MTLBlitCommandEncoder> ensure_begin_blit_encoder();
|
||||
id<MTLComputeCommandEncoder> ensure_begin_compute_encoder();
|
||||
|
||||
/* Workload Synchronization. */
|
||||
bool insert_memory_barrier(GPUBarrier barrier_bits,
|
||||
GPUStageBarrierBits before_stages,
|
||||
GPUStageBarrierBits after_stages);
|
||||
void encode_signal_event(id<MTLEvent> event, uint64_t value);
|
||||
void encode_wait_for_event(id<MTLEvent> event, uint64_t value);
|
||||
/* TODO(Metal): Support fences in command buffer class. */
|
||||
|
||||
/* Debug. */
|
||||
void push_debug_group(const char *name, int index);
|
||||
void pop_debug_group();
|
||||
|
||||
void inc_active_command_buffer_count()
|
||||
{
|
||||
num_active_cmd_bufs_in_system++;
|
||||
num_active_cmd_bufs++;
|
||||
}
|
||||
|
||||
void dec_active_command_buffer_count()
|
||||
{
|
||||
BLI_assert(num_active_cmd_bufs_in_system > 0 && num_active_cmd_bufs > 0);
|
||||
num_active_cmd_bufs_in_system--;
|
||||
num_active_cmd_bufs--;
|
||||
}
|
||||
|
||||
int get_active_command_buffer_count()
|
||||
{
|
||||
return num_active_cmd_bufs;
|
||||
}
|
||||
|
||||
void wait_until_active_command_buffers_complete()
|
||||
{
|
||||
while (get_active_command_buffer_count()) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
/* Debug group management. To be called before any low-level `pushDebugGroup`. */
|
||||
void unfold_pending_debug_groups();
|
||||
|
||||
private:
|
||||
/* Begin new command buffer. */
|
||||
id<MTLCommandBuffer> ensure_begin();
|
||||
|
||||
void register_encoder_counters();
|
||||
void fold_remaining_debug_groups();
|
||||
};
|
||||
|
||||
/**
|
||||
* MTLContext -- Core render loop and state management.
|
||||
*
|
||||
* NOTE(Metal): Partial #MTLContext stub to provide wrapper functionality
|
||||
* for work-in-progress `MTL*` classes.
|
||||
*/
|
||||
class MTLContext : public Context {
|
||||
friend class MTLBackend;
|
||||
friend class MTLRenderPassState;
|
||||
friend class MTLComputeState;
|
||||
|
||||
public:
|
||||
/* Swap-chain and latency management. */
|
||||
static std::atomic<int> max_drawables_in_flight;
|
||||
static std::atomic<int64_t> avg_drawable_latency_us;
|
||||
static int64_t frame_latency[MTL_FRAME_AVERAGE_COUNT];
|
||||
|
||||
public:
|
||||
/* Shaders and Pipeline state. */
|
||||
MTLContextGlobalShaderPipelineState pipeline_state;
|
||||
|
||||
/* Metal API Resource Handles. */
|
||||
id<MTLCommandQueue> queue = nil;
|
||||
id<MTLDevice> device = nil;
|
||||
|
||||
#ifndef NDEBUG
|
||||
/* Label for Context debug name assignment. */
|
||||
NSString *label = nil;
|
||||
#endif
|
||||
|
||||
/* Memory Management. */
|
||||
MTLScratchBufferManager memory_manager;
|
||||
static std::mutex global_memory_manager_reflock;
|
||||
static int global_memory_manager_refcount;
|
||||
static MTLBufferPool *global_memory_manager;
|
||||
|
||||
/* CommandBuffer managers. */
|
||||
MTLCommandBufferManager main_command_buffer;
|
||||
|
||||
private:
|
||||
/* Parent Context. */
|
||||
GHOST_ContextMTL *ghost_context_;
|
||||
|
||||
/* Render Passes and Frame-buffers. */
|
||||
id<MTLTexture> default_fbo_mtltexture_ = nil;
|
||||
gpu::MTLTexture *default_fbo_gputexture_ = nullptr;
|
||||
|
||||
/* Depth-stencil state cache. */
|
||||
Map<MTLContextDepthStencilState, id<MTLDepthStencilState>> depth_stencil_state_cache;
|
||||
|
||||
/* Compute and specialization caches. */
|
||||
MTLContextTextureUtils texture_utils_;
|
||||
MTLContextComputeUtils compute_utils_;
|
||||
|
||||
/* Texture Samplers. */
|
||||
/* Cache of generated #MTLSamplerState objects based on permutations of the members of
|
||||
* `GPUSamplerState`. */
|
||||
id<MTLSamplerState> sampler_state_cache_[GPU_SAMPLER_EXTEND_MODES_COUNT]
|
||||
[GPU_SAMPLER_EXTEND_MODES_COUNT]
|
||||
[GPU_SAMPLER_FILTERING_TYPES_COUNT];
|
||||
id<MTLSamplerState> custom_sampler_state_cache_[GPU_SAMPLER_CUSTOM_TYPES_COUNT];
|
||||
id<MTLSamplerState> default_sampler_state_ = nil;
|
||||
|
||||
/* When texture sampler count exceeds the resource bind limit, an
|
||||
* argument buffer is used to pass samplers to the shader.
|
||||
* Each unique configurations of multiple samplers can be cached, so as to not require
|
||||
* re-generation. `samplers_` stores the current list of bound sampler objects.
|
||||
* `cached_sampler_buffers_` is a cache of encoded argument buffers which can be re-used. */
|
||||
MTLSamplerArray samplers_;
|
||||
Map<MTLSamplerArray, gpu::MTLBuffer *> cached_sampler_buffers_;
|
||||
|
||||
/* Frame. */
|
||||
bool is_inside_frame_ = false;
|
||||
uint current_frame_index_;
|
||||
|
||||
/* Visibility buffer for MTLQuery results. */
|
||||
gpu::MTLBuffer *visibility_buffer_ = nullptr;
|
||||
bool visibility_is_dirty_ = false;
|
||||
|
||||
/* Null buffers for empty/uninitialized bindings.
|
||||
* Null attribute buffer follows default attribute format of OpenGL Backend. */
|
||||
id<MTLBuffer> null_buffer_; /* All zero's. */
|
||||
id<MTLBuffer> null_attribute_buffer_; /* Value float4(0.0,0.0,0.0,1.0). */
|
||||
|
||||
/** Dummy Resources */
|
||||
/* Maximum of 32 texture types. Though most combinations invalid. */
|
||||
gpu::MTLTexture *dummy_textures_[GPU_SAMPLER_TYPE_MAX][GPU_TEXTURE_BUFFER] = {{nullptr}};
|
||||
GPUVertFormat dummy_vertformat_[GPU_SAMPLER_TYPE_MAX];
|
||||
VertBuf *dummy_verts_[GPU_SAMPLER_TYPE_MAX] = {nullptr};
|
||||
|
||||
/* Debug scope timings. Adapted form GLContext::TimeQuery.
|
||||
* Only supports CPU timings for now. */
|
||||
struct ScopeTimings {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using TimePoint = Clock::time_point;
|
||||
using Nanoseconds = std::chrono::nanoseconds;
|
||||
|
||||
static TimePoint epoch;
|
||||
|
||||
std::string name;
|
||||
bool finished;
|
||||
TimePoint cpu_start, cpu_end;
|
||||
};
|
||||
Vector<ScopeTimings> scope_timings;
|
||||
|
||||
void process_frame_timings();
|
||||
|
||||
public:
|
||||
/* GPUContext interface. */
|
||||
MTLContext(GHOST_IWindow *ghost_window, GHOST_IContext *ghost_context);
|
||||
~MTLContext();
|
||||
|
||||
static void check_error(const char *info);
|
||||
|
||||
void activate() override;
|
||||
void deactivate() override;
|
||||
void begin_frame() override;
|
||||
void end_frame() override;
|
||||
|
||||
void flush() override;
|
||||
void finish() override;
|
||||
|
||||
void memory_statistics_get(int *r_total_mem, int *r_free_mem) override;
|
||||
|
||||
static MTLContext *get()
|
||||
{
|
||||
return static_cast<MTLContext *>(Context::get());
|
||||
}
|
||||
|
||||
void debug_group_begin(const char *name, int index) override;
|
||||
void debug_group_end() override;
|
||||
bool debug_capture_begin(const char *title) override;
|
||||
void debug_capture_end() override;
|
||||
void *debug_capture_scope_create(const char *name) override;
|
||||
bool debug_capture_scope_begin(void *scope) override;
|
||||
void debug_capture_scope_end(void *scope) override;
|
||||
|
||||
void debug_unbind_all_ubo() override {};
|
||||
void debug_unbind_all_ssbo() override {};
|
||||
|
||||
/*** MTLContext Utility functions. */
|
||||
/*
|
||||
* All below functions modify the global state for the context, controlling the flow of
|
||||
* rendering, binding resources, setting global state, resource management etc;
|
||||
*/
|
||||
|
||||
/** Metal Context Core functions. **/
|
||||
|
||||
/* Bind frame-buffer to context. */
|
||||
void framebuffer_bind(MTLFrameBuffer *framebuffer);
|
||||
|
||||
/* Restore frame-buffer used by active context to default back-buffer. */
|
||||
void framebuffer_restore();
|
||||
|
||||
/* Ensure a render-pass using the Context frame-buffer (active_fb_) is in progress. */
|
||||
id<MTLRenderCommandEncoder> ensure_begin_render_pass();
|
||||
|
||||
MTLFrameBuffer *get_current_framebuffer();
|
||||
MTLFrameBuffer *get_default_framebuffer();
|
||||
|
||||
/* Context Global-State Texture Binding. */
|
||||
void texture_bind(gpu::MTLTexture *mtl_texture, uint texture_unit, bool is_image);
|
||||
void sampler_bind(MTLSamplerState, uint sampler_unit);
|
||||
void texture_unbind(gpu::MTLTexture *mtl_texture, bool is_image, StateManager *state_manager);
|
||||
void texture_unbind_all(bool is_image);
|
||||
void sampler_state_cache_init();
|
||||
id<MTLSamplerState> get_sampler_from_state(MTLSamplerState state);
|
||||
id<MTLSamplerState> get_default_sampler_state();
|
||||
|
||||
/* Active shader specialization constants state. */
|
||||
shader::SpecializationConstants constants_state;
|
||||
|
||||
void specialization_constants_set(const shader::SpecializationConstants *constants_state);
|
||||
|
||||
/* Metal Context pipeline state. */
|
||||
void pipeline_state_init();
|
||||
MTLShader *get_active_shader();
|
||||
|
||||
/* These functions ensure that the current RenderCommandEncoder has
|
||||
* the correct global state assigned. This should be called prior
|
||||
* to every draw call, to ensure that all state is applied and up
|
||||
* to date. We handle:
|
||||
*
|
||||
* - Buffer bindings (Vertex buffers, Uniforms, UBOs)
|
||||
* - Texture bindings
|
||||
* - Sampler bindings (+ argument buffer bindings)
|
||||
* - Dynamic Render pipeline state (on encoder)
|
||||
* - Baking Pipeline State Objects (PSOs) for current shader, based
|
||||
* on final pipeline state.
|
||||
*
|
||||
* `ensure_render_pipeline_state` will return false if the state is
|
||||
* invalid and cannot be applied. This should cancel a draw call. */
|
||||
bool ensure_render_pipeline_state(MTLPrimitiveType prim_type);
|
||||
void ensure_depth_stencil_state();
|
||||
|
||||
id<MTLBuffer> get_null_buffer();
|
||||
id<MTLBuffer> get_null_attribute_buffer();
|
||||
gpu::MTLTexture *get_dummy_texture(GPUTextureType type, GPUSamplerFormat sampler_format);
|
||||
void free_dummy_resources();
|
||||
|
||||
/* Compute. */
|
||||
/* Ensure compute pipeline state for current config is compiled and return PSO instance. */
|
||||
const MTLComputePipelineStateInstance *ensure_compute_pipeline_state();
|
||||
void compute_dispatch(int groups_x_len, int groups_y_len, int groups_z_len);
|
||||
void compute_dispatch_indirect(StorageBuf *indirect_buf);
|
||||
|
||||
/* State assignment. */
|
||||
void set_viewport(int origin_x, int origin_y, int width, int height);
|
||||
void set_viewports(int count, const int (&viewports)[GPU_MAX_VIEWPORTS][4]);
|
||||
void set_scissor(int scissor_x, int scissor_y, int scissor_width, int scissor_height);
|
||||
void set_scissor_enabled(bool scissor_enabled);
|
||||
|
||||
/* Visibility buffer control. */
|
||||
void set_visibility_buffer(gpu::MTLBuffer *buffer);
|
||||
gpu::MTLBuffer *get_visibility_buffer() const;
|
||||
|
||||
/* Flag whether the visibility buffer for query results
|
||||
* has changed. This requires a new RenderPass in order
|
||||
* to update. */
|
||||
bool is_visibility_dirty() const;
|
||||
|
||||
/* Reset dirty flag state for visibility buffer. */
|
||||
void clear_visibility_dirty();
|
||||
|
||||
/* Texture utilities. */
|
||||
MTLContextTextureUtils &get_texture_utils()
|
||||
{
|
||||
return texture_utils_;
|
||||
}
|
||||
|
||||
/* Compute utilities. */
|
||||
MTLContextComputeUtils &get_compute_utils()
|
||||
{
|
||||
return compute_utils_;
|
||||
}
|
||||
|
||||
bool get_active()
|
||||
{
|
||||
return is_active_;
|
||||
}
|
||||
|
||||
bool get_inside_frame()
|
||||
{
|
||||
return is_inside_frame_;
|
||||
}
|
||||
|
||||
uint get_current_frame_index()
|
||||
{
|
||||
return current_frame_index_;
|
||||
}
|
||||
|
||||
MTLScratchBufferManager &get_scratch_buffer_manager()
|
||||
{
|
||||
return this->memory_manager;
|
||||
}
|
||||
|
||||
static void global_memory_manager_acquire_ref()
|
||||
{
|
||||
MTLContext::global_memory_manager_reflock.lock();
|
||||
if (MTLContext::global_memory_manager == nullptr) {
|
||||
BLI_assert(MTLContext::global_memory_manager_refcount == 0);
|
||||
MTLContext::global_memory_manager = new MTLBufferPool();
|
||||
}
|
||||
MTLContext::global_memory_manager_refcount++;
|
||||
MTLContext::global_memory_manager_reflock.unlock();
|
||||
}
|
||||
|
||||
static void global_memory_manager_release_ref()
|
||||
{
|
||||
MTLContext::global_memory_manager_reflock.lock();
|
||||
MTLContext::global_memory_manager_refcount--;
|
||||
BLI_assert(MTLContext::global_memory_manager_refcount >= 0);
|
||||
BLI_assert(MTLContext::global_memory_manager != nullptr);
|
||||
|
||||
if (MTLContext::global_memory_manager_refcount <= 0) {
|
||||
delete MTLContext::global_memory_manager;
|
||||
MTLContext::global_memory_manager = nullptr;
|
||||
}
|
||||
MTLContext::global_memory_manager_reflock.unlock();
|
||||
}
|
||||
|
||||
static MTLBufferPool *get_global_memory_manager()
|
||||
{
|
||||
BLI_assert(MTLContext::global_memory_manager != nullptr);
|
||||
return MTLContext::global_memory_manager;
|
||||
}
|
||||
|
||||
MTLSamplerArray &get_sampler_array()
|
||||
{
|
||||
return samplers_;
|
||||
}
|
||||
|
||||
Map<MTLSamplerArray, gpu::MTLBuffer *> &get_sampler_arg_buf_cache()
|
||||
{
|
||||
return cached_sampler_buffers_;
|
||||
}
|
||||
|
||||
/* Swap-chain and latency management. */
|
||||
static void latency_resolve_average(int64_t frame_latency_us)
|
||||
{
|
||||
int64_t avg = 0;
|
||||
int64_t frame_c = 0;
|
||||
for (int i = MTL_FRAME_AVERAGE_COUNT - 1; i > 0; i--) {
|
||||
MTLContext::frame_latency[i] = MTLContext::frame_latency[i - 1];
|
||||
avg += MTLContext::frame_latency[i];
|
||||
frame_c += (MTLContext::frame_latency[i] > 0) ? 1 : 0;
|
||||
}
|
||||
MTLContext::frame_latency[0] = frame_latency_us;
|
||||
avg += MTLContext::frame_latency[0];
|
||||
if (frame_c > 0) {
|
||||
avg /= frame_c;
|
||||
}
|
||||
else {
|
||||
avg = 0;
|
||||
}
|
||||
MTLContext::avg_drawable_latency_us = avg;
|
||||
}
|
||||
|
||||
private:
|
||||
void set_ghost_context(GHOST_IContext *ghostCtxHandle);
|
||||
void set_ghost_window(GHOST_IWindow *ghostWinHandle);
|
||||
};
|
||||
|
||||
/* GHOST Context callbacks. */
|
||||
void present(MTLRenderPassDescriptor *blit_descriptor,
|
||||
id<MTLRenderPipelineState> blit_pso,
|
||||
id<MTLTexture> swapchain_texture,
|
||||
id<CAMetalDrawable> drawable);
|
||||
|
||||
void xr_blit(id<MTLTexture> metal_xr_texture, int ofsx, int ofsy, int width, int height);
|
||||
|
||||
} // namespace blender::gpu
|
||||
2115
blender-5.2.0/source/blender/gpu/metal/mtl_context.mm
Normal file
2115
blender-5.2.0/source/blender/gpu/metal/mtl_context.mm
Normal file
File diff suppressed because it is too large
Load Diff
53
blender-5.2.0/source/blender/gpu/metal/mtl_debug.hh
Normal file
53
blender-5.2.0/source/blender/gpu/metal/mtl_debug.hh
Normal file
@@ -0,0 +1,53 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_global.hh"
|
||||
#include "BLI_system.h"
|
||||
#include "CLG_log.h"
|
||||
|
||||
/** Options for organizing Metal GPU debug captures. */
|
||||
/* Maximum nested debug group depth. Groups beyond this will still have the pass name pulled into
|
||||
* the RenderCommandEncoder, but will not display in the trace.
|
||||
* Use -1 for unlimited. */
|
||||
#define METAL_DEBUG_CAPTURE_MAX_NESTED_GROUPS -1
|
||||
|
||||
namespace blender::gpu::debug {
|
||||
|
||||
extern CLG_LogRef LOG;
|
||||
|
||||
/* Initialize debugging. */
|
||||
void mtl_debug_init();
|
||||
|
||||
/* Using Macro's instead of variadic template due to non-string-literal
|
||||
* warning for CLG_logf when indirectly passing format string. */
|
||||
#define EXPAND_ARGS(...) , ##__VA_ARGS__
|
||||
#define MTL_LOG_ERROR(info, ...) \
|
||||
{ \
|
||||
if (G.debug & G_DEBUG_GPU) { \
|
||||
CLOG_ERROR(&debug::LOG, info EXPAND_ARGS(__VA_ARGS__)); \
|
||||
BLI_system_backtrace(stderr); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define MTL_LOG_WARNING(info, ...) \
|
||||
{ \
|
||||
if (G.debug & G_DEBUG_GPU) { \
|
||||
CLOG_WARN(&debug::LOG, info EXPAND_ARGS(__VA_ARGS__)); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define MTL_LOG_DEBUG(info, ...) \
|
||||
{ \
|
||||
if (G.debug & G_DEBUG_GPU) { \
|
||||
CLOG_DEBUG(&debug::LOG, info EXPAND_ARGS(__VA_ARGS__)); \
|
||||
} \
|
||||
}
|
||||
|
||||
} // namespace blender::gpu::debug
|
||||
192
blender-5.2.0/source/blender/gpu/metal/mtl_debug.mm
Normal file
192
blender-5.2.0/source/blender/gpu/metal/mtl_debug.mm
Normal file
@@ -0,0 +1,192 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*
|
||||
* Debug features of OpenGL.
|
||||
*/
|
||||
|
||||
#include "BLI_compiler_attrs.h"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_system.h"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "BKE_global.hh"
|
||||
|
||||
#include "GPU_debug.hh"
|
||||
#include "GPU_platform.hh"
|
||||
|
||||
#include "mtl_context.hh"
|
||||
#include "mtl_debug.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
#include "gpu_profile_report.hh"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace blender::gpu::debug {
|
||||
|
||||
CLG_LogRef LOG = {"gpu.debug.metal"};
|
||||
|
||||
void mtl_debug_init()
|
||||
{
|
||||
CLOG_ENSURE(&LOG);
|
||||
}
|
||||
|
||||
} // namespace blender::gpu::debug
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Debug Groups
|
||||
*
|
||||
* Useful for debugging through XCode GPU Debugger. This ensures all the API calls grouped into
|
||||
* "passes".
|
||||
* \{ */
|
||||
|
||||
void MTLContext::debug_group_begin(const char *name, int /*index*/)
|
||||
{
|
||||
/* Note: Debug groups are pushed JIT to the command encoders. This avoids splitting and nesting
|
||||
* the command encoders which would make the debugging experience through Xcode more confusing.
|
||||
* See #unfold_pending_debug_groups(). */
|
||||
|
||||
if (!G.profile_gpu) {
|
||||
return;
|
||||
}
|
||||
|
||||
ScopeTimings timings = {};
|
||||
timings.name = name;
|
||||
timings.finished = false;
|
||||
timings.cpu_start = ScopeTimings::Clock::now();
|
||||
|
||||
scope_timings.append(timings);
|
||||
}
|
||||
|
||||
void MTLContext::debug_group_end()
|
||||
{
|
||||
/* Note: Debug groups are pushed JIT to the command encoders. This avoids splitting and nesting
|
||||
* the command encoders which would make the debugging experience through Xcode more confusing.
|
||||
* See #unfold_pending_debug_groups(). */
|
||||
|
||||
if (!G.profile_gpu) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = scope_timings.size() - 1; i >= 0; i--) {
|
||||
ScopeTimings &query = scope_timings[i];
|
||||
if (!query.finished) {
|
||||
query.finished = true;
|
||||
query.cpu_end = ScopeTimings::Clock::now();
|
||||
break;
|
||||
}
|
||||
if (i == 0) {
|
||||
CLOG_ERROR(&debug::LOG, "Profile GPU error: Extra GPU_debug_group_end() call.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MTLContext::ScopeTimings::TimePoint MTLContext::ScopeTimings::epoch =
|
||||
MTLContext::ScopeTimings::Clock::now();
|
||||
|
||||
void MTLContext::process_frame_timings()
|
||||
{
|
||||
if (!G.profile_gpu) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<ScopeTimings> &queries = scope_timings;
|
||||
|
||||
bool frame_is_valid = !queries.is_empty();
|
||||
|
||||
for (int i = queries.size() - 1; i >= 0; i--) {
|
||||
if (!queries[i].finished) {
|
||||
frame_is_valid = false;
|
||||
CLOG_ERROR(&debug::LOG, "Profile GPU error: Missing GPU_debug_group_end() call");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!frame_is_valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (ScopeTimings &query : queries) {
|
||||
ScopeTimings::Nanoseconds begin = query.cpu_start - ScopeTimings::epoch;
|
||||
ScopeTimings::Nanoseconds end = query.cpu_end - ScopeTimings::epoch;
|
||||
ProfileReport::get().add_group_cpu(query.name, begin.count(), end.count());
|
||||
}
|
||||
|
||||
queries.clear();
|
||||
}
|
||||
|
||||
bool MTLContext::debug_capture_begin(const char * /*title*/)
|
||||
{
|
||||
MTLCaptureManager *capture_manager = [MTLCaptureManager sharedCaptureManager];
|
||||
if (!capture_manager) {
|
||||
/* Early exit if frame capture is disabled. */
|
||||
return false;
|
||||
}
|
||||
MTLCaptureDescriptor *capture_descriptor = [[MTLCaptureDescriptor alloc] init];
|
||||
capture_descriptor.captureObject = this->device;
|
||||
NSError *error;
|
||||
if (![capture_manager startCaptureWithDescriptor:capture_descriptor error:&error]) {
|
||||
NSLog(@"Failed to start Metal frame capture, error %@", error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MTLContext::debug_capture_end()
|
||||
{
|
||||
MTLCaptureManager *capture_manager = [MTLCaptureManager sharedCaptureManager];
|
||||
if (!capture_manager) {
|
||||
/* Early exit if frame capture is disabled. */
|
||||
return;
|
||||
}
|
||||
[capture_manager stopCapture];
|
||||
}
|
||||
|
||||
void *MTLContext::debug_capture_scope_create(const char *name)
|
||||
{
|
||||
/* Create a capture scope visible to xCode Metal Frame capture utility. */
|
||||
MTLCaptureManager *capture_manager = [MTLCaptureManager sharedCaptureManager];
|
||||
if (!capture_manager) {
|
||||
/* Early exit if frame capture is disabled. */
|
||||
return nullptr;
|
||||
}
|
||||
id<MTLCaptureScope> capture_scope = [capture_manager newCaptureScopeWithDevice:this->device];
|
||||
capture_scope.label = [NSString stringWithUTF8String:name];
|
||||
[capture_scope retain];
|
||||
|
||||
return reinterpret_cast<void *>(capture_scope);
|
||||
}
|
||||
|
||||
bool MTLContext::debug_capture_scope_begin(void *scope)
|
||||
{
|
||||
if ([((id<MTLCaptureScope>)scope).label isEqual:@(G.gpu_debug_scope_name)]) {
|
||||
debug_capture_begin("Auto Capture");
|
||||
}
|
||||
|
||||
/* Declare opening boundary of scope.
|
||||
* When scope is selected for capture, GPU commands between begin/end scope will be captured. */
|
||||
[(id<MTLCaptureScope>)scope beginScope];
|
||||
|
||||
MTLCaptureManager *capture_manager = [MTLCaptureManager sharedCaptureManager];
|
||||
return [capture_manager isCapturing];
|
||||
}
|
||||
|
||||
void MTLContext::debug_capture_scope_end(void *scope)
|
||||
{
|
||||
if ([((id<MTLCaptureScope>)scope).label isEqual:@(G.gpu_debug_scope_name)]) {
|
||||
debug_capture_end();
|
||||
}
|
||||
|
||||
[(id<MTLCaptureScope>)scope endScope];
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::gpu
|
||||
258
blender-5.2.0/source/blender/gpu/metal/mtl_framebuffer.hh
Normal file
258
blender-5.2.0/source/blender/gpu/metal/mtl_framebuffer.hh
Normal file
@@ -0,0 +1,258 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*
|
||||
* Encapsulation of Frame-buffer states (attached textures, viewport, scissors).
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GPU_common_types.hh"
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "gpu_framebuffer_private.hh"
|
||||
#include "mtl_texture.hh"
|
||||
|
||||
#include <Metal/Metal.h>
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
class MTLContext;
|
||||
|
||||
struct MTLAttachment {
|
||||
bool used = false;
|
||||
bool ignored = false;
|
||||
gpu::MTLTexture *texture = nullptr;
|
||||
double4 clear_value;
|
||||
|
||||
GPULoadOp load_action = GPU_LOADACTION_DONT_CARE;
|
||||
GPUStoreOp store_action = GPU_STOREACTION_DONT_CARE;
|
||||
uint mip = 0;
|
||||
uint slice = 0;
|
||||
uint depth_plane = 0;
|
||||
|
||||
/* If Array Length is larger than zero, use multilayered rendering. */
|
||||
uint render_target_array_length = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of FrameBuffer object using Metal.
|
||||
*/
|
||||
class MTLFrameBuffer : public FrameBuffer {
|
||||
private:
|
||||
/* Context Handle. */
|
||||
MTLContext *context_;
|
||||
|
||||
/* Metal Attachment properties. */
|
||||
uint colour_attachment_count_;
|
||||
MTLAttachment mtl_color_attachments_[GPU_FB_MAX_COLOR_ATTACHMENT];
|
||||
MTLAttachment mtl_depth_attachment_;
|
||||
MTLAttachment mtl_stencil_attachment_;
|
||||
bool use_multilayered_rendering_ = false;
|
||||
|
||||
/* State. */
|
||||
|
||||
/**
|
||||
* Whether global frame-buffer properties have changed and require
|
||||
* re-generation of #MTLRenderPassDescriptor / #RenderCommandEncoders.
|
||||
*/
|
||||
bool is_dirty_;
|
||||
|
||||
/** Whether `loadstore` properties have changed (only affects certain cached configurations). */
|
||||
bool is_loadstore_dirty_;
|
||||
|
||||
/**
|
||||
* Context that the latest modified state was last applied to.
|
||||
* If this does not match current ctx, re-apply state.
|
||||
*/
|
||||
MTLContext *dirty_state_ctx_;
|
||||
|
||||
/**
|
||||
* Whether a clear is pending -- Used to toggle between clear and load FB configurations
|
||||
* (without dirtying the state) - Frame-buffer load configuration is used if no `GPU_clear_*`
|
||||
* command was issued after binding the #FrameBuffer.
|
||||
*/
|
||||
bool has_pending_clear_;
|
||||
|
||||
/**
|
||||
* Render Pass Descriptors:
|
||||
* There are 3 #MTLRenderPassDescriptors for different ways in which a frame-buffer
|
||||
* can be configured:
|
||||
* [0] = CLEAR CONFIG -- Used when a GPU_framebuffer_clear_* command has been issued.
|
||||
* [1] = LOAD CONFIG -- Used if bound, but no clear is required.
|
||||
* [2] = CUSTOM CONFIG -- When using GPU_framebuffer_bind_ex to manually specify
|
||||
* load-store configuration for optimal bandwidth utilization.
|
||||
* -- We cache these different configurations to avoid re-generation --
|
||||
*/
|
||||
enum {
|
||||
MTL_FB_CONFIG_CLEAR = 0,
|
||||
MTL_FB_CONFIG_LOAD = 1,
|
||||
MTL_FB_CONFIG_CUSTOM = 2,
|
||||
|
||||
MTL_FB_CONFIG_MAX = (MTL_FB_CONFIG_CUSTOM + 1),
|
||||
};
|
||||
|
||||
MTLRenderPassDescriptor *framebuffer_descriptor_[MTL_FB_CONFIG_MAX];
|
||||
MTLRenderPassColorAttachmentDescriptor
|
||||
*colour_attachment_descriptors_[GPU_FB_MAX_COLOR_ATTACHMENT];
|
||||
/** Whether `MTLRenderPassDescriptor[N]` requires updating with latest state. */
|
||||
bool descriptor_dirty_[MTL_FB_CONFIG_MAX];
|
||||
/** Whether SRGB is enabled for this frame-buffer configuration. */
|
||||
bool enabled_srgb_;
|
||||
/** Whether the primary Frame-buffer attachment is an SRGB target or not. */
|
||||
bool srgb_;
|
||||
|
||||
/** Attachment width/height represent raw size of active frame-buffer attachments.
|
||||
* For consistency with OpenGL backend, as width_/height_ can affect viewport and scissor
|
||||
* size, we need to track this differently to ensure viewport state does not get reset.
|
||||
* This size is only used to reset viewport/scissor regions when viewports and scissor are
|
||||
* disabled, as Metal does not provide a utility to fully disable either without manually
|
||||
* specifying the size.
|
||||
*/
|
||||
int attachment_width_ = 0;
|
||||
int attachment_height_ = 0;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Create a conventional frame-buffer to attach texture to.
|
||||
*/
|
||||
MTLFrameBuffer(MTLContext *ctx, const char *name);
|
||||
|
||||
~MTLFrameBuffer() override;
|
||||
|
||||
void bind(bool enabled_srgb) override;
|
||||
|
||||
bool check(char err_out[256]) override;
|
||||
|
||||
void clear(GPUFrameBufferBits buffers,
|
||||
const double4 clear_col,
|
||||
float clear_depth,
|
||||
uint clear_stencil) override;
|
||||
void clear_multi(Span<double4> clear_cols) override;
|
||||
void clear_attachment(GPUAttachmentType type, const double4 clear_value) override;
|
||||
|
||||
void attachment_set_loadstore_op(GPUAttachmentType type, GPULoadStore ls) override;
|
||||
|
||||
void read(GPUFrameBufferBits planes,
|
||||
eGPUDataFormat format,
|
||||
const int area[4],
|
||||
int channel_len,
|
||||
int slot,
|
||||
void *r_data) override;
|
||||
|
||||
void blit_to(GPUFrameBufferBits planes,
|
||||
int src_slot,
|
||||
FrameBuffer *dst,
|
||||
int dst_slot,
|
||||
int dst_offset_x,
|
||||
int dst_offset_y) override;
|
||||
|
||||
protected:
|
||||
void subpass_transition_impl(const GPUAttachmentState /*depth_attachment_state*/,
|
||||
Span<GPUAttachmentState> color_attachment_states) override;
|
||||
|
||||
public:
|
||||
void apply_state();
|
||||
|
||||
/* State. */
|
||||
/* Flag MTLFramebuffer configuration as having changed. */
|
||||
void mark_dirty();
|
||||
void mark_loadstore_dirty();
|
||||
/* Mark that a pending clear has been performed. */
|
||||
void mark_cleared();
|
||||
/* Mark that we have a pending clear. */
|
||||
void mark_do_clear();
|
||||
|
||||
/* Attachment management. */
|
||||
/* When dirty_attachments_ is true, we need to reprocess attachments to extract Metal
|
||||
* information. */
|
||||
void ensure_attachments_and_viewport();
|
||||
void update_attachments(bool update_viewport);
|
||||
bool add_color_attachment(gpu::MTLTexture *texture, uint slot, int miplevel, int layer);
|
||||
bool add_depth_attachment(gpu::MTLTexture *texture, int miplevel, int layer);
|
||||
bool add_stencil_attachment(gpu::MTLTexture *texture, int miplevel, int layer);
|
||||
bool remove_color_attachment(uint slot);
|
||||
bool remove_depth_attachment();
|
||||
bool remove_stencil_attachment();
|
||||
void remove_all_attachments();
|
||||
void ensure_render_target_size();
|
||||
|
||||
/* Clear values -> Load/store actions. */
|
||||
bool set_color_attachment_clear_color(uint slot, const double4 clear_color);
|
||||
bool set_depth_attachment_clear_value(float depth_clear);
|
||||
bool set_stencil_attachment_clear_value(uint stencil_clear);
|
||||
bool set_color_loadstore_op(uint slot, GPULoadOp load_action, GPUStoreOp store_action);
|
||||
bool set_depth_loadstore_op(GPULoadOp load_action, GPUStoreOp store_action);
|
||||
bool set_stencil_loadstore_op(GPULoadOp load_action, GPUStoreOp store_action);
|
||||
|
||||
/* Remove any pending clears - Ensure "load" configuration is used. */
|
||||
bool reset_clear_state();
|
||||
|
||||
/* Fetch values */
|
||||
bool has_attachment_at_slot(uint slot);
|
||||
bool has_color_attachment_with_texture(gpu::MTLTexture *texture);
|
||||
bool has_depth_attachment();
|
||||
bool has_stencil_attachment();
|
||||
int get_color_attachment_slot_from_texture(gpu::MTLTexture *texture);
|
||||
uint get_attachment_count();
|
||||
uint get_attachment_limit()
|
||||
{
|
||||
return GPU_FB_MAX_COLOR_ATTACHMENT;
|
||||
};
|
||||
MTLAttachment get_color_attachment(uint slot);
|
||||
MTLAttachment get_depth_attachment();
|
||||
MTLAttachment get_stencil_attachment();
|
||||
|
||||
/* Metal API resources and validation. */
|
||||
bool validate_render_pass();
|
||||
MTLRenderPassDescriptor *bake_render_pass_descriptor(bool load_contents);
|
||||
|
||||
/* Blitting. */
|
||||
void blit(uint read_slot,
|
||||
uint src_x_offset,
|
||||
uint src_y_offset,
|
||||
MTLFrameBuffer *metal_fb_write,
|
||||
uint write_slot,
|
||||
uint dst_x_offset,
|
||||
uint dst_y_offset,
|
||||
uint width,
|
||||
uint height,
|
||||
GPUFrameBufferBits blit_buffers);
|
||||
|
||||
int get_width();
|
||||
int get_height();
|
||||
int get_attachment_width();
|
||||
int get_attachment_height();
|
||||
void attachment_size_set(int w, int h);
|
||||
|
||||
bool get_dirty()
|
||||
{
|
||||
return is_dirty_ || is_loadstore_dirty_;
|
||||
}
|
||||
|
||||
bool get_pending_clear()
|
||||
{
|
||||
return has_pending_clear_;
|
||||
}
|
||||
|
||||
bool get_srgb_enabled()
|
||||
{
|
||||
return enabled_srgb_;
|
||||
}
|
||||
|
||||
bool get_is_srgb()
|
||||
{
|
||||
return srgb_;
|
||||
}
|
||||
|
||||
private:
|
||||
/* Clears a render target by force-opening a render pass. */
|
||||
void force_clear();
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLFrameBuffer");
|
||||
};
|
||||
|
||||
} // namespace blender::gpu
|
||||
1967
blender-5.2.0/source/blender/gpu/metal/mtl_framebuffer.mm
Normal file
1967
blender-5.2.0/source/blender/gpu/metal/mtl_framebuffer.mm
Normal file
File diff suppressed because it is too large
Load Diff
45
blender-5.2.0/source/blender/gpu/metal/mtl_immediate.hh
Normal file
45
blender-5.2.0/source/blender/gpu/metal/mtl_immediate.hh
Normal file
@@ -0,0 +1,45 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*
|
||||
* Mimics old style opengl immediate mode drawing.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "gpu_immediate_private.hh"
|
||||
|
||||
#include "mtl_context.hh"
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <Metal/Metal.h>
|
||||
#include <QuartzCore/QuartzCore.h>
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
class MTLImmediate : public Immediate {
|
||||
private:
|
||||
MTLContext *context_ = nullptr;
|
||||
MTLTemporaryBuffer current_allocation_;
|
||||
MTLPrimitiveTopologyClass metal_primitive_mode_;
|
||||
MTLPrimitiveType metal_primitive_type_;
|
||||
bool has_begun_ = false;
|
||||
|
||||
public:
|
||||
MTLImmediate(MTLContext *ctx);
|
||||
~MTLImmediate() override = default;
|
||||
|
||||
uchar *begin() override;
|
||||
void end() override;
|
||||
bool imm_is_recording()
|
||||
{
|
||||
return has_begun_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::gpu
|
||||
307
blender-5.2.0/source/blender/gpu/metal/mtl_immediate.mm
Normal file
307
blender-5.2.0/source/blender/gpu/metal/mtl_immediate.mm
Normal file
@@ -0,0 +1,307 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*
|
||||
* Mimics old style opengl immediate mode drawing.
|
||||
*/
|
||||
|
||||
#include "BKE_global.hh"
|
||||
|
||||
#include "BLI_bit_span.hh"
|
||||
|
||||
#include "GPU_vertex_format.hh"
|
||||
#include "gpu_context_private.hh"
|
||||
#include "gpu_shader_private.hh"
|
||||
#include "gpu_vertex_format_private.hh"
|
||||
|
||||
#include "mtl_context.hh"
|
||||
#include "mtl_debug.hh"
|
||||
#include "mtl_immediate.hh"
|
||||
#include "mtl_primitive.hh"
|
||||
#include "mtl_shader.hh"
|
||||
#include "mtl_vertex_buffer.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
MTLImmediate::MTLImmediate(MTLContext *ctx)
|
||||
{
|
||||
context_ = ctx;
|
||||
}
|
||||
|
||||
uchar *MTLImmediate::begin()
|
||||
{
|
||||
BLI_assert(!has_begun_);
|
||||
|
||||
/* Determine primitive type. */
|
||||
metal_primitive_type_ = gpu_prim_type_to_metal(this->prim_type);
|
||||
metal_primitive_mode_ = mtl_prim_type_to_topology_class(metal_primitive_type_);
|
||||
has_begun_ = true;
|
||||
|
||||
/* If prim type is line loop, add an extra vertex at the end for placing the closing line,
|
||||
* as metal does not support this primitive type. We treat this as a Line strip with one
|
||||
* extra value. */
|
||||
int vertex_alloc_length = vertex_len;
|
||||
if (prim_type == GPU_PRIM_LINE_LOOP) {
|
||||
vertex_alloc_length++;
|
||||
}
|
||||
|
||||
/* Allocate a range of data and return host-accessible pointer. */
|
||||
const size_t bytes_needed = vertex_buffer_size(&vertex_format, vertex_alloc_length);
|
||||
current_allocation_ = context_->get_scratch_buffer_manager()
|
||||
.scratch_buffer_allocate_range_aligned(bytes_needed, 256);
|
||||
[current_allocation_.metal_buffer retain];
|
||||
return reinterpret_cast<uchar *>(current_allocation_.data);
|
||||
}
|
||||
|
||||
void MTLImmediate::end()
|
||||
{
|
||||
/* Ensure we're between a `imm::begin` / `imm:end` pair. */
|
||||
BLI_assert(has_begun_);
|
||||
BLI_assert(prim_type != GPU_PRIM_NONE);
|
||||
|
||||
/* Verify context is valid, vertex data is written and a valid shader is bound. */
|
||||
if (context_ && this->vertex_idx > 0 && this->shader) {
|
||||
|
||||
MTLShader *active_mtl_shader = static_cast<MTLShader *>(shader);
|
||||
|
||||
/* Skip draw if Metal shader is not valid. */
|
||||
if (active_mtl_shader == nullptr || !active_mtl_shader->is_valid()) {
|
||||
const StringRefNull ptr = (active_mtl_shader) ? active_mtl_shader->name_get() : "";
|
||||
MTL_LOG_WARNING(
|
||||
"MTLImmediate::end -- cannot perform draw as active shader is NULL or invalid (likely "
|
||||
"unimplemented) (shader %p '%s')",
|
||||
active_mtl_shader,
|
||||
ptr.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
/* Ensure we are inside a render pass and fetch active RenderCommandEncoder. */
|
||||
id<MTLRenderCommandEncoder> rec = context_->ensure_begin_render_pass();
|
||||
BLI_assert(rec != nil);
|
||||
|
||||
/* Fetch active render pipeline state. */
|
||||
MTLRenderPassState &rps = context_->main_command_buffer.get_render_pass_state();
|
||||
|
||||
/* Bind Shader. */
|
||||
GPU_shader_bind(this->shader);
|
||||
|
||||
/* Debug markers for frame-capture and detailed error messages. */
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
context_->main_command_buffer.unfold_pending_debug_groups();
|
||||
[rec pushDebugGroup:[NSString stringWithFormat:@"immEnd(Shader:%s)",
|
||||
active_mtl_shader->name_get().c_str()]];
|
||||
}
|
||||
|
||||
/* Populate pipeline state vertex descriptor. */
|
||||
MTLStateManager *state_manager = static_cast<MTLStateManager *>(
|
||||
MTLContext::get()->state_manager);
|
||||
MTLRenderPipelineStateDescriptor &desc = state_manager->get_pipeline_descriptor();
|
||||
const MTLShaderInterface &interface = active_mtl_shader->get_interface();
|
||||
|
||||
/* Reset vertex descriptor to default state. */
|
||||
desc.reset_vertex_descriptor();
|
||||
desc.vertex_descriptor.total_attributes = interface.attr_len_;
|
||||
desc.vertex_descriptor.max_attribute_value = interface.attr_len_ - 1;
|
||||
desc.vertex_descriptor.num_vert_buffers = 1;
|
||||
|
||||
for (int i = 0; i < desc.vertex_descriptor.total_attributes; i++) {
|
||||
desc.vertex_descriptor.attributes[i].format = MTLVertexFormatInvalid;
|
||||
}
|
||||
|
||||
if (interface.vertex_buffer_mask() == 0) {
|
||||
MTL_LOG_ERROR("MTLImmediate::end Not enough buffer slot to bind attributes.");
|
||||
BLI_assert_unreachable();
|
||||
return;
|
||||
}
|
||||
|
||||
int imm_buffer_slot = bitscan_forward_uint(interface.vertex_buffer_mask());
|
||||
|
||||
/* Populate Vertex descriptor and verify attributes.
|
||||
* TODO(Metal): Cache this vertex state based on Vertex format and shaders. */
|
||||
for (int i : bits::iter_1_indices(interface.enabled_attr_mask_)) {
|
||||
const ShaderInput *input = interface.attr_get(i);
|
||||
BLI_assert(input != nullptr);
|
||||
StringRefNull input_name(interface.name_at_offset(input->name_offset));
|
||||
|
||||
GPUVertAttr *attr = nullptr;
|
||||
/* Scan through vertex_format attributes until one with a name matching the shader interface
|
||||
* is found. */
|
||||
for (uint32_t a_idx = 0; a_idx < this->vertex_format.attr_len && attr == nullptr; a_idx++) {
|
||||
GPUVertAttr *candidate = &this->vertex_format.attrs[a_idx];
|
||||
/* Attributes can have multiple name aliases associated with them. */
|
||||
for (uint32_t n_idx = 0; n_idx < candidate->name_len; n_idx++) {
|
||||
StringRefNull name = GPU_vertformat_attr_name_get(
|
||||
&this->vertex_format, candidate, n_idx);
|
||||
if (name == input_name) {
|
||||
attr = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attr == nullptr) {
|
||||
MTL_LOG_ERROR("MTLImmediate::end Could not find matching attribute '%s' in Vertex Format!",
|
||||
input_name.c_str());
|
||||
BLI_assert_unreachable();
|
||||
return;
|
||||
}
|
||||
|
||||
MTLVertexAttributeDescriptorPSO &pso_attr = desc.vertex_descriptor.attributes[i];
|
||||
pso_attr.format = gpu_vertex_format_to_metal(attr->type.format);
|
||||
pso_attr.format_conversion_mode = (is_fetch_float(attr->type.format)) ?
|
||||
GPUVertFetchMode(GPU_FETCH_FLOAT) :
|
||||
GPUVertFetchMode(GPU_FETCH_INT);
|
||||
/* Using attribute offset in vertex format, as this will be correct */
|
||||
pso_attr.offset = attr->offset;
|
||||
pso_attr.buffer_index = imm_buffer_slot;
|
||||
BLI_assert(pso_attr.format != MTLVertexFormatInvalid);
|
||||
}
|
||||
|
||||
/* Buffer bindings for singular vertex buffer. */
|
||||
desc.vertex_descriptor.buffer_layouts[0].step_function = MTLVertexStepFunctionPerVertex;
|
||||
desc.vertex_descriptor.buffer_layouts[0].step_rate = 1;
|
||||
desc.vertex_descriptor.buffer_layouts[0].stride = this->vertex_format.stride;
|
||||
desc.vertex_descriptor.buffer_layouts[0].buffer_slot = 0;
|
||||
BLI_assert(this->vertex_format.stride > 0);
|
||||
|
||||
/* Emulate LineLoop using LineStrip. */
|
||||
if (this->prim_type == GPU_PRIM_LINE_LOOP) {
|
||||
/* Patch final vertex of line loop to close. Rendered using LineStrip.
|
||||
* NOTE: vertex_len represents original length, however, allocated Metal
|
||||
* buffer contains space for one extra vertex when LineLoop is used. */
|
||||
uchar *buffer_data = reinterpret_cast<uchar *>(current_allocation_.data);
|
||||
memcpy(buffer_data + (vertex_len)*vertex_format.stride, buffer_data, vertex_format.stride);
|
||||
this->vertex_idx++;
|
||||
this->prim_type = GPU_PRIM_LINE_STRIP;
|
||||
}
|
||||
|
||||
if (this->shader->is_polyline) {
|
||||
context_->get_scratch_buffer_manager().bind_as_ssbo(GPU_SSBO_POLYLINE_POS_BUF_SLOT);
|
||||
context_->get_scratch_buffer_manager().bind_as_ssbo(GPU_SSBO_POLYLINE_COL_BUF_SLOT);
|
||||
context_->get_scratch_buffer_manager().bind_as_ssbo(GPU_SSBO_INDEX_BUF_SLOT);
|
||||
}
|
||||
|
||||
/* Bind Vertex Buffer. */
|
||||
rps.bind_vertex_buffer(
|
||||
current_allocation_.metal_buffer, current_allocation_.buffer_offset, imm_buffer_slot);
|
||||
|
||||
MTLPrimitiveType mtl_prim_type = gpu_prim_type_to_metal(this->prim_type);
|
||||
|
||||
if (context_->ensure_render_pipeline_state(mtl_prim_type)) {
|
||||
|
||||
/* Issue draw call. */
|
||||
BLI_assert(this->vertex_idx > 0);
|
||||
|
||||
/* Metal API does not support triangle fan, so we can emulate this
|
||||
* input data by generating an index buffer to re-map indices to
|
||||
* a TriangleList.
|
||||
*
|
||||
* NOTE(Metal): Consider caching generated triangle fan index buffers.
|
||||
* For immediate mode, generating these is currently very cheap, as we use
|
||||
* fast scratch buffer allocations. Though we may benefit from caching of
|
||||
* frequently used buffer sizes. */
|
||||
bool rendered = false;
|
||||
if (mtl_needs_topology_emulation(this->prim_type)) {
|
||||
|
||||
/* Emulate Tri-fan. */
|
||||
switch (this->prim_type) {
|
||||
case GPU_PRIM_TRI_FAN: {
|
||||
/* Prepare Triangle-Fan emulation index buffer on CPU based on number of input
|
||||
* vertices. */
|
||||
uint32_t base_vert_count = this->vertex_idx;
|
||||
uint32_t num_triangles = max_ii(base_vert_count - 2, 0);
|
||||
uint32_t fan_index_count = num_triangles * 3;
|
||||
BLI_assert(num_triangles > 0);
|
||||
|
||||
uint32_t alloc_size = sizeof(uint32_t) * fan_index_count;
|
||||
uint32_t *index_buffer = nullptr;
|
||||
|
||||
MTLTemporaryBuffer allocation =
|
||||
context_->get_scratch_buffer_manager().scratch_buffer_allocate_range_aligned(
|
||||
alloc_size, 128);
|
||||
index_buffer = (uint32_t *)allocation.data;
|
||||
|
||||
int a = 0;
|
||||
for (int i = 0; i < num_triangles; i++) {
|
||||
index_buffer[a++] = 0;
|
||||
index_buffer[a++] = i + 1;
|
||||
index_buffer[a++] = i + 2;
|
||||
}
|
||||
|
||||
@autoreleasepool {
|
||||
id<MTLBuffer> index_buffer_mtl = nil;
|
||||
uint64_t index_buffer_offset = 0;
|
||||
|
||||
/* Region of scratch buffer used for topology emulation element data.
|
||||
* NOTE(Metal): We do not need to manually flush as the entire scratch
|
||||
* buffer for current command buffer is flushed upon submission. */
|
||||
index_buffer_mtl = allocation.metal_buffer;
|
||||
index_buffer_offset = allocation.buffer_offset;
|
||||
|
||||
/* Set depth stencil state (TODO: Move it back upstream, is legacy of depth bias). */
|
||||
context_->ensure_depth_stencil_state();
|
||||
|
||||
/* Draw. */
|
||||
[rec drawIndexedPrimitives:MTLPrimitiveTypeTriangle
|
||||
indexCount:fan_index_count
|
||||
indexType:MTLIndexTypeUInt32
|
||||
indexBuffer:index_buffer_mtl
|
||||
indexBufferOffset:index_buffer_offset];
|
||||
context_->main_command_buffer.register_draw_counters(fan_index_count);
|
||||
}
|
||||
|
||||
rendered = true;
|
||||
} break;
|
||||
default: {
|
||||
BLI_assert_unreachable();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
/* If not yet rendered, run through main render path. LineLoop primitive topology emulation
|
||||
* will simply amend original data passed into default rendering path. */
|
||||
if (!rendered) {
|
||||
MTLPrimitiveType primitive_type = metal_primitive_type_;
|
||||
int vertex_count = this->vertex_idx;
|
||||
|
||||
/* Set depth stencil state (TODO: Move it back upstream, is legacy of depth bias). */
|
||||
context_->ensure_depth_stencil_state();
|
||||
|
||||
if (this->shader->is_polyline) {
|
||||
this->polyline_draw_workaround(current_allocation_.buffer_offset);
|
||||
}
|
||||
else {
|
||||
/* Regular draw. */
|
||||
[rec drawPrimitives:primitive_type vertexStart:0 vertexCount:vertex_count];
|
||||
context_->main_command_buffer.register_draw_counters(vertex_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
[rec popDebugGroup];
|
||||
}
|
||||
|
||||
if (this->shader->is_polyline) {
|
||||
context_->get_scratch_buffer_manager().unbind_as_ssbo();
|
||||
|
||||
context_->pipeline_state.ssbo_bindings[GPU_SSBO_POLYLINE_POS_BUF_SLOT].ssbo = nil;
|
||||
context_->pipeline_state.ssbo_bindings[GPU_SSBO_POLYLINE_COL_BUF_SLOT].ssbo = nil;
|
||||
context_->pipeline_state.ssbo_bindings[GPU_SSBO_INDEX_BUF_SLOT].ssbo = nil;
|
||||
context_->pipeline_state.ssbo_bindings[GPU_SSBO_POLYLINE_POS_BUF_SLOT].bound = false;
|
||||
context_->pipeline_state.ssbo_bindings[GPU_SSBO_POLYLINE_COL_BUF_SLOT].bound = false;
|
||||
context_->pipeline_state.ssbo_bindings[GPU_SSBO_INDEX_BUF_SLOT].bound = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset allocation after draw submission. */
|
||||
has_begun_ = false;
|
||||
if (current_allocation_.metal_buffer) {
|
||||
[current_allocation_.metal_buffer release];
|
||||
current_allocation_.metal_buffer = nil;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::gpu
|
||||
88
blender-5.2.0/source/blender/gpu/metal/mtl_index_buffer.hh
Normal file
88
blender-5.2.0/source/blender/gpu/metal/mtl_index_buffer.hh
Normal file
@@ -0,0 +1,88 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GPU_index_buffer.hh"
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "mtl_context.hh"
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <Metal/Metal.h>
|
||||
#include <QuartzCore/QuartzCore.h>
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
class MTLIndexBuf : public IndexBuf {
|
||||
friend class MTLBatch;
|
||||
friend class MTLDrawList;
|
||||
friend class MTLStorageBuf; /* For bind as SSBO resource access. */
|
||||
|
||||
private:
|
||||
/* Metal buffer resource. */
|
||||
gpu::MTLBuffer *ibo_ = nullptr;
|
||||
uint64_t alloc_size_ = 0;
|
||||
|
||||
/* SSBO wrapper for bind_as_ssbo support. */
|
||||
MTLStorageBuf *ssbo_wrapper_ = nullptr;
|
||||
|
||||
#ifndef NDEBUG
|
||||
/* Flags whether point index buffer has been compacted
|
||||
* to remove false restart indices. */
|
||||
bool point_restarts_stripped_ = false;
|
||||
#endif
|
||||
|
||||
/* Optimized index buffers.
|
||||
* NOTE(Metal): This optimization encodes a new index buffer following
|
||||
* #TriangleList topology. Parsing of Index buffers is more optimal
|
||||
* when not using restart-compatible primitive topology types. */
|
||||
GPUPrimType optimized_primitive_type_;
|
||||
gpu::MTLBuffer *optimized_ibo_ = nullptr;
|
||||
uint32_t emulated_v_count = 0;
|
||||
void free_optimized_buffer();
|
||||
|
||||
/* Flags whether an index buffer can be optimized.
|
||||
* For index buffers which are partially modified
|
||||
* on the host, or by the GPU, optimization cannot be performed. */
|
||||
bool can_optimize_ = true;
|
||||
|
||||
public:
|
||||
~MTLIndexBuf() override;
|
||||
|
||||
void bind_as_ssbo(uint32_t binding) override;
|
||||
void read(uint32_t *data) const override;
|
||||
|
||||
void upload_data() override;
|
||||
void update_sub(uint32_t start, uint32_t len, const void *data) override;
|
||||
|
||||
/* #get_index_buffer can conditionally return an optimized index buffer of a
|
||||
* differing format, if it is concluded that optimization is preferred
|
||||
* for the given inputs.
|
||||
* Index buffer optimization is used to replace restart-compatible
|
||||
* primitive types with non-restart-compatible ones such as #TriangleList and
|
||||
* #LineList. This improves GPU execution for these types significantly, while
|
||||
* only incurring a small performance penalty.
|
||||
*
|
||||
* This is also used to emulate unsupported topology types
|
||||
* such as triangle fan. */
|
||||
id<MTLBuffer> get_index_buffer(GPUPrimType &in_out_primitive_type, uint &in_out_v_count);
|
||||
void flag_can_optimize(bool can_optimize);
|
||||
|
||||
static MTLIndexType gpu_index_type_to_metal(GPUIndexBufType type)
|
||||
{
|
||||
return (type == GPU_INDEX_U16) ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32;
|
||||
}
|
||||
|
||||
private:
|
||||
void strip_restart_indices() override;
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLIndexBuf")
|
||||
};
|
||||
|
||||
} // namespace blender::gpu
|
||||
570
blender-5.2.0/source/blender/gpu/metal/mtl_index_buffer.mm
Normal file
570
blender-5.2.0/source/blender/gpu/metal/mtl_index_buffer.mm
Normal file
@@ -0,0 +1,570 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#include "mtl_index_buffer.hh"
|
||||
#include "mtl_context.hh"
|
||||
#include "mtl_debug.hh"
|
||||
#include "mtl_storage_buffer.hh"
|
||||
|
||||
#include "BLI_span.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Core MTLIndexBuf implementation.
|
||||
* \{ */
|
||||
|
||||
MTLIndexBuf::~MTLIndexBuf()
|
||||
{
|
||||
if (ibo_ != nullptr && !this->is_subrange_) {
|
||||
ibo_->free();
|
||||
}
|
||||
this->free_optimized_buffer();
|
||||
|
||||
if (ssbo_wrapper_) {
|
||||
delete ssbo_wrapper_;
|
||||
ssbo_wrapper_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void MTLIndexBuf::free_optimized_buffer()
|
||||
{
|
||||
if (optimized_ibo_) {
|
||||
optimized_ibo_->free();
|
||||
optimized_ibo_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void MTLIndexBuf::bind_as_ssbo(uint32_t binding)
|
||||
{
|
||||
/* Flag buffer as incompatible with optimized/patched buffers as contents
|
||||
* can now have partial modifications from the GPU. */
|
||||
this->flag_can_optimize(false);
|
||||
this->free_optimized_buffer();
|
||||
|
||||
/* Ensure resource is initialized. */
|
||||
this->upload_data();
|
||||
|
||||
/* Ensure we have a valid IBO. */
|
||||
BLI_assert(this->ibo_);
|
||||
|
||||
/* Create MTLStorageBuffer to wrap this resource and use conventional binding. */
|
||||
if (ssbo_wrapper_ == nullptr) {
|
||||
/* Buffer's size in bytes is required to be multiple of 16. */
|
||||
int multiple_of_16 = ceil_to_multiple_u(alloc_size_, 16);
|
||||
ssbo_wrapper_ = new MTLStorageBuf(this, multiple_of_16);
|
||||
}
|
||||
ssbo_wrapper_->bind(binding);
|
||||
}
|
||||
|
||||
void MTLIndexBuf::read(uint32_t *data) const
|
||||
{
|
||||
if (ibo_ != nullptr) {
|
||||
/* Fetch active context. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
|
||||
/* Ensure data is flushed for host caches. */
|
||||
id<MTLBuffer> source_buffer = ibo_->get_metal_buffer();
|
||||
if (source_buffer.storageMode == MTLStorageModeManaged) {
|
||||
id<MTLBlitCommandEncoder> enc = ctx->main_command_buffer.ensure_begin_blit_encoder();
|
||||
[enc synchronizeResource:source_buffer];
|
||||
}
|
||||
|
||||
/* Ensure GPU has finished operating on commands which may modify data. */
|
||||
GPU_finish();
|
||||
|
||||
/* Read data. */
|
||||
void *host_ptr = ibo_->get_host_ptr();
|
||||
memcpy(data, host_ptr, size_get());
|
||||
return;
|
||||
}
|
||||
BLI_assert(false && "Index buffer not ready to be read.");
|
||||
}
|
||||
|
||||
void MTLIndexBuf::upload_data()
|
||||
{
|
||||
/* Handle sub-range upload. */
|
||||
if (is_subrange_) {
|
||||
MTLIndexBuf *mtlsrc = static_cast<MTLIndexBuf *>(src_);
|
||||
mtlsrc->upload_data();
|
||||
|
||||
#ifndef NDEBUG
|
||||
BLI_assert_msg(!mtlsrc->point_restarts_stripped_,
|
||||
"Cannot use sub-range on stripped point buffer.");
|
||||
#endif
|
||||
|
||||
/* If parent sub-range allocation has changed,
|
||||
* update our index buffer. */
|
||||
if (alloc_size_ != mtlsrc->alloc_size_ || ibo_ != mtlsrc->ibo_) {
|
||||
|
||||
/* Update index buffer and allocation from source. */
|
||||
alloc_size_ = mtlsrc->alloc_size_;
|
||||
ibo_ = mtlsrc->ibo_;
|
||||
|
||||
/* Reset any allocated patched or optimized index buffers. */
|
||||
this->free_optimized_buffer();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* If new data ready, and index buffer already exists, release current. */
|
||||
if ((ibo_ != nullptr) && (this->data_ != nullptr)) {
|
||||
MTL_LOG_DEBUG("Re-creating index buffer with new data. IndexBuf %p", this);
|
||||
ibo_->free();
|
||||
ibo_ = nullptr;
|
||||
}
|
||||
|
||||
/* Prepare Buffer and Upload Data. */
|
||||
if (ibo_ == nullptr) {
|
||||
alloc_size_ = this->size_get();
|
||||
if (alloc_size_ == 0) {
|
||||
MTL_LOG_WARNING("Warning! Trying to allocate index buffer with size=0 bytes");
|
||||
}
|
||||
else {
|
||||
if (data_) {
|
||||
ibo_ = MTLContext::get_global_memory_manager()->allocate_with_data(
|
||||
alloc_size_, true, data_);
|
||||
}
|
||||
else {
|
||||
ibo_ = MTLContext::get_global_memory_manager()->allocate(alloc_size_, true);
|
||||
}
|
||||
BLI_assert(ibo_);
|
||||
#ifndef NDEBUG
|
||||
static std::atomic<int> global_counter = 0;
|
||||
int index = global_counter.fetch_add(1);
|
||||
ibo_->set_label([NSString stringWithFormat:@"IBO %i", index]);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* No need to keep copy of data_ in system memory. */
|
||||
if (data_) {
|
||||
MEM_SAFE_DELETE_VOID(data_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MTLIndexBuf::update_sub(uint32_t start, uint32_t len, const void *data)
|
||||
{
|
||||
BLI_assert(!is_subrange_);
|
||||
|
||||
/* If host-side data still exists, modify and upload as normal */
|
||||
if (data_ != nullptr) {
|
||||
|
||||
/* Free index buffer if one exists. */
|
||||
if (ibo_ != nullptr && !this->is_subrange_) {
|
||||
ibo_->free();
|
||||
ibo_ = nullptr;
|
||||
}
|
||||
|
||||
BLI_assert(start + len < this->size_get());
|
||||
|
||||
/* Apply start byte offset to data pointer. */
|
||||
void *modified_base_ptr = data_;
|
||||
uint8_t *ptr = static_cast<uint8_t *>(modified_base_ptr);
|
||||
ptr += start;
|
||||
modified_base_ptr = static_cast<void *>(ptr);
|
||||
|
||||
/* Modify host-side data. */
|
||||
memcpy(modified_base_ptr, data, len);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Verify buffer. */
|
||||
BLI_assert(ibo_ != nullptr);
|
||||
|
||||
/* Otherwise, we will inject a data update, using staged data, into the command stream.
|
||||
* Stage update contents in temporary buffer. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
MTLTemporaryBuffer range = ctx->get_scratch_buffer_manager().scratch_buffer_allocate_range(len);
|
||||
memcpy(range.data, data, len);
|
||||
|
||||
/* Copy updated contents into primary buffer.
|
||||
* These changes need to be uploaded via blit to ensure the data copies happen in-order. */
|
||||
id<MTLBuffer> dest_buffer = ibo_->get_metal_buffer();
|
||||
BLI_assert(dest_buffer != nil);
|
||||
|
||||
id<MTLBlitCommandEncoder> enc = ctx->main_command_buffer.ensure_begin_blit_encoder();
|
||||
[enc copyFromBuffer:range.metal_buffer
|
||||
sourceOffset:(uint32_t)range.buffer_offset
|
||||
toBuffer:dest_buffer
|
||||
destinationOffset:start
|
||||
size:len];
|
||||
|
||||
/* Synchronize changes back to host to ensure CPU-side data is up-to-date for non
|
||||
* Shared buffers. */
|
||||
if (dest_buffer.storageMode == MTLStorageModeManaged) {
|
||||
[enc synchronizeResource:dest_buffer];
|
||||
}
|
||||
|
||||
/* Invalidate patched/optimized buffers. */
|
||||
this->free_optimized_buffer();
|
||||
|
||||
/* Flag buffer as incompatible with optimized/patched buffers as contents
|
||||
* have partial modifications. */
|
||||
this->flag_can_optimize(false);
|
||||
|
||||
BLI_assert(false);
|
||||
}
|
||||
|
||||
void MTLIndexBuf::flag_can_optimize(bool can_optimize)
|
||||
{
|
||||
can_optimize_ = can_optimize;
|
||||
|
||||
/* NOTE: Index buffer optimization needs to be disabled for Indirect draws, as the index count is
|
||||
* unknown at submission time. However, if the index buffer has already been optimized by a
|
||||
* separate draw pass, errors will occur and these cases need to be resolved at the high-level,
|
||||
* ensuring primitive types without primitive restart are used instead, as these perform far
|
||||
* more optimally on hardware. */
|
||||
BLI_assert_msg(can_optimize_ || (optimized_ibo_ == nullptr),
|
||||
"Index buffer optimization disabled, but optimal buffer already generated.");
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/** \name Index buffer optimization and topology emulation
|
||||
*
|
||||
* Index buffer optimization and emulation. Optimize index buffers by
|
||||
* eliminating restart-indices.
|
||||
* Emulate unsupported index types e.g. Triangle Fan and Line Loop.
|
||||
* \{ */
|
||||
|
||||
/* Returns total vertices in new buffer. */
|
||||
template<typename T>
|
||||
static uint32_t populate_optimized_tri_strip_buf(Span<T> original_data,
|
||||
MutableSpan<T> output_data,
|
||||
uint32_t input_index_len)
|
||||
{
|
||||
/* Generate #TriangleList from #TriangleStrip. */
|
||||
uint32_t current_vert_len = 0;
|
||||
uint32_t current_output_ind = 0;
|
||||
T indices[3];
|
||||
|
||||
for (int c_index = 0; c_index < input_index_len; c_index++) {
|
||||
T current_index = original_data[c_index];
|
||||
if (current_index == T(-1)) {
|
||||
/* Stop current primitive. Move onto next. */
|
||||
current_vert_len = 0;
|
||||
}
|
||||
else {
|
||||
if (current_vert_len < 3) {
|
||||
/* Prepare first triangle.
|
||||
* Cache indices before generating a triangle, in case we have bad primitive-restarts. */
|
||||
indices[current_vert_len] = current_index;
|
||||
}
|
||||
|
||||
/* Emit triangle once we reach 3 input verts in current strip. */
|
||||
if (current_vert_len == 3) {
|
||||
/* First triangle in strip. */
|
||||
output_data[current_output_ind++] = indices[0];
|
||||
output_data[current_output_ind++] = indices[1];
|
||||
output_data[current_output_ind++] = indices[2];
|
||||
}
|
||||
else if (current_vert_len > 3) {
|
||||
/* All other triangles in strip.
|
||||
* These triangles are populated using data from previous 2 vertices
|
||||
* and the latest index. */
|
||||
uint32_t tri_id = current_vert_len - 3;
|
||||
uint32_t base_output_ind = current_output_ind;
|
||||
if ((tri_id % 2) == 0) {
|
||||
output_data[base_output_ind + 0] = output_data[base_output_ind - 2];
|
||||
output_data[base_output_ind + 1] = current_index;
|
||||
output_data[base_output_ind + 2] = output_data[base_output_ind - 1];
|
||||
}
|
||||
else {
|
||||
output_data[base_output_ind + 0] = output_data[base_output_ind - 1];
|
||||
output_data[base_output_ind + 1] = output_data[base_output_ind - 2];
|
||||
output_data[base_output_ind + 2] = current_index;
|
||||
}
|
||||
current_output_ind += 3;
|
||||
}
|
||||
|
||||
/* Increment relative vertex index. */
|
||||
current_vert_len++;
|
||||
}
|
||||
}
|
||||
return current_output_ind;
|
||||
}
|
||||
|
||||
/* Returns total vertices in new buffer. */
|
||||
template<typename T>
|
||||
static uint32_t populate_emulated_tri_fan_buf(Span<T> original_data,
|
||||
MutableSpan<T> output_data,
|
||||
uint32_t input_index_len)
|
||||
{
|
||||
/* Generate #TriangleList from #TriangleFan. */
|
||||
T base_prim_ind_val = 0;
|
||||
uint32_t current_vert_len = 0;
|
||||
uint32_t current_output_ind = 0;
|
||||
T indices[3];
|
||||
|
||||
for (int c_index = 0; c_index < input_index_len; c_index++) {
|
||||
T current_index = original_data[c_index];
|
||||
if (current_index == T(-1)) {
|
||||
/* Stop current primitive. Move onto next. */
|
||||
current_vert_len = 0;
|
||||
}
|
||||
else {
|
||||
if (current_vert_len < 3) {
|
||||
/* Prepare first triangle.
|
||||
* Cache indices before generating a triangle, in case we have bad primitive-restarts. */
|
||||
indices[current_vert_len] = current_index;
|
||||
}
|
||||
|
||||
/* emit triangle once we reach 3 input verts in current strip. */
|
||||
if (current_vert_len == 3) {
|
||||
/* First triangle in strip. */
|
||||
output_data[current_output_ind++] = indices[0];
|
||||
output_data[current_output_ind++] = indices[1];
|
||||
output_data[current_output_ind++] = indices[2];
|
||||
base_prim_ind_val = indices[0];
|
||||
}
|
||||
else if (current_vert_len > 3) {
|
||||
/* All other triangles in strip.
|
||||
* These triangles are populated using data from previous 2 vertices
|
||||
* and the latest index. */
|
||||
uint32_t base_output_ind = current_output_ind;
|
||||
|
||||
output_data[base_output_ind + 0] = base_prim_ind_val;
|
||||
output_data[base_output_ind + 1] = output_data[base_output_ind - 1];
|
||||
output_data[base_output_ind + 2] = current_index;
|
||||
current_output_ind += 3;
|
||||
}
|
||||
|
||||
/* Increment relative vertex index. */
|
||||
current_vert_len++;
|
||||
}
|
||||
}
|
||||
return current_output_ind;
|
||||
}
|
||||
|
||||
id<MTLBuffer> MTLIndexBuf::get_index_buffer(GPUPrimType &in_out_primitive_type,
|
||||
uint32_t &in_out_v_count)
|
||||
{
|
||||
/* Determine whether to return the original index buffer, or whether we
|
||||
* should emulate an unsupported primitive type, or optimize a restart-
|
||||
* compatible type for faster performance. */
|
||||
bool should_optimize_or_emulate = (in_out_primitive_type == GPU_PRIM_TRI_FAN) ||
|
||||
(in_out_primitive_type == GPU_PRIM_TRI_STRIP);
|
||||
if (!should_optimize_or_emulate || is_subrange_ || !can_optimize_) {
|
||||
/* Ensure we are not optimized. */
|
||||
BLI_assert(this->optimized_ibo_ == nullptr);
|
||||
|
||||
/* Return regular index buffer. */
|
||||
BLI_assert(this->ibo_ && this->ibo_->get_metal_buffer());
|
||||
return this->ibo_->get_metal_buffer();
|
||||
}
|
||||
|
||||
/* Perform optimization on type. */
|
||||
GPUPrimType input_prim_type = in_out_primitive_type;
|
||||
this->upload_data();
|
||||
if (!ibo_ && optimized_ibo_ == nullptr) {
|
||||
/* Cannot optimize buffer if no source IBO exists. */
|
||||
return nil;
|
||||
}
|
||||
|
||||
/* Verify whether existing index buffer is valid. */
|
||||
if (optimized_ibo_ != nullptr && optimized_primitive_type_ != input_prim_type) {
|
||||
BLI_assert_msg(false,
|
||||
"Cannot change the optimized primitive format after generation, as source "
|
||||
"index buffer data is discarded.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
/* Generate optimized index buffer. */
|
||||
if (optimized_ibo_ == nullptr) {
|
||||
|
||||
/* Generate unwrapped index buffer. */
|
||||
switch (input_prim_type) {
|
||||
case GPU_PRIM_TRI_FAN: {
|
||||
|
||||
/* Calculate maximum size. */
|
||||
uint32_t max_possible_verts = (this->index_len_ - 2) * 3;
|
||||
BLI_assert(max_possible_verts > 0);
|
||||
|
||||
/* Allocate new buffer. */
|
||||
optimized_ibo_ = MTLContext::get_global_memory_manager()->allocate(
|
||||
max_possible_verts *
|
||||
((index_type_ == GPU_INDEX_U16) ? sizeof(uint16_t) : sizeof(uint32_t)),
|
||||
true);
|
||||
|
||||
/* Populate new index buffer. */
|
||||
if (index_type_ == GPU_INDEX_U16) {
|
||||
Span<uint16_t> orig_data(static_cast<const uint16_t *>(ibo_->get_host_ptr()),
|
||||
this->index_len_);
|
||||
MutableSpan<uint16_t> output_data(
|
||||
static_cast<uint16_t *>(optimized_ibo_->get_host_ptr()), max_possible_verts);
|
||||
emulated_v_count = populate_emulated_tri_fan_buf<uint16_t>(
|
||||
orig_data, output_data, this->index_len_);
|
||||
}
|
||||
else {
|
||||
Span<uint32_t> orig_data(static_cast<const uint32_t *>(ibo_->get_host_ptr()),
|
||||
this->index_len_);
|
||||
MutableSpan<uint32_t> output_data(
|
||||
static_cast<uint32_t *>(optimized_ibo_->get_host_ptr()), max_possible_verts);
|
||||
emulated_v_count = populate_emulated_tri_fan_buf<uint32_t>(
|
||||
orig_data, output_data, this->index_len_);
|
||||
}
|
||||
|
||||
BLI_assert(emulated_v_count <= max_possible_verts);
|
||||
|
||||
/* Flush buffer and output. */
|
||||
optimized_ibo_->flush();
|
||||
optimized_primitive_type_ = input_prim_type;
|
||||
in_out_v_count = emulated_v_count;
|
||||
in_out_primitive_type = GPU_PRIM_TRIS;
|
||||
}
|
||||
|
||||
case GPU_PRIM_TRI_STRIP: {
|
||||
|
||||
/* Calculate maximum size. */
|
||||
uint32_t max_possible_verts = (this->index_len_ - 2) * 3;
|
||||
BLI_assert(max_possible_verts > 0);
|
||||
|
||||
/* Allocate new buffer. */
|
||||
optimized_ibo_ = MTLContext::get_global_memory_manager()->allocate(
|
||||
max_possible_verts *
|
||||
((index_type_ == GPU_INDEX_U16) ? sizeof(uint16_t) : sizeof(uint32_t)),
|
||||
true);
|
||||
|
||||
/* Populate new index buffer. */
|
||||
if (index_type_ == GPU_INDEX_U16) {
|
||||
Span<uint16_t> orig_data(static_cast<const uint16_t *>(ibo_->get_host_ptr()),
|
||||
this->index_len_);
|
||||
MutableSpan<uint16_t> output_data(
|
||||
static_cast<uint16_t *>(optimized_ibo_->get_host_ptr()), max_possible_verts);
|
||||
emulated_v_count = populate_optimized_tri_strip_buf<uint16_t>(
|
||||
orig_data, output_data, this->index_len_);
|
||||
}
|
||||
else {
|
||||
Span<uint32_t> orig_data(static_cast<const uint32_t *>(ibo_->get_host_ptr()),
|
||||
this->index_len_);
|
||||
MutableSpan<uint32_t> output_data(
|
||||
static_cast<uint32_t *>(optimized_ibo_->get_host_ptr()), max_possible_verts);
|
||||
emulated_v_count = populate_optimized_tri_strip_buf<uint32_t>(
|
||||
orig_data, output_data, this->index_len_);
|
||||
}
|
||||
|
||||
BLI_assert(emulated_v_count <= max_possible_verts);
|
||||
|
||||
/* Flush buffer and output. */
|
||||
optimized_ibo_->flush();
|
||||
optimized_primitive_type_ = input_prim_type;
|
||||
in_out_v_count = emulated_v_count;
|
||||
in_out_primitive_type = GPU_PRIM_TRIS;
|
||||
} break;
|
||||
|
||||
case GPU_PRIM_LINE_STRIP: {
|
||||
/* TODO(Metal): Line strip topology types would benefit from optimization to remove
|
||||
* primitive restarts, however, these do not occur frequently, nor with
|
||||
* significant geometry counts. */
|
||||
MTL_LOG_DEBUG("TODO: Primitive topology: Optimize line strip topology types");
|
||||
} break;
|
||||
|
||||
case GPU_PRIM_LINE_LOOP: {
|
||||
/* TODO(Metal): Line Loop primitive type requires use of optimized index buffer for
|
||||
* emulation, if used with indexed rendering. This path is currently not hit as #LineLoop
|
||||
* does not currently appear to be used alongside an index buffer. */
|
||||
MTL_LOG_WARNING(
|
||||
"TODO: Primitive topology: Line Loop Index buffer optimization required for "
|
||||
"emulation.");
|
||||
} break;
|
||||
|
||||
case GPU_PRIM_TRIS:
|
||||
case GPU_PRIM_LINES:
|
||||
case GPU_PRIM_POINTS: {
|
||||
/* Should not get here - TRIS/LINES/POINTS do not require emulation or optimization. */
|
||||
BLI_assert_unreachable();
|
||||
return nil;
|
||||
}
|
||||
|
||||
default:
|
||||
/* Should not get here - Invalid primitive type. */
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return optimized buffer. */
|
||||
if (optimized_ibo_ != nullptr) {
|
||||
|
||||
/* Delete original buffer if one still exists, as we do no need it. */
|
||||
if (ibo_ != nullptr) {
|
||||
ibo_->free();
|
||||
ibo_ = nullptr;
|
||||
}
|
||||
|
||||
/* Output parameters. */
|
||||
in_out_v_count = emulated_v_count;
|
||||
in_out_primitive_type = GPU_PRIM_TRIS;
|
||||
return optimized_ibo_->get_metal_buffer();
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
void MTLIndexBuf::strip_restart_indices()
|
||||
{
|
||||
/* We remove point buffer primitive restart indices by swapping restart indices
|
||||
* with the first valid index at the end of the index buffer and reducing the
|
||||
* length. Primitive restarts are invalid in Metal for non-restart-compatible
|
||||
* primitive types. We also cannot just use zero unlike for Lines and Triangles,
|
||||
* as we cannot create de-generative point primitives to hide geometry, as each
|
||||
* point is independent.
|
||||
* Instead, we must remove these hidden indices from the index buffer.
|
||||
* NOTE: This happens prior to index squeezing so operate on 32-bit indices. */
|
||||
MutableSpan<uint32_t> uint_idx(static_cast<uint32_t *>(data_), index_len_);
|
||||
for (uint i = 0; i < index_len_; i++) {
|
||||
if (uint_idx[i] == 0xFFFFFFFFu) {
|
||||
|
||||
/* Find swap index at end of index buffer. */
|
||||
int swap_index = -1;
|
||||
for (uint j = index_len_ - 1; j >= i && index_len_ > 0; j--) {
|
||||
/* If end index is restart, just reduce length. */
|
||||
if (uint_idx[j] == 0xFFFFFFFFu) {
|
||||
index_len_--;
|
||||
continue;
|
||||
}
|
||||
/* Otherwise assign swap index. */
|
||||
swap_index = j;
|
||||
break;
|
||||
}
|
||||
|
||||
/* If index_len_ == 0, this means all indices were flagged as hidden, with restart index
|
||||
* values. Hence we will entirely skip the draw. */
|
||||
if (index_len_ > 0) {
|
||||
/* If swap index is not valid, then there were no valid non-restart indices
|
||||
* to swap with. However, the above loop will have removed these indices by
|
||||
* reducing the length of indices. Debug assertions verify that the restart
|
||||
* index is no longer included. */
|
||||
if (swap_index == -1) {
|
||||
BLI_assert(index_len_ <= i);
|
||||
}
|
||||
else {
|
||||
/* If we have found an index we can swap with, flip the values.
|
||||
* We also reduce the length. As per above loop, swap_index should
|
||||
* now be outside the index length range. */
|
||||
uint32_t swap_index_value = uint_idx[swap_index];
|
||||
uint_idx[i] = swap_index_value;
|
||||
uint_idx[swap_index] = 0xFFFFFFFFu;
|
||||
index_len_--;
|
||||
BLI_assert(index_len_ <= swap_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
/* Flag as having been stripped to ensure invalid usage is tracked. */
|
||||
point_restarts_stripped_ = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::gpu
|
||||
528
blender-5.2.0/source/blender/gpu/metal/mtl_memory.hh
Normal file
528
blender-5.2.0/source/blender/gpu/metal/mtl_memory.hh
Normal file
@@ -0,0 +1,528 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_map.hh"
|
||||
|
||||
#include "mtl_common.hh"
|
||||
|
||||
#include <atomic>
|
||||
#include <ctime>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <Metal/Metal.h>
|
||||
#include <QuartzCore/QuartzCore.h>
|
||||
|
||||
@class CAMetalLayer;
|
||||
@class MTLCommandQueue;
|
||||
@class MTLRenderPipelineState;
|
||||
|
||||
/* Metal Memory Manager Overview. */
|
||||
/*
|
||||
* The Metal Backend Memory manager is designed to provide an interface
|
||||
* for all other MTL_* modules where memory allocation is required.
|
||||
*
|
||||
* Different allocation strategies and data-structures are used depending
|
||||
* on how the data is used by the backend. These aim to optimally handle
|
||||
* system memory and abstract away any complexity from the MTL_* modules
|
||||
* themselves.
|
||||
*
|
||||
* There are two primary allocation modes which can be used:
|
||||
*
|
||||
* ** MTLScratchBufferManager **
|
||||
*
|
||||
* Each MTLContext owns a ScratchBufferManager which is implemented
|
||||
* as a pool of circular buffers, designed to handle temporary
|
||||
* memory allocations which occur on a per-frame basis. The scratch
|
||||
* buffers allow flushing of host memory to the GPU to be batched.
|
||||
*
|
||||
* Each frame, the next scratch buffer is reset, then later flushed upon
|
||||
* command buffer submission.
|
||||
*
|
||||
* NOTE: This is allocated per-context due to allocations being tied
|
||||
* to workload submissions and context-specific submissions.
|
||||
*
|
||||
* Examples of scratch buffer usage are:
|
||||
* - Immediate-mode temporary vertex buffers.
|
||||
* - Shader uniform data updates
|
||||
* - Staging of data for resource copies, or, data reads/writes.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* MTLContext::get_scratch_buffer_manager() - to fetch active manager.
|
||||
*
|
||||
* MTLTemporaryBuffer scratch_buffer_allocate_range(size)
|
||||
* MTLTemporaryBuffer scratch_buffer_allocate_range_aligned(size, align)
|
||||
*
|
||||
* ---------------------------------------------------------------------------------
|
||||
* ** MTLBufferPool **
|
||||
*
|
||||
* For static and longer-lasting memory allocations, such as those for UBOs,
|
||||
* Vertex buffers, index buffers, etc; We want an optimal abstraction for
|
||||
* fetching a MTLBuffer of the desired size and resource options.
|
||||
*
|
||||
* Memory allocations can be expensive so the MTLBufferPool provides
|
||||
* functionality to track usage of these buffers and once a buffer
|
||||
* is no longer in use, it is returned to the buffer pool for use
|
||||
* by another backend resource.
|
||||
*
|
||||
* The MTLBufferPool provides functionality for safe tracking of resources,
|
||||
* as buffers freed on the host side must have their usage by the GPU tracked,
|
||||
* to ensure they are not prematurely re-used before they have finished being
|
||||
* used by the GPU.
|
||||
*
|
||||
* NOTE: The MTLBufferPool is a global construct which can be fetched from anywhere.
|
||||
*
|
||||
* Usage:
|
||||
* MTLContext::get_global_memory_manager(); - static routine to fetch global memory manager.
|
||||
*
|
||||
* gpu::MTLBuffer *allocate(size, is_cpu_visible)
|
||||
* gpu::MTLBuffer *allocate_aligned(size, alignment, is_cpu_visible)
|
||||
* gpu::MTLBuffer *allocate_with_data(size, is_cpu_visible, data_ptr)
|
||||
* gpu::MTLBuffer *allocate_aligned_with_data(size, alignment, is_cpu_visible, data_ptr)
|
||||
*/
|
||||
|
||||
/* Debug memory statistics: Disabled by Macro rather than guarded for
|
||||
* performance considerations. */
|
||||
#define MTL_DEBUG_MEMORY_STATISTICS 0
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/* Forward Declarations. */
|
||||
class MTLContext;
|
||||
class MTLCommandBufferManager;
|
||||
class MTLUniformBuf;
|
||||
class MTLStorageBuf;
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Memory Management.
|
||||
* \{ */
|
||||
|
||||
/* MTLBuffer allocation wrapper. */
|
||||
class MTLBuffer {
|
||||
|
||||
public:
|
||||
/* NOTE: ListBaseT API is not used due to custom destructor operation required to release
|
||||
* Metal objective C buffer resource. */
|
||||
gpu::MTLBuffer *next, *prev;
|
||||
|
||||
private:
|
||||
/* Metal resource. */
|
||||
id<MTLBuffer> metal_buffer_;
|
||||
|
||||
/* Host-visible mapped-memory pointer. Behavior depends on buffer type:
|
||||
* - Shared buffers: pointer represents base address of #MTLBuffer whose data
|
||||
* access has shared access by both the CPU and GPU on
|
||||
* Unified Memory Architectures (UMA).
|
||||
* - Managed buffer: Host-side mapped buffer region for CPU (Host) access. Managed buffers
|
||||
* must be manually flushed to transfer data to GPU-resident buffer.
|
||||
* - Private buffer: Host access is invalid, `data` will be nullptr. */
|
||||
void *data_;
|
||||
|
||||
/* Whether buffer is allocated from an external source. */
|
||||
bool is_external_ = false;
|
||||
|
||||
/* Allocation info. */
|
||||
MTLResourceOptions options_;
|
||||
id<MTLDevice> device_;
|
||||
uint64_t alignment_;
|
||||
uint64_t size_;
|
||||
|
||||
/* Allocated size may be larger than actual size. */
|
||||
uint64_t usage_size_;
|
||||
|
||||
/* Lifetime info - whether the current buffer is actively in use. A buffer
|
||||
* should be in use after it has been allocated. De-allocating the buffer, and
|
||||
* returning it to the free buffer pool will set in_use to false. Using a buffer
|
||||
* while it is not in-use should not be allowed and result in an error. */
|
||||
std::atomic<bool> in_use_;
|
||||
|
||||
public:
|
||||
MTLBuffer(id<MTLDevice> device, uint64_t size, MTLResourceOptions options, uint alignment = 1);
|
||||
MTLBuffer(id<MTLBuffer> external_buffer);
|
||||
~MTLBuffer();
|
||||
|
||||
/* Fetch information about backing MTLBuffer. */
|
||||
id<MTLBuffer> get_metal_buffer() const;
|
||||
void *get_host_ptr() const;
|
||||
uint64_t get_size_used() const;
|
||||
uint64_t get_size() const;
|
||||
|
||||
/* Flush data to GPU. */
|
||||
void flush();
|
||||
void flush_range(uint64_t offset, uint64_t length);
|
||||
bool requires_flush();
|
||||
|
||||
/* Buffer usage tracking. */
|
||||
void flag_in_use(bool used);
|
||||
bool get_in_use();
|
||||
void set_usage_size(uint64_t size_used);
|
||||
|
||||
/* Debug. */
|
||||
void set_label(NSString *str);
|
||||
|
||||
/* Read properties. */
|
||||
MTLResourceOptions get_resource_options();
|
||||
uint64_t get_alignment();
|
||||
|
||||
/* Resource-local free: For buffers allocated via memory manager,
|
||||
* this will call the context `free_buffer` method to return the buffer to the context memory
|
||||
* pool.
|
||||
*
|
||||
* Otherwise, free will release the associated metal resource.
|
||||
* As a note, calling the destructor will also destroy the buffer and associated metal
|
||||
* resource. */
|
||||
void free();
|
||||
|
||||
/* Safety check to ensure buffers are not used after free. */
|
||||
void debug_ensure_used();
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLBuffer");
|
||||
};
|
||||
|
||||
/* View into part of an MTLBuffer. */
|
||||
struct MTLBufferRange {
|
||||
id<MTLBuffer> metal_buffer;
|
||||
void *data;
|
||||
uint64_t buffer_offset;
|
||||
uint64_t size;
|
||||
MTLResourceOptions options;
|
||||
|
||||
void flush();
|
||||
bool requires_flush();
|
||||
};
|
||||
|
||||
/* Circular scratch buffer allocations should be seen as temporary and only used within the
|
||||
* lifetime of the frame. */
|
||||
using MTLTemporaryBuffer = MTLBufferRange;
|
||||
|
||||
/* Round-Robin Circular-buffer. */
|
||||
class MTLCircularBuffer {
|
||||
friend class MTLScratchBufferManager;
|
||||
|
||||
private:
|
||||
MTLContext &own_context_;
|
||||
|
||||
/* Wrapped MTLBuffer allocation handled. */
|
||||
gpu::MTLBuffer *cbuffer_;
|
||||
/* Allocated SSBO that serves as source for cbuffer. */
|
||||
MTLStorageBuf *ssbo_source_ = nullptr;
|
||||
|
||||
/* Current offset where next allocation will begin. */
|
||||
uint64_t current_offset_;
|
||||
|
||||
/* Whether the Circular Buffer can grow during re-allocation if
|
||||
* the size is exceeded. */
|
||||
bool can_resize_;
|
||||
|
||||
/* Usage information. */
|
||||
uint64_t used_frame_index_;
|
||||
uint64_t last_flush_base_offset_;
|
||||
|
||||
public:
|
||||
MTLCircularBuffer(MTLContext &ctx, uint64_t initial_size, bool allow_grow);
|
||||
~MTLCircularBuffer();
|
||||
MTLTemporaryBuffer allocate_range(uint64_t alloc_size);
|
||||
MTLTemporaryBuffer allocate_range_aligned(uint64_t alloc_size, uint alignment);
|
||||
void flush();
|
||||
|
||||
/* Reset pointer back to start of circular buffer. */
|
||||
void reset();
|
||||
};
|
||||
|
||||
/* Wrapper struct used by Memory Manager to sort and compare gpu::MTLBuffer resources inside the
|
||||
* memory pools. */
|
||||
struct MTLBufferHandle {
|
||||
gpu::MTLBuffer *buffer;
|
||||
uint64_t buffer_size;
|
||||
time_t insert_time;
|
||||
|
||||
inline MTLBufferHandle(gpu::MTLBuffer *buf)
|
||||
{
|
||||
this->buffer = buf;
|
||||
this->buffer_size = this->buffer->get_size();
|
||||
this->insert_time = std::time(nullptr);
|
||||
}
|
||||
|
||||
inline MTLBufferHandle(uint64_t compare_size)
|
||||
{
|
||||
this->buffer = nullptr;
|
||||
this->buffer_size = compare_size;
|
||||
this->insert_time = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct CompareMTLBuffer {
|
||||
bool operator()(const MTLBufferHandle &lhs, const MTLBufferHandle &rhs) const
|
||||
{
|
||||
return lhs.buffer_size < rhs.buffer_size;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An #MTLSafeFreeList is a temporary list of #gpu::MTLBuffers which have
|
||||
* been freed by the high level backend, but are pending GPU work execution before
|
||||
* the #gpu::MTLBuffers can be returned to the Memory manager pools.
|
||||
* This list is implemented as a chunked linked-list.
|
||||
*
|
||||
* Only a single #MTLSafeFreeList is active at one time and is associated with current command
|
||||
* buffer submissions. If an #MTLBuffer is freed during the lifetime of a command buffer, it could
|
||||
* still possibly be in-use and as such, the #MTLSafeFreeList will increment its reference count
|
||||
* for each command buffer submitted while the current pool is active.
|
||||
*
|
||||
* - Reference count is incremented upon #MTLCommandBuffer commit.
|
||||
* - Reference count is decremented in the #MTLCommandBuffer completion callback handler.
|
||||
*
|
||||
* A new #MTLSafeFreeList will begin each render step (frame). This pooling of buffers, rather than
|
||||
* individual buffer resource tracking reduces performance overhead.
|
||||
*
|
||||
* - The reference count starts at 1 to ensure that the reference count cannot prematurely reach
|
||||
* zero until any command buffers have been submitted. This additional decrement happens
|
||||
* when the next #MTLSafeFreeList is created, to allow the existing pool to be released once
|
||||
* the reference count hits zero after submitted command buffers complete.
|
||||
*
|
||||
* NOTE: the Metal API independently tracks resources used by command buffers for the purpose of
|
||||
* keeping resources alive while in-use by the driver and CPU, however, this differs from the
|
||||
* #MTLSafeFreeList mechanism in the Metal backend, which exists for the purpose of allowing
|
||||
* previously allocated #MTLBuffer resources to be re-used. This allows us to save on the expensive
|
||||
* cost of memory allocation.
|
||||
*/
|
||||
class MTLSafeFreeList {
|
||||
friend class MTLBufferPool;
|
||||
|
||||
private:
|
||||
std::atomic<int> reference_count_;
|
||||
std::atomic<bool> in_free_queue_;
|
||||
std::atomic<bool> referenced_by_workload_;
|
||||
std::recursive_mutex lock_;
|
||||
/* Linked list of next MTLSafeFreeList chunk if current chunk is full. */
|
||||
std::atomic<MTLSafeFreeList *> next_;
|
||||
|
||||
/* Lockless list. MAX_NUM_BUFFERS_ within a chunk based on considerations
|
||||
* for performance and memory. Higher chunk counts are preferable for efficiently
|
||||
* performing block operations such as copying several objects simultaneously.
|
||||
*
|
||||
* MIN_BUFFER_FLUSH_COUNT refers to the minimum count of buffers in the MTLSafeFreeList
|
||||
* before buffers are returned to global memory pool. This is set at a point to reduce
|
||||
* overhead of small pool flushes, while ensuring floating memory overhead is not excessive. */
|
||||
static const int MAX_NUM_BUFFERS_ = 8192;
|
||||
static const int MIN_BUFFER_FLUSH_COUNT = 120;
|
||||
std::atomic<int> current_list_index_;
|
||||
gpu::MTLBuffer *safe_free_pool_[MAX_NUM_BUFFERS_];
|
||||
|
||||
public:
|
||||
MTLSafeFreeList();
|
||||
|
||||
/* Can be used from multiple threads. Performs insertion into Safe Free List with the least
|
||||
* amount of threading synchronization. */
|
||||
void insert_buffer(gpu::MTLBuffer *buffer);
|
||||
|
||||
/* Whether we need to start a new safe free list, or can carry on using the existing one. */
|
||||
bool should_flush();
|
||||
|
||||
/* Increments command buffer reference count. */
|
||||
void increment_reference();
|
||||
|
||||
/* Decrement and return of buffers to pool occur on MTLCommandBuffer completion callback. */
|
||||
void decrement_reference();
|
||||
|
||||
void flag_in_queue()
|
||||
{
|
||||
in_free_queue_ = true;
|
||||
if (current_list_index_ >= MTLSafeFreeList::MAX_NUM_BUFFERS_) {
|
||||
MTLSafeFreeList *next_pool = next_.load();
|
||||
if (next_pool) {
|
||||
next_pool->flag_in_queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLSafeFreeList");
|
||||
};
|
||||
|
||||
/* MTLBuffer pools. */
|
||||
/* Allocating Metal buffers is expensive, so we cache all allocated buffers,
|
||||
* and when requesting a new buffer, find one which fits the required dimensions
|
||||
* from an existing pool of buffers.
|
||||
*
|
||||
* When freeing MTLBuffers, we insert them into the current MTLSafeFreeList, which defers
|
||||
* release of the buffer until the associated command buffers have finished executing.
|
||||
* This prevents a buffer from being re-used while it is still in-use by the GPU.
|
||||
*
|
||||
* * Once command buffers complete, MTLSafeFreeList's associated with the current
|
||||
* command buffer submission are added to the `completed_safelist_queue_`.
|
||||
*
|
||||
* * At a set point in time, all MTLSafeFreeList's in `completed_safelist_queue_` have their
|
||||
* MTLBuffers re-inserted into the Memory Manager's pools. */
|
||||
class MTLBufferPool {
|
||||
|
||||
private:
|
||||
#if MTL_DEBUG_MEMORY_STATISTICS == 1
|
||||
/* Memory statistics. */
|
||||
std::atomic<int64_t> total_allocation_bytes_;
|
||||
|
||||
/* Debug statistics. */
|
||||
std::atomic<int> per_frame_allocation_count_;
|
||||
std::atomic<int64_t> buffers_in_pool_;
|
||||
#endif
|
||||
|
||||
/* Metal resources. */
|
||||
bool initialized_ = false;
|
||||
id<MTLDevice> device_ = nil;
|
||||
|
||||
/* The buffer selection aims to pick a buffer which meets the minimum size requirements.
|
||||
* To do this, we keep an ordered set of all available buffers. If the buffer is larger than the
|
||||
* desired allocation size, we check it against `mtl_buffer_size_threshold_factor_`,
|
||||
* which defines what % larger than the original allocation the buffer can be.
|
||||
* - A higher value results in greater re-use of previously allocated buffers of similar sizes.
|
||||
* - A lower value may result in more dynamic allocations, but minimized memory usage for a given
|
||||
* scenario.
|
||||
* The current value of 1.26 is calibrated for optimal performance and memory utilization. */
|
||||
static constexpr float mtl_buffer_size_threshold_factor_ = 1.26;
|
||||
|
||||
/* Buffer pools using MTLResourceOptions as key for allocation type.
|
||||
* Aliased as 'uint64_t' for map type compatibility.
|
||||
* - A size-ordered list (MultiSet) of allocated buffers is kept per MTLResourceOptions
|
||||
* permutation. This allows efficient lookup for buffers of a given requested size.
|
||||
* - MTLBufferHandle wraps a gpu::MTLBuffer pointer to achieve easy size-based sorting
|
||||
* via CompareMTLBuffer.
|
||||
*
|
||||
* NOTE: buffer_pool_lock_ guards against concurrent access to the memory allocator. This
|
||||
* can occur during light baking or rendering operations. */
|
||||
using MTLBufferPoolOrderedList = std::multiset<MTLBufferHandle, CompareMTLBuffer>;
|
||||
using MTLBufferResourceOptions = uint64_t;
|
||||
|
||||
std::mutex buffer_pool_lock_;
|
||||
Map<MTLBufferResourceOptions, MTLBufferPoolOrderedList *> buffer_pools_;
|
||||
|
||||
/* Linked list to track all existing allocations. Prioritizing fast insert/deletion. */
|
||||
gpu::MTLBuffer *allocations_list_base_;
|
||||
uint allocations_list_size_;
|
||||
|
||||
/* Maintain a queue of all MTLSafeFreeList's that have been released
|
||||
* by the GPU and are ready to have their buffers re-inserted into the
|
||||
* MemoryManager pools.
|
||||
* Access to this queue is made thread-safe through safelist_lock_. */
|
||||
std::mutex safelist_lock_;
|
||||
Vector<MTLSafeFreeList *> completed_safelist_queue_;
|
||||
|
||||
/* Current free list, associated with active MTLCommandBuffer submission. */
|
||||
/* MTLBuffer::free() can be called from separate threads, due to usage within animation
|
||||
* system/worker threads. */
|
||||
std::atomic<MTLSafeFreeList *> current_free_list_;
|
||||
std::atomic<int64_t> allocations_in_pool_;
|
||||
|
||||
/* Previous list, to be released after one full frame. */
|
||||
MTLSafeFreeList *prev_free_buffer_list_ = nullptr;
|
||||
|
||||
public:
|
||||
void init(id<MTLDevice> device);
|
||||
~MTLBufferPool();
|
||||
|
||||
gpu::MTLBuffer *allocate(uint64_t size, bool cpu_visible);
|
||||
gpu::MTLBuffer *allocate_aligned(uint64_t size, uint alignment, bool cpu_visible);
|
||||
gpu::MTLBuffer *allocate_with_data(uint64_t size, bool cpu_visible, const void *data = nullptr);
|
||||
gpu::MTLBuffer *allocate_aligned_with_data(uint64_t size,
|
||||
uint alignment,
|
||||
bool cpu_visible,
|
||||
const void *data = nullptr);
|
||||
bool free_buffer(gpu::MTLBuffer *buffer);
|
||||
|
||||
/* Flush MTLSafeFreeList buffers, for completed lists in `completed_safelist_queue_`,
|
||||
* back to memory pools. */
|
||||
void update_memory_pools();
|
||||
|
||||
/* Access and control over active MTLSafeFreeList. */
|
||||
MTLSafeFreeList *get_current_safe_list();
|
||||
void begin_new_safe_list();
|
||||
|
||||
/* Add a completed MTLSafeFreeList to completed_safelist_queue_. */
|
||||
void push_completed_safe_list(MTLSafeFreeList *list);
|
||||
|
||||
private:
|
||||
void ensure_buffer_pool(MTLResourceOptions options);
|
||||
void insert_buffer_into_pool(MTLResourceOptions options, gpu::MTLBuffer *buffer);
|
||||
void free();
|
||||
|
||||
/* Allocations list. */
|
||||
void allocations_list_insert(gpu::MTLBuffer *buffer);
|
||||
void allocations_list_delete(gpu::MTLBuffer *buffer);
|
||||
void allocations_list_delete_all();
|
||||
};
|
||||
|
||||
/* Scratch buffers are circular-buffers used for temporary data within the current frame.
|
||||
* In order to preserve integrity of contents when having multiple-frames-in-flight,
|
||||
* we cycle through a collection of scratch buffers which are reset upon next use.
|
||||
*
|
||||
* Below are a series of properties, declared to manage scratch buffers. If a scratch buffer
|
||||
* overflows, then the original buffer will be flushed and submitted, with retained references
|
||||
* by usage within the command buffer, and a new buffer will be created.
|
||||
* - The new buffer will grow in size to account for increased demand in temporary memory.
|
||||
*/
|
||||
class MTLScratchBufferManager {
|
||||
|
||||
private:
|
||||
/* Maximum number of scratch buffers to allocate. This should be the maximum number of
|
||||
* simultaneous frames in flight. */
|
||||
static constexpr uint mtl_max_scratch_buffers_ = MTL_NUM_SAFE_FRAMES;
|
||||
|
||||
public:
|
||||
/* Maximum size of single scratch buffer allocation. When re-sizing, this is the maximum size the
|
||||
* newly allocated buffers will grow to. Larger allocations are possible if
|
||||
* `MTL_SCRATCH_BUFFER_ALLOW_TEMPORARY_EXPANSION` is enabled, but these will instead allocate new
|
||||
* buffers from the memory pools on the fly. */
|
||||
static constexpr uint mtl_scratch_buffer_max_size_ = 128 * 1024 * 1024;
|
||||
|
||||
/* Initial size of circular scratch buffers prior to growth. */
|
||||
static constexpr uint mtl_scratch_buffer_initial_size_ = 16 * 1024 * 1024;
|
||||
|
||||
private:
|
||||
/* Parent MTLContext. */
|
||||
MTLContext &context_;
|
||||
bool initialised_ = false;
|
||||
|
||||
/* Scratch buffer currently in-use. */
|
||||
uint current_scratch_buffer_ = 0;
|
||||
|
||||
/* Scratch buffer pool. */
|
||||
MTLCircularBuffer *scratch_buffers_[mtl_max_scratch_buffers_];
|
||||
|
||||
public:
|
||||
MTLScratchBufferManager(MTLContext &context) : context_(context) {};
|
||||
~MTLScratchBufferManager();
|
||||
|
||||
/* Explicit initialization and freeing of resources.
|
||||
* Initialization must occur after device creation. */
|
||||
void init();
|
||||
void free();
|
||||
|
||||
/* Allocation functions for creating temporary allocations from active circular buffer. */
|
||||
MTLTemporaryBuffer scratch_buffer_allocate_range(uint64_t alloc_size);
|
||||
MTLTemporaryBuffer scratch_buffer_allocate_range_aligned(uint64_t alloc_size, uint alignment);
|
||||
|
||||
/* Ensure a new scratch buffer is started if we move onto a new frame.
|
||||
* Called when a new command buffer begins. */
|
||||
void ensure_increment_scratch_buffer();
|
||||
|
||||
/* Flush memory for active scratch buffer to GPU.
|
||||
* This call will perform a partial flush of the buffer starting from
|
||||
* the last offset the data was flushed from, to the current offset. */
|
||||
void flush_active_scratch_buffer();
|
||||
|
||||
/* Bind the whole scratch buffer as a SSBO resource. */
|
||||
void bind_as_ssbo(int slot);
|
||||
void unbind_as_ssbo();
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLBufferPool");
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::gpu
|
||||
1121
blender-5.2.0/source/blender/gpu/metal/mtl_memory.mm
Normal file
1121
blender-5.2.0/source/blender/gpu/metal/mtl_memory.mm
Normal file
File diff suppressed because it is too large
Load Diff
102
blender-5.2.0/source/blender/gpu/metal/mtl_primitive.hh
Normal file
102
blender-5.2.0/source/blender/gpu/metal/mtl_primitive.hh
Normal file
@@ -0,0 +1,102 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*
|
||||
* Encapsulation of Frame-buffer states (attached textures, viewport, scissors).
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_assert.h"
|
||||
|
||||
#include "GPU_primitive.hh"
|
||||
|
||||
#include <Metal/Metal.h>
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/** Utility functions **/
|
||||
static inline MTLPrimitiveTopologyClass mtl_prim_type_to_topology_class(MTLPrimitiveType prim_type)
|
||||
{
|
||||
switch (prim_type) {
|
||||
case MTLPrimitiveTypePoint:
|
||||
return MTLPrimitiveTopologyClassPoint;
|
||||
case MTLPrimitiveTypeLine:
|
||||
case MTLPrimitiveTypeLineStrip:
|
||||
return MTLPrimitiveTopologyClassLine;
|
||||
case MTLPrimitiveTypeTriangle:
|
||||
case MTLPrimitiveTypeTriangleStrip:
|
||||
return MTLPrimitiveTopologyClassTriangle;
|
||||
}
|
||||
return MTLPrimitiveTopologyClassUnspecified;
|
||||
}
|
||||
|
||||
static inline MTLPrimitiveType gpu_prim_type_to_metal(GPUPrimType prim_type)
|
||||
{
|
||||
switch (prim_type) {
|
||||
case GPU_PRIM_POINTS:
|
||||
return MTLPrimitiveTypePoint;
|
||||
case GPU_PRIM_LINES:
|
||||
case GPU_PRIM_LINES_ADJ:
|
||||
return MTLPrimitiveTypeLine;
|
||||
case GPU_PRIM_LINE_STRIP:
|
||||
case GPU_PRIM_LINE_STRIP_ADJ:
|
||||
case GPU_PRIM_LINE_LOOP:
|
||||
return MTLPrimitiveTypeLineStrip;
|
||||
case GPU_PRIM_TRIS:
|
||||
case GPU_PRIM_TRI_FAN:
|
||||
case GPU_PRIM_TRIS_ADJ:
|
||||
return MTLPrimitiveTypeTriangle;
|
||||
case GPU_PRIM_TRI_STRIP:
|
||||
return MTLPrimitiveTypeTriangleStrip;
|
||||
case GPU_PRIM_NONE:
|
||||
return MTLPrimitiveTypePoint;
|
||||
};
|
||||
}
|
||||
|
||||
/* Certain primitive types are not supported in Metal, and require emulation.
|
||||
* `GPU_PRIM_LINE_LOOP` and `GPU_PRIM_TRI_FAN` required index buffer patching.
|
||||
* Adjacency types do not need emulation as the input structure is the same,
|
||||
* and access is controlled from the vertex shader through SSBO vertex pulling.
|
||||
* -- These Adj cases are only used in geometry shaders in OpenGL. */
|
||||
static inline bool mtl_needs_topology_emulation(GPUPrimType prim_type)
|
||||
{
|
||||
|
||||
BLI_assert(prim_type != GPU_PRIM_NONE);
|
||||
switch (prim_type) {
|
||||
case GPU_PRIM_LINE_LOOP:
|
||||
case GPU_PRIM_TRI_FAN:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool mtl_vertex_count_fits_primitive_type(uint32_t vertex_count,
|
||||
MTLPrimitiveType prim_type)
|
||||
{
|
||||
if (vertex_count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (prim_type) {
|
||||
case MTLPrimitiveTypeLineStrip:
|
||||
return (vertex_count > 1);
|
||||
case MTLPrimitiveTypeLine:
|
||||
return (vertex_count % 2 == 0);
|
||||
case MTLPrimitiveTypePoint:
|
||||
return (vertex_count > 0);
|
||||
case MTLPrimitiveTypeTriangle:
|
||||
return (vertex_count % 3 == 0);
|
||||
case MTLPrimitiveTypeTriangleStrip:
|
||||
return (vertex_count > 2);
|
||||
}
|
||||
BLI_assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace blender::gpu
|
||||
@@ -0,0 +1,327 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "BLI_math_bits.h"
|
||||
#include "GPU_batch.hh"
|
||||
#include "GPU_vertex_format.hh"
|
||||
|
||||
#include <Metal/Metal.h>
|
||||
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "gpu_framebuffer_private.hh"
|
||||
#include "gpu_shader_private.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/**
|
||||
* Vertex attribute and buffer descriptor wrappers
|
||||
* for use in PSO construction and caching.
|
||||
*/
|
||||
struct MTLVertexAttributeDescriptorPSO {
|
||||
::MTLVertexFormat format;
|
||||
int offset;
|
||||
int buffer_index;
|
||||
GPUVertFetchMode format_conversion_mode;
|
||||
|
||||
bool operator==(const MTLVertexAttributeDescriptorPSO &other) const
|
||||
{
|
||||
return (format == other.format) && (offset == other.offset) &&
|
||||
(buffer_index == other.buffer_index) &&
|
||||
(format_conversion_mode == other.format_conversion_mode);
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return uint64_t((uint64_t(this->format) ^ (this->offset << 4) ^ (this->buffer_index << 8) ^
|
||||
(this->format_conversion_mode << 12)));
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
format = MTLVertexFormatInvalid;
|
||||
offset = 0;
|
||||
buffer_index = 0;
|
||||
format_conversion_mode = GPU_FETCH_FLOAT;
|
||||
}
|
||||
};
|
||||
|
||||
struct MTLVertexBufferLayoutDescriptorPSO {
|
||||
::MTLVertexStepFunction step_function;
|
||||
int step_rate;
|
||||
int stride;
|
||||
int buffer_slot;
|
||||
|
||||
bool operator==(const MTLVertexBufferLayoutDescriptorPSO &other) const
|
||||
{
|
||||
return (step_function == other.step_function) && (step_rate == other.step_rate) &&
|
||||
(stride == other.stride) && (buffer_slot == other.buffer_slot);
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return uint64_t(uint64_t(this->step_function) ^ (this->step_rate << 4) ^ (this->stride << 8) ^
|
||||
(uint64_t(this->buffer_slot) << 32));
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
step_function = MTLVertexStepFunctionPerVertex;
|
||||
step_rate = 1;
|
||||
stride = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct MTLVertexDescriptor {
|
||||
/* Core Vertex Attributes. */
|
||||
MTLVertexAttributeDescriptorPSO attributes[GPU_VERT_ATTR_MAX_LEN];
|
||||
MTLVertexBufferLayoutDescriptorPSO buffer_layouts[GPU_BATCH_VBO_MAX_LEN];
|
||||
int max_attribute_value;
|
||||
int total_attributes;
|
||||
int num_vert_buffers;
|
||||
::MTLPrimitiveTopologyClass prim_topology_class;
|
||||
|
||||
bool operator==(const MTLVertexDescriptor &other) const
|
||||
{
|
||||
if ((this->max_attribute_value != other.max_attribute_value) ||
|
||||
(this->total_attributes != other.total_attributes) ||
|
||||
(this->num_vert_buffers != other.num_vert_buffers))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (this->prim_topology_class != other.prim_topology_class) {
|
||||
return false;
|
||||
};
|
||||
|
||||
for (const int a : IndexRange(this->max_attribute_value + 1)) {
|
||||
if (!(this->attributes[a] == other.attributes[a])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const int b : IndexRange(ARRAY_SIZE(this->buffer_layouts))) {
|
||||
if (!(this->buffer_layouts[b] == other.buffer_layouts[b])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* NOTE: No need to compare SSBO attributes, as these will match attribute bindings for the
|
||||
* given shader. These are simply extra pre-resolved properties we want to include in the
|
||||
* cache. */
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
uint64_t hash = uint64_t(this->max_attribute_value ^ this->num_vert_buffers);
|
||||
for (const int a : IndexRange(this->max_attribute_value + 1)) {
|
||||
hash ^= this->attributes[a].hash() << a;
|
||||
}
|
||||
|
||||
for (const int b : IndexRange(this->num_vert_buffers)) {
|
||||
hash ^= this->buffer_layouts[b].hash() << (b + 10);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
struct SpecializationStateDescriptor {
|
||||
Vector<shader::SpecializationConstant::Value> values;
|
||||
|
||||
SpecializationStateDescriptor() = default;
|
||||
SpecializationStateDescriptor(Vector<shader::SpecializationConstant::Value> source)
|
||||
: values(source)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const SpecializationStateDescriptor &other) const
|
||||
{
|
||||
return values == other.values;
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
uint64_t hash = values.size();
|
||||
uint seed = 0xFF;
|
||||
for (const shader::SpecializationConstant::Value &value : values) {
|
||||
seed = seed << 1;
|
||||
hash ^= seed ^ value.u;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
/* Metal Render Pipeline State Descriptor -- All unique information which feeds PSO creation. */
|
||||
struct MTLRenderPipelineStateDescriptor {
|
||||
/* This state descriptor will contain ALL parameters which generate a unique PSO.
|
||||
* We will then use this state-object to efficiently look-up or create a
|
||||
* new PSO for the current shader.
|
||||
*
|
||||
* Unlike the 'MTLContextGlobalShaderPipelineState', this struct contains a subset of
|
||||
* parameters used to distinguish between unique PSOs. This struct is hash-able and only
|
||||
* contains those parameters which are required by PSO generation. Non-unique state such as
|
||||
* bound resources is not tracked here, as it does not require a unique PSO permutation if
|
||||
* changed. */
|
||||
|
||||
/* Input Vertex Descriptor. */
|
||||
MTLVertexDescriptor vertex_descriptor;
|
||||
|
||||
/* Render Target attachment state.
|
||||
* Assign to #MTLPixelFormatInvalid if not used. */
|
||||
int num_color_attachments;
|
||||
MTLPixelFormat color_attachment_format[GPU_FB_MAX_COLOR_ATTACHMENT];
|
||||
MTLPixelFormat depth_attachment_format;
|
||||
MTLPixelFormat stencil_attachment_format;
|
||||
uint8_t color_attachment_mask = 0xFF;
|
||||
|
||||
/* Render Pipeline State affecting PSO creation. */
|
||||
bool blending_enabled;
|
||||
MTLBlendOperation alpha_blend_op;
|
||||
MTLBlendOperation rgb_blend_op;
|
||||
MTLBlendFactor dest_alpha_blend_factor;
|
||||
MTLBlendFactor dest_rgb_blend_factor;
|
||||
MTLBlendFactor src_alpha_blend_factor;
|
||||
MTLBlendFactor src_rgb_blend_factor;
|
||||
|
||||
/* Global color write mask as this cannot be specified per attachment. */
|
||||
MTLColorWriteMask color_write_mask;
|
||||
|
||||
/* Clip distance enablement. */
|
||||
uchar clipping_plane_enable_mask = 0;
|
||||
|
||||
/* Point size required by point primitives. */
|
||||
float point_size = 0.0f;
|
||||
|
||||
/* Specialization constants map. */
|
||||
SpecializationStateDescriptor specialization_state;
|
||||
|
||||
/* Comparison Operator for caching. */
|
||||
bool operator==(const MTLRenderPipelineStateDescriptor &other) const
|
||||
{
|
||||
if (!(vertex_descriptor == other.vertex_descriptor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (clipping_plane_enable_mask != other.clipping_plane_enable_mask) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((num_color_attachments != other.num_color_attachments) ||
|
||||
(depth_attachment_format != other.depth_attachment_format) ||
|
||||
(stencil_attachment_format != other.stencil_attachment_format) ||
|
||||
(color_write_mask != other.color_write_mask) ||
|
||||
(blending_enabled != other.blending_enabled) || (alpha_blend_op != other.alpha_blend_op) ||
|
||||
(rgb_blend_op != other.rgb_blend_op) ||
|
||||
(dest_alpha_blend_factor != other.dest_alpha_blend_factor) ||
|
||||
(dest_rgb_blend_factor != other.dest_rgb_blend_factor) ||
|
||||
(src_alpha_blend_factor != other.src_alpha_blend_factor) ||
|
||||
(src_rgb_blend_factor != other.src_rgb_blend_factor) ||
|
||||
(color_attachment_mask != other.color_attachment_mask) ||
|
||||
(vertex_descriptor.prim_topology_class != other.vertex_descriptor.prim_topology_class) ||
|
||||
(point_size != other.point_size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Attachments can be skipped, so num_color_attachments will not define the range. */
|
||||
for (const int c : IndexRange(GPU_FB_MAX_COLOR_ATTACHMENT)) {
|
||||
if (color_attachment_format[c] != other.color_attachment_format[c]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(specialization_state == other.specialization_state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
/* NOTE(Metal): Current setup aims to minimize overlap of parameters
|
||||
* which are more likely to be different, to ensure earlier hash
|
||||
* differences without having to fallback to comparisons.
|
||||
* Though this could likely be further improved to remove
|
||||
* has collisions. */
|
||||
|
||||
uint64_t hash = this->vertex_descriptor.hash();
|
||||
hash ^= uint64_t(this->num_color_attachments) << 16; /* up to 6 (3 bits). */
|
||||
hash ^= uint64_t(this->depth_attachment_format) << 18; /* up to 555 (9 bits). */
|
||||
hash ^= uint64_t(this->stencil_attachment_format) << 20; /* up to 555 (9 bits). */
|
||||
hash ^= (*((uint64_t *)&this->vertex_descriptor.prim_topology_class)); /* Up to 3 (2 bits). */
|
||||
|
||||
/* Only include elements in Hash if they are needed - avoids variable null assignments
|
||||
* influencing hash. */
|
||||
if (this->num_color_attachments > 0) {
|
||||
hash ^= uint64_t(this->color_write_mask) << 22; /* 4 bit bit-mask. */
|
||||
hash ^= uint64_t(this->alpha_blend_op) << 26; /* Up to 4 (3 bits). */
|
||||
hash ^= uint64_t(this->rgb_blend_op) << 29; /* Up to 4 (3 bits). */
|
||||
hash ^= uint64_t(this->dest_alpha_blend_factor) << 32; /* Up to 18 (5 bits). */
|
||||
hash ^= uint64_t(this->dest_rgb_blend_factor) << 37; /* Up to 18 (5 bits). */
|
||||
hash ^= uint64_t(this->src_alpha_blend_factor) << 42; /* Up to 18 (5 bits). */
|
||||
hash ^= uint64_t(this->src_rgb_blend_factor) << 47; /* Up to 18 (5 bits). */
|
||||
hash ^= uint64_t(this->color_attachment_mask) << 47; /* 8 bit bit-mask. */
|
||||
|
||||
for (const uint c : IndexRange(GPU_FB_MAX_COLOR_ATTACHMENT)) {
|
||||
hash ^= uint64_t(this->color_attachment_format[c]) << (c + 52); /* Up to 555 (9 bits). */
|
||||
}
|
||||
}
|
||||
|
||||
hash |= uint64_t((this->blending_enabled && (this->num_color_attachments > 0)) ? 1 : 0) << 62;
|
||||
hash ^= uint64_t(float_as_uint(this->point_size));
|
||||
|
||||
/* Clipping plane enablement. */
|
||||
hash ^= uint64_t(clipping_plane_enable_mask) << 20;
|
||||
|
||||
/* Specialization constants. We can treat the raw bytes as uint. */
|
||||
hash ^= specialization_state.hash();
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/* Reset the Vertex Descriptor to default. */
|
||||
void reset_vertex_descriptor()
|
||||
{
|
||||
vertex_descriptor.total_attributes = 0;
|
||||
vertex_descriptor.max_attribute_value = 0;
|
||||
vertex_descriptor.num_vert_buffers = 0;
|
||||
vertex_descriptor.prim_topology_class = MTLPrimitiveTopologyClassUnspecified;
|
||||
for (int i = 0; i < GPU_VERT_ATTR_MAX_LEN; i++) {
|
||||
vertex_descriptor.attributes[i].reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* Metal Compute Pipeline State Descriptor containing all unique information which feeds PSO
|
||||
* creation. */
|
||||
struct MTLComputePipelineStateDescriptor {
|
||||
|
||||
/* Specialization constants map. */
|
||||
SpecializationStateDescriptor specialization_state;
|
||||
|
||||
MTLComputePipelineStateDescriptor() = default;
|
||||
MTLComputePipelineStateDescriptor(Vector<shader::SpecializationConstant::Value> values)
|
||||
{
|
||||
specialization_state.values = values;
|
||||
}
|
||||
|
||||
/* Comparison Operator for caching. */
|
||||
bool operator==(const MTLComputePipelineStateDescriptor &other) const
|
||||
{
|
||||
return (specialization_state == other.specialization_state);
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return specialization_state.hash();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::gpu
|
||||
84
blender-5.2.0/source/blender/gpu/metal/mtl_push_constant.cc
Normal file
84
blender-5.2.0/source/blender/gpu/metal/mtl_push_constant.cc
Normal file
@@ -0,0 +1,84 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#include "BKE_global.hh"
|
||||
|
||||
#include "mtl_push_constant.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
static size_t padded_size(const shader::ShaderCreateInfo::PushConst &push_constant,
|
||||
size_t &alignment)
|
||||
{
|
||||
int comp = to_component_count(push_constant.type);
|
||||
size_t size;
|
||||
if (comp == 3) {
|
||||
/* Padded size for float3. */
|
||||
size = 4 * sizeof(float);
|
||||
alignment = 16;
|
||||
}
|
||||
else if (comp == 9) {
|
||||
/* Padded size for float3x3. */
|
||||
size = 3 * 4 * sizeof(float);
|
||||
alignment = 16;
|
||||
}
|
||||
else if (comp == 16) {
|
||||
/* Special alignment case for float4x4. */
|
||||
size = 4 * 4 * sizeof(float);
|
||||
alignment = 16;
|
||||
}
|
||||
else {
|
||||
size = comp * sizeof(float);
|
||||
alignment = size;
|
||||
}
|
||||
return size * push_constant.array_size_safe();
|
||||
}
|
||||
|
||||
MTLPushConstantBuf::MTLPushConstantBuf(const shader::ShaderCreateInfo &info)
|
||||
{
|
||||
BLI_assert(info.push_constants_.is_empty() == false);
|
||||
size_t max_alignement = 0;
|
||||
/* Compute size of backing buffer. */
|
||||
size_ = 0;
|
||||
for (const shader::ShaderCreateInfo::PushConst &push_constant : info.push_constants_) {
|
||||
size_t alignment;
|
||||
size_t pc_size = padded_size(push_constant, alignment);
|
||||
max_alignement = max_uu(max_alignement, alignment);
|
||||
/* Padding for alignment. */
|
||||
size_ = ceil_to_multiple_u(size_, alignment);
|
||||
size_ += pc_size;
|
||||
}
|
||||
/* Pad to max alignment. */
|
||||
size_ = ceil_to_multiple_u(size_, max_alignement);
|
||||
data_ = reinterpret_cast<uint8_t *>(
|
||||
MEM_new_array_zeroed_aligned(1, size_, 128, "MTLPushConstantData"));
|
||||
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
/* Poison values to detect unset values. */
|
||||
memset(data_, 0xFD, size_);
|
||||
}
|
||||
}
|
||||
|
||||
MTLPushConstantBuf::~MTLPushConstantBuf()
|
||||
{
|
||||
MEM_delete(data_);
|
||||
}
|
||||
|
||||
int MTLPushConstantBuf::append(shader::ShaderCreateInfo::PushConst push_constant)
|
||||
{
|
||||
size_t alignment;
|
||||
size_t pc_size = padded_size(push_constant, alignment);
|
||||
/* Padding for alignment. */
|
||||
offset_ = ceil_to_multiple_u(offset_, alignment);
|
||||
int loc = offset_;
|
||||
offset_ += pc_size;
|
||||
BLI_assert(offset_ <= size_);
|
||||
return loc;
|
||||
}
|
||||
|
||||
} // namespace blender::gpu
|
||||
69
blender-5.2.0/source/blender/gpu/metal/mtl_push_constant.hh
Normal file
69
blender-5.2.0/source/blender/gpu/metal/mtl_push_constant.hh
Normal file
@@ -0,0 +1,69 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gpu_shader_create_info.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/* Metal Shader Uniform data store.
|
||||
* This blocks is used to store current shader push_constant
|
||||
* data before it is submitted to the GPU. This is currently
|
||||
* stored per shader instance, though depending on GPU module
|
||||
* functionality, this could potentially be a global data store.
|
||||
* This data is associated with the PushConstantBlock. */
|
||||
class MTLPushConstantBuf {
|
||||
private:
|
||||
uint8_t *data_ = nullptr;
|
||||
/* Offset inside buffer for allocating push constants. */
|
||||
size_t offset_ = 0;
|
||||
/* Size of the allocated buffer in `data_`. */
|
||||
size_t size_ = 0;
|
||||
/* True if the data has been touched and must be resent to GPU. */
|
||||
bool is_dirty_ = true;
|
||||
|
||||
public:
|
||||
MTLPushConstantBuf(const shader::ShaderCreateInfo &info);
|
||||
~MTLPushConstantBuf();
|
||||
|
||||
/* Append uniform description to the buffer and return the location inside the buffer. */
|
||||
int append(shader::ShaderCreateInfo::PushConst push_constant);
|
||||
|
||||
uint8_t *data()
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
const uint8_t *data() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
bool is_dirty() const
|
||||
{
|
||||
return is_dirty_;
|
||||
}
|
||||
|
||||
void tag_dirty()
|
||||
{
|
||||
is_dirty_ = true;
|
||||
}
|
||||
|
||||
void tag_updated()
|
||||
{
|
||||
is_dirty_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::gpu
|
||||
43
blender-5.2.0/source/blender/gpu/metal/mtl_query.hh
Normal file
43
blender-5.2.0/source/blender/gpu/metal/mtl_query.hh
Normal file
@@ -0,0 +1,43 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "gpu_query.hh"
|
||||
#include "mtl_context.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
class MTLQueryPool : public QueryPool {
|
||||
private:
|
||||
/** Number of queries that have been issued since last initialization.
|
||||
* Should be equal to query_ids_.size(). */
|
||||
uint32_t query_issued_;
|
||||
/** Type of this query pool. */
|
||||
GPUQueryType type_;
|
||||
/** Can only be initialized once. */
|
||||
bool initialized_ = false;
|
||||
MTLVisibilityResultMode mtl_type_;
|
||||
Vector<gpu::MTLBuffer *> buffer_;
|
||||
|
||||
void allocate();
|
||||
|
||||
public:
|
||||
MTLQueryPool();
|
||||
~MTLQueryPool() override;
|
||||
|
||||
void init(GPUQueryType type) override;
|
||||
|
||||
void begin_query() override;
|
||||
void end_query() override;
|
||||
|
||||
void get_occlusion_result(MutableSpan<uint32_t> r_values) override;
|
||||
};
|
||||
} // namespace blender::gpu
|
||||
129
blender-5.2.0/source/blender/gpu/metal/mtl_query.mm
Normal file
129
blender-5.2.0/source/blender/gpu/metal/mtl_query.mm
Normal file
@@ -0,0 +1,129 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#include "mtl_query.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
static const size_t VISIBILITY_COUNT_PER_BUFFER = 512;
|
||||
/* Defined in the documentation but can't be queried programmatically:
|
||||
* https://developer.apple.com/documentation/metal/mtlvisibilityresultmode/mtlvisibilityresultmodeboolean?language=objc
|
||||
*/
|
||||
static const size_t VISIBILITY_RESULT_SIZE_IN_BYTES = 8;
|
||||
|
||||
MTLQueryPool::MTLQueryPool()
|
||||
{
|
||||
allocate();
|
||||
}
|
||||
MTLQueryPool::~MTLQueryPool()
|
||||
{
|
||||
for (gpu::MTLBuffer *buf : buffer_) {
|
||||
BLI_assert(buf);
|
||||
buf->free();
|
||||
}
|
||||
}
|
||||
|
||||
void MTLQueryPool::allocate()
|
||||
{
|
||||
/* Allocate Metal buffer for visibility results. */
|
||||
size_t buffer_size_in_bytes = VISIBILITY_COUNT_PER_BUFFER * VISIBILITY_RESULT_SIZE_IN_BYTES;
|
||||
gpu::MTLBuffer *buffer = MTLContext::get_global_memory_manager()->allocate(buffer_size_in_bytes,
|
||||
true);
|
||||
BLI_assert(buffer);
|
||||
|
||||
/* We must zero-initialize the query buffer as visibility queries with no draws between
|
||||
* begin and end will not write any result to the buffer. */
|
||||
memset(buffer->get_host_ptr(), 0, buffer_size_in_bytes);
|
||||
buffer->flush();
|
||||
buffer_.append(buffer);
|
||||
}
|
||||
|
||||
static inline MTLVisibilityResultMode to_mtl_type(GPUQueryType type)
|
||||
{
|
||||
if (type == GPU_QUERY_OCCLUSION) {
|
||||
return MTLVisibilityResultModeBoolean;
|
||||
}
|
||||
BLI_assert(0);
|
||||
return MTLVisibilityResultModeBoolean;
|
||||
}
|
||||
|
||||
void MTLQueryPool::init(GPUQueryType type)
|
||||
{
|
||||
BLI_assert(initialized_ == false);
|
||||
initialized_ = true;
|
||||
type_ = type;
|
||||
mtl_type_ = to_mtl_type(type);
|
||||
query_issued_ = 0;
|
||||
}
|
||||
|
||||
void MTLQueryPool::begin_query()
|
||||
{
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
|
||||
/* Ensure our allocated buffer pool has enough space for the current queries. */
|
||||
int query_id = query_issued_;
|
||||
int requested_buffer = query_id / VISIBILITY_COUNT_PER_BUFFER;
|
||||
if (requested_buffer >= buffer_.size()) {
|
||||
allocate();
|
||||
}
|
||||
|
||||
BLI_assert(requested_buffer < buffer_.size());
|
||||
gpu::MTLBuffer *buffer = buffer_[requested_buffer];
|
||||
|
||||
/* Ensure visibility buffer is set on the context. If visibility buffer changes,
|
||||
* we need to begin a new render pass with an updated reference in the
|
||||
* MTLRenderPassDescriptor. */
|
||||
ctx->set_visibility_buffer(buffer);
|
||||
|
||||
ctx->ensure_begin_render_pass();
|
||||
id<MTLRenderCommandEncoder> rec = ctx->main_command_buffer.get_active_render_command_encoder();
|
||||
[rec setVisibilityResultMode:mtl_type_
|
||||
offset:(query_id % VISIBILITY_COUNT_PER_BUFFER) *
|
||||
VISIBILITY_RESULT_SIZE_IN_BYTES];
|
||||
query_issued_ += 1;
|
||||
}
|
||||
|
||||
void MTLQueryPool::end_query()
|
||||
{
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
|
||||
id<MTLRenderCommandEncoder> rec = ctx->main_command_buffer.get_active_render_command_encoder();
|
||||
[rec setVisibilityResultMode:MTLVisibilityResultModeDisabled offset:0];
|
||||
}
|
||||
|
||||
void MTLQueryPool::get_occlusion_result(MutableSpan<uint32_t> r_values)
|
||||
{
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
|
||||
/* Create a blit encoder to synchronize the query buffer results between
|
||||
* GPU and CPU when not using shared-memory. */
|
||||
if ([ctx->device hasUnifiedMemory] == false) {
|
||||
id<MTLBlitCommandEncoder> blit_encoder = ctx->main_command_buffer.ensure_begin_blit_encoder();
|
||||
BLI_assert(blit_encoder);
|
||||
for (gpu::MTLBuffer *buf : buffer_) {
|
||||
[blit_encoder synchronizeResource:buf->get_metal_buffer()];
|
||||
}
|
||||
BLI_assert(ctx->get_inside_frame());
|
||||
}
|
||||
|
||||
/* Wait for GPU operations to complete and for query buffer contents
|
||||
* to be synchronized back to host memory. */
|
||||
GPU_finish();
|
||||
|
||||
/* Iterate through all possible visibility buffers and copy results into provided
|
||||
* container. */
|
||||
for (const int i : IndexRange(query_issued_)) {
|
||||
int requested_buffer = i / VISIBILITY_COUNT_PER_BUFFER;
|
||||
const uint64_t *queries = static_cast<const uint64_t *>(
|
||||
buffer_[requested_buffer]->get_host_ptr());
|
||||
r_values[i] = uint32_t(queries[i % VISIBILITY_COUNT_PER_BUFFER]);
|
||||
}
|
||||
ctx->set_visibility_buffer(nullptr);
|
||||
}
|
||||
|
||||
} // namespace blender::gpu
|
||||
496
blender-5.2.0/source/blender/gpu/metal/mtl_shader.hh
Normal file
496
blender-5.2.0/source/blender/gpu/metal/mtl_shader.hh
Normal file
@@ -0,0 +1,496 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "GPU_batch.hh"
|
||||
#include "GPU_capabilities.hh"
|
||||
#include "GPU_shader.hh"
|
||||
#include "GPU_vertex_format.hh"
|
||||
|
||||
#include <Metal/Metal.h>
|
||||
#include <QuartzCore/QuartzCore.h>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "mtl_framebuffer.hh"
|
||||
#include "mtl_shader_interface.hh"
|
||||
#include "mtl_shader_shared.hh"
|
||||
#include "mtl_state.hh"
|
||||
#include "mtl_texture.hh"
|
||||
|
||||
#include "gpu_shader_create_info.hh"
|
||||
#include "gpu_shader_private.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
class MTLShaderInterface;
|
||||
class MTLContext;
|
||||
|
||||
/* Debug control. */
|
||||
#define MTL_SHADER_DEBUG_EXPORT_SOURCE 0
|
||||
#define MTL_SHADER_TRANSLATION_DEBUG_OUTPUT 0
|
||||
|
||||
/* Separate print used only during development and debugging. */
|
||||
#if MTL_SHADER_TRANSLATION_DEBUG_OUTPUT
|
||||
# define shader_debug_printf printf
|
||||
#else
|
||||
# define shader_debug_printf(...) /* Null print. */
|
||||
#endif
|
||||
|
||||
/* Maximum threshold for specialized shader variant count.
|
||||
* This is a catch-all to prevent excessive PSO permutations from being created and also catch
|
||||
* parameters which should ideally not be used for specialization. */
|
||||
#define MTL_SHADER_MAX_SPECIALIZED_PSOS 5
|
||||
|
||||
struct MTLRenderPipelineStateInstance {
|
||||
/* Function instances with specialization.
|
||||
* Required for argument encoder construction. */
|
||||
id<MTLFunction> vert;
|
||||
id<MTLFunction> frag;
|
||||
|
||||
/* PSO handle. */
|
||||
id<MTLRenderPipelineState> pso;
|
||||
|
||||
/** Derived information. */
|
||||
/* Unique index for PSO variant. */
|
||||
uint32_t shader_pso_index;
|
||||
/* buffer bind slot used for null attributes (-1 if not needed). */
|
||||
int null_attribute_buffer_index;
|
||||
/* Topology class. */
|
||||
MTLPrimitiveTopologyClass prim_type;
|
||||
|
||||
/**
|
||||
* Reflection Data.
|
||||
* This information can also be used to eliminate redundant/unused bindings.
|
||||
* Does only contains SSBO, UBO, Argument and Push Constant buffers. VBO bindings are masked out.
|
||||
*/
|
||||
uint32_t used_buf_vert_mask = 0;
|
||||
uint32_t used_buf_frag_mask = 0;
|
||||
/* Same thing for images. */
|
||||
uint16_t used_ima_vert_mask = 0;
|
||||
uint16_t used_ima_frag_mask = 0;
|
||||
/* Same thing for samplers. */
|
||||
uint64_t used_tex_vert_mask = 0;
|
||||
uint64_t used_tex_frag_mask = 0;
|
||||
|
||||
void parse_reflection_data(::MTLRenderPipelineReflection *reflection_data);
|
||||
};
|
||||
|
||||
struct MTLComputePipelineStateCommon {
|
||||
/* Thread-group information is common for all PSO variants. */
|
||||
int threadgroup_x_len = 1;
|
||||
int threadgroup_y_len = 1;
|
||||
int threadgroup_z_len = 1;
|
||||
|
||||
inline void set_compute_workgroup_size(int workgroup_size_x,
|
||||
int workgroup_size_y,
|
||||
int workgroup_size_z)
|
||||
{
|
||||
this->threadgroup_x_len = workgroup_size_x;
|
||||
this->threadgroup_y_len = workgroup_size_y;
|
||||
this->threadgroup_z_len = workgroup_size_z;
|
||||
}
|
||||
};
|
||||
|
||||
struct MTLComputePipelineStateInstance {
|
||||
/** Derived information. */
|
||||
/* Unique index for PSO variant. */
|
||||
uint32_t shader_pso_index;
|
||||
|
||||
/* Function instances with specialization.
|
||||
* Required for argument encoder construction. */
|
||||
id<MTLFunction> compute = nil;
|
||||
/* PSO handle. */
|
||||
id<MTLComputePipelineState> pso = nil;
|
||||
};
|
||||
|
||||
/**
|
||||
* #MTLShader implements shader compilation, Pipeline State Object (PSO)
|
||||
* creation for rendering and uniform data binding.
|
||||
* Shaders can either be created from native MSL, or generated
|
||||
* from a GLSL source shader using #GPUShaderCreateInfo.
|
||||
*
|
||||
* Shader creation process:
|
||||
* - Create #MTLShader:
|
||||
* - Convert GLSL to MSL source if required.
|
||||
* - set MSL source.
|
||||
* - set Vertex/Fragment function names.
|
||||
* - Create and populate #MTLShaderInterface.
|
||||
*/
|
||||
class MTLShader : public Shader {
|
||||
friend shader::ShaderCreateInfo;
|
||||
friend shader::StageInterfaceInfo;
|
||||
|
||||
private:
|
||||
/* Context Handle. */
|
||||
MTLContext *context_ = nullptr;
|
||||
|
||||
/* Can be nullptr if no uniform is present inside the shader. */
|
||||
MTLPushConstantBuf *push_constant_buf_ = nullptr;
|
||||
|
||||
/** Compiled shader resources. */
|
||||
id<MTLLibrary> shader_library_vert_ = nil;
|
||||
id<MTLLibrary> shader_library_frag_ = nil;
|
||||
id<MTLLibrary> shader_library_comp_ = nil;
|
||||
bool valid_ = false;
|
||||
|
||||
/** Render pipeline state and PSO caching. */
|
||||
/* Metal API Descriptor used for creation of unique PSOs based on rendering state. */
|
||||
::MTLRenderPipelineDescriptor *pso_descriptor_ = nil;
|
||||
/* Metal backend struct containing all high-level pipeline state parameters
|
||||
* which contribute to instantiation of a unique PSO. */
|
||||
MTLRenderPipelineStateDescriptor current_pipeline_state_;
|
||||
/* Cache of compiled PipelineStateObjects. */
|
||||
Map<MTLRenderPipelineStateDescriptor, MTLRenderPipelineStateInstance *> pso_cache_;
|
||||
std::mutex pso_cache_lock_;
|
||||
|
||||
/** Compute pipeline state and Compute PSO caching. */
|
||||
MTLComputePipelineStateCommon compute_pso_common_state_;
|
||||
Map<MTLComputePipelineStateDescriptor, MTLComputePipelineStateInstance *> compute_pso_cache_;
|
||||
|
||||
public:
|
||||
MTLShader(MTLContext *ctx, const char *name);
|
||||
~MTLShader();
|
||||
|
||||
void init(const shader::ShaderCreateInfo & /*info*/, bool /*is_codegen_only*/) override {};
|
||||
|
||||
/* Patch create infos for any additional resources that could be needed. */
|
||||
const shader::ShaderCreateInfo &patch_create_info(
|
||||
const shader::ShaderCreateInfo &original_info) override;
|
||||
|
||||
/* Assign GLSL source. */
|
||||
void vertex_shader_from_glsl(const shader::ShaderCreateInfo &info,
|
||||
MutableSpan<StringRefNull> sources) override;
|
||||
void geometry_shader_from_glsl(const shader::ShaderCreateInfo &info,
|
||||
MutableSpan<StringRefNull> sources) override;
|
||||
void fragment_shader_from_glsl(const shader::ShaderCreateInfo &info,
|
||||
MutableSpan<StringRefNull> sources) override;
|
||||
void compute_shader_from_glsl(const shader::ShaderCreateInfo &info,
|
||||
MutableSpan<StringRefNull> sources) override;
|
||||
|
||||
/* Compile and build - Return true if successful. */
|
||||
bool finalize(const shader::ShaderCreateInfo *info = nullptr) override;
|
||||
bool finalize_compute(const shader::ShaderCreateInfo *info);
|
||||
void warm_cache(int limit) override;
|
||||
|
||||
/* Utility. */
|
||||
bool is_valid()
|
||||
{
|
||||
return valid_;
|
||||
}
|
||||
bool has_compute_shader_lib()
|
||||
{
|
||||
return (shader_library_comp_ != nil);
|
||||
}
|
||||
bool has_parent_shader()
|
||||
{
|
||||
return (parent_shader_ != nil);
|
||||
}
|
||||
MTLRenderPipelineStateDescriptor &get_current_pipeline_state()
|
||||
{
|
||||
return current_pipeline_state_;
|
||||
}
|
||||
|
||||
MTLShaderInterface &get_interface()
|
||||
{
|
||||
return *static_cast<MTLShaderInterface *>(this->interface);
|
||||
}
|
||||
|
||||
/* Might return nullptr if no push constants are present in the interface. */
|
||||
MTLPushConstantBuf *get_push_constant_buf()
|
||||
{
|
||||
return push_constant_buf_;
|
||||
}
|
||||
|
||||
/* Shader source generators from create-info.
|
||||
* These aren't all used by Metal, as certain parts of source code generation
|
||||
* for shader entry-points and resource mapping occur during `finalize`. */
|
||||
std::string resources_declare(const shader::ShaderCreateInfo & /*info*/) const override
|
||||
{
|
||||
return "";
|
||||
}
|
||||
std::string vertex_interface_declare(const shader::ShaderCreateInfo & /*info*/) const override
|
||||
{
|
||||
return "";
|
||||
}
|
||||
std::string fragment_interface_declare(const shader::ShaderCreateInfo & /*info*/) const override
|
||||
{
|
||||
return "";
|
||||
}
|
||||
std::string geometry_interface_declare(const shader::ShaderCreateInfo & /*info*/) const override
|
||||
{
|
||||
return "";
|
||||
}
|
||||
std::string geometry_layout_declare(const shader::ShaderCreateInfo & /*info*/) const override
|
||||
{
|
||||
return "";
|
||||
}
|
||||
std::string compute_layout_declare(const shader::ShaderCreateInfo & /*info*/) const override
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
void bind(const shader::SpecializationConstants *constants_state) override;
|
||||
void unbind() override;
|
||||
|
||||
void uniform_float(int location, int comp_len, int array_size, const float *data) override;
|
||||
void uniform_int(int location, int comp_len, int array_size, const int *data) override;
|
||||
|
||||
MTLRenderPipelineStateInstance *bake_current_pipeline_state(MTLContext *ctx,
|
||||
MTLPrimitiveTopologyClass prim_type);
|
||||
/* Bakes and caches a PSO for graphic. */
|
||||
MTLRenderPipelineStateInstance *bake_graphic_pipeline_state(
|
||||
MTLContext *ctx,
|
||||
MTLPrimitiveTopologyClass prim_type,
|
||||
const MTLRenderPipelineStateDescriptor &pipeline_descriptor);
|
||||
|
||||
/* Bakes and caches a PSO for compute. */
|
||||
MTLComputePipelineStateInstance *bake_compute_pipeline_state(
|
||||
MTLContext *ctx, MTLComputePipelineStateDescriptor &compute_pipeline_descriptor);
|
||||
|
||||
const MTLComputePipelineStateCommon &get_compute_common_state()
|
||||
{
|
||||
return compute_pso_common_state_;
|
||||
}
|
||||
|
||||
private:
|
||||
/** Create, compile and attach the shader stage to the shader program. */
|
||||
id<MTLLibrary> create_shader_library(const shader::ShaderCreateInfo &info,
|
||||
ShaderStage stage,
|
||||
MutableSpan<StringRefNull> sources);
|
||||
|
||||
std::string entry_point_name_get(const ShaderStage stage);
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLShader");
|
||||
};
|
||||
|
||||
class MTLShaderCompiler : public ShaderCompiler {
|
||||
public:
|
||||
MTLShaderCompiler();
|
||||
|
||||
Shader *compile_shader(const shader::ShaderCreateInfo &info) override;
|
||||
void specialize_shader(const ShaderSpecialization &specialization) override;
|
||||
};
|
||||
|
||||
/* Vertex format conversion.
|
||||
* Determines whether it is possible to resize a vertex attribute type
|
||||
* during input assembly. A conversion is implied by the difference
|
||||
* between the input vertex descriptor (from MTLBatch/MTLImmediate)
|
||||
* and the type specified in the shader source.
|
||||
*
|
||||
* e.g. vec3 to vec4 expansion, or vec4 to vec2 truncation.
|
||||
* NOTE: Vector expansion will replace empty elements with the values
|
||||
* (0,0,0,1).
|
||||
*
|
||||
* If implicit format resize is not possible, this function
|
||||
* returns false.
|
||||
*
|
||||
* Implicitly supported conversions in Metal are described here:
|
||||
* https://developer.apple.com/documentation/metal/mtlvertexattributedescriptor/1516081-format?language=objc
|
||||
*/
|
||||
inline MTLVertexFormat format_resize_comp(MTLVertexFormat mtl_format, uint32_t components)
|
||||
{
|
||||
#define RESIZE_TYPE(_type, _suffix) \
|
||||
case MTLVertexFormat##_type##_suffix: \
|
||||
case MTLVertexFormat##_type##2##_suffix: \
|
||||
case MTLVertexFormat##_type##3##_suffix: \
|
||||
case MTLVertexFormat##_type##4##_suffix: \
|
||||
switch (components) { \
|
||||
case 1: \
|
||||
return MTLVertexFormat##_type##_suffix; \
|
||||
case 2: \
|
||||
return MTLVertexFormat##_type##2##_suffix; \
|
||||
case 3: \
|
||||
return MTLVertexFormat##_type##3##_suffix; \
|
||||
case 4: \
|
||||
return MTLVertexFormat##_type##4##_suffix; \
|
||||
} \
|
||||
break;
|
||||
|
||||
switch (mtl_format) {
|
||||
RESIZE_TYPE(Char, )
|
||||
RESIZE_TYPE(Char, Normalized)
|
||||
RESIZE_TYPE(UChar, )
|
||||
RESIZE_TYPE(UChar, Normalized)
|
||||
RESIZE_TYPE(Short, )
|
||||
RESIZE_TYPE(Short, Normalized)
|
||||
RESIZE_TYPE(UShort, )
|
||||
RESIZE_TYPE(UShort, Normalized)
|
||||
RESIZE_TYPE(Int, )
|
||||
RESIZE_TYPE(UInt, )
|
||||
RESIZE_TYPE(Half, )
|
||||
RESIZE_TYPE(Float, )
|
||||
default:
|
||||
/* Can only call this function on format that can be resized. */
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
|
||||
#undef RESIZE_TYPE
|
||||
return MTLVertexFormatInvalid;
|
||||
}
|
||||
|
||||
inline MTLVertexFormat format_get_component_type(MTLVertexFormat mtl_format)
|
||||
{
|
||||
return format_resize_comp(mtl_format, 1);
|
||||
}
|
||||
|
||||
inline MTLVertexFormat to_mtl(GPUVertCompType component_type,
|
||||
GPUVertFetchMode fetch_mode,
|
||||
uint32_t component_len)
|
||||
{
|
||||
#define FORMAT_PER_COMP(_type, _suffix) \
|
||||
switch (component_len) { \
|
||||
case 1: \
|
||||
return MTLVertexFormat##_type##_suffix; \
|
||||
case 2: \
|
||||
return MTLVertexFormat##_type##2##_suffix; \
|
||||
case 3: \
|
||||
return MTLVertexFormat##_type##3##_suffix; \
|
||||
case 4: \
|
||||
return MTLVertexFormat##_type##4##_suffix; \
|
||||
default: \
|
||||
BLI_assert_msg(0, "Invalid attribute component count"); \
|
||||
break; \
|
||||
} \
|
||||
break;
|
||||
|
||||
#define FORMAT_PER_COMP_SMALL_INT(_type) \
|
||||
switch (fetch_mode) { \
|
||||
case GPU_FETCH_INT: \
|
||||
FORMAT_PER_COMP(_type, ) \
|
||||
case GPU_FETCH_INT_TO_FLOAT_UNIT: \
|
||||
FORMAT_PER_COMP(_type, Normalized) \
|
||||
case GPU_FETCH_FLOAT: \
|
||||
BLI_assert_msg(0, "Invalid fetch mode for integer attribute"); \
|
||||
break; \
|
||||
} \
|
||||
break;
|
||||
|
||||
#define FORMAT_PER_COMP_INT(_type) \
|
||||
switch (fetch_mode) { \
|
||||
case GPU_FETCH_INT: \
|
||||
FORMAT_PER_COMP(_type, ) \
|
||||
case GPU_FETCH_INT_TO_FLOAT_UNIT: \
|
||||
case GPU_FETCH_FLOAT: \
|
||||
BLI_assert_msg(0, "Invalid fetch mode for integer attribute"); \
|
||||
break; \
|
||||
} \
|
||||
break;
|
||||
|
||||
switch (component_type) {
|
||||
case GPU_COMP_I8:
|
||||
FORMAT_PER_COMP_SMALL_INT(Char)
|
||||
case GPU_COMP_U8:
|
||||
FORMAT_PER_COMP_SMALL_INT(UChar)
|
||||
case GPU_COMP_I16:
|
||||
FORMAT_PER_COMP_SMALL_INT(Short)
|
||||
case GPU_COMP_U16:
|
||||
FORMAT_PER_COMP_SMALL_INT(UShort)
|
||||
case GPU_COMP_I32:
|
||||
FORMAT_PER_COMP_INT(Int)
|
||||
case GPU_COMP_U32:
|
||||
FORMAT_PER_COMP_INT(UInt)
|
||||
case GPU_COMP_F32:
|
||||
switch (fetch_mode) {
|
||||
case GPU_FETCH_FLOAT:
|
||||
FORMAT_PER_COMP(Float, )
|
||||
break;
|
||||
case GPU_FETCH_INT:
|
||||
case GPU_FETCH_INT_TO_FLOAT_UNIT:
|
||||
BLI_assert_msg(0, "Invalid fetch mode for float attribute");
|
||||
break;
|
||||
}
|
||||
case GPU_COMP_I10:
|
||||
switch (fetch_mode) {
|
||||
case GPU_FETCH_INT_TO_FLOAT_UNIT:
|
||||
return MTLVertexFormatInt1010102Normalized;
|
||||
case GPU_FETCH_FLOAT:
|
||||
case GPU_FETCH_INT:
|
||||
BLI_assert_msg(0, "Invalid fetch mode for compressed attribute");
|
||||
break;
|
||||
}
|
||||
case GPU_COMP_MAX:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
#undef FORMAT_PER_COMP
|
||||
/* Loading mode not natively supported. */
|
||||
return MTLVertexFormatInvalid;
|
||||
}
|
||||
|
||||
inline int mtl_format_component_len(MTLVertexFormat format)
|
||||
{
|
||||
#define FORMAT_PER_TYPE(_comp, _value) \
|
||||
case MTLVertexFormatChar##_comp: \
|
||||
case MTLVertexFormatChar##_comp##Normalized: \
|
||||
case MTLVertexFormatUChar##_comp: \
|
||||
case MTLVertexFormatUChar##_comp##Normalized: \
|
||||
case MTLVertexFormatShort##_comp: \
|
||||
case MTLVertexFormatShort##_comp##Normalized: \
|
||||
case MTLVertexFormatUShort##_comp: \
|
||||
case MTLVertexFormatUShort##_comp##Normalized: \
|
||||
case MTLVertexFormatInt##_comp: \
|
||||
case MTLVertexFormatUInt##_comp: \
|
||||
case MTLVertexFormatHalf##_comp: \
|
||||
case MTLVertexFormatFloat##_comp: \
|
||||
return _value;
|
||||
|
||||
switch (format) {
|
||||
FORMAT_PER_TYPE(, 1)
|
||||
FORMAT_PER_TYPE(2, 2)
|
||||
FORMAT_PER_TYPE(3, 3)
|
||||
FORMAT_PER_TYPE(4, 4)
|
||||
case MTLVertexFormatUInt1010102Normalized:
|
||||
case MTLVertexFormatInt1010102Normalized:
|
||||
case MTLVertexFormatUChar4Normalized_BGRA:
|
||||
return 4;
|
||||
#if defined(MAC_OS_VERSION_14_0)
|
||||
case MTLVertexFormatFloatRG11B10:
|
||||
return 3;
|
||||
case MTLVertexFormatFloatRGB9E5:
|
||||
return 3;
|
||||
#endif
|
||||
case MTLVertexFormatInvalid:
|
||||
return -1;
|
||||
}
|
||||
|
||||
#undef FORMAT_PER_TYPE
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline bool mtl_format_is_normalized(MTLVertexFormat format)
|
||||
{
|
||||
#define FORMAT_PER_TYPE(_comp) \
|
||||
case MTLVertexFormatChar##_comp##Normalized: \
|
||||
case MTLVertexFormatUChar##_comp##Normalized: \
|
||||
case MTLVertexFormatShort##_comp##Normalized: \
|
||||
case MTLVertexFormatUShort##_comp##Normalized: \
|
||||
return true;
|
||||
|
||||
switch (format) {
|
||||
FORMAT_PER_TYPE()
|
||||
FORMAT_PER_TYPE(2)
|
||||
FORMAT_PER_TYPE(3)
|
||||
FORMAT_PER_TYPE(4)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
#undef FORMAT_PER_TYPE
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace blender::gpu
|
||||
1153
blender-5.2.0/source/blender/gpu/metal/mtl_shader.mm
Normal file
1153
blender-5.2.0/source/blender/gpu/metal/mtl_shader.mm
Normal file
File diff suppressed because it is too large
Load Diff
1547
blender-5.2.0/source/blender/gpu/metal/mtl_shader_generate.cc
Normal file
1547
blender-5.2.0/source/blender/gpu/metal/mtl_shader_generate.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,72 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gpu_shader_create_info.hh"
|
||||
#include "gpu_shader_private.hh"
|
||||
|
||||
#include "mtl_capabilities.hh"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
/**
|
||||
* In Metal UBO, SSBO and Push Constants all share the same binding space with a maximum of 31
|
||||
* bindings per stage. To avoid bind location clash, we associate different ranges to different
|
||||
* usage. Given that vertex and index buffers are not present in the shader code, we try to pack
|
||||
* them in the remaining unused slots. This is done inside the PSO description building (inside the
|
||||
* Batch API).
|
||||
*
|
||||
* +-----------------------------+--------+------------+
|
||||
* | Type | Count | Slot Range |
|
||||
* +-----------------------------+--------+------------+
|
||||
* | Vertex Buffers | 16 | 0..30 |
|
||||
* | Storage Buffers | 16 | 0..15 |
|
||||
* | Uniform Buffers | 13 | 16..28 |
|
||||
* | Push Constant Buffer | 1 | 29..29 |
|
||||
* | Sampler Argument Buffer | 1 | 30..30 |
|
||||
* +-----------------------------+--------+------------+
|
||||
*/
|
||||
#define MTL_MAX_SSBO 16
|
||||
#define MTL_MAX_UBO 13
|
||||
#define MTL_SSBO_SLOT_OFFSET 0
|
||||
#define MTL_UBO_SLOT_OFFSET MTL_MAX_SSBO
|
||||
#define MTL_PUSH_CONSTANT_BUFFER_SLOT (MTL_MAX_SSBO + MTL_MAX_UBO)
|
||||
#define MTL_SAMPLER_ARGUMENT_BUFFER_SLOT (MTL_PUSH_CONSTANT_BUFFER_SLOT + 1)
|
||||
|
||||
/**
|
||||
* Whether they are used for arbitrary load/store or sampling, all textures share a binding space
|
||||
* per stage (up to 128 slots on our target devices). However, we keep the same combined
|
||||
* texture+sampler semantic as GLSL. The sampler binding space is much more limited (16 on target
|
||||
* hardware) which limits the maximum texture we can bind for sampling. We lift this limit by using
|
||||
* an Argument Buffer to store the samplers. So we reserve the first 16 slots to images and the
|
||||
* remaining ones for sampler textures.
|
||||
*
|
||||
* +-----------------------------+--------+------------+
|
||||
* | Type | Count | Slot Range |
|
||||
* +-----------------------------+--------+------------+
|
||||
* | Image Textures | 8 | 0..7 |
|
||||
* | Sampler Textures | 64 | 8..71 |
|
||||
* +-----------------------------+--------+------------+
|
||||
*/
|
||||
#define MTL_IMAGE_SLOT_OFFSET 0
|
||||
#define MTL_SAMPLER_SLOT_OFFSET MTL_MAX_IMAGE_SLOTS
|
||||
|
||||
/* Other parts of the backend also use specialization constants. */
|
||||
#define MTL_SPECIALIZATION_CONSTANT_OFFSET 30
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
struct PatchedShaderCreateInfo;
|
||||
|
||||
uint32_t available_buffer_slots(const shader::ShaderCreateInfo &info);
|
||||
|
||||
std::pair<std::string, std::string> generate_entry_point(const shader::ShaderCreateInfo &info,
|
||||
const ShaderStage stage,
|
||||
const StringRefNull entry_point_name);
|
||||
|
||||
void patch_create_info_atomic_workaround(std::unique_ptr<PatchedShaderCreateInfo> &patched_info,
|
||||
const shader::ShaderCreateInfo &original_info);
|
||||
|
||||
} // namespace blender::gpu
|
||||
150
blender-5.2.0/source/blender/gpu/metal/mtl_shader_interface.hh
Normal file
150
blender-5.2.0/source/blender/gpu/metal/mtl_shader_interface.hh
Normal file
@@ -0,0 +1,150 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "gpu_shader_interface.hh"
|
||||
#include "mtl_capabilities.hh"
|
||||
#include "mtl_push_constant.hh"
|
||||
#include "mtl_shader_interface_type.hh"
|
||||
|
||||
#include "GPU_common.hh"
|
||||
#include "GPU_common_types.hh"
|
||||
#include "GPU_texture.hh"
|
||||
#include "gpu_texture_private.hh"
|
||||
#include <Metal/Metal.h>
|
||||
#include <functional>
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/* #MTLShaderInterface describes the layout and properties of a given shader,
|
||||
* including input and output bindings, and any special properties or modes
|
||||
* that the shader may require.
|
||||
*
|
||||
* -- Shader input/output bindings --
|
||||
*
|
||||
* We require custom data-structures for the binding information in Metal.
|
||||
* This is because certain bindings contain and require more information to
|
||||
* be stored than can be tracked solely within the `ShaderInput` struct.
|
||||
* e.g. data sizes and offsets.
|
||||
*
|
||||
* Upon interface completion, `prepare_common_shader_inputs` is used to
|
||||
* populate the global `ShaderInput*` array to enable correct functionality
|
||||
* of shader binding location lookups. These returned locations act as indices
|
||||
* into the arrays stored here in the #MTLShaderInterface, such that extraction
|
||||
* of required information can be performed within the back-end.
|
||||
*
|
||||
* e.g. `int loc = GPU_shader_get_uniform(...)`
|
||||
* `loc` will match the index into the `MTLShaderUniform uniforms_[]` array
|
||||
* to fetch the required Metal specific information.
|
||||
*
|
||||
*
|
||||
*
|
||||
* -- Argument Buffers and Argument Encoders --
|
||||
*
|
||||
* We can use #ArgumentBuffers (AB's) in Metal to extend the resource bind limitations
|
||||
* by providing bind-less support.
|
||||
*
|
||||
* Argument Buffers are used for sampler bindings when the builtin
|
||||
* sampler limit of 16 is exceeded, as in all cases for Blender,
|
||||
* each individual texture is associated with a given sampler, and this
|
||||
* lower limit would otherwise reduce the total availability of textures
|
||||
* used in shaders.
|
||||
*
|
||||
* In future, argument buffers may be extended to support other resource
|
||||
* types, if overall bind limits are ever increased within Blender.
|
||||
*
|
||||
* The #ArgumentEncoder cache used to store the generated #ArgumentEncoders for a given
|
||||
* shader permutation. The #ArgumentEncoder is the resource used to write resource binding
|
||||
* information to a specified buffer, and is unique to the shader's resource interface.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of Shader interface for Metal Back-end.
|
||||
*/
|
||||
class MTLShaderInterface : public ShaderInterface {
|
||||
private:
|
||||
/* Argument buffer encoder for sampler buffer. */
|
||||
id<MTLArgumentEncoder> arg_encoder_ = nil;
|
||||
|
||||
/* Bit Mask representing the free buffer slots from this interface.
|
||||
* Used to bind the vertex and index buffers.
|
||||
* This references the Metal Buffer bind space, not the GPU one. */
|
||||
uint32_t vertex_buffer_mask_ = ~(0xFFFFFFFFu << MTL_MAX_BUFFER_BINDINGS);
|
||||
|
||||
shader::BuiltinBits shader_builtins_ = shader::BuiltinBits::NONE;
|
||||
|
||||
/* Used for texture atomic workaround. */
|
||||
int image_names_offsets_[MTL_MAX_IMAGE_SLOTS];
|
||||
int sampler_names_offsets_[MTL_MAX_SAMPLER_SLOTS];
|
||||
|
||||
/* Debug. */
|
||||
char name[256];
|
||||
|
||||
public:
|
||||
MTLShaderInterface(const char *name,
|
||||
const shader::ShaderCreateInfo &info,
|
||||
MTLPushConstantBuf *push_constant_buf = nullptr);
|
||||
~MTLShaderInterface() override;
|
||||
|
||||
uint32_t vertex_buffer_mask() const
|
||||
{
|
||||
return vertex_buffer_mask_;
|
||||
}
|
||||
|
||||
bool use_layer() const
|
||||
{
|
||||
return bool(shader_builtins_ & shader::BuiltinBits::LAYER);
|
||||
}
|
||||
bool use_viewport_index() const
|
||||
{
|
||||
return bool(shader_builtins_ & shader::BuiltinBits::VIEWPORT_INDEX);
|
||||
}
|
||||
bool use_samplers_argument_buffer() const
|
||||
{
|
||||
return bool(shader_builtins_ & shader::BuiltinBits::USE_SAMPLER_ARG_BUFFER);
|
||||
}
|
||||
bool use_texture_atomic() const
|
||||
{
|
||||
return bool(shader_builtins_ & shader::BuiltinBits::TEXTURE_ATOMIC);
|
||||
}
|
||||
|
||||
bool is_point_shader() const
|
||||
{
|
||||
return (shader_builtins_ & shader::BuiltinBits::POINT_SIZE) == shader::BuiltinBits::POINT_SIZE;
|
||||
}
|
||||
|
||||
/* Name buffer fetching. */
|
||||
const char *name_at_offset(uint32_t offset) const;
|
||||
|
||||
/* Interface name. */
|
||||
const char *name_get() const
|
||||
{
|
||||
return this->name;
|
||||
}
|
||||
|
||||
const char *image_name_get(int slot) const
|
||||
{
|
||||
return name_at_offset(image_names_offsets_[slot]);
|
||||
}
|
||||
|
||||
const char *sampler_name_get(int slot) const
|
||||
{
|
||||
return name_at_offset(sampler_names_offsets_[slot]);
|
||||
}
|
||||
|
||||
id<MTLArgumentEncoder> ensure_argument_encoder(id<MTLFunction> mtl_function);
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLShaderInterface");
|
||||
};
|
||||
|
||||
} // namespace blender::gpu
|
||||
193
blender-5.2.0/source/blender/gpu/metal/mtl_shader_interface.mm
Normal file
193
blender-5.2.0/source/blender/gpu/metal/mtl_shader_interface.mm
Normal file
@@ -0,0 +1,193 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*
|
||||
* GPU shader interface (C --> GLSL)
|
||||
*/
|
||||
|
||||
#include "BLI_bitmap.h"
|
||||
|
||||
#include "GPU_capabilities.hh"
|
||||
|
||||
#include "mtl_common.hh"
|
||||
#include "mtl_debug.hh"
|
||||
#include "mtl_push_constant.hh"
|
||||
#include "mtl_shader_generate.hh"
|
||||
#include "mtl_shader_interface.hh"
|
||||
#include "mtl_shader_interface_type.hh"
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_utildefines.h"
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
MTLShaderInterface::MTLShaderInterface(const char *name,
|
||||
const shader::ShaderCreateInfo &info,
|
||||
MTLPushConstantBuf *push_constant_buf)
|
||||
{
|
||||
using namespace blender::gpu::shader;
|
||||
|
||||
if (name != nullptr) {
|
||||
STRNCPY(this->name, name);
|
||||
}
|
||||
|
||||
shader_builtins_ = info.builtins_;
|
||||
|
||||
attr_len_ = info.vertex_inputs_.size();
|
||||
uniform_len_ = info.push_constants_.size();
|
||||
constant_len_ = info.specialization_constants_.size();
|
||||
ssbo_len_ = 0;
|
||||
ubo_len_ = 0;
|
||||
Vector<ShaderCreateInfo::Resource> all_resources;
|
||||
all_resources.extend(info.pass_resources_);
|
||||
all_resources.extend(info.batch_resources_);
|
||||
all_resources.extend(info.geometry_resources_);
|
||||
|
||||
for (ShaderCreateInfo::Resource &res : all_resources) {
|
||||
switch (res.bind_type) {
|
||||
case ShaderCreateInfo::Resource::BindType::IMAGE:
|
||||
case ShaderCreateInfo::Resource::BindType::SAMPLER:
|
||||
uniform_len_++;
|
||||
break;
|
||||
case ShaderCreateInfo::Resource::BindType::UNIFORM_BUFFER:
|
||||
ubo_len_++;
|
||||
break;
|
||||
case ShaderCreateInfo::Resource::BindType::STORAGE_BUFFER:
|
||||
ssbo_len_++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t input_tot_len = attr_len_ + ubo_len_ + uniform_len_ + ssbo_len_ + constant_len_;
|
||||
inputs_ = MEM_new_array_zeroed<ShaderInput>(input_tot_len, __func__);
|
||||
ShaderInput *input = inputs_;
|
||||
|
||||
size_t names_size = info.interface_names_size_;
|
||||
name_buffer_ = MEM_new_array_uninitialized<char>(names_size, "name_buffer");
|
||||
uint32_t name_buffer_offset = 0;
|
||||
|
||||
/* Attributes */
|
||||
for (const ShaderCreateInfo::VertIn &attr : info.vertex_inputs_) {
|
||||
copy_input_name(input, attr.name, name_buffer_, name_buffer_offset);
|
||||
input->location = input->binding = attr.index;
|
||||
if (input->location != -1) {
|
||||
enabled_attr_mask_ |= (1 << input->location);
|
||||
/* Used in `GPU_shader_get_attribute_info`. */
|
||||
attr_types_[input->location] = uint8_t(attr.type);
|
||||
}
|
||||
input++;
|
||||
}
|
||||
|
||||
/* Uniform blocks */
|
||||
for (const ShaderCreateInfo::Resource &res : all_resources) {
|
||||
if (res.bind_type == ShaderCreateInfo::Resource::BindType::UNIFORM_BUFFER) {
|
||||
copy_input_name(input, res.uniformbuf.name, name_buffer_, name_buffer_offset);
|
||||
input->location = input->binding = res.slot;
|
||||
enabled_ubo_mask_ |= (1 << input->binding);
|
||||
vertex_buffer_mask_ &= ~(1 << (input->binding + MTL_UBO_SLOT_OFFSET));
|
||||
input++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Images, Samplers and buffers. */
|
||||
for (const ShaderCreateInfo::Resource &res : all_resources) {
|
||||
if (res.bind_type == ShaderCreateInfo::Resource::BindType::SAMPLER) {
|
||||
sampler_names_offsets_[res.slot] = name_buffer_offset;
|
||||
copy_input_name(input, res.sampler.name, name_buffer_, name_buffer_offset);
|
||||
input->location = -1; /* Setting location is not possible in MSL. */
|
||||
input->binding = res.slot;
|
||||
enabled_tex_mask_ |= (1ull << input->binding);
|
||||
input++;
|
||||
}
|
||||
else if (res.bind_type == ShaderCreateInfo::Resource::BindType::IMAGE) {
|
||||
image_names_offsets_[res.slot] = name_buffer_offset;
|
||||
copy_input_name(input, res.image.name, name_buffer_, name_buffer_offset);
|
||||
input->location = -1; /* Setting location is not possible in MSL. */
|
||||
input->binding = res.slot;
|
||||
enabled_ima_mask_ |= (1 << input->binding);
|
||||
input++;
|
||||
}
|
||||
}
|
||||
set_image_formats_from_info(info);
|
||||
|
||||
/* Push constants. */
|
||||
for (const ShaderCreateInfo::PushConst &push_constant : info.push_constants_) {
|
||||
copy_input_name(input, push_constant.name, name_buffer_, name_buffer_offset);
|
||||
input->location = push_constant_buf->append(push_constant);
|
||||
input->binding = -1;
|
||||
input++;
|
||||
}
|
||||
|
||||
/* Storage buffers */
|
||||
for (const ShaderCreateInfo::Resource &res : all_resources) {
|
||||
if (res.bind_type == ShaderCreateInfo::Resource::BindType::STORAGE_BUFFER) {
|
||||
copy_input_name(input, res.storagebuf.name, name_buffer_, name_buffer_offset);
|
||||
input->location = input->binding = res.slot;
|
||||
enabled_ssbo_mask_ |= (1 << input->binding);
|
||||
vertex_buffer_mask_ &= ~(1 << (input->binding + MTL_SSBO_SLOT_OFFSET));
|
||||
input++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const ShaderCreateInfo::Resource &res : info.geometry_resources_) {
|
||||
if (res.bind_type == ShaderCreateInfo::Resource::BindType::STORAGE_BUFFER) {
|
||||
ssbo_attr_mask_ |= (1 << res.slot);
|
||||
}
|
||||
else {
|
||||
BLI_assert_msg(0, "Resource type is not supported for Geometry frequency");
|
||||
}
|
||||
}
|
||||
|
||||
/* Constants */
|
||||
int constant_id = 0;
|
||||
for (const SpecializationConstant &constant : info.specialization_constants_) {
|
||||
copy_input_name(input, constant.name, name_buffer_, name_buffer_offset);
|
||||
input->location = constant_id++;
|
||||
input++;
|
||||
}
|
||||
|
||||
sort_inputs();
|
||||
|
||||
/* Builtin Uniforms */
|
||||
for (int32_t u_int = 0; u_int < GPU_NUM_UNIFORMS; u_int++) {
|
||||
GPUUniformBuiltin u = static_cast<GPUUniformBuiltin>(u_int);
|
||||
const ShaderInput *uni = this->uniform_get(builtin_uniform_name(u));
|
||||
builtins_[u] = (uni != nullptr) ? uni->location : -1;
|
||||
}
|
||||
|
||||
/* Builtin Uniforms Blocks */
|
||||
for (int32_t u_int = 0; u_int < GPU_NUM_UNIFORM_BLOCKS; u_int++) {
|
||||
GPUUniformBlockBuiltin u = static_cast<GPUUniformBlockBuiltin>(u_int);
|
||||
const ShaderInput *block = this->ubo_get(builtin_uniform_block_name(u));
|
||||
builtin_blocks_[u] = (block != nullptr) ? block->binding : -1;
|
||||
}
|
||||
}
|
||||
|
||||
MTLShaderInterface::~MTLShaderInterface()
|
||||
{
|
||||
if (arg_encoder_ != nil) {
|
||||
[arg_encoder_ release];
|
||||
arg_encoder_ = nil;
|
||||
}
|
||||
}
|
||||
|
||||
const char *MTLShaderInterface::name_at_offset(uint32_t offset) const
|
||||
{
|
||||
return name_buffer_ + offset;
|
||||
}
|
||||
|
||||
id<MTLArgumentEncoder> MTLShaderInterface::ensure_argument_encoder(id<MTLFunction> mtl_function)
|
||||
{
|
||||
if (arg_encoder_ == nil) {
|
||||
arg_encoder_ = [mtl_function
|
||||
newArgumentEncoderWithBufferIndex:MTL_SAMPLER_ARGUMENT_BUFFER_SLOT];
|
||||
}
|
||||
return arg_encoder_;
|
||||
}
|
||||
|
||||
} // namespace blender::gpu
|
||||
@@ -0,0 +1,358 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "GPU_material.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
enum MTLInterfaceDataType {
|
||||
MTL_DATATYPE_CHAR,
|
||||
MTL_DATATYPE_CHAR2,
|
||||
MTL_DATATYPE_CHAR3,
|
||||
MTL_DATATYPE_CHAR4,
|
||||
|
||||
MTL_DATATYPE_UCHAR,
|
||||
MTL_DATATYPE_UCHAR2,
|
||||
MTL_DATATYPE_UCHAR3,
|
||||
MTL_DATATYPE_UCHAR4,
|
||||
|
||||
MTL_DATATYPE_BOOL,
|
||||
MTL_DATATYPE_BOOL2,
|
||||
MTL_DATATYPE_BOOL3,
|
||||
MTL_DATATYPE_BOOL4,
|
||||
|
||||
MTL_DATATYPE_SHORT,
|
||||
MTL_DATATYPE_SHORT2,
|
||||
MTL_DATATYPE_SHORT3,
|
||||
MTL_DATATYPE_SHORT4,
|
||||
|
||||
MTL_DATATYPE_USHORT,
|
||||
MTL_DATATYPE_USHORT2,
|
||||
MTL_DATATYPE_USHORT3,
|
||||
MTL_DATATYPE_USHORT4,
|
||||
|
||||
MTL_DATATYPE_INT,
|
||||
MTL_DATATYPE_INT2,
|
||||
MTL_DATATYPE_INT3,
|
||||
MTL_DATATYPE_INT4,
|
||||
|
||||
MTL_DATATYPE_UINT,
|
||||
MTL_DATATYPE_UINT2,
|
||||
MTL_DATATYPE_UINT3,
|
||||
MTL_DATATYPE_UINT4,
|
||||
|
||||
MTL_DATATYPE_FLOAT,
|
||||
MTL_DATATYPE_FLOAT2,
|
||||
MTL_DATATYPE_FLOAT3,
|
||||
MTL_DATATYPE_FLOAT4,
|
||||
MTL_DATATYPE_PACKED_FLOAT2,
|
||||
MTL_DATATYPE_PACKED_FLOAT3,
|
||||
|
||||
MTL_DATATYPE_LONG,
|
||||
MTL_DATATYPE_LONG2,
|
||||
MTL_DATATYPE_LONG3,
|
||||
MTL_DATATYPE_LONG4,
|
||||
|
||||
MTL_DATATYPE_ULONG,
|
||||
MTL_DATATYPE_ULONG2,
|
||||
MTL_DATATYPE_ULONG3,
|
||||
MTL_DATATYPE_ULONG4,
|
||||
|
||||
MTL_DATATYPE_HALF2x2,
|
||||
MTL_DATATYPE_HALF2x3,
|
||||
MTL_DATATYPE_HALF2x4,
|
||||
MTL_DATATYPE_HALF3x2,
|
||||
MTL_DATATYPE_HALF3x3,
|
||||
MTL_DATATYPE_HALF3x4,
|
||||
MTL_DATATYPE_HALF4x2,
|
||||
MTL_DATATYPE_HALF4x3,
|
||||
MTL_DATATYPE_HALF4x4,
|
||||
|
||||
MTL_DATATYPE_FLOAT2x2,
|
||||
MTL_DATATYPE_FLOAT2x3,
|
||||
MTL_DATATYPE_FLOAT2x4,
|
||||
MTL_DATATYPE_FLOAT3x2,
|
||||
MTL_DATATYPE_FLOAT3x3,
|
||||
MTL_DATATYPE_FLOAT3x4,
|
||||
MTL_DATATYPE_FLOAT4x2,
|
||||
MTL_DATATYPE_FLOAT4x3,
|
||||
MTL_DATATYPE_FLOAT4x4,
|
||||
|
||||
MTL_DATATYPE_UINT1010102_NORM,
|
||||
MTL_DATATYPE_INT1010102_NORM
|
||||
};
|
||||
|
||||
inline uint mtl_get_data_type_size(MTLInterfaceDataType type)
|
||||
{
|
||||
switch (type) {
|
||||
case MTL_DATATYPE_CHAR:
|
||||
case MTL_DATATYPE_UCHAR:
|
||||
case MTL_DATATYPE_BOOL:
|
||||
return 1;
|
||||
case MTL_DATATYPE_CHAR2:
|
||||
case MTL_DATATYPE_UCHAR2:
|
||||
case MTL_DATATYPE_BOOL2:
|
||||
case MTL_DATATYPE_SHORT:
|
||||
case MTL_DATATYPE_USHORT:
|
||||
return 2;
|
||||
|
||||
case MTL_DATATYPE_CHAR3:
|
||||
case MTL_DATATYPE_UCHAR3:
|
||||
case MTL_DATATYPE_BOOL3:
|
||||
return 3;
|
||||
case MTL_DATATYPE_CHAR4:
|
||||
case MTL_DATATYPE_UCHAR4:
|
||||
case MTL_DATATYPE_INT:
|
||||
case MTL_DATATYPE_UINT:
|
||||
case MTL_DATATYPE_BOOL4:
|
||||
case MTL_DATATYPE_SHORT2:
|
||||
case MTL_DATATYPE_USHORT2:
|
||||
case MTL_DATATYPE_FLOAT:
|
||||
case MTL_DATATYPE_UINT1010102_NORM:
|
||||
case MTL_DATATYPE_INT1010102_NORM:
|
||||
return 4;
|
||||
|
||||
case MTL_DATATYPE_SHORT3:
|
||||
case MTL_DATATYPE_USHORT3:
|
||||
case MTL_DATATYPE_SHORT4:
|
||||
case MTL_DATATYPE_USHORT4:
|
||||
case MTL_DATATYPE_INT2:
|
||||
case MTL_DATATYPE_UINT2:
|
||||
case MTL_DATATYPE_FLOAT2:
|
||||
case MTL_DATATYPE_LONG:
|
||||
case MTL_DATATYPE_ULONG:
|
||||
case MTL_DATATYPE_HALF2x2:
|
||||
case MTL_DATATYPE_PACKED_FLOAT2:
|
||||
return 8;
|
||||
|
||||
case MTL_DATATYPE_HALF3x2:
|
||||
case MTL_DATATYPE_PACKED_FLOAT3:
|
||||
return 12;
|
||||
|
||||
case MTL_DATATYPE_INT3:
|
||||
case MTL_DATATYPE_INT4:
|
||||
case MTL_DATATYPE_UINT3:
|
||||
case MTL_DATATYPE_UINT4:
|
||||
case MTL_DATATYPE_FLOAT3:
|
||||
case MTL_DATATYPE_FLOAT4:
|
||||
case MTL_DATATYPE_LONG2:
|
||||
case MTL_DATATYPE_ULONG2:
|
||||
case MTL_DATATYPE_HALF2x3:
|
||||
case MTL_DATATYPE_HALF2x4:
|
||||
case MTL_DATATYPE_HALF4x2:
|
||||
return 16;
|
||||
|
||||
case MTL_DATATYPE_HALF3x3:
|
||||
case MTL_DATATYPE_HALF3x4:
|
||||
case MTL_DATATYPE_FLOAT3x2:
|
||||
return 24;
|
||||
|
||||
case MTL_DATATYPE_LONG3:
|
||||
case MTL_DATATYPE_LONG4:
|
||||
case MTL_DATATYPE_ULONG3:
|
||||
case MTL_DATATYPE_ULONG4:
|
||||
case MTL_DATATYPE_HALF4x3:
|
||||
case MTL_DATATYPE_HALF4x4:
|
||||
case MTL_DATATYPE_FLOAT2x3:
|
||||
case MTL_DATATYPE_FLOAT2x4:
|
||||
case MTL_DATATYPE_FLOAT4x2:
|
||||
return 32;
|
||||
|
||||
case MTL_DATATYPE_FLOAT3x3:
|
||||
case MTL_DATATYPE_FLOAT3x4:
|
||||
return 48;
|
||||
|
||||
case MTL_DATATYPE_FLOAT4x3:
|
||||
case MTL_DATATYPE_FLOAT4x4:
|
||||
return 64;
|
||||
default:
|
||||
BLI_assert(false);
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
inline MTLInterfaceDataType to_mtl_type(shader::Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case shader::Type::float_t:
|
||||
return MTL_DATATYPE_FLOAT;
|
||||
case shader::Type::float2_t:
|
||||
return MTL_DATATYPE_FLOAT2;
|
||||
case shader::Type::float3_t:
|
||||
return MTL_DATATYPE_FLOAT3;
|
||||
case shader::Type::float4_t:
|
||||
return MTL_DATATYPE_FLOAT4;
|
||||
case shader::Type::float3x3_t:
|
||||
return MTL_DATATYPE_FLOAT3x3;
|
||||
case shader::Type::float4x4_t:
|
||||
return MTL_DATATYPE_FLOAT4x4;
|
||||
case shader::Type::uint_t:
|
||||
return MTL_DATATYPE_UINT;
|
||||
case shader::Type::uint2_t:
|
||||
return MTL_DATATYPE_UINT2;
|
||||
case shader::Type::uint3_t:
|
||||
return MTL_DATATYPE_UINT3;
|
||||
case shader::Type::uint4_t:
|
||||
return MTL_DATATYPE_UINT4;
|
||||
case shader::Type::int_t:
|
||||
return MTL_DATATYPE_INT;
|
||||
case shader::Type::int2_t:
|
||||
return MTL_DATATYPE_INT2;
|
||||
case shader::Type::int3_t:
|
||||
return MTL_DATATYPE_INT3;
|
||||
case shader::Type::int4_t:
|
||||
return MTL_DATATYPE_INT4;
|
||||
case shader::Type::bool_t:
|
||||
return MTL_DATATYPE_BOOL;
|
||||
case shader::Type::float3_10_10_10_2_t:
|
||||
return MTL_DATATYPE_UINT1010102_NORM;
|
||||
case shader::Type::uchar_t:
|
||||
return MTL_DATATYPE_UCHAR;
|
||||
case shader::Type::uchar2_t:
|
||||
return MTL_DATATYPE_UCHAR2;
|
||||
case shader::Type::uchar3_t:
|
||||
return MTL_DATATYPE_UCHAR3;
|
||||
case shader::Type::uchar4_t:
|
||||
return MTL_DATATYPE_UCHAR4;
|
||||
case shader::Type::char_t:
|
||||
return MTL_DATATYPE_CHAR;
|
||||
case shader::Type::char2_t:
|
||||
return MTL_DATATYPE_CHAR2;
|
||||
case shader::Type::char3_t:
|
||||
return MTL_DATATYPE_CHAR3;
|
||||
case shader::Type::char4_t:
|
||||
return MTL_DATATYPE_CHAR4;
|
||||
case shader::Type::ushort_t:
|
||||
return MTL_DATATYPE_USHORT;
|
||||
case shader::Type::ushort2_t:
|
||||
return MTL_DATATYPE_USHORT2;
|
||||
case shader::Type::ushort3_t:
|
||||
return MTL_DATATYPE_USHORT3;
|
||||
case shader::Type::ushort4_t:
|
||||
return MTL_DATATYPE_USHORT4;
|
||||
case shader::Type::short_t:
|
||||
return MTL_DATATYPE_SHORT;
|
||||
case shader::Type::short2_t:
|
||||
return MTL_DATATYPE_SHORT2;
|
||||
case shader::Type::short3_t:
|
||||
return MTL_DATATYPE_SHORT3;
|
||||
case shader::Type::short4_t:
|
||||
return MTL_DATATYPE_SHORT4;
|
||||
default:
|
||||
BLI_assert(false);
|
||||
return MTL_DATATYPE_FLOAT;
|
||||
};
|
||||
}
|
||||
|
||||
inline uint mtl_get_data_type_alignment(MTLInterfaceDataType type)
|
||||
{
|
||||
switch (type) {
|
||||
case MTL_DATATYPE_CHAR:
|
||||
case MTL_DATATYPE_UCHAR:
|
||||
case MTL_DATATYPE_BOOL:
|
||||
return 1;
|
||||
case MTL_DATATYPE_CHAR2:
|
||||
case MTL_DATATYPE_UCHAR2:
|
||||
case MTL_DATATYPE_BOOL2:
|
||||
case MTL_DATATYPE_SHORT:
|
||||
case MTL_DATATYPE_USHORT:
|
||||
return 2;
|
||||
|
||||
case MTL_DATATYPE_CHAR3:
|
||||
case MTL_DATATYPE_UCHAR3:
|
||||
case MTL_DATATYPE_BOOL3:
|
||||
case MTL_DATATYPE_CHAR4:
|
||||
case MTL_DATATYPE_UCHAR4:
|
||||
case MTL_DATATYPE_INT:
|
||||
case MTL_DATATYPE_UINT:
|
||||
case MTL_DATATYPE_BOOL4:
|
||||
case MTL_DATATYPE_SHORT2:
|
||||
case MTL_DATATYPE_USHORT2:
|
||||
case MTL_DATATYPE_FLOAT:
|
||||
case MTL_DATATYPE_HALF2x2:
|
||||
case MTL_DATATYPE_HALF3x2:
|
||||
case MTL_DATATYPE_HALF4x2:
|
||||
case MTL_DATATYPE_UINT1010102_NORM:
|
||||
case MTL_DATATYPE_INT1010102_NORM:
|
||||
return 4;
|
||||
|
||||
case MTL_DATATYPE_SHORT3:
|
||||
case MTL_DATATYPE_USHORT3:
|
||||
case MTL_DATATYPE_SHORT4:
|
||||
case MTL_DATATYPE_USHORT4:
|
||||
case MTL_DATATYPE_INT2:
|
||||
case MTL_DATATYPE_UINT2:
|
||||
case MTL_DATATYPE_FLOAT2:
|
||||
case MTL_DATATYPE_PACKED_FLOAT2:
|
||||
case MTL_DATATYPE_LONG:
|
||||
case MTL_DATATYPE_ULONG:
|
||||
case MTL_DATATYPE_HALF2x3:
|
||||
case MTL_DATATYPE_HALF2x4:
|
||||
case MTL_DATATYPE_HALF3x3:
|
||||
case MTL_DATATYPE_HALF3x4:
|
||||
case MTL_DATATYPE_HALF4x3:
|
||||
case MTL_DATATYPE_HALF4x4:
|
||||
case MTL_DATATYPE_FLOAT2x2:
|
||||
case MTL_DATATYPE_FLOAT3x2:
|
||||
case MTL_DATATYPE_FLOAT4x2:
|
||||
return 8;
|
||||
|
||||
case MTL_DATATYPE_INT3:
|
||||
case MTL_DATATYPE_INT4:
|
||||
case MTL_DATATYPE_UINT3:
|
||||
case MTL_DATATYPE_UINT4:
|
||||
case MTL_DATATYPE_FLOAT3:
|
||||
case MTL_DATATYPE_PACKED_FLOAT3:
|
||||
case MTL_DATATYPE_FLOAT4:
|
||||
case MTL_DATATYPE_LONG2:
|
||||
case MTL_DATATYPE_ULONG2:
|
||||
case MTL_DATATYPE_FLOAT2x3:
|
||||
case MTL_DATATYPE_FLOAT2x4:
|
||||
case MTL_DATATYPE_FLOAT3x3:
|
||||
case MTL_DATATYPE_FLOAT3x4:
|
||||
case MTL_DATATYPE_FLOAT4x3:
|
||||
case MTL_DATATYPE_FLOAT4x4:
|
||||
return 16;
|
||||
|
||||
case MTL_DATATYPE_LONG3:
|
||||
case MTL_DATATYPE_LONG4:
|
||||
case MTL_DATATYPE_ULONG3:
|
||||
case MTL_DATATYPE_ULONG4:
|
||||
return 32;
|
||||
|
||||
default:
|
||||
BLI_assert_msg(false, "Unrecognized MTL datatype.");
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
inline MTLInterfaceDataType gpu_type_to_mtl_type(GPUType type)
|
||||
{
|
||||
switch (type) {
|
||||
case GPU_FLOAT:
|
||||
return MTL_DATATYPE_FLOAT;
|
||||
case GPU_VEC2:
|
||||
return MTL_DATATYPE_FLOAT2;
|
||||
case GPU_VEC3:
|
||||
return MTL_DATATYPE_FLOAT3;
|
||||
case GPU_VEC4:
|
||||
return MTL_DATATYPE_FLOAT4;
|
||||
case GPU_MAT3:
|
||||
return MTL_DATATYPE_FLOAT3x3;
|
||||
case GPU_MAT4:
|
||||
return MTL_DATATYPE_FLOAT4x4;
|
||||
default:
|
||||
BLI_assert(false && "Other types unsupported");
|
||||
return MTL_DATATYPE_FLOAT;
|
||||
}
|
||||
return MTL_DATATYPE_FLOAT;
|
||||
}
|
||||
|
||||
} // namespace blender::gpu
|
||||
24
blender-5.2.0/source/blender/gpu/metal/mtl_shader_log.hh
Normal file
24
blender-5.2.0/source/blender/gpu/metal/mtl_shader_log.hh
Normal file
@@ -0,0 +1,24 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "gpu_shader_private.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
class MTLLogParser : public GPULogParser {
|
||||
public:
|
||||
const char *parse_line(const char *source_combined,
|
||||
const char *log_line,
|
||||
GPULogItem &log_item) override;
|
||||
|
||||
protected:
|
||||
bool wrapper_error_ = false;
|
||||
bool parsed_error_ = false;
|
||||
|
||||
const char *skip_name(const char *log_line);
|
||||
const char *skip_severity_keyword(const char *log_line, GPULogItem &log_item);
|
||||
const char *skip_line(const char *cursor) const;
|
||||
};
|
||||
|
||||
} // namespace blender::gpu
|
||||
96
blender-5.2.0/source/blender/gpu/metal/mtl_shader_log.mm
Normal file
96
blender-5.2.0/source/blender/gpu/metal/mtl_shader_log.mm
Normal file
@@ -0,0 +1,96 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include "GPU_platform.hh"
|
||||
|
||||
#include "mtl_shader_log.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
const char *MTLLogParser::parse_line(const char *source_combined,
|
||||
const char *log_line,
|
||||
GPULogItem &log_item)
|
||||
{
|
||||
const char *name_start = log_line;
|
||||
log_line = skip_name(log_line);
|
||||
const char *name_end = log_line;
|
||||
log_line = skip_separators(log_line, ":");
|
||||
|
||||
/* Parse error line & char numbers. */
|
||||
if (at_number(log_line)) {
|
||||
/* Reset skip line if two errors follow. */
|
||||
parsed_error_ = false;
|
||||
wrapper_error_ = false;
|
||||
|
||||
const char *error_line_number_end;
|
||||
|
||||
log_item.cursor.row = parse_number(log_line, &error_line_number_end);
|
||||
log_line = error_line_number_end;
|
||||
log_line = skip_separators(log_line, ": ");
|
||||
log_item.cursor.column = parse_number(log_line, &error_line_number_end);
|
||||
log_line = error_line_number_end;
|
||||
/* Simply copy the start of the error line since it is already in the format we want. */
|
||||
log_item.cursor.file_name_and_error_line = StringRef(name_start, error_line_number_end);
|
||||
|
||||
StringRef source_name(name_start, name_end);
|
||||
if (log_item.cursor.row != -1) {
|
||||
/* Get to the wanted line. */
|
||||
size_t line_start_character = line_start_get(source_combined, log_item.cursor.row);
|
||||
StringRef filename = filename_get(source_combined, line_start_character);
|
||||
size_t line_number = source_line_get(source_combined, line_start_character);
|
||||
log_item.cursor.file_name_and_error_line = std::string(filename) + ':' +
|
||||
std::to_string(line_number);
|
||||
if (log_item.cursor.column != -1) {
|
||||
log_item.cursor.file_name_and_error_line += ':' + std::to_string(log_item.cursor.column);
|
||||
log_item.cursor.column -= 1; /* Caret printing expect 0 based index. */
|
||||
}
|
||||
parsed_error_ = true;
|
||||
}
|
||||
return log_line;
|
||||
}
|
||||
|
||||
if (parsed_error_) {
|
||||
/* Skip the redundant lines that we be outputted above the error. */
|
||||
return skip_line(log_line);
|
||||
}
|
||||
|
||||
if (wrapper_error_) {
|
||||
/* Display full lines of error in case of wrapper (non parsed) errors.
|
||||
* Avoids weirdly aligned '^' and underlined suggestions. */
|
||||
return name_start;
|
||||
}
|
||||
log_line = skip_separators(log_line, ": ");
|
||||
|
||||
/* Skip to message. Avoid redundant info. */
|
||||
log_line = skip_severity_keyword(log_line, log_item);
|
||||
log_line = skip_separators(log_line, ": ");
|
||||
|
||||
return log_line;
|
||||
}
|
||||
|
||||
const char *MTLLogParser::skip_name(const char *log_line)
|
||||
{
|
||||
return skip_until(log_line, ':');
|
||||
}
|
||||
|
||||
const char *MTLLogParser::skip_severity_keyword(const char *log_line, GPULogItem &log_item)
|
||||
{
|
||||
return skip_severity(log_line, log_item, "error", "warning", "note");
|
||||
}
|
||||
|
||||
const char *MTLLogParser::skip_line(const char *cursor) const
|
||||
{
|
||||
while (!ELEM(cursor[0], '\n', '\0')) {
|
||||
cursor++;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
} // namespace blender::gpu
|
||||
30
blender-5.2.0/source/blender/gpu/metal/mtl_shader_shared.hh
Normal file
30
blender-5.2.0/source/blender/gpu/metal/mtl_shader_shared.hh
Normal file
@@ -0,0 +1,30 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* Add Types as needed (Also need to be added to mtl_shader.h). */
|
||||
#define GPU_SHADER_ATTR_TYPE_FLOAT 0
|
||||
#define GPU_SHADER_ATTR_TYPE_INT 1
|
||||
#define GPU_SHADER_ATTR_TYPE_SHORT 2
|
||||
#define GPU_SHADER_ATTR_TYPE_CHAR 3
|
||||
#define GPU_SHADER_ATTR_TYPE_VEC2 4
|
||||
#define GPU_SHADER_ATTR_TYPE_VEC3 5
|
||||
#define GPU_SHADER_ATTR_TYPE_VEC4 6
|
||||
#define GPU_SHADER_ATTR_TYPE_UVEC2 7
|
||||
#define GPU_SHADER_ATTR_TYPE_UVEC3 8
|
||||
#define GPU_SHADER_ATTR_TYPE_UVEC4 9
|
||||
#define GPU_SHADER_ATTR_TYPE_IVEC2 10
|
||||
#define GPU_SHADER_ATTR_TYPE_IVEC3 11
|
||||
#define GPU_SHADER_ATTR_TYPE_IVEC4 12
|
||||
#define GPU_SHADER_ATTR_TYPE_MAT3 13
|
||||
#define GPU_SHADER_ATTR_TYPE_MAT4 14
|
||||
#define GPU_SHADER_ATTR_TYPE_UCHAR_NORM 15
|
||||
#define GPU_SHADER_ATTR_TYPE_UCHAR2_NORM 16
|
||||
#define GPU_SHADER_ATTR_TYPE_UCHAR3_NORM 17
|
||||
#define GPU_SHADER_ATTR_TYPE_UCHAR4_NORM 18
|
||||
#define GPU_SHADER_ATTR_TYPE_INT1010102_NORM 19
|
||||
#define GPU_SHADER_ATTR_TYPE_SHORT3_NORM 20
|
||||
#define GPU_SHADER_ATTR_TYPE_CHAR2 21
|
||||
#define GPU_SHADER_ATTR_TYPE_CHAR3 22
|
||||
#define GPU_SHADER_ATTR_TYPE_CHAR4 23
|
||||
#define GPU_SHADER_ATTR_TYPE_UINT 24
|
||||
107
blender-5.2.0/source/blender/gpu/metal/mtl_state.hh
Normal file
107
blender-5.2.0/source/blender/gpu/metal/mtl_state.hh
Normal file
@@ -0,0 +1,107 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "GPU_state.hh"
|
||||
#include "gpu_state_private.hh"
|
||||
|
||||
#include "mtl_pso_descriptor_state.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/* Forward Declarations. */
|
||||
class MTLContext;
|
||||
|
||||
/**
|
||||
* State manager keeping track of the draw state and applying it before drawing.
|
||||
* Metal Implementation.
|
||||
*/
|
||||
class MTLStateManager : public StateManager {
|
||||
|
||||
private:
|
||||
/* Current state of the associated MTLContext.
|
||||
* Avoids resetting the whole state for every change. */
|
||||
GPUState current_;
|
||||
GPUStateMutable current_mutable_;
|
||||
MTLContext *context_;
|
||||
|
||||
/* Global pipeline descriptors. */
|
||||
MTLRenderPipelineStateDescriptor pipeline_descriptor_;
|
||||
|
||||
public:
|
||||
MTLStateManager(MTLContext *ctx);
|
||||
|
||||
void apply_state() override;
|
||||
void force_state() override;
|
||||
|
||||
void issue_barrier(GPUBarrier barrier_bits) override;
|
||||
|
||||
void texture_bind(Texture *tex, GPUSamplerState sampler, int unit) override;
|
||||
void texture_unbind(Texture *tex) override;
|
||||
void texture_unbind_all() override;
|
||||
|
||||
void image_bind(Texture *tex, int unit) override;
|
||||
void image_unbind(Texture *tex) override;
|
||||
void image_unbind_all() override;
|
||||
|
||||
/* Global pipeline descriptors. */
|
||||
MTLRenderPipelineStateDescriptor &get_pipeline_descriptor()
|
||||
{
|
||||
return pipeline_descriptor_;
|
||||
}
|
||||
|
||||
private:
|
||||
void set_write_mask(const GPUWriteMask value);
|
||||
void set_depth_test(const GPUDepthTest value);
|
||||
void set_stencil_test(const GPUStencilTest test, const GPUStencilOp operation);
|
||||
void set_stencil_mask(const GPUStencilTest test, const GPUStateMutable &state);
|
||||
void set_clip_distances(const int new_dist_len, const int old_dist_len);
|
||||
void set_logic_op(const bool enable);
|
||||
void set_facing(const bool invert);
|
||||
void set_backface_culling(const GPUFaceCullTest test);
|
||||
void set_provoking_vert(const GPUProvokingVertex vert);
|
||||
void set_blend(const GPUBlend value);
|
||||
|
||||
void set_state(const GPUState &state);
|
||||
void set_mutable_state(const GPUStateMutable &state);
|
||||
|
||||
/* METAL State utility functions. */
|
||||
void mtl_state_init();
|
||||
void mtl_stencil_mask(uint mask);
|
||||
void mtl_stencil_set_func(GPUStencilTest stencil_func, int ref, uint mask);
|
||||
void mtl_clip_plane_enable(uint i);
|
||||
void mtl_clip_plane_disable(uint i);
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLStateManager")
|
||||
};
|
||||
|
||||
/* Fence synchronization primitive. */
|
||||
class MTLFence : public Fence {
|
||||
private:
|
||||
/* Using an event in this instance, as this is global for the command stream, rather than being
|
||||
* inserted at the encoder level. This has the behavior to match the GL functionality. */
|
||||
id<MTLEvent> mtl_event_ = nil;
|
||||
/* Events can be re-used multiple times. We can track a counter flagging the latest value
|
||||
* signalled. */
|
||||
uint64_t last_signalled_value_ = 0;
|
||||
|
||||
public:
|
||||
MTLFence() : Fence() {};
|
||||
~MTLFence() override;
|
||||
|
||||
void signal() override;
|
||||
void wait() override;
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLFence")
|
||||
};
|
||||
|
||||
} // namespace blender::gpu
|
||||
681
blender-5.2.0/source/blender/gpu/metal/mtl_state.mm
Normal file
681
blender-5.2.0/source/blender/gpu/metal/mtl_state.mm
Normal file
@@ -0,0 +1,681 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_math_bits.h"
|
||||
|
||||
#include "GPU_framebuffer.hh"
|
||||
|
||||
#include "mtl_context.hh"
|
||||
#include "mtl_framebuffer.hh"
|
||||
#include "mtl_shader_interface_type.hh"
|
||||
#include "mtl_state.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name MTLStateManager
|
||||
* \{ */
|
||||
|
||||
void MTLStateManager::mtl_state_init()
|
||||
{
|
||||
BLI_assert(context_);
|
||||
context_->pipeline_state_init();
|
||||
}
|
||||
|
||||
MTLStateManager::MTLStateManager(MTLContext *ctx) : StateManager()
|
||||
{
|
||||
/* Initialize State. */
|
||||
context_ = ctx;
|
||||
mtl_state_init();
|
||||
|
||||
/* Force update using default state. */
|
||||
current_ = ~state;
|
||||
current_mutable_ = ~mutable_state;
|
||||
|
||||
/* Clip distances initial mask forces to 0x111, which exceeds
|
||||
* max clip plane count of 6, so limit to ensure all clipping
|
||||
* planes get disabled. */
|
||||
current_.clip_distances = 6;
|
||||
|
||||
set_state(state);
|
||||
set_mutable_state(mutable_state);
|
||||
}
|
||||
|
||||
void MTLStateManager::apply_state()
|
||||
{
|
||||
this->set_state(this->state);
|
||||
this->set_mutable_state(this->mutable_state);
|
||||
|
||||
/* Apply active FrameBuffer state. */
|
||||
static_cast<MTLFrameBuffer *>(context_->active_fb)->apply_state();
|
||||
};
|
||||
|
||||
void MTLStateManager::force_state()
|
||||
{
|
||||
/* Little exception for clip distances since they need to keep the old count correct. */
|
||||
uint32_t clip_distances = current_.clip_distances;
|
||||
BLI_assert(clip_distances <= 6);
|
||||
current_ = ~this->state;
|
||||
current_.clip_distances = clip_distances;
|
||||
current_mutable_ = ~this->mutable_state;
|
||||
this->set_state(this->state);
|
||||
this->set_mutable_state(this->mutable_state);
|
||||
};
|
||||
|
||||
void MTLStateManager::set_state(const GPUState &state)
|
||||
{
|
||||
GPUState changed = state ^ current_;
|
||||
|
||||
if (changed.blend != 0) {
|
||||
set_blend((GPUBlend)state.blend);
|
||||
}
|
||||
if (changed.write_mask != 0) {
|
||||
set_write_mask((GPUWriteMask)state.write_mask);
|
||||
}
|
||||
if (changed.depth_test != 0) {
|
||||
set_depth_test((GPUDepthTest)state.depth_test);
|
||||
}
|
||||
if (changed.stencil_test != 0 || changed.stencil_op != 0) {
|
||||
set_stencil_test((GPUStencilTest)state.stencil_test, (GPUStencilOp)state.stencil_op);
|
||||
set_stencil_mask((GPUStencilTest)state.stencil_test, mutable_state);
|
||||
}
|
||||
if (changed.clip_distances != 0) {
|
||||
set_clip_distances(state.clip_distances, current_.clip_distances);
|
||||
}
|
||||
if (changed.culling_test != 0) {
|
||||
set_backface_culling((GPUFaceCullTest)state.culling_test);
|
||||
}
|
||||
if (changed.logic_op_xor != 0) {
|
||||
set_logic_op(state.logic_op_xor);
|
||||
}
|
||||
if (changed.invert_facing != 0) {
|
||||
set_facing(state.invert_facing);
|
||||
}
|
||||
if (changed.provoking_vert != 0) {
|
||||
set_provoking_vert((GPUProvokingVertex)state.provoking_vert);
|
||||
}
|
||||
|
||||
/* TODO remove (Following GLState). */
|
||||
if (changed.polygon_smooth) {
|
||||
/* NOTE: Unsupported in Metal. */
|
||||
}
|
||||
if (changed.line_smooth) {
|
||||
/* NOTE: Unsupported in Metal. */
|
||||
}
|
||||
|
||||
current_ = state;
|
||||
}
|
||||
|
||||
void MTLStateManager::set_mutable_state(const GPUStateMutable &state)
|
||||
{
|
||||
GPUStateMutable changed = state ^ current_mutable_;
|
||||
MTLContextGlobalShaderPipelineState &pipeline_state = context_->pipeline_state;
|
||||
|
||||
if (float_as_uint(changed.point_size) != 0) {
|
||||
pipeline_state.point_size = state.point_size;
|
||||
pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_PSO_FLAG;
|
||||
}
|
||||
|
||||
if (changed.line_width != 0) {
|
||||
pipeline_state.line_width = state.line_width;
|
||||
pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_PSO_FLAG;
|
||||
}
|
||||
|
||||
if (changed.stencil_compare_mask != 0 || changed.stencil_reference != 0 ||
|
||||
changed.stencil_write_mask != 0)
|
||||
{
|
||||
set_stencil_mask((GPUStencilTest)current_.stencil_test, state);
|
||||
}
|
||||
|
||||
current_mutable_ = state;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name State setting functions
|
||||
* \{ */
|
||||
|
||||
void MTLStateManager::set_write_mask(const GPUWriteMask value)
|
||||
{
|
||||
BLI_assert(context_);
|
||||
MTLContextGlobalShaderPipelineState &pipeline_state = context_->pipeline_state;
|
||||
pipeline_state.depth_stencil_state.depth_write_enable = ((value & GPU_WRITE_DEPTH) != 0);
|
||||
pipeline_state.color_write_mask =
|
||||
(((value & GPU_WRITE_RED) != 0) ? MTLColorWriteMaskRed : MTLColorWriteMaskNone) |
|
||||
(((value & GPU_WRITE_GREEN) != 0) ? MTLColorWriteMaskGreen : MTLColorWriteMaskNone) |
|
||||
(((value & GPU_WRITE_BLUE) != 0) ? MTLColorWriteMaskBlue : MTLColorWriteMaskNone) |
|
||||
(((value & GPU_WRITE_ALPHA) != 0) ? MTLColorWriteMaskAlpha : MTLColorWriteMaskNone);
|
||||
pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_PSO_FLAG;
|
||||
}
|
||||
|
||||
static MTLCompareFunction gpu_depth_function_to_metal(GPUDepthTest depth_func)
|
||||
{
|
||||
switch (depth_func) {
|
||||
case GPU_DEPTH_NONE:
|
||||
return MTLCompareFunctionNever;
|
||||
case GPU_DEPTH_LESS:
|
||||
return MTLCompareFunctionLess;
|
||||
case GPU_DEPTH_EQUAL:
|
||||
return MTLCompareFunctionEqual;
|
||||
case GPU_DEPTH_LESS_EQUAL:
|
||||
return MTLCompareFunctionLessEqual;
|
||||
case GPU_DEPTH_GREATER:
|
||||
return MTLCompareFunctionGreater;
|
||||
case GPU_DEPTH_GREATER_EQUAL:
|
||||
return MTLCompareFunctionGreaterEqual;
|
||||
case GPU_DEPTH_ALWAYS:
|
||||
return MTLCompareFunctionAlways;
|
||||
default:
|
||||
BLI_assert(false && "Invalid GPUDepthTest");
|
||||
break;
|
||||
}
|
||||
return MTLCompareFunctionAlways;
|
||||
}
|
||||
|
||||
static MTLCompareFunction gpu_stencil_func_to_metal(GPUStencilTest stencil_func)
|
||||
{
|
||||
switch (stencil_func) {
|
||||
case GPU_STENCIL_NONE:
|
||||
return MTLCompareFunctionAlways;
|
||||
case GPU_STENCIL_EQUAL:
|
||||
return MTLCompareFunctionEqual;
|
||||
case GPU_STENCIL_NEQUAL:
|
||||
return MTLCompareFunctionNotEqual;
|
||||
case GPU_STENCIL_ALWAYS:
|
||||
return MTLCompareFunctionAlways;
|
||||
default:
|
||||
BLI_assert(false && "Unrecognized GPUStencilTest function");
|
||||
break;
|
||||
}
|
||||
return MTLCompareFunctionAlways;
|
||||
}
|
||||
|
||||
void MTLStateManager::set_depth_test(const GPUDepthTest value)
|
||||
{
|
||||
BLI_assert(context_);
|
||||
MTLContextGlobalShaderPipelineState &pipeline_state = context_->pipeline_state;
|
||||
MTLContextDepthStencilState &ds_state = pipeline_state.depth_stencil_state;
|
||||
|
||||
ds_state.depth_test_enabled = (value != GPU_DEPTH_NONE);
|
||||
ds_state.depth_function = gpu_depth_function_to_metal(value);
|
||||
pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_DEPTHSTENCIL_FLAG;
|
||||
}
|
||||
|
||||
void MTLStateManager::mtl_stencil_mask(uint mask)
|
||||
{
|
||||
BLI_assert(context_);
|
||||
MTLContextGlobalShaderPipelineState &pipeline_state = context_->pipeline_state;
|
||||
pipeline_state.depth_stencil_state.stencil_write_mask = mask;
|
||||
pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_DEPTHSTENCIL_FLAG;
|
||||
}
|
||||
|
||||
void MTLStateManager::mtl_stencil_set_func(GPUStencilTest stencil_func, int ref, uint mask)
|
||||
{
|
||||
BLI_assert(context_);
|
||||
MTLContextGlobalShaderPipelineState &pipeline_state = context_->pipeline_state;
|
||||
MTLContextDepthStencilState &ds_state = pipeline_state.depth_stencil_state;
|
||||
|
||||
ds_state.stencil_func = gpu_stencil_func_to_metal(stencil_func);
|
||||
ds_state.stencil_ref = ref;
|
||||
ds_state.stencil_read_mask = mask;
|
||||
pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_DEPTHSTENCIL_FLAG;
|
||||
}
|
||||
|
||||
static void mtl_stencil_set_op_separate(MTLContext *context,
|
||||
GPUFaceCullTest face,
|
||||
MTLStencilOperation stencil_fail,
|
||||
MTLStencilOperation depth_test_fail,
|
||||
MTLStencilOperation depthstencil_pass)
|
||||
{
|
||||
BLI_assert(context);
|
||||
MTLContextGlobalShaderPipelineState &pipeline_state = context->pipeline_state;
|
||||
MTLContextDepthStencilState &ds_state = pipeline_state.depth_stencil_state;
|
||||
|
||||
if (face == GPU_CULL_FRONT) {
|
||||
ds_state.stencil_op_front_stencil_fail = stencil_fail;
|
||||
ds_state.stencil_op_front_depth_fail = depth_test_fail;
|
||||
ds_state.stencil_op_front_depthstencil_pass = depthstencil_pass;
|
||||
}
|
||||
else if (face == GPU_CULL_BACK) {
|
||||
ds_state.stencil_op_back_stencil_fail = stencil_fail;
|
||||
ds_state.stencil_op_back_depth_fail = depth_test_fail;
|
||||
ds_state.stencil_op_back_depthstencil_pass = depthstencil_pass;
|
||||
}
|
||||
|
||||
pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_DEPTHSTENCIL_FLAG;
|
||||
}
|
||||
|
||||
static void mtl_stencil_set_op(MTLContext *context,
|
||||
MTLStencilOperation stencil_fail,
|
||||
MTLStencilOperation depth_test_fail,
|
||||
MTLStencilOperation depthstencil_pass)
|
||||
{
|
||||
mtl_stencil_set_op_separate(
|
||||
context, GPU_CULL_FRONT, stencil_fail, depth_test_fail, depthstencil_pass);
|
||||
mtl_stencil_set_op_separate(
|
||||
context, GPU_CULL_BACK, stencil_fail, depth_test_fail, depthstencil_pass);
|
||||
}
|
||||
|
||||
void MTLStateManager::set_stencil_test(const GPUStencilTest test, const GPUStencilOp operation)
|
||||
{
|
||||
switch (operation) {
|
||||
case GPU_STENCIL_OP_REPLACE:
|
||||
mtl_stencil_set_op(
|
||||
context_, MTLStencilOperationKeep, MTLStencilOperationKeep, MTLStencilOperationReplace);
|
||||
break;
|
||||
case GPU_STENCIL_OP_COUNT_DEPTH_PASS:
|
||||
mtl_stencil_set_op_separate(context_,
|
||||
GPU_CULL_BACK,
|
||||
MTLStencilOperationKeep,
|
||||
MTLStencilOperationKeep,
|
||||
MTLStencilOperationIncrementWrap);
|
||||
mtl_stencil_set_op_separate(context_,
|
||||
GPU_CULL_FRONT,
|
||||
MTLStencilOperationKeep,
|
||||
MTLStencilOperationKeep,
|
||||
MTLStencilOperationDecrementWrap);
|
||||
break;
|
||||
case GPU_STENCIL_OP_COUNT_DEPTH_FAIL:
|
||||
mtl_stencil_set_op_separate(context_,
|
||||
GPU_CULL_BACK,
|
||||
MTLStencilOperationKeep,
|
||||
MTLStencilOperationDecrementWrap,
|
||||
MTLStencilOperationKeep);
|
||||
mtl_stencil_set_op_separate(context_,
|
||||
GPU_CULL_FRONT,
|
||||
MTLStencilOperationKeep,
|
||||
MTLStencilOperationIncrementWrap,
|
||||
MTLStencilOperationKeep);
|
||||
break;
|
||||
case GPU_STENCIL_OP_NONE:
|
||||
default:
|
||||
mtl_stencil_set_op(
|
||||
context_, MTLStencilOperationKeep, MTLStencilOperationKeep, MTLStencilOperationKeep);
|
||||
}
|
||||
|
||||
BLI_assert(context_);
|
||||
MTLContextGlobalShaderPipelineState &pipeline_state = context_->pipeline_state;
|
||||
pipeline_state.depth_stencil_state.stencil_test_enabled = (test != GPU_STENCIL_NONE);
|
||||
pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_DEPTHSTENCIL_FLAG;
|
||||
}
|
||||
|
||||
void MTLStateManager::set_stencil_mask(const GPUStencilTest test, const GPUStateMutable &state)
|
||||
{
|
||||
if (test == GPU_STENCIL_NONE) {
|
||||
mtl_stencil_mask(0x00);
|
||||
mtl_stencil_set_func(GPU_STENCIL_ALWAYS, 0x00, 0x00);
|
||||
}
|
||||
else {
|
||||
mtl_stencil_mask(state.stencil_write_mask);
|
||||
mtl_stencil_set_func(test, state.stencil_reference, state.stencil_compare_mask);
|
||||
}
|
||||
}
|
||||
|
||||
void MTLStateManager::mtl_clip_plane_enable(uint i)
|
||||
{
|
||||
BLI_assert(context_);
|
||||
MTLContextGlobalShaderPipelineState &pipeline_state = context_->pipeline_state;
|
||||
pipeline_state.clip_distance_enabled[i] = true;
|
||||
pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_PSO_FLAG;
|
||||
}
|
||||
|
||||
void MTLStateManager::mtl_clip_plane_disable(uint i)
|
||||
{
|
||||
BLI_assert(context_);
|
||||
MTLContextGlobalShaderPipelineState &pipeline_state = context_->pipeline_state;
|
||||
pipeline_state.clip_distance_enabled[i] = false;
|
||||
pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_PSO_FLAG;
|
||||
}
|
||||
|
||||
void MTLStateManager::set_clip_distances(const int new_dist_len, const int old_dist_len)
|
||||
{
|
||||
BLI_assert(new_dist_len <= 6);
|
||||
BLI_assert(old_dist_len <= 6);
|
||||
for (uint i = 0; i < new_dist_len; i++) {
|
||||
mtl_clip_plane_enable(i);
|
||||
}
|
||||
for (uint i = new_dist_len; i < old_dist_len; i++) {
|
||||
mtl_clip_plane_disable(i);
|
||||
}
|
||||
}
|
||||
|
||||
void MTLStateManager::set_logic_op(const bool /*enable*/)
|
||||
{
|
||||
/* NOTE(Metal): Logic Operations not directly supported. */
|
||||
}
|
||||
|
||||
void MTLStateManager::set_facing(const bool invert)
|
||||
{
|
||||
/* Check Current Context. */
|
||||
BLI_assert(context_);
|
||||
MTLContextGlobalShaderPipelineState &pipeline_state = context_->pipeline_state;
|
||||
|
||||
/* Apply State -- opposite of GL, as METAL default is GPU_CLOCKWISE, GL default is
|
||||
* COUNTERCLOCKWISE. This needs to be the inverse of the default. */
|
||||
pipeline_state.front_face = (invert) ? GPU_COUNTERCLOCKWISE : GPU_CLOCKWISE;
|
||||
|
||||
/* Mark Dirty - Ensure context updates state between draws. */
|
||||
pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_FRONT_FACING_FLAG;
|
||||
pipeline_state.dirty = true;
|
||||
}
|
||||
|
||||
void MTLStateManager::set_backface_culling(const GPUFaceCullTest test)
|
||||
{
|
||||
/* Check Current Context. */
|
||||
BLI_assert(context_);
|
||||
MTLContextGlobalShaderPipelineState &pipeline_state = context_->pipeline_state;
|
||||
|
||||
/* Apply State. */
|
||||
pipeline_state.culling_enabled = (test != GPU_CULL_NONE);
|
||||
pipeline_state.cull_mode = test;
|
||||
|
||||
/* Mark Dirty - Ensure context updates state between draws. */
|
||||
pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_CULLMODE_FLAG;
|
||||
pipeline_state.dirty = true;
|
||||
}
|
||||
|
||||
void MTLStateManager::set_provoking_vert(const GPUProvokingVertex /*vert*/)
|
||||
{
|
||||
/* NOTE(Metal): Provoking vertex is not a feature in the Metal API.
|
||||
* Shaders are handled on a case-by-case basis using a modified vertex shader.
|
||||
* For example, wireframe rendering and edit-mesh shaders utilize an SSBO-based
|
||||
* vertex pulling mechanism which considers the inverse convention for flat
|
||||
* shading, to ensure consistent results with OpenGL. */
|
||||
}
|
||||
|
||||
void MTLStateManager::set_blend(const GPUBlend value)
|
||||
{
|
||||
/**
|
||||
* Factors to the equation.
|
||||
* SRC is fragment shader output.
|
||||
* DST is frame-buffer color.
|
||||
* final.rgb = SRC.rgb * src_rgb + DST.rgb * dst_rgb;
|
||||
* final.a = SRC.a * src_alpha + DST.a * dst_alpha;
|
||||
*/
|
||||
MTLBlendFactor src_rgb;
|
||||
MTLBlendFactor dst_rgb;
|
||||
MTLBlendFactor src_alpha;
|
||||
MTLBlendFactor dst_alpha;
|
||||
switch (value) {
|
||||
default:
|
||||
case GPU_BLEND_ALPHA: {
|
||||
src_rgb = MTLBlendFactorSourceAlpha;
|
||||
dst_rgb = MTLBlendFactorOneMinusSourceAlpha;
|
||||
src_alpha = MTLBlendFactorOne;
|
||||
dst_alpha = MTLBlendFactorOneMinusSourceAlpha;
|
||||
break;
|
||||
}
|
||||
case GPU_BLEND_ALPHA_PREMULT: {
|
||||
src_rgb = MTLBlendFactorOne;
|
||||
dst_rgb = MTLBlendFactorOneMinusSourceAlpha;
|
||||
src_alpha = MTLBlendFactorOne;
|
||||
dst_alpha = MTLBlendFactorOneMinusSourceAlpha;
|
||||
break;
|
||||
}
|
||||
case GPU_BLEND_ADDITIVE: {
|
||||
/* Do not let alpha accumulate but pre-multiply the source RGB by it. */
|
||||
src_rgb = MTLBlendFactorSourceAlpha;
|
||||
dst_rgb = MTLBlendFactorOne;
|
||||
src_alpha = MTLBlendFactorZero;
|
||||
dst_alpha = MTLBlendFactorOne;
|
||||
break;
|
||||
}
|
||||
/* Factors are not use in min or max mode, but avoid uninitialized values. */;
|
||||
case GPU_BLEND_MIN:
|
||||
case GPU_BLEND_MAX:
|
||||
case GPU_BLEND_SUBTRACT:
|
||||
case GPU_BLEND_ADDITIVE_PREMULT: {
|
||||
/* Let alpha accumulate. */
|
||||
src_rgb = MTLBlendFactorOne;
|
||||
dst_rgb = MTLBlendFactorOne;
|
||||
src_alpha = MTLBlendFactorOne;
|
||||
dst_alpha = MTLBlendFactorOne;
|
||||
break;
|
||||
}
|
||||
case GPU_BLEND_MULTIPLY: {
|
||||
src_rgb = MTLBlendFactorDestinationColor;
|
||||
dst_rgb = MTLBlendFactorZero;
|
||||
src_alpha = MTLBlendFactorDestinationAlpha;
|
||||
dst_alpha = MTLBlendFactorZero;
|
||||
break;
|
||||
}
|
||||
case GPU_BLEND_INVERT: {
|
||||
src_rgb = MTLBlendFactorOneMinusDestinationColor;
|
||||
dst_rgb = MTLBlendFactorZero;
|
||||
src_alpha = MTLBlendFactorZero;
|
||||
dst_alpha = MTLBlendFactorOne;
|
||||
break;
|
||||
}
|
||||
case GPU_BLEND_OIT: {
|
||||
src_rgb = MTLBlendFactorOne;
|
||||
dst_rgb = MTLBlendFactorOne;
|
||||
src_alpha = MTLBlendFactorZero;
|
||||
dst_alpha = MTLBlendFactorOneMinusSourceAlpha;
|
||||
break;
|
||||
}
|
||||
case GPU_BLEND_BACKGROUND: {
|
||||
src_rgb = MTLBlendFactorOneMinusDestinationAlpha;
|
||||
dst_rgb = MTLBlendFactorSourceAlpha;
|
||||
src_alpha = MTLBlendFactorZero;
|
||||
dst_alpha = MTLBlendFactorSourceAlpha;
|
||||
break;
|
||||
}
|
||||
case GPU_BLEND_ALPHA_UNDER_PREMUL: {
|
||||
src_rgb = MTLBlendFactorOneMinusDestinationAlpha;
|
||||
dst_rgb = MTLBlendFactorOne;
|
||||
src_alpha = MTLBlendFactorOneMinusDestinationAlpha;
|
||||
dst_alpha = MTLBlendFactorOne;
|
||||
break;
|
||||
}
|
||||
case GPU_BLEND_CUSTOM: {
|
||||
src_rgb = MTLBlendFactorOne;
|
||||
dst_rgb = MTLBlendFactorSource1Color;
|
||||
src_alpha = MTLBlendFactorOne;
|
||||
dst_alpha = MTLBlendFactorSource1Alpha;
|
||||
break;
|
||||
}
|
||||
case GPU_BLEND_OVERLAY_MASK_FROM_ALPHA: {
|
||||
src_rgb = MTLBlendFactorZero;
|
||||
dst_rgb = MTLBlendFactorOneMinusSourceAlpha;
|
||||
src_alpha = MTLBlendFactorZero;
|
||||
dst_alpha = MTLBlendFactorOneMinusSourceAlpha;
|
||||
break;
|
||||
}
|
||||
case GPU_BLEND_TRANSPARENCY: {
|
||||
src_rgb = MTLBlendFactorOne;
|
||||
dst_rgb = MTLBlendFactorSourceAlpha;
|
||||
src_alpha = MTLBlendFactorZero;
|
||||
dst_alpha = MTLBlendFactorSourceAlpha;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check Current Context. */
|
||||
BLI_assert(context_);
|
||||
MTLContextGlobalShaderPipelineState &pipeline_state = context_->pipeline_state;
|
||||
|
||||
if (value == GPU_BLEND_MIN) {
|
||||
pipeline_state.rgb_blend_op = MTLBlendOperationMin;
|
||||
pipeline_state.alpha_blend_op = MTLBlendOperationMin;
|
||||
}
|
||||
else if (value == GPU_BLEND_MAX) {
|
||||
pipeline_state.rgb_blend_op = MTLBlendOperationMax;
|
||||
pipeline_state.alpha_blend_op = MTLBlendOperationMax;
|
||||
}
|
||||
else if (value == GPU_BLEND_SUBTRACT) {
|
||||
pipeline_state.rgb_blend_op = MTLBlendOperationReverseSubtract;
|
||||
pipeline_state.alpha_blend_op = MTLBlendOperationReverseSubtract;
|
||||
}
|
||||
else {
|
||||
pipeline_state.rgb_blend_op = MTLBlendOperationAdd;
|
||||
pipeline_state.alpha_blend_op = MTLBlendOperationAdd;
|
||||
}
|
||||
|
||||
/* Apply State. */
|
||||
pipeline_state.blending_enabled = (value != GPU_BLEND_NONE);
|
||||
pipeline_state.src_rgb_blend_factor = src_rgb;
|
||||
pipeline_state.dest_rgb_blend_factor = dst_rgb;
|
||||
pipeline_state.src_alpha_blend_factor = src_alpha;
|
||||
pipeline_state.dest_alpha_blend_factor = dst_alpha;
|
||||
|
||||
/* Mark Dirty - Ensure context updates PSOs between draws. */
|
||||
pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_PSO_FLAG;
|
||||
pipeline_state.dirty = true;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Memory barrier
|
||||
* \{ */
|
||||
|
||||
/* NOTE(Metal): Granular option for specifying before/after stages for a barrier
|
||||
* Would be a useful feature. */
|
||||
#if 0
|
||||
void MTLStateManager::issue_barrier(GPUBarrier barrier_bits,
|
||||
GPUStageBarrierBits before_stages,
|
||||
GPUStageBarrierBits after_stages)
|
||||
#endif
|
||||
void MTLStateManager::issue_barrier(GPUBarrier barrier_bits)
|
||||
{
|
||||
/* NOTE(Metal): The Metal API implicitly tracks dependencies between resources.
|
||||
* Memory barriers and execution barriers (Fences/Events) can be used to coordinate
|
||||
* this explicitly, however, in most cases, the driver will be able to
|
||||
* resolve these dependencies automatically.
|
||||
* For untracked resources, such as MTLHeap's, explicit barriers are necessary. */
|
||||
GPUStageBarrierBits before_stages = GPU_BARRIER_STAGE_ANY;
|
||||
GPUStageBarrierBits after_stages = GPU_BARRIER_STAGE_ANY;
|
||||
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
|
||||
ctx->main_command_buffer.insert_memory_barrier(barrier_bits, before_stages, after_stages);
|
||||
}
|
||||
|
||||
MTLFence::~MTLFence()
|
||||
{
|
||||
if (mtl_event_) {
|
||||
[mtl_event_ release];
|
||||
mtl_event_ = nil;
|
||||
}
|
||||
}
|
||||
|
||||
void MTLFence::signal()
|
||||
{
|
||||
if (mtl_event_ == nil) {
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
mtl_event_ = [ctx->device newEvent];
|
||||
}
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
ctx->main_command_buffer.encode_signal_event(mtl_event_, ++last_signalled_value_);
|
||||
|
||||
signalled_ = true;
|
||||
}
|
||||
|
||||
void MTLFence::wait()
|
||||
{
|
||||
/* do not attempt to wait if event has not yet been signalled for the first time. */
|
||||
if (mtl_event_ == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (signalled_) {
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
|
||||
ctx->main_command_buffer.encode_wait_for_event(mtl_event_, last_signalled_value_);
|
||||
signalled_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Texture State Management
|
||||
* \{ */
|
||||
|
||||
void MTLStateManager::texture_bind(Texture *tex_, GPUSamplerState sampler_type, int unit)
|
||||
{
|
||||
BLI_assert(tex_);
|
||||
gpu::MTLTexture *mtl_tex = static_cast<gpu::MTLTexture *>(tex_);
|
||||
BLI_assert(mtl_tex);
|
||||
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
if (unit >= 0) {
|
||||
ctx->texture_bind(mtl_tex, unit, false);
|
||||
|
||||
/* Fetching textures default sampler configuration and applying
|
||||
* eGPUSampler State on top. This path exists to support
|
||||
* Any of the sampler state which is associated with the
|
||||
* texture itself such as min/max mip levels. */
|
||||
MTLSamplerState sampler = mtl_tex->get_sampler_state();
|
||||
sampler.state = sampler_type;
|
||||
|
||||
ctx->sampler_bind(sampler, unit);
|
||||
}
|
||||
}
|
||||
|
||||
void MTLStateManager::texture_unbind(Texture *tex_)
|
||||
{
|
||||
BLI_assert(tex_);
|
||||
gpu::MTLTexture *mtl_tex = static_cast<gpu::MTLTexture *>(tex_);
|
||||
BLI_assert(mtl_tex);
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
ctx->texture_unbind(mtl_tex, false, this);
|
||||
}
|
||||
|
||||
void MTLStateManager::texture_unbind_all()
|
||||
{
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
ctx->texture_unbind_all(false);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Image Binding (from image load store)
|
||||
* \{ */
|
||||
|
||||
void MTLStateManager::image_bind(Texture *tex_, int unit)
|
||||
{
|
||||
BLI_assert(tex_);
|
||||
gpu::MTLTexture *mtl_tex = static_cast<gpu::MTLTexture *>(tex_);
|
||||
BLI_assert(mtl_tex);
|
||||
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
if (unit >= 0) {
|
||||
ctx->texture_bind(mtl_tex, unit, true);
|
||||
image_formats[unit] = TextureWriteFormat(tex_->format_get());
|
||||
}
|
||||
}
|
||||
|
||||
void MTLStateManager::image_unbind(Texture *tex_)
|
||||
{
|
||||
BLI_assert(tex_);
|
||||
gpu::MTLTexture *mtl_tex = static_cast<gpu::MTLTexture *>(tex_);
|
||||
BLI_assert(mtl_tex);
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
ctx->texture_unbind(mtl_tex, true, this);
|
||||
}
|
||||
|
||||
void MTLStateManager::image_unbind_all()
|
||||
{
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
ctx->texture_unbind_all(true);
|
||||
image_formats.fill(TextureWriteFormat::Invalid);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::gpu
|
||||
99
blender-5.2.0/source/blender/gpu/metal/mtl_storage_buffer.hh
Normal file
99
blender-5.2.0/source/blender/gpu/metal/mtl_storage_buffer.hh
Normal file
@@ -0,0 +1,99 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
#include "gpu_storage_buffer_private.hh"
|
||||
|
||||
#include "mtl_context.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
class MTLUniformBuf;
|
||||
class MTLVertBuf;
|
||||
class MTLIndexBuf;
|
||||
class MTLCircularBuffer;
|
||||
|
||||
/**
|
||||
* Implementation of Storage Buffers using Metal.
|
||||
*/
|
||||
class MTLStorageBuf : public StorageBuf {
|
||||
friend MTLCircularBuffer;
|
||||
|
||||
private:
|
||||
/** Allocation Handle or indirect wrapped instance.
|
||||
* MTLStorageBuf can wrap a MTLVertBuf, MTLIndexBuf or MTLUniformBuf for binding as a writeable
|
||||
* resource. */
|
||||
enum {
|
||||
MTL_STORAGE_BUF_TYPE_DEFAULT = 0,
|
||||
MTL_STORAGE_BUF_TYPE_UNIFORMBUF = 1,
|
||||
MTL_STORAGE_BUF_TYPE_VERTBUF = 2,
|
||||
MTL_STORAGE_BUF_TYPE_INDEXBUF = 3,
|
||||
MTL_STORAGE_BUF_TYPE_TEXTURE = 4,
|
||||
} storage_source_ = MTL_STORAGE_BUF_TYPE_DEFAULT;
|
||||
|
||||
union {
|
||||
/** Own allocation. */
|
||||
gpu::MTLBuffer *metal_buffer_;
|
||||
/* Wrapped type. */
|
||||
MTLUniformBuf *uniform_buffer_;
|
||||
MTLVertBuf *vertex_buffer_;
|
||||
MTLIndexBuf *index_buffer_;
|
||||
gpu::MTLTexture *texture_;
|
||||
};
|
||||
|
||||
/* Whether buffer has contents, if false, no GPU buffer will
|
||||
* have yet been allocated. */
|
||||
bool has_data_ = false;
|
||||
/** Bind-state tracking. */
|
||||
int bind_slot_ = -1;
|
||||
MTLContext *bound_ctx_ = nullptr;
|
||||
|
||||
/** Usage type. */
|
||||
GPUUsageType usage_;
|
||||
|
||||
/* Synchronization event for host reads. */
|
||||
id<MTLSharedEvent> gpu_write_fence_ = nil;
|
||||
uint64_t host_read_signal_value_ = 0;
|
||||
|
||||
public:
|
||||
MTLStorageBuf(size_t size, GPUUsageType usage, const char *name);
|
||||
~MTLStorageBuf() override;
|
||||
|
||||
MTLStorageBuf(MTLUniformBuf *uniform_buf, size_t size);
|
||||
MTLStorageBuf(MTLVertBuf *vert_buf, size_t size);
|
||||
MTLStorageBuf(MTLIndexBuf *index_buf, size_t size);
|
||||
MTLStorageBuf(MTLTexture *texture, size_t size);
|
||||
|
||||
/* Only used internally to create a bindable buffer for #Immediate. */
|
||||
MTLStorageBuf(size_t size);
|
||||
|
||||
void update(const void *data) override;
|
||||
void bind(int slot) override;
|
||||
void unbind() override;
|
||||
void clear(uint32_t clear_value) override;
|
||||
void copy_sub(VertBuf *src, uint dst_offset, uint src_offset, uint copy_size) override;
|
||||
void read(void *data) override;
|
||||
void async_flush_to_host() override;
|
||||
void sync_as_indirect_buffer() override { /* No-Op. */ };
|
||||
|
||||
void init();
|
||||
|
||||
id<MTLBuffer> get_metal_buffer();
|
||||
size_t get_size();
|
||||
const char *get_name()
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
private:
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLStorageBuf");
|
||||
};
|
||||
|
||||
} // namespace blender::gpu
|
||||
529
blender-5.2.0/source/blender/gpu/metal/mtl_storage_buffer.mm
Normal file
529
blender-5.2.0/source/blender/gpu/metal/mtl_storage_buffer.mm
Normal file
@@ -0,0 +1,529 @@
|
||||
/* SPDX-FileCopyrightText: 2022 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_time.h"
|
||||
|
||||
#include "GPU_state.hh"
|
||||
#include "gpu_backend.hh"
|
||||
#include "gpu_context_private.hh"
|
||||
|
||||
#include "mtl_backend.hh"
|
||||
#include "mtl_context.hh"
|
||||
#include "mtl_debug.hh"
|
||||
#include "mtl_index_buffer.hh"
|
||||
#include "mtl_memory.hh"
|
||||
#include "mtl_storage_buffer.hh"
|
||||
#include "mtl_uniform_buffer.hh"
|
||||
#include "mtl_vertex_buffer.hh"
|
||||
#include <cstdio>
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Creation & Deletion
|
||||
* \{ */
|
||||
|
||||
MTLStorageBuf::MTLStorageBuf(size_t size) : StorageBuf(size, "Immediate")
|
||||
{
|
||||
usage_ = GPU_USAGE_STREAM;
|
||||
storage_source_ = MTL_STORAGE_BUF_TYPE_DEFAULT;
|
||||
metal_buffer_ = MTLContext::get_global_memory_manager()->allocate_aligned(size, 256, true);
|
||||
}
|
||||
|
||||
MTLStorageBuf::MTLStorageBuf(size_t size, GPUUsageType usage, const char *name)
|
||||
: StorageBuf(size, name)
|
||||
{
|
||||
usage_ = usage;
|
||||
/* Do not create SSBO MTL buffer here to allow allocation from any thread. */
|
||||
storage_source_ = MTL_STORAGE_BUF_TYPE_DEFAULT;
|
||||
metal_buffer_ = nullptr;
|
||||
}
|
||||
|
||||
MTLStorageBuf::MTLStorageBuf(MTLUniformBuf *uniform_buf, size_t size)
|
||||
: StorageBuf(size, "UniformBuffer_as_SSBO")
|
||||
{
|
||||
usage_ = GPU_USAGE_DYNAMIC;
|
||||
storage_source_ = MTL_STORAGE_BUF_TYPE_UNIFORMBUF;
|
||||
uniform_buffer_ = uniform_buf;
|
||||
BLI_assert(uniform_buffer_ != nullptr);
|
||||
}
|
||||
|
||||
MTLStorageBuf::MTLStorageBuf(MTLVertBuf *vert_buf, size_t size)
|
||||
: StorageBuf(size, "VertexBuffer_as_SSBO")
|
||||
{
|
||||
usage_ = GPU_USAGE_DYNAMIC;
|
||||
storage_source_ = MTL_STORAGE_BUF_TYPE_VERTBUF;
|
||||
vertex_buffer_ = vert_buf;
|
||||
BLI_assert(vertex_buffer_ != nullptr);
|
||||
}
|
||||
|
||||
MTLStorageBuf::MTLStorageBuf(MTLIndexBuf *index_buf, size_t size)
|
||||
: StorageBuf(size, "IndexBuffer_as_SSBO")
|
||||
{
|
||||
usage_ = GPU_USAGE_DYNAMIC;
|
||||
storage_source_ = MTL_STORAGE_BUF_TYPE_INDEXBUF;
|
||||
index_buffer_ = index_buf;
|
||||
BLI_assert(index_buffer_ != nullptr);
|
||||
}
|
||||
|
||||
MTLStorageBuf::MTLStorageBuf(MTLTexture *texture, size_t size)
|
||||
: StorageBuf(size, "Texture_as_SSBO")
|
||||
{
|
||||
usage_ = GPU_USAGE_DYNAMIC;
|
||||
storage_source_ = MTL_STORAGE_BUF_TYPE_TEXTURE;
|
||||
texture_ = texture;
|
||||
BLI_assert(texture_ != nullptr);
|
||||
}
|
||||
|
||||
MTLStorageBuf::~MTLStorageBuf()
|
||||
{
|
||||
if (storage_source_ == MTL_STORAGE_BUF_TYPE_DEFAULT) {
|
||||
if (metal_buffer_ != nullptr) {
|
||||
metal_buffer_->free();
|
||||
metal_buffer_ = nullptr;
|
||||
}
|
||||
has_data_ = false;
|
||||
}
|
||||
|
||||
if (gpu_write_fence_ != nil) {
|
||||
[gpu_write_fence_ release];
|
||||
gpu_write_fence_ = nil;
|
||||
}
|
||||
|
||||
/* Ensure SSBO is not bound to active CTX.
|
||||
* SSBO bindings are reset upon Context-switch so we do not need
|
||||
* to check deactivated context's. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
if (ctx) {
|
||||
for (MTLStorageBufferBinding &slot : ctx->pipeline_state.ssbo_bindings) {
|
||||
if (slot.bound && slot.ssbo == this) {
|
||||
slot.bound = false;
|
||||
slot.ssbo = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Data upload / update
|
||||
* \{ */
|
||||
|
||||
void MTLStorageBuf::init()
|
||||
{
|
||||
/* We only need to initialize the storage buffer for default buffer types. */
|
||||
if (storage_source_ != MTL_STORAGE_BUF_TYPE_DEFAULT) {
|
||||
return;
|
||||
}
|
||||
BLI_assert(this);
|
||||
BLI_assert(size_in_bytes_ > 0);
|
||||
|
||||
/* Allocate MTL buffer */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
BLI_assert(ctx->device);
|
||||
UNUSED_VARS_NDEBUG(ctx);
|
||||
|
||||
metal_buffer_ = MTLContext::get_global_memory_manager()->allocate(
|
||||
size_in_bytes_, (usage_ == GPU_USAGE_DEVICE_ONLY) ? false : true);
|
||||
|
||||
#ifndef NDEBUG
|
||||
static std::atomic<int> global_counter = 0;
|
||||
int index = global_counter.fetch_add(1);
|
||||
metal_buffer_->set_label([NSString stringWithFormat:@"SSBO %i %s", index, name_]);
|
||||
#endif
|
||||
BLI_assert(metal_buffer_ != nullptr);
|
||||
BLI_assert(metal_buffer_->get_metal_buffer() != nil);
|
||||
|
||||
has_data_ = false;
|
||||
}
|
||||
|
||||
void MTLStorageBuf::update(const void *data)
|
||||
{
|
||||
/* We only need to initialize the storage buffer for default buffer types. */
|
||||
if (storage_source_ != MTL_STORAGE_BUF_TYPE_DEFAULT) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* For device-only Storage buffers, update private resource via staging buffer in command
|
||||
* stream. */
|
||||
bool device_only = (usage_ == GPU_USAGE_DEVICE_ONLY);
|
||||
bool do_upload_data = (data != nullptr);
|
||||
|
||||
/* If host-visible, upload data to new buffer, as previous data may still be in-use by executing
|
||||
* GPU commands. */
|
||||
if (!device_only && do_upload_data) {
|
||||
if (metal_buffer_ != nullptr) {
|
||||
metal_buffer_->free();
|
||||
metal_buffer_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure buffer has been allocated. */
|
||||
if (metal_buffer_ == nullptr) {
|
||||
init();
|
||||
}
|
||||
|
||||
BLI_assert(do_upload_data);
|
||||
if (do_upload_data) {
|
||||
if (device_only) {
|
||||
|
||||
/* Fetch active context. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
|
||||
/* Prepare staging buffer. */
|
||||
gpu::MTLBuffer *staging_buf = MTLContext::get_global_memory_manager()->allocate(
|
||||
size_in_bytes_, true);
|
||||
memcpy(staging_buf->get_host_ptr(), data, size_in_bytes_);
|
||||
staging_buf->flush_range(0, size_in_bytes_);
|
||||
id<MTLBuffer> staging_buf_mtl = staging_buf->get_metal_buffer();
|
||||
BLI_assert(staging_buf_mtl != nil);
|
||||
|
||||
/* Ensure destination buffer. */
|
||||
id<MTLBuffer> dst_buf = this->metal_buffer_->get_metal_buffer();
|
||||
BLI_assert(dst_buf != nil);
|
||||
|
||||
id<MTLBlitCommandEncoder> blit_encoder =
|
||||
ctx->main_command_buffer.ensure_begin_blit_encoder();
|
||||
[blit_encoder copyFromBuffer:staging_buf_mtl
|
||||
sourceOffset:0
|
||||
toBuffer:dst_buf
|
||||
destinationOffset:0
|
||||
size:size_in_bytes_];
|
||||
staging_buf->free();
|
||||
}
|
||||
else {
|
||||
/* Upload data. */
|
||||
BLI_assert(data != nullptr);
|
||||
BLI_assert(!(metal_buffer_->get_resource_options() & MTLResourceStorageModePrivate));
|
||||
BLI_assert(size_in_bytes_ <= metal_buffer_->get_size());
|
||||
BLI_assert(size_in_bytes_ <= [metal_buffer_->get_metal_buffer() length]);
|
||||
memcpy(metal_buffer_->get_host_ptr(), data, size_in_bytes_);
|
||||
metal_buffer_->flush_range(0, size_in_bytes_);
|
||||
}
|
||||
has_data_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Usage
|
||||
* \{ */
|
||||
|
||||
void MTLStorageBuf::bind(int slot)
|
||||
{
|
||||
if (slot >= MTL_MAX_BUFFER_BINDINGS) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Error: Trying to bind \"%s\" ssbo to slot %d which is above the reported limit of %d.\n",
|
||||
name_,
|
||||
slot,
|
||||
MTL_MAX_BUFFER_BINDINGS);
|
||||
BLI_assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (metal_buffer_ == nullptr) {
|
||||
this->init();
|
||||
}
|
||||
|
||||
if (data_ != nullptr) {
|
||||
this->update(data_);
|
||||
MEM_SAFE_DELETE_VOID(data_);
|
||||
}
|
||||
|
||||
/* Bind current UBO to active context. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
|
||||
MTLStorageBufferBinding &ctx_ssbo_bind_slot = ctx->pipeline_state.ssbo_bindings[slot];
|
||||
ctx_ssbo_bind_slot.ssbo = this;
|
||||
ctx_ssbo_bind_slot.bound = true;
|
||||
|
||||
bind_slot_ = slot;
|
||||
bound_ctx_ = ctx;
|
||||
}
|
||||
|
||||
void MTLStorageBuf::unbind()
|
||||
{
|
||||
/* Unbind in debug mode to validate missing binds.
|
||||
* Otherwise, only perform a full unbind upon destruction
|
||||
* to ensure no lingering references. */
|
||||
#ifndef NDEBUG
|
||||
if (true)
|
||||
#else
|
||||
if (G.debug & G_DEBUG_GPU)
|
||||
#endif
|
||||
{
|
||||
if (bound_ctx_ != nullptr && bind_slot_ > -1) {
|
||||
MTLStorageBufferBinding &ctx_ssbo_bind_slot =
|
||||
bound_ctx_->pipeline_state.ssbo_bindings[bind_slot_];
|
||||
if (ctx_ssbo_bind_slot.bound && ctx_ssbo_bind_slot.ssbo == this) {
|
||||
ctx_ssbo_bind_slot.bound = false;
|
||||
ctx_ssbo_bind_slot.ssbo = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset bind index. */
|
||||
bind_slot_ = -1;
|
||||
bound_ctx_ = nullptr;
|
||||
}
|
||||
|
||||
void MTLStorageBuf::clear(uint32_t clear_value)
|
||||
{
|
||||
/* Fetch active context. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert_msg(ctx, "Clears should always be performed while a valid context exists.");
|
||||
|
||||
if (metal_buffer_ == nullptr) {
|
||||
this->init();
|
||||
}
|
||||
|
||||
if (ctx) {
|
||||
/* If all 4 bytes within clear value are equal, use the builtin fast-path for clearing. */
|
||||
uint clear_byte = clear_value & 0xFF;
|
||||
bool clear_value_bytes_equal = (clear_byte == ((clear_value >> 8) & 0xFF)) &&
|
||||
(clear_byte == ((clear_value >> 16) & 0xFF)) &&
|
||||
(clear_byte == ((clear_value >> 24) & 0xFF));
|
||||
if (clear_value_bytes_equal) {
|
||||
id<MTLBlitCommandEncoder> blit_encoder =
|
||||
ctx->main_command_buffer.ensure_begin_blit_encoder();
|
||||
[blit_encoder fillBuffer:metal_buffer_->get_metal_buffer()
|
||||
range:NSMakeRange(0, size_in_bytes_)
|
||||
value:clear_byte];
|
||||
}
|
||||
else {
|
||||
/* We need a special compute routine to update 32 bit values efficiently. */
|
||||
id<MTLComputePipelineState> pso = ctx->get_compute_utils().get_buffer_clear_pso();
|
||||
id<MTLComputeCommandEncoder> compute_encoder =
|
||||
ctx->main_command_buffer.ensure_begin_compute_encoder();
|
||||
|
||||
MTLComputeState &cs = ctx->main_command_buffer.get_compute_state();
|
||||
cs.bind_pso(pso);
|
||||
cs.bind_compute_bytes(&clear_value, sizeof(uint32_t), 0);
|
||||
cs.bind_compute_buffer(metal_buffer_->get_metal_buffer(), 0, 1);
|
||||
[compute_encoder dispatchThreads:MTLSizeMake(size_in_bytes_ / sizeof(uint32_t), 1, 1)
|
||||
threadsPerThreadgroup:MTLSizeMake(128, 1, 1)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MTLStorageBuf::copy_sub(VertBuf *src_, uint dst_offset, uint src_offset, uint copy_size)
|
||||
{
|
||||
MTLVertBuf *src = static_cast<MTLVertBuf *>(src_);
|
||||
MTLStorageBuf *dst = this;
|
||||
|
||||
if (dst->metal_buffer_ == nullptr) {
|
||||
dst->init();
|
||||
}
|
||||
if (copy_size == 0) {
|
||||
return;
|
||||
}
|
||||
if (src->vbo_ == nullptr) {
|
||||
src->bind();
|
||||
}
|
||||
|
||||
/* Fetch active context. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
|
||||
/* Fetch Metal buffers. */
|
||||
id<MTLBuffer> src_buf = src->vbo_->get_metal_buffer();
|
||||
id<MTLBuffer> dst_buf = dst->metal_buffer_->get_metal_buffer();
|
||||
BLI_assert(src_buf != nil);
|
||||
BLI_assert(dst_buf != nil);
|
||||
|
||||
id<MTLBlitCommandEncoder> blit_encoder = ctx->main_command_buffer.ensure_begin_blit_encoder();
|
||||
[blit_encoder copyFromBuffer:src_buf
|
||||
sourceOffset:src_offset
|
||||
toBuffer:dst_buf
|
||||
destinationOffset:dst_offset
|
||||
size:copy_size];
|
||||
}
|
||||
|
||||
void MTLStorageBuf::async_flush_to_host()
|
||||
{
|
||||
bool device_only = (usage_ == GPU_USAGE_DEVICE_ONLY);
|
||||
BLI_assert_msg(!device_only,
|
||||
"Storage buffers with usage GPU_USAGE_DEVICE_ONLY cannot have their data "
|
||||
"synchronized to the host.");
|
||||
if (device_only) {
|
||||
return;
|
||||
}
|
||||
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
|
||||
if (gpu_write_fence_ == nil) {
|
||||
gpu_write_fence_ = [ctx->device newSharedEvent];
|
||||
}
|
||||
|
||||
if (metal_buffer_ == nullptr) {
|
||||
this->init();
|
||||
}
|
||||
|
||||
/* For discrete memory systems, explicitly flush GPU-resident memory back to host. */
|
||||
id<MTLBuffer> storage_buf_mtl = this->metal_buffer_->get_metal_buffer();
|
||||
if (storage_buf_mtl.storageMode == MTLStorageModeManaged) {
|
||||
id<MTLBlitCommandEncoder> blit_encoder = ctx->main_command_buffer.ensure_begin_blit_encoder();
|
||||
[blit_encoder synchronizeResource:storage_buf_mtl];
|
||||
}
|
||||
|
||||
/* Encode event signal and flush command buffer to ensure GPU work is in the pipeline for future
|
||||
* reads. */
|
||||
ctx->main_command_buffer.encode_signal_event(gpu_write_fence_, ++host_read_signal_value_);
|
||||
ctx->flush();
|
||||
}
|
||||
|
||||
void MTLStorageBuf::read(void *data)
|
||||
{
|
||||
if (data == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (metal_buffer_ == nullptr) {
|
||||
this->init();
|
||||
}
|
||||
|
||||
/* Device-only storage buffers cannot be read directly and require staging.
|
||||
* This path should only be used for unit testing. */
|
||||
bool device_only = (usage_ == GPU_USAGE_DEVICE_ONLY);
|
||||
if (device_only) {
|
||||
/** Read storage buffer contents via staging buffer. */
|
||||
/* Fetch active context. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
|
||||
/* Prepare staging buffer. */
|
||||
gpu::MTLBuffer *staging_buf = MTLContext::get_global_memory_manager()->allocate(size_in_bytes_,
|
||||
true);
|
||||
id<MTLBuffer> staging_buf_mtl = staging_buf->get_metal_buffer();
|
||||
BLI_assert(staging_buf_mtl != nil);
|
||||
|
||||
/* Ensure destination buffer. */
|
||||
id<MTLBuffer> storage_buf_mtl = this->metal_buffer_->get_metal_buffer();
|
||||
BLI_assert(storage_buf_mtl != nil);
|
||||
|
||||
id<MTLBlitCommandEncoder> blit_encoder = ctx->main_command_buffer.ensure_begin_blit_encoder();
|
||||
[blit_encoder copyFromBuffer:storage_buf_mtl
|
||||
sourceOffset:0
|
||||
toBuffer:staging_buf_mtl
|
||||
destinationOffset:0
|
||||
size:size_in_bytes_];
|
||||
if (staging_buf_mtl.storageMode == MTLStorageModeManaged) {
|
||||
[blit_encoder synchronizeResource:staging_buf_mtl];
|
||||
}
|
||||
|
||||
/* Device-only reads will always stall the GPU pipe. */
|
||||
GPU_finish();
|
||||
MTL_LOG_WARNING(
|
||||
"Device-only storage buffer being read. This will stall the GPU pipeline. Ensure this "
|
||||
"path is only used in testing.");
|
||||
|
||||
/* Read contents back to data. */
|
||||
memcpy(data, staging_buf->get_host_ptr(), size_in_bytes_);
|
||||
staging_buf->free();
|
||||
}
|
||||
else {
|
||||
/** Direct storage buffer read. */
|
||||
/* If we have a synchronization event from a prior memory sync, ensure memory is fully synced.
|
||||
* Otherwise, assume read is synchronous and stall until in-flight work is complete. */
|
||||
if (gpu_write_fence_ != nil) {
|
||||
/* Ensure the GPU updates are visible to the host before reading. */
|
||||
while (gpu_write_fence_.signaledValue < host_read_signal_value_) {
|
||||
BLI_time_sleep_ms(1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* In the case of unified memory. Wait for all pending operation. */
|
||||
GPU_finish();
|
||||
}
|
||||
|
||||
/* Managed buffers need to be explicitly flushed back to host. */
|
||||
if (metal_buffer_->get_resource_options() & MTLResourceStorageModeManaged) {
|
||||
/* Fetch active context. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
|
||||
/* Ensure GPU updates are flushed back to CPU. */
|
||||
id<MTLBlitCommandEncoder> blit_encoder =
|
||||
ctx->main_command_buffer.ensure_begin_blit_encoder();
|
||||
[blit_encoder synchronizeResource:metal_buffer_->get_metal_buffer()];
|
||||
|
||||
/* Wait for the blit to finish. */
|
||||
GPU_finish();
|
||||
}
|
||||
|
||||
/* Read data. NOTE: Unless explicitly synchronized with GPU work, results may not be ready. */
|
||||
memcpy(data, metal_buffer_->get_host_ptr(), size_in_bytes_);
|
||||
}
|
||||
}
|
||||
|
||||
id<MTLBuffer> MTLStorageBuf::get_metal_buffer()
|
||||
{
|
||||
|
||||
gpu::MTLBuffer *source_buffer = nullptr;
|
||||
switch (storage_source_) {
|
||||
/* Default SSBO buffer comes from own allocation. */
|
||||
case MTL_STORAGE_BUF_TYPE_DEFAULT: {
|
||||
/* NOTE: We should always ensure that the data is primed prior to requiring fetching of the
|
||||
* buffer. If get_metal_buffer is called during a resource bind phase, invoking a blit
|
||||
* command encoder to upload data would override the active encoder state being prepared.
|
||||
* Resource generation and data upload should happen earlier as a resource is bound. */
|
||||
BLI_assert_msg(metal_buffer_ != nullptr,
|
||||
"Storage Buffer backing resource does not yet exist. Ensure the resource is "
|
||||
"bound with data before the calling code requires its underlying MTLBuffer.");
|
||||
BLI_assert_msg(
|
||||
data_ == nullptr,
|
||||
"Storage Buffer backing resource data has not yet been uploaded. Ensure the resource is "
|
||||
"bound with data before the calling code requires its underlying MTLBuffer.");
|
||||
source_buffer = metal_buffer_;
|
||||
} break;
|
||||
/* SSBO buffer comes from Uniform Buffer. */
|
||||
case MTL_STORAGE_BUF_TYPE_UNIFORMBUF: {
|
||||
source_buffer = uniform_buffer_->metal_buffer_;
|
||||
} break;
|
||||
/* SSBO buffer comes from Vertex Buffer. */
|
||||
case MTL_STORAGE_BUF_TYPE_VERTBUF: {
|
||||
source_buffer = vertex_buffer_->vbo_;
|
||||
} break;
|
||||
/* SSBO buffer comes from Index Buffer. */
|
||||
case MTL_STORAGE_BUF_TYPE_INDEXBUF: {
|
||||
source_buffer = index_buffer_->ibo_;
|
||||
} break;
|
||||
/* SSBO buffer comes from Texture. */
|
||||
case MTL_STORAGE_BUF_TYPE_TEXTURE: {
|
||||
BLI_assert(texture_);
|
||||
/* Fetch metal texture to ensure it has been initialized. */
|
||||
id<MTLTexture> tex = texture_->get_metal_handle_base();
|
||||
BLI_assert(tex != nil);
|
||||
UNUSED_VARS_NDEBUG(tex);
|
||||
source_buffer = texture_->backing_buffer_;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return Metal allocation handle and flag as used. */
|
||||
BLI_assert(source_buffer != nullptr);
|
||||
source_buffer->debug_ensure_used();
|
||||
return source_buffer->get_metal_buffer();
|
||||
}
|
||||
|
||||
size_t MTLStorageBuf::get_size()
|
||||
{
|
||||
BLI_assert(this);
|
||||
return size_in_bytes_;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::gpu
|
||||
709
blender-5.2.0/source/blender/gpu/metal/mtl_texture.hh
Normal file
709
blender-5.2.0/source/blender/gpu/metal/mtl_texture.hh
Normal file
@@ -0,0 +1,709 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_map.hh"
|
||||
#include "GPU_texture.hh"
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "gpu_texture_private.hh"
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <Metal/Metal.h>
|
||||
#include <QuartzCore/QuartzCore.h>
|
||||
|
||||
@class CAMetalLayer;
|
||||
@class MTLCommandQueue;
|
||||
@class MTLRenderPipelineState;
|
||||
|
||||
namespace blender {
|
||||
|
||||
namespace gpu {
|
||||
class FrameBuffer;
|
||||
} // namespace gpu
|
||||
|
||||
/* Texture Update system structs. */
|
||||
struct TextureUpdateRoutineSpecialisation {
|
||||
|
||||
/* The METAL type of data in input array, e.g. half, float, short, int */
|
||||
std::string input_data_type;
|
||||
|
||||
/* The type of the texture data texture2d<T,..>, e.g. T=float, half, int etc. */
|
||||
std::string output_data_type;
|
||||
|
||||
/* Number of image channels provided in input texture data array (min=1, max=4). */
|
||||
int component_count_input;
|
||||
|
||||
/* Number of channels the destination texture has (min=1, max=4). */
|
||||
int component_count_output;
|
||||
|
||||
/* Whether the update routine is a clear, and only the first texel of the input data buffer will
|
||||
* be read. */
|
||||
bool is_clear;
|
||||
|
||||
bool operator==(const TextureUpdateRoutineSpecialisation &other) const
|
||||
{
|
||||
return ((input_data_type == other.input_data_type) &&
|
||||
(output_data_type == other.output_data_type) &&
|
||||
(component_count_input == other.component_count_input) &&
|
||||
(component_count_output == other.component_count_output) &&
|
||||
(is_clear == other.is_clear));
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
DefaultHash<std::string> string_hasher;
|
||||
return uint64_t(string_hasher(this->input_data_type + this->output_data_type +
|
||||
std::to_string((this->component_count_input << 9) |
|
||||
(this->component_count_output << 5) |
|
||||
(this->is_clear ? 1 : 0))));
|
||||
}
|
||||
};
|
||||
|
||||
/* Type of data is being written to the depth target:
|
||||
* 0 = floating point (0.0 - 1.0)
|
||||
* 1 = 24 bit integer (0 - 2^24)
|
||||
* 2 = 32 bit integer (0 - 2^32) */
|
||||
|
||||
enum DepthTextureUpdateMode {
|
||||
MTL_DEPTH_UPDATE_MODE_FLOAT = 0,
|
||||
MTL_DEPTH_UPDATE_MODE_INT24 = 1,
|
||||
MTL_DEPTH_UPDATE_MODE_INT32 = 2
|
||||
};
|
||||
|
||||
struct DepthTextureUpdateRoutineSpecialisation {
|
||||
DepthTextureUpdateMode data_mode;
|
||||
|
||||
bool operator==(const DepthTextureUpdateRoutineSpecialisation &other) const
|
||||
{
|
||||
return ((data_mode == other.data_mode));
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return uint64_t(this->data_mode);
|
||||
}
|
||||
};
|
||||
|
||||
/* Texture Read system structs. */
|
||||
struct TextureReadRoutineSpecialisation {
|
||||
std::string input_data_type;
|
||||
std::string output_data_type;
|
||||
int component_count_input;
|
||||
int component_count_output;
|
||||
|
||||
/* Format for depth data.
|
||||
* 0 = Not a Depth format,
|
||||
* 1 = FLOAT DEPTH,
|
||||
* 2 = 24Bit Integer Depth,
|
||||
* 4 = 32bit Unsigned-Integer Depth. */
|
||||
int depth_format_mode;
|
||||
|
||||
bool operator==(const TextureReadRoutineSpecialisation &other) const
|
||||
{
|
||||
return ((input_data_type == other.input_data_type) &&
|
||||
(output_data_type == other.output_data_type) &&
|
||||
(component_count_input == other.component_count_input) &&
|
||||
(component_count_output == other.component_count_output) &&
|
||||
(depth_format_mode == other.depth_format_mode));
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
DefaultHash<std::string> string_hasher;
|
||||
return uint64_t(string_hasher(this->input_data_type + this->output_data_type +
|
||||
std::to_string((this->component_count_input << 8) +
|
||||
this->component_count_output +
|
||||
(this->depth_format_mode << 28))));
|
||||
}
|
||||
};
|
||||
|
||||
namespace gpu {
|
||||
|
||||
class MTLContext;
|
||||
class MTLVertBuf;
|
||||
class MTLStorageBuf;
|
||||
class MTLBuffer;
|
||||
|
||||
/* Metal Texture internal implementation. */
|
||||
static const int MTL_MAX_MIPMAP_COUNT = 15; /* Max: 16384x16384 */
|
||||
static const int MTL_MAX_FBO_ATTACHED = 16;
|
||||
|
||||
/* Samplers */
|
||||
struct MTLSamplerState {
|
||||
GPUSamplerState state;
|
||||
|
||||
/* Mip min and mip max on sampler state always the same.
|
||||
* Level range now controlled with textureView to be consistent with GL baseLevel. */
|
||||
bool operator==(const MTLSamplerState &other) const
|
||||
{
|
||||
/* Add other parameters as needed. */
|
||||
return (this->state == other.state);
|
||||
}
|
||||
|
||||
operator uint() const
|
||||
{
|
||||
uint integer_representation = 0;
|
||||
integer_representation |= this->state.filtering;
|
||||
integer_representation |= this->state.extend_x << 8;
|
||||
integer_representation |= this->state.extend_yz << 12;
|
||||
integer_representation |= this->state.custom_type << 16;
|
||||
integer_representation |= this->state.type << 24;
|
||||
return integer_representation;
|
||||
}
|
||||
|
||||
operator uint64_t() const
|
||||
{
|
||||
uint64_t integer_representation = 0;
|
||||
integer_representation |= this->state.filtering;
|
||||
integer_representation |= this->state.extend_x << 8;
|
||||
integer_representation |= this->state.extend_yz << 12;
|
||||
integer_representation |= this->state.custom_type << 16;
|
||||
integer_representation |= this->state.type << 24;
|
||||
return integer_representation;
|
||||
}
|
||||
};
|
||||
|
||||
const MTLSamplerState DEFAULT_SAMPLER_STATE = {GPUSamplerState::default_sampler() /* , 0, 9999 */};
|
||||
|
||||
class MTLTexture : public Texture {
|
||||
friend class MTLContext;
|
||||
friend class MTLStateManager;
|
||||
friend class MTLFrameBuffer;
|
||||
friend class MTLStorageBuf;
|
||||
friend class MTLTexturePool;
|
||||
|
||||
/* Special case: The XR blitting function needs access to the Metal blit encoder and handles
|
||||
* for directly blitting from an UNORM to an SRGB texture without color space conversion. */
|
||||
friend void xr_blit(id<MTLTexture> metal_xr_texture, int ofsx, int ofsy, int width, int height);
|
||||
|
||||
private:
|
||||
/* Where the textures data comes from. */
|
||||
enum {
|
||||
MTL_TEXTURE_MODE_DEFAULT, /* Texture is self-initialized (Standard). */
|
||||
MTL_TEXTURE_MODE_EXTERNAL, /* Texture source from external id<MTLTexture> handle */
|
||||
MTL_TEXTURE_MODE_VBO, /* Texture source initialized from VBO */
|
||||
MTL_TEXTURE_MODE_TEXTURE_VIEW /* Texture is a view into an existing texture. */
|
||||
} resource_mode_;
|
||||
|
||||
/* 'baking' refers to the generation of GPU-backed resources. This flag ensures GPU resources are
|
||||
* ready. Baking is generally deferred until as late as possible, to ensure all associated
|
||||
* resource state has been specified up-front. */
|
||||
bool is_baked_ = false;
|
||||
MTLTextureDescriptor *texture_descriptor_ = nullptr;
|
||||
id<MTLTexture> texture_ = nil;
|
||||
|
||||
/* Texture Storage. */
|
||||
size_t aligned_w_ = 0;
|
||||
|
||||
/* Storage buffer view.
|
||||
* Buffer backed textures can be wrapped with a storage buffer instance for direct data
|
||||
* reading/writing. Required for atomic operations on texture data when texture atomics are
|
||||
* unsupported.
|
||||
*
|
||||
* tex_buffer_metadata_ packs 4 parameters required by the shader to perform texture space
|
||||
* remapping: (x, y, z) = (width, height, depth/layers) (w) = aligned width. */
|
||||
MTLBuffer *backing_buffer_ = nullptr;
|
||||
MTLStorageBuf *storage_buffer_ = nullptr;
|
||||
int tex_buffer_metadata_[4];
|
||||
|
||||
/* Blit Frame-buffer. */
|
||||
gpu::FrameBuffer *blit_fb_ = nullptr;
|
||||
uint blit_fb_slice_ = 0;
|
||||
uint blit_fb_mip_ = 0;
|
||||
|
||||
/* Non-SRGB texture view, used for when a framebuffer is bound with SRGB disabled. */
|
||||
id<MTLTexture> texture_no_srgb_ = nil;
|
||||
|
||||
/* Texture view properties */
|
||||
/* In Metal, we use texture views to either limit mipmap ranges,
|
||||
* , apply a swizzle mask, or both.
|
||||
*
|
||||
* We apply the mip limit in the view rather than in the sampler, as
|
||||
* certain effects and functionality such as textureSize rely on the base level
|
||||
* being modified.
|
||||
*
|
||||
* Texture views can also point to external textures, rather than the owned
|
||||
* texture if MTL_TEXTURE_MODE_TEXTURE_VIEW is used.
|
||||
* If this mode is used, source_texture points to a gpu::Texture from which
|
||||
* we pull their texture handle as a root.
|
||||
*/
|
||||
const gpu::Texture *source_texture_ = nullptr;
|
||||
|
||||
enum TextureViewDirtyState {
|
||||
TEXTURE_VIEW_NOT_DIRTY = 0,
|
||||
TEXTURE_VIEW_SWIZZLE_DIRTY = (1 << 0),
|
||||
TEXTURE_VIEW_MIP_DIRTY = (1 << 1)
|
||||
};
|
||||
id<MTLTexture> mip_swizzle_view_ = nil;
|
||||
char tex_swizzle_mask_[4];
|
||||
MTLTextureSwizzleChannels mtl_swizzle_mask_;
|
||||
bool mip_range_dirty_ = false;
|
||||
|
||||
bool texture_view_stencil_ = false;
|
||||
int mip_texture_base_level_ = 0;
|
||||
int mip_texture_max_level_ = 1000;
|
||||
int mip_texture_base_layer_ = 0;
|
||||
int texture_view_dirty_flags_ = TEXTURE_VIEW_NOT_DIRTY;
|
||||
|
||||
/* Max mip-maps for currently allocated texture resource. */
|
||||
int mtl_max_mips_ = 1;
|
||||
bool has_generated_mips_ = false;
|
||||
|
||||
/* We may modify the requested usage flags so store them separately. */
|
||||
eGPUTextureUsage internal_gpu_image_usage_flags_;
|
||||
|
||||
/* VBO. */
|
||||
MTLVertBuf *vert_buffer_;
|
||||
id<MTLBuffer> vert_buffer_mtl_;
|
||||
|
||||
/* Whether the texture's properties or state has changed (e.g. mipmap range), and re-baking of
|
||||
* GPU resource is required. */
|
||||
bool is_dirty_;
|
||||
|
||||
public:
|
||||
MTLTexture(const char *name);
|
||||
MTLTexture(const char *name,
|
||||
TextureFormat format,
|
||||
GPUTextureType type,
|
||||
id<MTLTexture> metal_texture);
|
||||
~MTLTexture() override;
|
||||
|
||||
void update_sub(int mip,
|
||||
int offset[3],
|
||||
int extent[3],
|
||||
eGPUDataFormat type,
|
||||
const void *data,
|
||||
const uint unpack_row_length) override;
|
||||
void update_sub(int offset[3],
|
||||
int extent[3],
|
||||
eGPUDataFormat format,
|
||||
GPUPixelBuffer *pixbuf) override;
|
||||
|
||||
void generate_mipmap() override;
|
||||
void copy_to(Texture *dst, IndexRange mip_levels) override;
|
||||
void clear(const double4 data) override;
|
||||
void swizzle_set(const char swizzle_mask[4]) override;
|
||||
void mip_range_set(int min, int max) override;
|
||||
void read(int mip, eGPUDataFormat type, void *data) override;
|
||||
|
||||
bool is_format_srgb();
|
||||
bool texture_is_baked();
|
||||
const char *get_name()
|
||||
{
|
||||
return name_.c_str();
|
||||
}
|
||||
|
||||
bool has_custom_swizzle()
|
||||
{
|
||||
return (mtl_swizzle_mask_.red != MTLTextureSwizzleRed ||
|
||||
mtl_swizzle_mask_.green != MTLTextureSwizzleGreen ||
|
||||
mtl_swizzle_mask_.blue != MTLTextureSwizzleBlue ||
|
||||
mtl_swizzle_mask_.alpha != MTLTextureSwizzleAlpha);
|
||||
}
|
||||
|
||||
id<MTLBuffer> get_vertex_buffer() const
|
||||
{
|
||||
if (resource_mode_ == MTL_TEXTURE_MODE_VBO) {
|
||||
return vert_buffer_mtl_;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
MTLStorageBuf *get_storagebuf();
|
||||
|
||||
const int *get_texture_metadata_ptr() const
|
||||
{
|
||||
return tex_buffer_metadata_;
|
||||
}
|
||||
|
||||
id<MTLTexture> get_metal_handle();
|
||||
id<MTLTexture> get_metal_handle_base();
|
||||
id<MTLTexture> get_non_srgb_handle();
|
||||
MTLSamplerState get_sampler_state();
|
||||
|
||||
protected:
|
||||
bool init_internal() override;
|
||||
bool init_internal(VertBuf *vbo) override;
|
||||
bool init_internal(gpu::Texture *src,
|
||||
int mip_offset,
|
||||
int layer_offset,
|
||||
bool use_stencil) override; /* Texture View */
|
||||
|
||||
private:
|
||||
/* Common Constructor, default initialization. */
|
||||
void mtl_texture_init();
|
||||
|
||||
/* Post-construction and member initialization, prior to baking.
|
||||
* Called during init_internal */
|
||||
void prepare_internal();
|
||||
|
||||
/* Generate Metal GPU resources and upload data if needed */
|
||||
void ensure_baked();
|
||||
|
||||
/* Delete associated Metal GPU resources. */
|
||||
void reset();
|
||||
void ensure_mipmaps(int miplvl);
|
||||
|
||||
/* Flags a given mip level as being used. */
|
||||
void add_subresource(uint level);
|
||||
|
||||
void read_internal(int mip,
|
||||
int x_off,
|
||||
int y_off,
|
||||
int z_off,
|
||||
int width,
|
||||
int height,
|
||||
int depth,
|
||||
eGPUDataFormat desired_output_format,
|
||||
int num_output_components,
|
||||
size_t debug_data_size,
|
||||
void *r_data);
|
||||
void bake_mip_swizzle_view();
|
||||
|
||||
void blit(id<MTLBlitCommandEncoder> blit_encoder,
|
||||
uint src_x_offset,
|
||||
uint src_y_offset,
|
||||
uint src_z_offset,
|
||||
uint src_slice,
|
||||
uint src_mip,
|
||||
gpu::MTLTexture *dst,
|
||||
uint dst_x_offset,
|
||||
uint dst_y_offset,
|
||||
uint dst_z_offset,
|
||||
uint dst_slice,
|
||||
uint dst_mip,
|
||||
uint width,
|
||||
uint height,
|
||||
uint depth);
|
||||
void blit(gpu::MTLTexture *dst,
|
||||
uint src_x_offset,
|
||||
uint src_y_offset,
|
||||
uint dst_x_offset,
|
||||
uint dst_y_offset,
|
||||
uint src_mip,
|
||||
uint dst_mip,
|
||||
uint dst_slice,
|
||||
int width,
|
||||
int height);
|
||||
gpu::FrameBuffer *get_blit_framebuffer(int dst_slice, uint dst_mip);
|
||||
/* Texture Update function Utilities. */
|
||||
/* Metal texture updating does not provide the same range of functionality for type conversion
|
||||
* and format compatibility as are available in OpenGL. To achieve the same level of
|
||||
* functionality, we need to instead use compute kernels to perform texture data conversions
|
||||
* where appropriate.
|
||||
* There are a number of different inputs which affect permutations and thus require different
|
||||
* shaders and PSOs, such as:
|
||||
* - Texture format
|
||||
* - Texture type (e.g. 2D, 3D, 2D Array, Depth etc;)
|
||||
* - Source data format and component count (e.g. floating point)
|
||||
*
|
||||
* MECHANISM:
|
||||
*
|
||||
* map<INPUT DEFINES STRUCT, compute PSO> update_2d_array_kernel_psos;
|
||||
* - Generate compute shader with configured kernel below with variable parameters depending
|
||||
* on input/output format configurations. Do not need to keep source or descriptors around,
|
||||
* just PSO, as same input defines will always generate the same code.
|
||||
*
|
||||
* - IF datatype IS an exact match e.g. :
|
||||
* - Per-component size matches (e.g. GPU_DATA_UBYTE)
|
||||
* OR GPU_DATA_10_11_11_REV && GPU_R11G11B10 (equiv)
|
||||
* OR D24S8 and GPU_DATA_UINT_24_8_DEPRECATED
|
||||
* We can use BLIT ENCODER.
|
||||
*
|
||||
* OTHERWISE TRIGGER COMPUTE:
|
||||
* - Compute sizes will vary. Threads per grid WILL match 'extent'.
|
||||
* Dimensions will vary depending on texture type.
|
||||
* - Will use setBytes with 'TextureUpdateParams' struct to pass in useful member params.
|
||||
*/
|
||||
struct TextureUpdateParams {
|
||||
int mip_index;
|
||||
int extent[3]; /* Width, Height, Slice on 2D Array tex. */
|
||||
int offset[3]; /* Width, Height, Slice on 2D Array tex. */
|
||||
int unpack_row_length; /* Number of pixels between bytes in input data. */
|
||||
};
|
||||
|
||||
id<MTLComputePipelineState> texture_update_1d_get_kernel(
|
||||
TextureUpdateRoutineSpecialisation specialization);
|
||||
id<MTLComputePipelineState> texture_update_1d_array_get_kernel(
|
||||
TextureUpdateRoutineSpecialisation specialization);
|
||||
id<MTLComputePipelineState> texture_update_2d_get_kernel(
|
||||
TextureUpdateRoutineSpecialisation specialization);
|
||||
id<MTLComputePipelineState> texture_update_2d_array_get_kernel(
|
||||
TextureUpdateRoutineSpecialisation specialization);
|
||||
id<MTLComputePipelineState> texture_update_3d_get_kernel(
|
||||
TextureUpdateRoutineSpecialisation specialization);
|
||||
|
||||
id<MTLComputePipelineState> mtl_texture_update_impl(
|
||||
TextureUpdateRoutineSpecialisation specialization_params,
|
||||
Map<TextureUpdateRoutineSpecialisation, id<MTLComputePipelineState>> &specialization_cache,
|
||||
GPUTextureType texture_type);
|
||||
|
||||
/* Depth Update Utilities */
|
||||
/* Depth texture updates are not directly supported with Blit operations, similarly, we cannot
|
||||
* use a compute shader to write to depth, so we must instead render to a depth target.
|
||||
* These processes use vertex/fragment shaders to render texture data from an intermediate
|
||||
* source, in order to prime the depth buffer. */
|
||||
gpu::Shader *depth_2d_update_sh_get(DepthTextureUpdateRoutineSpecialisation specialization);
|
||||
|
||||
void update_sub_depth_2d(
|
||||
int mip, int offset[3], int extent[3], eGPUDataFormat type, const void *data);
|
||||
|
||||
/* Texture Read function utilities -- Follows a similar mechanism to the updating routines */
|
||||
struct TextureReadParams {
|
||||
int mip_index;
|
||||
int extent[3]; /* Width, Height, Slice on 2D Array tex. */
|
||||
int offset[3]; /* Width, Height, Slice on 2D Array tex. */
|
||||
};
|
||||
|
||||
id<MTLComputePipelineState> texture_read_1d_get_kernel(
|
||||
TextureReadRoutineSpecialisation specialization);
|
||||
id<MTLComputePipelineState> texture_read_1d_array_get_kernel(
|
||||
TextureReadRoutineSpecialisation specialization);
|
||||
id<MTLComputePipelineState> texture_read_2d_get_kernel(
|
||||
TextureReadRoutineSpecialisation specialization);
|
||||
id<MTLComputePipelineState> texture_read_2d_array_get_kernel(
|
||||
TextureReadRoutineSpecialisation specialization);
|
||||
id<MTLComputePipelineState> texture_read_3d_get_kernel(
|
||||
TextureReadRoutineSpecialisation specialization);
|
||||
|
||||
id<MTLComputePipelineState> mtl_texture_read_impl(
|
||||
TextureReadRoutineSpecialisation specialization_params,
|
||||
Map<TextureReadRoutineSpecialisation, id<MTLComputePipelineState>> &specialization_cache,
|
||||
GPUTextureType texture_type);
|
||||
|
||||
/* fullscreen blit utilities. */
|
||||
gpu::Shader *fullscreen_blit_sh_get();
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLTexture")
|
||||
};
|
||||
|
||||
class MTLPixelBuffer : public PixelBuffer {
|
||||
private:
|
||||
id<MTLBuffer> buffer_ = nil;
|
||||
|
||||
public:
|
||||
MTLPixelBuffer(size_t size);
|
||||
~MTLPixelBuffer();
|
||||
|
||||
void *map() override;
|
||||
void unmap() override;
|
||||
GPUPixelBufferNativeHandle get_native_handle() override;
|
||||
size_t get_size() override;
|
||||
|
||||
id<MTLBuffer> get_metal_buffer();
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLPixelBuffer")
|
||||
};
|
||||
|
||||
/* Utility */
|
||||
MTLPixelFormat gpu_texture_format_to_metal(TextureFormat tex_format);
|
||||
size_t get_mtl_format_bytesize(MTLPixelFormat tex_format);
|
||||
int get_mtl_format_num_components(MTLPixelFormat tex_format);
|
||||
bool mtl_format_supports_blending(MTLPixelFormat format);
|
||||
|
||||
/* The type used to define the per-component data in the input buffer. */
|
||||
inline std::string tex_data_format_to_msl_type_str(eGPUDataFormat type)
|
||||
{
|
||||
switch (type) {
|
||||
case GPU_DATA_FLOAT:
|
||||
return "float";
|
||||
case GPU_DATA_HALF_FLOAT:
|
||||
return "half";
|
||||
case GPU_DATA_INT:
|
||||
return "int";
|
||||
case GPU_DATA_UINT:
|
||||
return "uint";
|
||||
case GPU_DATA_UBYTE:
|
||||
return "uchar";
|
||||
case GPU_DATA_UINT_24_8_DEPRECATED:
|
||||
return "uint"; /* Problematic type - but will match alignment. */
|
||||
case GPU_DATA_10_11_11_REV:
|
||||
case GPU_DATA_2_10_10_10_REV:
|
||||
return "float"; /* Problematic type - each component will be read as a float. */
|
||||
default:
|
||||
BLI_assert(false);
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/* The type T which goes into texture2d<T, access>. */
|
||||
inline std::string tex_data_format_to_msl_texture_template_type(eGPUDataFormat type)
|
||||
{
|
||||
switch (type) {
|
||||
case GPU_DATA_FLOAT:
|
||||
return "float";
|
||||
case GPU_DATA_HALF_FLOAT:
|
||||
return "half";
|
||||
case GPU_DATA_INT:
|
||||
return "int";
|
||||
case GPU_DATA_UINT:
|
||||
return "uint";
|
||||
case GPU_DATA_UBYTE:
|
||||
return "ushort";
|
||||
case GPU_DATA_UINT_24_8_DEPRECATED:
|
||||
return "uint"; /* Problematic type. */
|
||||
case GPU_DATA_10_11_11_REV:
|
||||
case GPU_DATA_2_10_10_10_REV:
|
||||
return "float"; /* Problematic type. */
|
||||
default:
|
||||
BLI_assert(false);
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/* Fetch Metal texture type from GPU texture type. */
|
||||
inline MTLTextureType to_metal_type(GPUTextureType type)
|
||||
{
|
||||
switch (type) {
|
||||
case GPU_TEXTURE_1D:
|
||||
return MTLTextureType1D;
|
||||
case GPU_TEXTURE_2D:
|
||||
return MTLTextureType2D;
|
||||
case GPU_TEXTURE_3D:
|
||||
return MTLTextureType3D;
|
||||
case GPU_TEXTURE_CUBE:
|
||||
return MTLTextureTypeCube;
|
||||
case GPU_TEXTURE_BUFFER:
|
||||
return MTLTextureTypeTextureBuffer;
|
||||
case GPU_TEXTURE_1D_ARRAY:
|
||||
return MTLTextureType1DArray;
|
||||
case GPU_TEXTURE_2D_ARRAY:
|
||||
return MTLTextureType2DArray;
|
||||
case GPU_TEXTURE_CUBE_ARRAY:
|
||||
return MTLTextureTypeCubeArray;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
return MTLTextureType2D;
|
||||
}
|
||||
|
||||
/* Determine whether format is writable or not. Use mtl_format_get_writeable_view_format(..) for
|
||||
* these. */
|
||||
inline bool mtl_format_is_writable(MTLPixelFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case MTLPixelFormatRGBA8Unorm_sRGB:
|
||||
case MTLPixelFormatBGRA8Unorm_sRGB:
|
||||
case MTLPixelFormatDepth16Unorm:
|
||||
case MTLPixelFormatDepth32Float:
|
||||
case MTLPixelFormatDepth32Float_Stencil8:
|
||||
case MTLPixelFormatBGR10A2Unorm:
|
||||
case MTLPixelFormatDepth24Unorm_Stencil8:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* For the cases where a texture format is unwritable, we can create a texture view of a similar
|
||||
* format */
|
||||
inline MTLPixelFormat mtl_format_get_writeable_view_format(MTLPixelFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case MTLPixelFormatRGBA8Unorm_sRGB:
|
||||
return MTLPixelFormatRGBA8Unorm;
|
||||
case MTLPixelFormatBGRA8Unorm_sRGB:
|
||||
return MTLPixelFormatBGRA8Unorm;
|
||||
case MTLPixelFormatDepth16Unorm:
|
||||
return MTLPixelFormatR16Unorm;
|
||||
case MTLPixelFormatDepth32Float:
|
||||
return MTLPixelFormatR32Float;
|
||||
case MTLPixelFormatDepth32Float_Stencil8:
|
||||
// return MTLPixelFormatRG32Float;
|
||||
/* No alternative mirror format. This should not be used for
|
||||
* manual data upload */
|
||||
return MTLPixelFormatInvalid;
|
||||
case MTLPixelFormatBGR10A2Unorm:
|
||||
// return MTLPixelFormatBGRA8Unorm;
|
||||
/* No alternative mirror format. This should not be used for
|
||||
* manual data upload */
|
||||
return MTLPixelFormatInvalid;
|
||||
case MTLPixelFormatDepth24Unorm_Stencil8:
|
||||
/* No direct format, but we'll just mirror the bytes -- `Uint`
|
||||
* should ensure bytes are not re-normalized or manipulated */
|
||||
// return MTLPixelFormatR32Uint;
|
||||
return MTLPixelFormatInvalid;
|
||||
default:
|
||||
return format;
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
inline MTLTextureUsage mtl_usage_from_gpu(eGPUTextureUsage usage)
|
||||
{
|
||||
MTLTextureUsage mtl_usage = MTLTextureUsageUnknown;
|
||||
if (usage == GPU_TEXTURE_USAGE_GENERAL) {
|
||||
return MTLTextureUsageUnknown;
|
||||
}
|
||||
/* Host read implies general read support, as the compute-based host read routine requires
|
||||
* reading of texture data. */
|
||||
if (usage & GPU_TEXTURE_USAGE_SHADER_READ || usage & GPU_TEXTURE_USAGE_HOST_READ) {
|
||||
mtl_usage = mtl_usage | MTLTextureUsageShaderRead;
|
||||
}
|
||||
if (usage & GPU_TEXTURE_USAGE_SHADER_WRITE) {
|
||||
mtl_usage = mtl_usage | MTLTextureUsageShaderWrite;
|
||||
}
|
||||
if (usage & GPU_TEXTURE_USAGE_ATTACHMENT) {
|
||||
mtl_usage = mtl_usage | MTLTextureUsageRenderTarget;
|
||||
}
|
||||
if (usage & GPU_TEXTURE_USAGE_FORMAT_VIEW) {
|
||||
mtl_usage = mtl_usage | MTLTextureUsagePixelFormatView;
|
||||
}
|
||||
#if defined(MAC_OS_VERSION_14_0)
|
||||
if (@available(macOS 14.0, *)) {
|
||||
if (usage & GPU_TEXTURE_USAGE_ATOMIC) {
|
||||
mtl_usage = mtl_usage | MTLTextureUsageShaderAtomic;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return mtl_usage;
|
||||
}
|
||||
|
||||
inline eGPUTextureUsage gpu_usage_from_mtl(MTLTextureUsage mtl_usage)
|
||||
{
|
||||
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ;
|
||||
if (mtl_usage == MTLTextureUsageUnknown) {
|
||||
return GPU_TEXTURE_USAGE_GENERAL;
|
||||
}
|
||||
if (mtl_usage & MTLTextureUsageShaderRead) {
|
||||
usage = usage | GPU_TEXTURE_USAGE_SHADER_READ;
|
||||
}
|
||||
if (mtl_usage & MTLTextureUsageShaderWrite) {
|
||||
usage = usage | GPU_TEXTURE_USAGE_SHADER_WRITE;
|
||||
}
|
||||
if (mtl_usage & MTLTextureUsageRenderTarget) {
|
||||
usage = usage | GPU_TEXTURE_USAGE_ATTACHMENT;
|
||||
}
|
||||
if (mtl_usage & MTLTextureUsagePixelFormatView) {
|
||||
usage = usage | GPU_TEXTURE_USAGE_FORMAT_VIEW;
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
|
||||
BLI_INLINE MTLTexture *unwrap(Texture *tex)
|
||||
{
|
||||
return static_cast<MTLTexture *>(tex);
|
||||
}
|
||||
|
||||
BLI_INLINE Texture *wrap(MTLTexture *texture)
|
||||
{
|
||||
return static_cast<Texture *>(texture);
|
||||
}
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace blender
|
||||
2621
blender-5.2.0/source/blender/gpu/metal/mtl_texture.mm
Normal file
2621
blender-5.2.0/source/blender/gpu/metal/mtl_texture.mm
Normal file
File diff suppressed because it is too large
Load Diff
85
blender-5.2.0/source/blender/gpu/metal/mtl_texture_pool.hh
Normal file
85
blender-5.2.0/source/blender/gpu/metal/mtl_texture_pool.hh
Normal file
@@ -0,0 +1,85 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gpu_texture_pool_private.hh"
|
||||
#include "mtl_texture.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
class MTLTexturePool : public TexturePoolBase {
|
||||
/* Defer deallocation enough cycles to avoid interleaved calls to different viewport render
|
||||
* functions (selection / display) causing constant allocation / deallocation (See #113024). */
|
||||
static constexpr int max_unused_cycles_ = 8;
|
||||
|
||||
struct AllocationHandle {
|
||||
MTLTexture *texture = nullptr;
|
||||
|
||||
/* Counter to track the number of unused cycles before deallocation in `pool_`. */
|
||||
int unused_cycles_count = 0;
|
||||
};
|
||||
|
||||
struct TextureHandle {
|
||||
MTLTexture *view = nullptr;
|
||||
MTLTexture *texture = nullptr;
|
||||
|
||||
/* Counter to track texture acquire/retain mismatches in `acquire_`. */
|
||||
int users_count = 1;
|
||||
|
||||
/* We use the pointer as hash/comparator, as a texture cannot be acquired twice. */
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(view);
|
||||
}
|
||||
|
||||
bool operator==(const TextureHandle &o) const
|
||||
{
|
||||
return view == o.view;
|
||||
}
|
||||
};
|
||||
|
||||
Vector<AllocationHandle> pool_;
|
||||
Set<TextureHandle> acquired_;
|
||||
|
||||
/* Debug storage to log memory usage. Log is only output
|
||||
* if values have changed since the last `::reset()`. */
|
||||
struct LogUsageData {
|
||||
int64_t usage_count = 0;
|
||||
int64_t usage_count_max = 0;
|
||||
|
||||
bool operator==(const LogUsageData &o) const
|
||||
{
|
||||
return std::tie(usage_count, usage_count_max) == std::tie(o.usage_count, o.usage_count_max);
|
||||
}
|
||||
};
|
||||
LogUsageData previous_usage_data_ = {};
|
||||
LogUsageData current_usage_data_ = {};
|
||||
|
||||
/* Output usage data to debug log. Called on `--debug-gpu` */
|
||||
void log_usage_data() const;
|
||||
|
||||
protected:
|
||||
Texture *acquire_texture_impl(int3 extent,
|
||||
int mip_len,
|
||||
GPUTextureType type,
|
||||
TextureFormat format,
|
||||
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_GENERAL,
|
||||
const char *name = nullptr) override;
|
||||
|
||||
public:
|
||||
~MTLTexturePool();
|
||||
|
||||
void release_texture(Texture *tex) override;
|
||||
|
||||
void reset(bool force_free = false) override;
|
||||
|
||||
void offset_users_count(Texture *tex, int offset) override;
|
||||
};
|
||||
|
||||
} // namespace blender::gpu
|
||||
298
blender-5.2.0/source/blender/gpu/metal/mtl_texture_pool.mm
Normal file
298
blender-5.2.0/source/blender/gpu/metal/mtl_texture_pool.mm
Normal file
@@ -0,0 +1,298 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "BKE_global.hh"
|
||||
|
||||
#include "mtl_backend.hh"
|
||||
#include "mtl_texture_pool.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
static CLG_LogRef LOG = {"gpu.metal"};
|
||||
|
||||
/* Given if the formats can alias. */
|
||||
static bool check_texture_format_compatability(TextureFormat src_format, TextureFormat dst_format)
|
||||
{
|
||||
/* If the format matches we're good. */
|
||||
if (src_format == dst_format) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Metal's newTextureViewWithPixelFormat doesn't support aliasing on depth, stencil,
|
||||
* or compressed formats. */
|
||||
GPUTextureFormatFlag src_format_flag = to_format_flag(src_format);
|
||||
if (bool(src_format_flag & (GPU_FORMAT_DEPTH_STENCIL | GPU_FORMAT_COMPRESSED))) {
|
||||
return false;
|
||||
}
|
||||
GPUTextureFormatFlag dst_format_flag = to_format_flag(dst_format);
|
||||
if (bool(dst_format_flag & (GPU_FORMAT_DEPTH_STENCIL | GPU_FORMAT_COMPRESSED))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Metal docs say: All 8-, 16-, 32-, 64-, and 128-bit color formats are compatible
|
||||
* with other formats with the same bit length. */
|
||||
return to_bytesize(src_format) == to_bytesize(dst_format);
|
||||
}
|
||||
|
||||
MTLTexturePool::~MTLTexturePool()
|
||||
{
|
||||
for (const TextureHandle &handle : acquired_) {
|
||||
release_texture(wrap(handle.view));
|
||||
}
|
||||
for (AllocationHandle &handle : pool_) {
|
||||
GPU_texture_free(handle.texture);
|
||||
}
|
||||
}
|
||||
|
||||
Texture *MTLTexturePool::acquire_texture_impl(int3 extent,
|
||||
int mip_len,
|
||||
GPUTextureType type,
|
||||
TextureFormat format,
|
||||
eGPUTextureUsage usage,
|
||||
const char *name)
|
||||
{
|
||||
eGPUTextureUsage usage_flag = usage | GPU_TEXTURE_USAGE_FORMAT_VIEW;
|
||||
|
||||
/* Search for the first compatible existing texture. */
|
||||
int64_t match_index = -1;
|
||||
bool exact_format_match = false;
|
||||
for (uint64_t i : pool_.index_range()) {
|
||||
const AllocationHandle &handle = pool_[i];
|
||||
/* Do dimensions match? */
|
||||
if (int3(handle.texture->w_, handle.texture->h_, handle.texture->d_) != extent) {
|
||||
/* TODO(not_mark): sub-view on `texture->d_`. */
|
||||
continue;
|
||||
}
|
||||
if (handle.texture->mip_count() != mip_len) {
|
||||
/* TODO(not_mark): sub-view on mip levels. */
|
||||
continue;
|
||||
}
|
||||
/* Are the formats compatible? */
|
||||
if (!check_texture_format_compatability(format, handle.texture->format_get())) {
|
||||
continue;
|
||||
}
|
||||
/* Metal enforces usage flags at texture creation time, so the pooled texture
|
||||
* must have been created with at least all the usage flags the caller needs. */
|
||||
if ((handle.texture->usage_get() & usage_flag) != usage_flag) {
|
||||
continue;
|
||||
}
|
||||
/* Is the format an exact match? */
|
||||
exact_format_match = (format == handle.texture->format_get());
|
||||
|
||||
/* Do we need a writable texture? */
|
||||
bool writable_texture = usage &
|
||||
(GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_SHADER_WRITE);
|
||||
|
||||
/* Annoyingly Metal's newTextureViewWithPixelFormat strips MTLTextureUsageRenderTarget
|
||||
* and MTLTextureUsageShaderWrite from cross-format views (of course this is not
|
||||
* documented). If the caller needs either of these, only accept an exact format match
|
||||
* as in that case we don't actually create a view. */
|
||||
if (!exact_format_match && writable_texture) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match_index = i;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Return value. */
|
||||
TextureHandle texture_handle;
|
||||
|
||||
/* Acquire the compatible texture, or create a new one as a last resort. */
|
||||
if (match_index != -1) {
|
||||
texture_handle.texture = pool_[match_index].texture;
|
||||
pool_.remove_and_reorder(match_index);
|
||||
}
|
||||
else {
|
||||
/* Debug label attached to allocated texture object. */
|
||||
std::string texture_name_str;
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
texture_name_str = fmt::format("TexFromPool_{}", pool_.size());
|
||||
}
|
||||
|
||||
Texture *texture = GPUBackend::get()->texture_alloc(texture_name_str.c_str());
|
||||
texture->usage_set(usage | GPU_TEXTURE_USAGE_FORMAT_VIEW);
|
||||
bool texture_result = false;
|
||||
UNUSED_VARS_NDEBUG(texture_result);
|
||||
switch (type) {
|
||||
case GPU_TEXTURE_1D:
|
||||
case GPU_TEXTURE_1D_ARRAY:
|
||||
texture_result = texture->init_1D(extent.x, extent.y, mip_len, format);
|
||||
break;
|
||||
case GPU_TEXTURE_2D:
|
||||
case GPU_TEXTURE_2D_ARRAY:
|
||||
texture_result = texture->init_2D(extent.x, extent.y, extent.z, mip_len, format);
|
||||
break;
|
||||
case GPU_TEXTURE_3D:
|
||||
texture_result = texture->init_3D(extent.x, extent.y, extent.z, mip_len, format);
|
||||
break;
|
||||
case GPU_TEXTURE_CUBE:
|
||||
case GPU_TEXTURE_CUBE_ARRAY:
|
||||
texture_result = texture->init_cubemap(extent.x, extent.y, mip_len, format);
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
BLI_assert(texture_result);
|
||||
|
||||
texture_handle.texture = unwrap(texture);
|
||||
exact_format_match = true;
|
||||
}
|
||||
|
||||
/* On acquire, issue barriers; backing texture or view may still be in flight somewhere. */
|
||||
GPUBarrier barrier = {};
|
||||
if (usage & GPU_TEXTURE_USAGE_SHADER_READ) {
|
||||
barrier |= (GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_TEXTURE_FETCH);
|
||||
}
|
||||
if (usage & GPU_TEXTURE_USAGE_SHADER_WRITE) {
|
||||
barrier |= GPU_BARRIER_SHADER_IMAGE_ACCESS;
|
||||
}
|
||||
if (usage & GPU_TEXTURE_USAGE_ATTACHMENT) {
|
||||
barrier |= GPU_BARRIER_FRAMEBUFFER;
|
||||
}
|
||||
GPU_memory_barrier(barrier);
|
||||
|
||||
/* Only create a view if the format actually differs.
|
||||
* This removes the limitation that the view will strip
|
||||
* any render-target or shader-write usage flags. */
|
||||
if (exact_format_match) {
|
||||
/* No view needed; set view to texture so lookups in release_texture work
|
||||
* (the returned pointer is used as the key in the acquired set). */
|
||||
texture_handle.view = texture_handle.texture;
|
||||
}
|
||||
else {
|
||||
/* Debug label attached to view texture object. */
|
||||
std::string view_name_str;
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
view_name_str = name ? name : texture_handle.texture->name_;
|
||||
}
|
||||
|
||||
/* Assemble texture view and add to handle. */
|
||||
Texture *view = GPUBackend::get()->texture_alloc(view_name_str.c_str());
|
||||
bool view_result = false;
|
||||
UNUSED_VARS_NDEBUG(view_result);
|
||||
switch (type) {
|
||||
case GPU_TEXTURE_1D:
|
||||
case GPU_TEXTURE_2D:
|
||||
case GPU_TEXTURE_3D:
|
||||
case GPU_TEXTURE_CUBE:
|
||||
view_result = view->init_view(
|
||||
texture_handle.texture, format, type, 0, mip_len, 0, 1, false, false);
|
||||
break;
|
||||
case GPU_TEXTURE_1D_ARRAY:
|
||||
view_result = view->init_view(
|
||||
texture_handle.texture, format, type, 0, mip_len, 0, extent.y, false, false);
|
||||
break;
|
||||
case GPU_TEXTURE_2D_ARRAY:
|
||||
case GPU_TEXTURE_CUBE_ARRAY:
|
||||
view_result = view->init_view(
|
||||
texture_handle.texture, format, type, 0, mip_len, 0, extent.z, false, false);
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
BLI_assert(view_result);
|
||||
|
||||
texture_handle.view = unwrap(view);
|
||||
}
|
||||
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
current_usage_data_.usage_count++;
|
||||
current_usage_data_.usage_count_max = std::max(current_usage_data_.usage_count,
|
||||
current_usage_data_.usage_count_max);
|
||||
}
|
||||
|
||||
acquired_.add(texture_handle);
|
||||
return wrap(texture_handle.view);
|
||||
}
|
||||
|
||||
void MTLTexturePool::release_texture(Texture *tex)
|
||||
{
|
||||
BLI_assert_msg(acquired_.contains({unwrap(tex)}),
|
||||
"Unacquired texture passed to TexturePool::release_texture()");
|
||||
TextureHandle texture_handle = acquired_.lookup_key({unwrap(tex), {}, 1});
|
||||
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
current_usage_data_.usage_count--;
|
||||
}
|
||||
|
||||
/* Move allocation back to `pool_`. */
|
||||
AllocationHandle allocation_handle;
|
||||
allocation_handle.texture = texture_handle.texture;
|
||||
pool_.append(allocation_handle);
|
||||
|
||||
/* Destroy view if one was created (view equals texture when no aliasing was needed). */
|
||||
if (texture_handle.view && texture_handle.view != texture_handle.texture) {
|
||||
GPU_texture_free(texture_handle.view);
|
||||
}
|
||||
acquired_.remove(texture_handle);
|
||||
}
|
||||
|
||||
void MTLTexturePool::offset_users_count(Texture *tex, int offset)
|
||||
{
|
||||
BLI_assert_msg(acquired_.contains({unwrap(tex)}),
|
||||
"Unacquired texture passed to TexturePool::offset_users_count()");
|
||||
TextureHandle texture_handle = acquired_.lookup_key({unwrap(tex), {}, 1});
|
||||
texture_handle.users_count += offset;
|
||||
acquired_.add_overwrite(texture_handle);
|
||||
}
|
||||
|
||||
void MTLTexturePool::reset(bool force_free)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
/* Iterate acquired textures, and ensure the internal counter equals 0; otherwise
|
||||
* this indicates a missing `::retain()` or `::release()`. */
|
||||
for (const TextureHandle &tex : acquired_) {
|
||||
BLI_assert_msg(tex.users_count == 0,
|
||||
"Missing texture release/retain. Likely TextureFromPool::release(), "
|
||||
"TextureFromPool::retain() or TexturePool::release_texture().");
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Reverse iterate unused allocations, to make sure we only reorder known good handles. */
|
||||
for (int i = pool_.size() - 1; i >= 0; i--) {
|
||||
AllocationHandle &handle = pool_[i];
|
||||
if (handle.unused_cycles_count >= max_unused_cycles_ || force_free) {
|
||||
GPU_texture_free(handle.texture);
|
||||
pool_.remove_and_reorder(i);
|
||||
}
|
||||
else {
|
||||
handle.unused_cycles_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (G.debug & G_DEBUG_GPU) {
|
||||
/* Log debug usage if it differs from the last reset. */
|
||||
if (!(previous_usage_data_ == current_usage_data_)) {
|
||||
log_usage_data();
|
||||
}
|
||||
|
||||
/* Reset usage data to track it for the next reset. */
|
||||
previous_usage_data_ = current_usage_data_;
|
||||
current_usage_data_ = {};
|
||||
current_usage_data_.usage_count = acquired_.size();
|
||||
}
|
||||
}
|
||||
|
||||
void MTLTexturePool::log_usage_data() const
|
||||
{
|
||||
int64_t total_texture_count = acquired_.size() + pool_.size();
|
||||
CLOG_TRACE(&LOG,
|
||||
"MTLTexturePool uses %ld textures (%ld consecutively)",
|
||||
static_cast<long>(total_texture_count),
|
||||
static_cast<long>(current_usage_data_.usage_count_max));
|
||||
}
|
||||
} // namespace blender::gpu
|
||||
706
blender-5.2.0/source/blender/gpu/metal/mtl_texture_util.mm
Normal file
706
blender-5.2.0/source/blender/gpu/metal/mtl_texture_util.mm
Normal file
@@ -0,0 +1,706 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#include "BKE_global.hh"
|
||||
|
||||
#include "DNA_userdef_types.h"
|
||||
|
||||
#include "GPU_batch.hh"
|
||||
#include "GPU_batch_presets.hh"
|
||||
#include "GPU_capabilities.hh"
|
||||
#include "GPU_framebuffer.hh"
|
||||
#include "GPU_platform.hh"
|
||||
#include "GPU_state.hh"
|
||||
|
||||
#include "mtl_backend.hh"
|
||||
#include "mtl_context.hh"
|
||||
#include "mtl_texture.hh"
|
||||
|
||||
/* Utility file for secondary functionality which supports mtl_texture.mm. */
|
||||
|
||||
extern char datatoc_compute_texture_update_msl[];
|
||||
extern char datatoc_compute_texture_read_msl[];
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Texture Utility Functions
|
||||
* \{ */
|
||||
|
||||
MTLPixelFormat gpu_texture_format_to_metal(TextureFormat tex_format)
|
||||
{
|
||||
#define CASE(a, b, c, blender_enum, d, mtl_pixel_enum, f, g, h) \
|
||||
case TextureFormat::blender_enum: \
|
||||
return MTLPixelFormat##mtl_pixel_enum;
|
||||
|
||||
switch (tex_format) {
|
||||
GPU_TEXTURE_FORMAT_EXPAND(CASE)
|
||||
case TextureFormat::Invalid:
|
||||
break;
|
||||
}
|
||||
#undef CASE
|
||||
BLI_assert_msg(false, "Unrecognised GPU pixel format!\n");
|
||||
return MTLPixelFormatRGBA8Unorm;
|
||||
}
|
||||
|
||||
size_t get_mtl_format_bytesize(MTLPixelFormat tex_format)
|
||||
{
|
||||
switch (tex_format) {
|
||||
case MTLPixelFormatRGBA8Uint:
|
||||
case MTLPixelFormatRGBA8Sint:
|
||||
case MTLPixelFormatRGBA8Unorm:
|
||||
case MTLPixelFormatRGBA8Snorm:
|
||||
case MTLPixelFormatRGB10A2Uint:
|
||||
case MTLPixelFormatRGB10A2Unorm:
|
||||
return 4;
|
||||
case MTLPixelFormatRGBA32Uint:
|
||||
case MTLPixelFormatRGBA32Sint:
|
||||
case MTLPixelFormatRGBA32Float:
|
||||
return 16;
|
||||
case MTLPixelFormatRGBA16Uint:
|
||||
case MTLPixelFormatRGBA16Sint:
|
||||
case MTLPixelFormatRGBA16Float:
|
||||
case MTLPixelFormatRGBA16Unorm:
|
||||
case MTLPixelFormatRGBA16Snorm:
|
||||
return 8;
|
||||
case MTLPixelFormatRG8Uint:
|
||||
case MTLPixelFormatRG8Sint:
|
||||
case MTLPixelFormatRG8Unorm:
|
||||
case MTLPixelFormatRG8Snorm:
|
||||
case MTLPixelFormatRG8Unorm_sRGB:
|
||||
return 2;
|
||||
case MTLPixelFormatRG32Uint:
|
||||
case MTLPixelFormatRG32Sint:
|
||||
case MTLPixelFormatRG32Float:
|
||||
return 8;
|
||||
case MTLPixelFormatRG16Uint:
|
||||
case MTLPixelFormatRG16Sint:
|
||||
case MTLPixelFormatRG16Float:
|
||||
case MTLPixelFormatRG16Unorm:
|
||||
case MTLPixelFormatRG16Snorm:
|
||||
return 4;
|
||||
case MTLPixelFormatR8Uint:
|
||||
case MTLPixelFormatR8Sint:
|
||||
case MTLPixelFormatR8Unorm:
|
||||
case MTLPixelFormatR8Snorm:
|
||||
return 1;
|
||||
case MTLPixelFormatR32Uint:
|
||||
case MTLPixelFormatR32Sint:
|
||||
case MTLPixelFormatR32Float:
|
||||
return 4;
|
||||
case MTLPixelFormatR16Uint:
|
||||
case MTLPixelFormatR16Sint:
|
||||
case MTLPixelFormatR16Float:
|
||||
case MTLPixelFormatR16Snorm:
|
||||
case MTLPixelFormatR16Unorm:
|
||||
return 2;
|
||||
case MTLPixelFormatRG11B10Float:
|
||||
return 4;
|
||||
case MTLPixelFormatDepth32Float_Stencil8:
|
||||
return 8;
|
||||
case MTLPixelFormatRGBA8Unorm_sRGB:
|
||||
case MTLPixelFormatDepth32Float:
|
||||
case MTLPixelFormatDepth24Unorm_Stencil8:
|
||||
return 4;
|
||||
case MTLPixelFormatDepth16Unorm:
|
||||
return 2;
|
||||
case MTLPixelFormatBC1_RGBA:
|
||||
case MTLPixelFormatBC1_RGBA_sRGB:
|
||||
return 1; /* NOTE: not quite correct (BC1 is 0.5 BPP). */
|
||||
case MTLPixelFormatBC2_RGBA:
|
||||
case MTLPixelFormatBC2_RGBA_sRGB:
|
||||
case MTLPixelFormatBC3_RGBA:
|
||||
case MTLPixelFormatBC3_RGBA_sRGB:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
BLI_assert_msg(false, "Unrecognised GPU pixel format!\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int get_mtl_format_num_components(MTLPixelFormat tex_format)
|
||||
{
|
||||
switch (tex_format) {
|
||||
case MTLPixelFormatRGBA8Uint:
|
||||
case MTLPixelFormatRGBA8Sint:
|
||||
case MTLPixelFormatRGBA8Unorm:
|
||||
case MTLPixelFormatRGBA8Snorm:
|
||||
case MTLPixelFormatRGBA32Uint:
|
||||
case MTLPixelFormatRGBA32Sint:
|
||||
case MTLPixelFormatRGBA32Float:
|
||||
case MTLPixelFormatRGBA16Uint:
|
||||
case MTLPixelFormatRGBA16Sint:
|
||||
case MTLPixelFormatRGBA16Float:
|
||||
case MTLPixelFormatRGBA16Unorm:
|
||||
case MTLPixelFormatRGBA16Snorm:
|
||||
case MTLPixelFormatRGBA8Unorm_sRGB:
|
||||
case MTLPixelFormatRGB10A2Uint:
|
||||
case MTLPixelFormatRGB10A2Unorm:
|
||||
case MTLPixelFormatBC1_RGBA_sRGB:
|
||||
case MTLPixelFormatBC2_RGBA_sRGB:
|
||||
case MTLPixelFormatBC3_RGBA_sRGB:
|
||||
case MTLPixelFormatBC1_RGBA:
|
||||
case MTLPixelFormatBC2_RGBA:
|
||||
case MTLPixelFormatBC3_RGBA:
|
||||
return 4;
|
||||
|
||||
case MTLPixelFormatRG11B10Float:
|
||||
return 3;
|
||||
|
||||
case MTLPixelFormatRG8Uint:
|
||||
case MTLPixelFormatRG8Sint:
|
||||
case MTLPixelFormatRG8Unorm:
|
||||
case MTLPixelFormatRG32Uint:
|
||||
case MTLPixelFormatRG32Sint:
|
||||
case MTLPixelFormatRG32Float:
|
||||
case MTLPixelFormatRG16Uint:
|
||||
case MTLPixelFormatRG16Sint:
|
||||
case MTLPixelFormatRG16Float:
|
||||
case MTLPixelFormatDepth32Float_Stencil8:
|
||||
case MTLPixelFormatRG16Snorm:
|
||||
case MTLPixelFormatRG16Unorm:
|
||||
case MTLPixelFormatRG8Snorm:
|
||||
return 2;
|
||||
|
||||
case MTLPixelFormatR8Uint:
|
||||
case MTLPixelFormatR8Sint:
|
||||
case MTLPixelFormatR8Unorm:
|
||||
case MTLPixelFormatR8Snorm:
|
||||
case MTLPixelFormatR32Uint:
|
||||
case MTLPixelFormatR32Sint:
|
||||
case MTLPixelFormatR32Float:
|
||||
case MTLPixelFormatR16Uint:
|
||||
case MTLPixelFormatR16Sint:
|
||||
case MTLPixelFormatR16Float:
|
||||
case MTLPixelFormatR16Unorm:
|
||||
case MTLPixelFormatR16Snorm:
|
||||
case MTLPixelFormatDepth32Float:
|
||||
case MTLPixelFormatDepth16Unorm:
|
||||
case MTLPixelFormatDepth24Unorm_Stencil8:
|
||||
/* Treating this format as single-channel for direct data copies -- Stencil component is not
|
||||
* addressable. */
|
||||
return 1;
|
||||
|
||||
default:
|
||||
BLI_assert_msg(false, "Unrecognised GPU pixel format!\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool mtl_format_supports_blending(MTLPixelFormat format)
|
||||
{
|
||||
/* Add formats as needed -- Verify platforms. */
|
||||
const MTLCapabilities &capabilities = MTLBackend::get_capabilities();
|
||||
|
||||
if (capabilities.supports_family_mac1 || capabilities.supports_family_mac_catalyst1) {
|
||||
|
||||
switch (format) {
|
||||
case MTLPixelFormatA8Unorm:
|
||||
case MTLPixelFormatR8Uint:
|
||||
case MTLPixelFormatR8Sint:
|
||||
case MTLPixelFormatR16Uint:
|
||||
case MTLPixelFormatR16Sint:
|
||||
case MTLPixelFormatRG32Uint:
|
||||
case MTLPixelFormatRG32Sint:
|
||||
case MTLPixelFormatRGBA8Uint:
|
||||
case MTLPixelFormatRGBA8Sint:
|
||||
case MTLPixelFormatRGBA32Uint:
|
||||
case MTLPixelFormatRGBA32Sint:
|
||||
case MTLPixelFormatDepth16Unorm:
|
||||
case MTLPixelFormatDepth32Float:
|
||||
case MTLPixelFormatInvalid:
|
||||
case MTLPixelFormatBGR10A2Unorm:
|
||||
case MTLPixelFormatRGB10A2Uint:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (format) {
|
||||
case MTLPixelFormatA8Unorm:
|
||||
case MTLPixelFormatR8Uint:
|
||||
case MTLPixelFormatR8Sint:
|
||||
case MTLPixelFormatR16Uint:
|
||||
case MTLPixelFormatR16Sint:
|
||||
case MTLPixelFormatRG32Uint:
|
||||
case MTLPixelFormatRG32Sint:
|
||||
case MTLPixelFormatRGBA8Uint:
|
||||
case MTLPixelFormatRGBA8Sint:
|
||||
case MTLPixelFormatRGBA32Uint:
|
||||
case MTLPixelFormatRGBA32Sint:
|
||||
case MTLPixelFormatRGBA32Float:
|
||||
case MTLPixelFormatDepth16Unorm:
|
||||
case MTLPixelFormatDepth32Float:
|
||||
case MTLPixelFormatInvalid:
|
||||
case MTLPixelFormatBGR10A2Unorm:
|
||||
case MTLPixelFormatRGB10A2Uint:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Texture data upload routines
|
||||
* \{ */
|
||||
|
||||
id<MTLComputePipelineState> gpu::MTLTexture::mtl_texture_update_impl(
|
||||
TextureUpdateRoutineSpecialisation specialization_params,
|
||||
Map<TextureUpdateRoutineSpecialisation, id<MTLComputePipelineState>> &specialization_cache,
|
||||
GPUTextureType texture_type)
|
||||
{
|
||||
/* Check whether the Kernel exists. */
|
||||
id<MTLComputePipelineState> *result = specialization_cache.lookup_ptr(specialization_params);
|
||||
if (result != nullptr) {
|
||||
return *result;
|
||||
}
|
||||
|
||||
id<MTLComputePipelineState> return_pso = nil;
|
||||
@autoreleasepool {
|
||||
|
||||
/* Fetch active context. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
|
||||
/** SOURCE. **/
|
||||
NSString *tex_update_kernel_src = [NSString
|
||||
stringWithUTF8String:datatoc_compute_texture_update_msl];
|
||||
|
||||
/* Prepare options and specializations. */
|
||||
MTLCompileOptions *options = [[[MTLCompileOptions alloc] init] autorelease];
|
||||
options.languageVersion = MTLLanguageVersion2_2;
|
||||
options.preprocessorMacros = @{
|
||||
@"INPUT_DATA_TYPE" :
|
||||
[NSString stringWithUTF8String:specialization_params.input_data_type.c_str()],
|
||||
@"OUTPUT_DATA_TYPE" :
|
||||
[NSString stringWithUTF8String:specialization_params.output_data_type.c_str()],
|
||||
@"COMPONENT_COUNT_INPUT" :
|
||||
[NSNumber numberWithInt:specialization_params.component_count_input],
|
||||
@"COMPONENT_COUNT_OUTPUT" :
|
||||
[NSNumber numberWithInt:specialization_params.component_count_output],
|
||||
@"TEX_TYPE" : [NSNumber numberWithInt:((int)(texture_type))],
|
||||
@"IS_TEXTURE_CLEAR" :
|
||||
[NSNumber numberWithInt:((int)(specialization_params.is_clear ? 1 : 0))]
|
||||
};
|
||||
|
||||
/* Prepare shader library for conversion routine. */
|
||||
NSError *error = nullptr;
|
||||
id<MTLLibrary> temp_lib = [[ctx->device newLibraryWithSource:tex_update_kernel_src
|
||||
options:options
|
||||
error:&error] autorelease];
|
||||
if (error) {
|
||||
/* Only exit out if genuine error and not warning. */
|
||||
if ([[error localizedDescription] rangeOfString:@"Compilation succeeded"].location ==
|
||||
NSNotFound)
|
||||
{
|
||||
NSLog(@"Compile Error - Metal Shader Library error %@ ", error);
|
||||
BLI_assert(false);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fetch compute function. */
|
||||
BLI_assert(temp_lib != nil);
|
||||
id<MTLFunction> temp_compute_function = [[temp_lib
|
||||
newFunctionWithName:@"compute_texture_update"] autorelease];
|
||||
BLI_assert(temp_compute_function);
|
||||
|
||||
/* Otherwise, bake new Kernel. */
|
||||
id<MTLComputePipelineState> compute_pso = [ctx->device
|
||||
newComputePipelineStateWithFunction:temp_compute_function
|
||||
error:&error];
|
||||
if (error || compute_pso == nil) {
|
||||
NSLog(@"Failed to prepare texture_update MTLComputePipelineState %@", error);
|
||||
BLI_assert(false);
|
||||
}
|
||||
|
||||
/* Store PSO. */
|
||||
specialization_cache.add_new(specialization_params, compute_pso);
|
||||
return_pso = compute_pso;
|
||||
}
|
||||
|
||||
BLI_assert(return_pso != nil);
|
||||
return return_pso;
|
||||
}
|
||||
|
||||
id<MTLComputePipelineState> gpu::MTLTexture::texture_update_1d_get_kernel(
|
||||
TextureUpdateRoutineSpecialisation specialization)
|
||||
{
|
||||
MTLContext *mtl_context = MTLContext::get();
|
||||
BLI_assert(mtl_context != nullptr);
|
||||
return mtl_texture_update_impl(specialization,
|
||||
mtl_context->get_texture_utils().texture_1d_update_compute_psos,
|
||||
GPU_TEXTURE_1D);
|
||||
}
|
||||
|
||||
id<MTLComputePipelineState> gpu::MTLTexture::texture_update_1d_array_get_kernel(
|
||||
TextureUpdateRoutineSpecialisation specialization)
|
||||
{
|
||||
MTLContext *mtl_context = MTLContext::get();
|
||||
BLI_assert(mtl_context != nullptr);
|
||||
return mtl_texture_update_impl(
|
||||
specialization,
|
||||
mtl_context->get_texture_utils().texture_1d_array_update_compute_psos,
|
||||
GPU_TEXTURE_1D_ARRAY);
|
||||
}
|
||||
|
||||
id<MTLComputePipelineState> gpu::MTLTexture::texture_update_2d_get_kernel(
|
||||
TextureUpdateRoutineSpecialisation specialization)
|
||||
{
|
||||
MTLContext *mtl_context = MTLContext::get();
|
||||
BLI_assert(mtl_context != nullptr);
|
||||
return mtl_texture_update_impl(specialization,
|
||||
mtl_context->get_texture_utils().texture_2d_update_compute_psos,
|
||||
GPU_TEXTURE_2D);
|
||||
}
|
||||
|
||||
id<MTLComputePipelineState> gpu::MTLTexture::texture_update_2d_array_get_kernel(
|
||||
TextureUpdateRoutineSpecialisation specialization)
|
||||
{
|
||||
MTLContext *mtl_context = MTLContext::get();
|
||||
BLI_assert(mtl_context != nullptr);
|
||||
return mtl_texture_update_impl(
|
||||
specialization,
|
||||
mtl_context->get_texture_utils().texture_2d_array_update_compute_psos,
|
||||
GPU_TEXTURE_2D_ARRAY);
|
||||
}
|
||||
|
||||
id<MTLComputePipelineState> gpu::MTLTexture::texture_update_3d_get_kernel(
|
||||
TextureUpdateRoutineSpecialisation specialization)
|
||||
{
|
||||
MTLContext *mtl_context = MTLContext::get();
|
||||
BLI_assert(mtl_context != nullptr);
|
||||
return mtl_texture_update_impl(specialization,
|
||||
mtl_context->get_texture_utils().texture_3d_update_compute_psos,
|
||||
GPU_TEXTURE_3D);
|
||||
}
|
||||
|
||||
/* TODO(Metal): Data upload routine kernel for texture cube and texture cube array.
|
||||
* Currently does not appear to be hit. */
|
||||
|
||||
gpu::Shader *gpu::MTLTexture::depth_2d_update_sh_get(
|
||||
DepthTextureUpdateRoutineSpecialisation specialization)
|
||||
{
|
||||
|
||||
/* Check whether the Kernel exists. */
|
||||
MTLContext *mtl_context = MTLContext::get();
|
||||
BLI_assert(mtl_context != nullptr);
|
||||
|
||||
gpu::Shader **result = mtl_context->get_texture_utils().depth_2d_update_shaders.lookup_ptr(
|
||||
specialization);
|
||||
if (result != nullptr) {
|
||||
return *result;
|
||||
}
|
||||
|
||||
const char *depth_2d_info_variant = nullptr;
|
||||
switch (specialization.data_mode) {
|
||||
case MTL_DEPTH_UPDATE_MODE_FLOAT:
|
||||
depth_2d_info_variant = "depth_2d_update_float";
|
||||
break;
|
||||
case MTL_DEPTH_UPDATE_MODE_INT24:
|
||||
depth_2d_info_variant = "depth_2d_update_int24";
|
||||
break;
|
||||
case MTL_DEPTH_UPDATE_MODE_INT32:
|
||||
depth_2d_info_variant = "depth_2d_update_int32";
|
||||
break;
|
||||
default:
|
||||
BLI_assert(false && "Invalid format mode\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
gpu::Shader *shader = GPU_shader_create_from_info_name(depth_2d_info_variant);
|
||||
mtl_context->get_texture_utils().depth_2d_update_shaders.add_new(specialization, shader);
|
||||
return shader;
|
||||
}
|
||||
|
||||
gpu::Shader *gpu::MTLTexture::fullscreen_blit_sh_get()
|
||||
{
|
||||
MTLContext *mtl_context = MTLContext::get();
|
||||
BLI_assert(mtl_context != nullptr);
|
||||
if (mtl_context->get_texture_utils().fullscreen_blit_shader == nullptr) {
|
||||
gpu::Shader *shader = GPU_shader_create_from_info_name("fullscreen_blit");
|
||||
|
||||
mtl_context->get_texture_utils().fullscreen_blit_shader = shader;
|
||||
}
|
||||
return mtl_context->get_texture_utils().fullscreen_blit_shader;
|
||||
}
|
||||
|
||||
/* Special routine for updating 2D depth textures using the rendering pipeline. */
|
||||
void gpu::MTLTexture::update_sub_depth_2d(
|
||||
int mip, int offset[3], int extent[3], eGPUDataFormat type, const void *data)
|
||||
{
|
||||
/* Verify we are in a valid configuration. */
|
||||
BLI_assert(ELEM(format_,
|
||||
TextureFormat::SFLOAT_32_DEPTH,
|
||||
TextureFormat::UNORM_16_DEPTH,
|
||||
TextureFormat::SFLOAT_32_DEPTH_UINT_8));
|
||||
BLI_assert(validate_data_format(format_, type));
|
||||
BLI_assert(ELEM(type, GPU_DATA_FLOAT, GPU_DATA_UINT_24_8_DEPRECATED, GPU_DATA_UINT));
|
||||
|
||||
/* Determine whether we are in GPU_DATA_UINT_24_8_DEPRECATED or GPU_DATA_FLOAT mode. */
|
||||
bool is_float = (type == GPU_DATA_FLOAT);
|
||||
TextureFormat format = (is_float) ? TextureFormat::SFLOAT_32 : TextureFormat::SINT_32;
|
||||
|
||||
/* Shader key - Add parameters here for different configurations. */
|
||||
DepthTextureUpdateRoutineSpecialisation specialization;
|
||||
switch (type) {
|
||||
case GPU_DATA_FLOAT:
|
||||
specialization.data_mode = MTL_DEPTH_UPDATE_MODE_FLOAT;
|
||||
break;
|
||||
|
||||
case GPU_DATA_UINT_24_8_DEPRECATED:
|
||||
specialization.data_mode = MTL_DEPTH_UPDATE_MODE_INT24;
|
||||
break;
|
||||
|
||||
case GPU_DATA_UINT:
|
||||
specialization.data_mode = MTL_DEPTH_UPDATE_MODE_INT32;
|
||||
break;
|
||||
|
||||
default:
|
||||
BLI_assert_msg(false, "Unsupported eGPUDataFormat being passed to depth texture update\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Push contents into an r32_tex and render contents to depth using a shader. */
|
||||
gpu::Texture *r32_tex_tmp = GPU_texture_create_2d("depth_intermediate_copy_tex",
|
||||
w_,
|
||||
h_,
|
||||
1,
|
||||
format,
|
||||
GPU_TEXTURE_USAGE_SHADER_READ |
|
||||
GPU_TEXTURE_USAGE_ATTACHMENT,
|
||||
nullptr);
|
||||
GPU_texture_filter_mode(r32_tex_tmp, false);
|
||||
GPU_texture_extend_mode(r32_tex_tmp, GPU_SAMPLER_EXTEND_MODE_EXTEND);
|
||||
gpu::MTLTexture *mtl_tex = static_cast<gpu::MTLTexture *>(r32_tex_tmp);
|
||||
mtl_tex->update_sub(mip, offset, extent, type, data, 0);
|
||||
|
||||
gpu::FrameBuffer *restore_fb = GPU_framebuffer_active_get();
|
||||
gpu::FrameBuffer *depth_fb_temp = GPU_framebuffer_create("depth_intermediate_copy_fb");
|
||||
GPU_framebuffer_texture_attach(depth_fb_temp, this, 0, mip);
|
||||
GPU_framebuffer_bind(depth_fb_temp);
|
||||
if (extent[0] == w_ && extent[1] == h_) {
|
||||
/* Skip load if the whole texture is being updated. */
|
||||
GPU_framebuffer_clear_depth(depth_fb_temp, 0.0);
|
||||
GPU_framebuffer_clear_stencil(depth_fb_temp, 0);
|
||||
}
|
||||
|
||||
gpu::Shader *depth_2d_update_sh = depth_2d_update_sh_get(specialization);
|
||||
BLI_assert(depth_2d_update_sh != nullptr);
|
||||
Batch *quad = GPU_batch_preset_quad();
|
||||
GPU_batch_set_shader(quad, depth_2d_update_sh);
|
||||
|
||||
GPU_batch_texture_bind(quad, "source_data", r32_tex_tmp);
|
||||
GPU_batch_uniform_1i(quad, "mip", mip);
|
||||
GPU_batch_uniform_2f(quad, "extent", (float)extent[0], (float)extent[1]);
|
||||
GPU_batch_uniform_2f(quad, "offset", (float)offset[0], (float)offset[1]);
|
||||
GPU_batch_uniform_2f(quad, "size", (float)w_, (float)h_);
|
||||
|
||||
bool depth_write_prev = GPU_depth_mask_get();
|
||||
uint stencil_mask_prev = GPU_stencil_mask_get();
|
||||
GPUDepthTest depth_test_prev = GPU_depth_test_get();
|
||||
GPUStencilTest stencil_test_prev = GPU_stencil_test_get();
|
||||
GPU_scissor_test(true);
|
||||
GPU_scissor(offset[0], offset[1], extent[0], extent[1]);
|
||||
|
||||
GPU_stencil_write_mask_set(0xFF);
|
||||
GPU_stencil_reference_set(0);
|
||||
GPU_stencil_test(GPU_STENCIL_ALWAYS);
|
||||
GPU_depth_mask(true);
|
||||
GPU_depth_test(GPU_DEPTH_ALWAYS);
|
||||
|
||||
GPU_batch_draw(quad);
|
||||
|
||||
GPU_depth_mask(depth_write_prev);
|
||||
GPU_stencil_write_mask_set(stencil_mask_prev);
|
||||
GPU_stencil_test(stencil_test_prev);
|
||||
GPU_depth_test(depth_test_prev);
|
||||
|
||||
if (restore_fb != nullptr) {
|
||||
GPU_framebuffer_bind(restore_fb);
|
||||
}
|
||||
else {
|
||||
GPU_framebuffer_restore();
|
||||
}
|
||||
GPU_framebuffer_free(depth_fb_temp);
|
||||
GPU_texture_free(r32_tex_tmp);
|
||||
}
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Texture data read routines
|
||||
* \{ */
|
||||
|
||||
id<MTLComputePipelineState> gpu::MTLTexture::mtl_texture_read_impl(
|
||||
TextureReadRoutineSpecialisation specialization_params,
|
||||
Map<TextureReadRoutineSpecialisation, id<MTLComputePipelineState>> &specialization_cache,
|
||||
GPUTextureType texture_type)
|
||||
{
|
||||
/* Check whether the Kernel exists. */
|
||||
id<MTLComputePipelineState> *result = specialization_cache.lookup_ptr(specialization_params);
|
||||
if (result != nullptr) {
|
||||
return *result;
|
||||
}
|
||||
|
||||
id<MTLComputePipelineState> return_pso = nil;
|
||||
@autoreleasepool {
|
||||
|
||||
/* Fetch active context. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
|
||||
/** SOURCE. **/
|
||||
NSString *tex_update_kernel_src = [NSString
|
||||
stringWithUTF8String:datatoc_compute_texture_read_msl];
|
||||
|
||||
/* Defensive Debug Checks. */
|
||||
int64_t depth_scale_factor = 1;
|
||||
if (specialization_params.depth_format_mode > 0) {
|
||||
BLI_assert(specialization_params.component_count_input == 1);
|
||||
BLI_assert(specialization_params.component_count_output == 1);
|
||||
switch (specialization_params.depth_format_mode) {
|
||||
case 1:
|
||||
/* FLOAT */
|
||||
depth_scale_factor = 1;
|
||||
break;
|
||||
case 2:
|
||||
/* D24 uint */
|
||||
depth_scale_factor = 0xFFFFFFu;
|
||||
break;
|
||||
case 4:
|
||||
/* D32 uint */
|
||||
depth_scale_factor = 0xFFFFFFFFu;
|
||||
break;
|
||||
default:
|
||||
BLI_assert_msg(0, "Unrecognized mode");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Prepare options and specializations. */
|
||||
MTLCompileOptions *options = [[[MTLCompileOptions alloc] init] autorelease];
|
||||
options.languageVersion = MTLLanguageVersion2_2;
|
||||
options.preprocessorMacros = @{
|
||||
@"INPUT_DATA_TYPE" :
|
||||
[NSString stringWithUTF8String:specialization_params.input_data_type.c_str()],
|
||||
@"OUTPUT_DATA_TYPE" :
|
||||
[NSString stringWithUTF8String:specialization_params.output_data_type.c_str()],
|
||||
@"COMPONENT_COUNT_INPUT" :
|
||||
[NSNumber numberWithInt:specialization_params.component_count_input],
|
||||
@"COMPONENT_COUNT_OUTPUT" :
|
||||
[NSNumber numberWithInt:specialization_params.component_count_output],
|
||||
@"WRITE_COMPONENT_COUNT" :
|
||||
[NSNumber numberWithInt:min_ii(specialization_params.component_count_input,
|
||||
specialization_params.component_count_output)],
|
||||
@"IS_DEPTH_FORMAT" :
|
||||
[NSNumber numberWithInt:((specialization_params.depth_format_mode > 0) ? 1 : 0)],
|
||||
@"DEPTH_SCALE_FACTOR" : [NSNumber numberWithLongLong:depth_scale_factor],
|
||||
@"TEX_TYPE" : [NSNumber numberWithInt:((int)(texture_type))],
|
||||
@"IS_DEPTHSTENCIL_24_8" :
|
||||
[NSNumber numberWithInt:(specialization_params.depth_format_mode == 2) ? 1 : 0]
|
||||
};
|
||||
|
||||
/* Prepare shader library for conversion routine. */
|
||||
NSError *error = nullptr;
|
||||
id<MTLLibrary> temp_lib = [[ctx->device newLibraryWithSource:tex_update_kernel_src
|
||||
options:options
|
||||
error:&error] autorelease];
|
||||
if (error) {
|
||||
/* Only exit out if genuine error and not warning. */
|
||||
if ([[error localizedDescription] rangeOfString:@"Compilation succeeded"].location ==
|
||||
NSNotFound)
|
||||
{
|
||||
NSLog(@"Compile Error - Metal Shader Library error %@ ", error);
|
||||
BLI_assert(false);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fetch compute function. */
|
||||
BLI_assert(temp_lib != nil);
|
||||
id<MTLFunction> temp_compute_function = [[temp_lib newFunctionWithName:@"compute_texture_read"]
|
||||
autorelease];
|
||||
BLI_assert(temp_compute_function);
|
||||
|
||||
/* Otherwise, bake new Kernel. */
|
||||
id<MTLComputePipelineState> compute_pso = [ctx->device
|
||||
newComputePipelineStateWithFunction:temp_compute_function
|
||||
error:&error];
|
||||
if (error || compute_pso == nil) {
|
||||
NSLog(@"Failed to prepare texture_read MTLComputePipelineState %@", error);
|
||||
BLI_assert(false);
|
||||
return nil;
|
||||
}
|
||||
|
||||
/* Store PSO. */
|
||||
specialization_cache.add_new(specialization_params, compute_pso);
|
||||
return_pso = compute_pso;
|
||||
}
|
||||
|
||||
BLI_assert(return_pso != nil);
|
||||
return return_pso;
|
||||
}
|
||||
|
||||
id<MTLComputePipelineState> gpu::MTLTexture::texture_read_2d_get_kernel(
|
||||
TextureReadRoutineSpecialisation specialization)
|
||||
{
|
||||
MTLContext *mtl_context = MTLContext::get();
|
||||
BLI_assert(mtl_context != nullptr);
|
||||
return mtl_texture_read_impl(specialization,
|
||||
mtl_context->get_texture_utils().texture_2d_read_compute_psos,
|
||||
GPU_TEXTURE_2D);
|
||||
}
|
||||
|
||||
id<MTLComputePipelineState> gpu::MTLTexture::texture_read_2d_array_get_kernel(
|
||||
TextureReadRoutineSpecialisation specialization)
|
||||
{
|
||||
MTLContext *mtl_context = MTLContext::get();
|
||||
BLI_assert(mtl_context != nullptr);
|
||||
return mtl_texture_read_impl(specialization,
|
||||
mtl_context->get_texture_utils().texture_2d_array_read_compute_psos,
|
||||
GPU_TEXTURE_2D_ARRAY);
|
||||
}
|
||||
|
||||
id<MTLComputePipelineState> gpu::MTLTexture::texture_read_1d_get_kernel(
|
||||
TextureReadRoutineSpecialisation specialization)
|
||||
{
|
||||
MTLContext *mtl_context = MTLContext::get();
|
||||
BLI_assert(mtl_context != nullptr);
|
||||
return mtl_texture_read_impl(specialization,
|
||||
mtl_context->get_texture_utils().texture_1d_read_compute_psos,
|
||||
GPU_TEXTURE_1D);
|
||||
}
|
||||
|
||||
id<MTLComputePipelineState> gpu::MTLTexture::texture_read_1d_array_get_kernel(
|
||||
TextureReadRoutineSpecialisation specialization)
|
||||
{
|
||||
MTLContext *mtl_context = MTLContext::get();
|
||||
BLI_assert(mtl_context != nullptr);
|
||||
return mtl_texture_read_impl(specialization,
|
||||
mtl_context->get_texture_utils().texture_1d_array_read_compute_psos,
|
||||
GPU_TEXTURE_1D_ARRAY);
|
||||
}
|
||||
|
||||
id<MTLComputePipelineState> gpu::MTLTexture::texture_read_3d_get_kernel(
|
||||
TextureReadRoutineSpecialisation specialization)
|
||||
{
|
||||
MTLContext *mtl_context = MTLContext::get();
|
||||
BLI_assert(mtl_context != nullptr);
|
||||
return mtl_texture_read_impl(specialization,
|
||||
mtl_context->get_texture_utils().texture_3d_read_compute_psos,
|
||||
GPU_TEXTURE_3D);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::gpu
|
||||
58
blender-5.2.0/source/blender/gpu/metal/mtl_uniform_buffer.hh
Normal file
58
blender-5.2.0/source/blender/gpu/metal/mtl_uniform_buffer.hh
Normal file
@@ -0,0 +1,58 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "gpu_uniform_buffer_private.hh"
|
||||
|
||||
#include "mtl_context.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
class MTLStorageBuf;
|
||||
|
||||
/**
|
||||
* Implementation of Uniform Buffers using Metal.
|
||||
*/
|
||||
class MTLUniformBuf : public UniformBuf {
|
||||
friend class MTLStorageBuf; /* For bind as SSBO resource access. */
|
||||
|
||||
private:
|
||||
/* Allocation Handle. */
|
||||
gpu::MTLBuffer *metal_buffer_ = nullptr;
|
||||
|
||||
/* Bind-state tracking. */
|
||||
int bind_slot_ = -1;
|
||||
MTLContext *bound_ctx_ = nullptr;
|
||||
|
||||
/* SSBO wrapper for bind_as_ssbo support. */
|
||||
MTLStorageBuf *ssbo_wrapper_ = nullptr;
|
||||
|
||||
public:
|
||||
MTLUniformBuf(size_t size, const char *name);
|
||||
~MTLUniformBuf() override;
|
||||
|
||||
void update(const void *data) override;
|
||||
void bind(int slot) override;
|
||||
void bind_as_ssbo(int slot) override;
|
||||
void unbind() override;
|
||||
void clear_to_zero() override;
|
||||
|
||||
id<MTLBuffer> get_metal_buffer();
|
||||
size_t get_size();
|
||||
const char *get_name()
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLUniformBuf");
|
||||
};
|
||||
|
||||
} // namespace blender::gpu
|
||||
199
blender-5.2.0/source/blender/gpu/metal/mtl_uniform_buffer.mm
Normal file
199
blender-5.2.0/source/blender/gpu/metal/mtl_uniform_buffer.mm
Normal file
@@ -0,0 +1,199 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#include "BKE_global.hh"
|
||||
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "gpu_backend.hh"
|
||||
#include "gpu_context_private.hh"
|
||||
|
||||
#include "mtl_backend.hh"
|
||||
#include "mtl_context.hh"
|
||||
#include "mtl_debug.hh"
|
||||
#include "mtl_storage_buffer.hh"
|
||||
#include "mtl_uniform_buffer.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
MTLUniformBuf::MTLUniformBuf(size_t size, const char *name) : UniformBuf(size, name) {}
|
||||
|
||||
MTLUniformBuf::~MTLUniformBuf()
|
||||
{
|
||||
if (metal_buffer_ != nullptr) {
|
||||
metal_buffer_->free();
|
||||
metal_buffer_ = nullptr;
|
||||
}
|
||||
|
||||
/* Ensure UBO is not bound to active CTX.
|
||||
* UBO bindings are reset upon Context-switch so we do not need
|
||||
* to check deactivated context's. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
if (ctx) {
|
||||
for (MTLUniformBufferBinding &slot : ctx->pipeline_state.ubo_bindings) {
|
||||
if (slot.bound && slot.ubo == this) {
|
||||
slot.bound = false;
|
||||
slot.ubo = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ssbo_wrapper_) {
|
||||
delete ssbo_wrapper_;
|
||||
ssbo_wrapper_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void MTLUniformBuf::update(const void *data)
|
||||
{
|
||||
BLI_assert(this);
|
||||
BLI_assert(size_in_bytes_ > 0);
|
||||
|
||||
/* Free existing allocation.
|
||||
* The previous UBO resource will be tracked by the memory manager,
|
||||
* in case dependent GPU work is still executing. */
|
||||
if (metal_buffer_ != nullptr) {
|
||||
metal_buffer_->free();
|
||||
metal_buffer_ = nullptr;
|
||||
}
|
||||
|
||||
/* Allocate MTL buffer */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
BLI_assert(ctx->device);
|
||||
UNUSED_VARS_NDEBUG(ctx);
|
||||
|
||||
if (data) {
|
||||
metal_buffer_ = MTLContext::get_global_memory_manager()->allocate_with_data(
|
||||
size_in_bytes_, true, data);
|
||||
}
|
||||
else {
|
||||
metal_buffer_ = MTLContext::get_global_memory_manager()->allocate(size_in_bytes_, true);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
static std::atomic<int> global_counter = 0;
|
||||
int index = global_counter.fetch_add(1);
|
||||
metal_buffer_->set_label([NSString stringWithFormat:@"UBO %i %s", index, name_]);
|
||||
#endif
|
||||
|
||||
BLI_assert(metal_buffer_ != nullptr);
|
||||
BLI_assert(metal_buffer_->get_metal_buffer() != nil);
|
||||
}
|
||||
|
||||
void MTLUniformBuf::clear_to_zero()
|
||||
{
|
||||
/* TODO(fclem): Avoid another allocation and just do the clear on the GPU if possible. */
|
||||
void *clear_data = calloc(1, size_in_bytes_);
|
||||
this->update(clear_data);
|
||||
free(clear_data);
|
||||
}
|
||||
|
||||
void MTLUniformBuf::bind(int slot)
|
||||
{
|
||||
if (slot < 0) {
|
||||
MTL_LOG_WARNING("Failed to bind UBO %p. uniform location %d invalid.", this, slot);
|
||||
return;
|
||||
}
|
||||
|
||||
BLI_assert(slot < MTL_MAX_BUFFER_BINDINGS);
|
||||
|
||||
/* Bind current UBO to active context. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
|
||||
MTLUniformBufferBinding &ctx_ubo_bind_slot = ctx->pipeline_state.ubo_bindings[slot];
|
||||
ctx_ubo_bind_slot.ubo = this;
|
||||
ctx_ubo_bind_slot.bound = true;
|
||||
|
||||
bind_slot_ = slot;
|
||||
bound_ctx_ = ctx;
|
||||
|
||||
/* Check if we have any deferred data to upload. */
|
||||
if (data_ != nullptr) {
|
||||
this->update(data_);
|
||||
MEM_SAFE_DELETE_VOID(data_);
|
||||
}
|
||||
|
||||
/* Ensure there is at least an empty dummy buffer. */
|
||||
if (metal_buffer_ == nullptr) {
|
||||
this->update(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void MTLUniformBuf::bind_as_ssbo(int slot)
|
||||
{
|
||||
if (slot < 0) {
|
||||
MTL_LOG_WARNING("Failed to bind UBO %p as SSBO. uniform location %d invalid.", this, slot);
|
||||
return;
|
||||
}
|
||||
|
||||
/* We need to ensure data is actually allocated if using as an SSBO, as resource may be written
|
||||
* to. */
|
||||
if (metal_buffer_ == nullptr) {
|
||||
/* Check if we have any deferred data to upload. */
|
||||
if (data_ != nullptr) {
|
||||
this->update(data_);
|
||||
MEM_SAFE_DELETE_VOID(data_);
|
||||
}
|
||||
else {
|
||||
this->clear_to_zero();
|
||||
}
|
||||
}
|
||||
|
||||
/* Create MTLStorageBuffer to wrap this resource and use conventional binding. */
|
||||
if (ssbo_wrapper_ == nullptr) {
|
||||
ssbo_wrapper_ = new MTLStorageBuf(this, size_in_bytes_);
|
||||
}
|
||||
|
||||
ssbo_wrapper_->bind(slot);
|
||||
}
|
||||
|
||||
void MTLUniformBuf::unbind()
|
||||
{
|
||||
/* Unbind in debug mode to validate missing binds.
|
||||
* Otherwise, only perform a full unbind upon destruction
|
||||
* to ensure no lingering references. */
|
||||
#ifndef NDEBUG
|
||||
if (true)
|
||||
#else
|
||||
if (G.debug & G_DEBUG_GPU)
|
||||
#endif
|
||||
{
|
||||
if (bound_ctx_ != nullptr && bind_slot_ > -1) {
|
||||
MTLUniformBufferBinding &ctx_ubo_bind_slot =
|
||||
bound_ctx_->pipeline_state.ubo_bindings[bind_slot_];
|
||||
if (ctx_ubo_bind_slot.bound && ctx_ubo_bind_slot.ubo == this) {
|
||||
ctx_ubo_bind_slot.bound = false;
|
||||
ctx_ubo_bind_slot.ubo = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset bind index. */
|
||||
bind_slot_ = -1;
|
||||
bound_ctx_ = nullptr;
|
||||
}
|
||||
|
||||
id<MTLBuffer> MTLUniformBuf::get_metal_buffer()
|
||||
{
|
||||
BLI_assert(this);
|
||||
if (metal_buffer_ != nullptr) {
|
||||
metal_buffer_->debug_ensure_used();
|
||||
return metal_buffer_->get_metal_buffer();
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
size_t MTLUniformBuf::get_size()
|
||||
{
|
||||
BLI_assert(this);
|
||||
return size_in_bytes_;
|
||||
}
|
||||
|
||||
} // namespace blender::gpu
|
||||
79
blender-5.2.0/source/blender/gpu/metal/mtl_vertex_buffer.hh
Normal file
79
blender-5.2.0/source/blender/gpu/metal/mtl_vertex_buffer.hh
Normal file
@@ -0,0 +1,79 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GPU_vertex_buffer.hh"
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "mtl_context.hh"
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <Metal/Metal.h>
|
||||
#include <QuartzCore/QuartzCore.h>
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
MTLVertexFormat gpu_vertex_format_to_metal(VertAttrType vert_format);
|
||||
MTLVertexFormat gpu_type_to_metal_vertex_format(shader::Type type);
|
||||
|
||||
class MTLVertBuf : public VertBuf {
|
||||
friend class gpu::MTLTexture; /* For buffer texture. */
|
||||
friend class MTLBatch;
|
||||
friend class MTLStorageBuf; /* For bind as SSBO resource access and copy sub. */
|
||||
|
||||
private:
|
||||
/** Metal buffer allocation. */
|
||||
gpu::MTLBuffer *vbo_ = nullptr;
|
||||
/** Texture used if the buffer is bound as buffer texture. Init on first use. */
|
||||
gpu::Texture *buffer_texture_ = nullptr;
|
||||
/** Defines whether the buffer handle is wrapped by this MTLVertBuf, i.e. we do not own it and
|
||||
* should not free it. */
|
||||
bool is_wrapper_ = false;
|
||||
/** Requested allocation size for Metal buffer.
|
||||
* Differs from raw buffer size as alignment is not included. */
|
||||
uint64_t alloc_size_ = 0;
|
||||
/** Whether existing allocation has been submitted for use by the GPU. */
|
||||
bool contents_in_flight_ = false;
|
||||
/* SSBO wrapper for bind_as_ssbo support. */
|
||||
MTLStorageBuf *ssbo_wrapper_ = nullptr;
|
||||
|
||||
/* Fetch Metal buffer and offset into allocation if necessary.
|
||||
* Access limited to friend classes. */
|
||||
id<MTLBuffer> get_metal_buffer()
|
||||
{
|
||||
BLI_assert(vbo_ != nullptr);
|
||||
vbo_->debug_ensure_used();
|
||||
return vbo_->get_metal_buffer();
|
||||
}
|
||||
|
||||
public:
|
||||
MTLVertBuf();
|
||||
~MTLVertBuf() override;
|
||||
|
||||
void bind();
|
||||
void flag_used();
|
||||
|
||||
void update_sub(uint start, uint len, const void *data) override;
|
||||
|
||||
void read(void *data) const override;
|
||||
|
||||
void wrap_handle(uint64_t handle) override;
|
||||
|
||||
protected:
|
||||
void acquire_data() override;
|
||||
void resize_data() override;
|
||||
void release_data() override;
|
||||
void upload_data() override;
|
||||
void bind_as_ssbo(uint binding) override;
|
||||
void bind_as_texture(uint binding) override;
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("MTLVertBuf");
|
||||
};
|
||||
|
||||
} // namespace blender::gpu
|
||||
468
blender-5.2.0/source/blender/gpu/metal/mtl_vertex_buffer.mm
Normal file
468
blender-5.2.0/source/blender/gpu/metal/mtl_vertex_buffer.mm
Normal file
@@ -0,0 +1,468 @@
|
||||
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup gpu
|
||||
*/
|
||||
|
||||
#include "GPU_vertex_format.hh"
|
||||
|
||||
#include "mtl_debug.hh"
|
||||
#include "mtl_storage_buffer.hh"
|
||||
#include "mtl_vertex_buffer.hh"
|
||||
|
||||
namespace blender::gpu {
|
||||
|
||||
MTLVertBuf::MTLVertBuf() : VertBuf() {}
|
||||
|
||||
MTLVertBuf::~MTLVertBuf()
|
||||
{
|
||||
this->release_data();
|
||||
}
|
||||
|
||||
void MTLVertBuf::acquire_data()
|
||||
{
|
||||
/* Discard previous data, if any. */
|
||||
MEM_SAFE_DELETE(data_);
|
||||
if (usage_ == GPU_USAGE_DEVICE_ONLY) {
|
||||
data_ = nullptr;
|
||||
}
|
||||
else {
|
||||
data_ = MEM_new_array_uninitialized<uchar>(this->size_alloc_get(), __func__);
|
||||
}
|
||||
}
|
||||
|
||||
void MTLVertBuf::resize_data()
|
||||
{
|
||||
if (usage_ == GPU_USAGE_DEVICE_ONLY) {
|
||||
data_ = nullptr;
|
||||
}
|
||||
else {
|
||||
data_ = (uchar *)MEM_realloc_uninitialized(data_, sizeof(uchar) * this->size_alloc_get());
|
||||
}
|
||||
}
|
||||
|
||||
void MTLVertBuf::release_data()
|
||||
{
|
||||
if (vbo_ != nullptr) {
|
||||
vbo_->free();
|
||||
vbo_ = nullptr;
|
||||
is_wrapper_ = false;
|
||||
}
|
||||
|
||||
GPU_TEXTURE_FREE_SAFE(buffer_texture_);
|
||||
|
||||
MEM_SAFE_DELETE(data_);
|
||||
|
||||
if (ssbo_wrapper_) {
|
||||
delete ssbo_wrapper_;
|
||||
ssbo_wrapper_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void MTLVertBuf::upload_data()
|
||||
{
|
||||
this->bind();
|
||||
}
|
||||
|
||||
void MTLVertBuf::bind()
|
||||
{
|
||||
/* Determine allocation size. Set minimum allocation size to be
|
||||
* the maximal of a single attribute to avoid validation and
|
||||
* correctness errors. */
|
||||
uint64_t required_size_raw = sizeof(uchar) * this->size_used_get();
|
||||
uint64_t required_size = max_ulul(required_size_raw, 128);
|
||||
|
||||
if (required_size_raw == 0) {
|
||||
MTL_LOG_DEBUG("Vertex buffer required_size = 0");
|
||||
}
|
||||
|
||||
/* If the vertex buffer has already been allocated, but new data is ready,
|
||||
* or the usage size has changed, we release the existing buffer and
|
||||
* allocate a new buffer to ensure we do not overwrite in-use GPU resources.
|
||||
*
|
||||
* NOTE: We only need to free the existing allocation if contents have been
|
||||
* submitted to the GPU. Otherwise we can simply upload new data to the
|
||||
* existing buffer, if it will fit.
|
||||
*
|
||||
* NOTE: If a buffer is re-sized, but no new data is provided, the previous
|
||||
* contents are copied into the newly allocated buffer. */
|
||||
bool requires_reallocation = (vbo_ != nullptr) && (alloc_size_ != required_size);
|
||||
bool new_data_ready = (this->flag & GPU_VERTBUF_DATA_DIRTY) && this->data_;
|
||||
|
||||
gpu::MTLBuffer *prev_vbo = nullptr;
|
||||
GPUVertBufStatus prev_flag = this->flag;
|
||||
|
||||
if (vbo_ != nullptr) {
|
||||
if (requires_reallocation || (new_data_ready && contents_in_flight_)) {
|
||||
/* Track previous VBO to copy data from. */
|
||||
prev_vbo = vbo_;
|
||||
|
||||
/* Reset current allocation status. */
|
||||
vbo_ = nullptr;
|
||||
is_wrapper_ = false;
|
||||
alloc_size_ = 0;
|
||||
|
||||
/* Flag as requiring data upload. */
|
||||
if (requires_reallocation) {
|
||||
this->flag &= ~GPU_VERTBUF_DATA_UPLOADED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Create MTLBuffer of requested size. */
|
||||
if (vbo_ == nullptr) {
|
||||
vbo_ = MTLContext::get_global_memory_manager()->allocate(
|
||||
required_size, (this->get_usage_type() != GPU_USAGE_DEVICE_ONLY));
|
||||
#ifndef NDEBUG
|
||||
static std::atomic<int> global_counter = 0;
|
||||
int index = global_counter.fetch_add(1);
|
||||
vbo_->set_label([NSString stringWithFormat:@"VBO %i", index]);
|
||||
#endif
|
||||
|
||||
BLI_assert(vbo_ != nullptr);
|
||||
BLI_assert(vbo_->get_metal_buffer() != nil);
|
||||
|
||||
is_wrapper_ = false;
|
||||
alloc_size_ = required_size;
|
||||
contents_in_flight_ = false;
|
||||
}
|
||||
|
||||
/* Upload new data, if provided. */
|
||||
if (new_data_ready) {
|
||||
|
||||
/* Only upload data if usage size is greater than zero.
|
||||
* Do not upload data for device-only buffers. */
|
||||
if (required_size_raw > 0 && usage_ != GPU_USAGE_DEVICE_ONLY) {
|
||||
|
||||
/* Debug: Verify allocation is large enough. */
|
||||
BLI_assert(vbo_->get_size() >= required_size_raw);
|
||||
|
||||
/* Fetch mapped buffer host ptr and upload data. */
|
||||
void *dst_data = vbo_->get_host_ptr();
|
||||
memcpy((uint8_t *)dst_data, this->data_, required_size_raw);
|
||||
vbo_->flush_range(0, required_size_raw);
|
||||
}
|
||||
|
||||
/* If static usage, free host-side data. */
|
||||
if (usage_ == GPU_USAGE_STATIC) {
|
||||
MEM_SAFE_DELETE(data_);
|
||||
}
|
||||
|
||||
/* Flag data as having been uploaded. */
|
||||
this->flag &= ~GPU_VERTBUF_DATA_DIRTY;
|
||||
this->flag |= GPU_VERTBUF_DATA_UPLOADED;
|
||||
}
|
||||
else if (requires_reallocation) {
|
||||
|
||||
/* If buffer has been re-sized, copy existing data if host
|
||||
* data had been previously uploaded. */
|
||||
BLI_assert(prev_vbo != nullptr);
|
||||
|
||||
if (prev_flag & GPU_VERTBUF_DATA_UPLOADED) {
|
||||
|
||||
/* Fetch active context. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
|
||||
id<MTLBuffer> copy_prev_buffer = prev_vbo->get_metal_buffer();
|
||||
id<MTLBuffer> copy_new_buffer = vbo_->get_metal_buffer();
|
||||
BLI_assert(copy_prev_buffer != nil);
|
||||
BLI_assert(copy_new_buffer != nil);
|
||||
|
||||
/* Ensure a blit command encoder is active for buffer copy operation. */
|
||||
id<MTLBlitCommandEncoder> enc = ctx->main_command_buffer.ensure_begin_blit_encoder();
|
||||
[enc copyFromBuffer:copy_prev_buffer
|
||||
sourceOffset:0
|
||||
toBuffer:copy_new_buffer
|
||||
destinationOffset:0
|
||||
size:min_ulul([copy_new_buffer length], [copy_prev_buffer length])];
|
||||
|
||||
/* Flush newly copied data back to host-side buffer, if one exists.
|
||||
* Ensures data and cache coherency for managed MTLBuffers. */
|
||||
if (copy_new_buffer.storageMode == MTLStorageModeManaged) {
|
||||
[enc synchronizeResource:copy_new_buffer];
|
||||
}
|
||||
|
||||
/* For VBOs flagged as static, release host data as it will no longer be needed. */
|
||||
if (usage_ == GPU_USAGE_STATIC) {
|
||||
MEM_SAFE_DELETE(data_);
|
||||
}
|
||||
|
||||
/* Flag data as uploaded. */
|
||||
this->flag |= GPU_VERTBUF_DATA_UPLOADED;
|
||||
|
||||
/* Flag as in-use, as contents have been updated via GPU commands. */
|
||||
this->flag_used();
|
||||
}
|
||||
}
|
||||
|
||||
/* Release previous buffer if re-allocated. */
|
||||
if (prev_vbo != nullptr) {
|
||||
prev_vbo->free();
|
||||
}
|
||||
|
||||
/* Ensure buffer has been created. */
|
||||
BLI_assert(vbo_ != nullptr);
|
||||
}
|
||||
|
||||
/* Update Sub currently only used by hair */
|
||||
void MTLVertBuf::update_sub(uint start, uint len, const void *data)
|
||||
{
|
||||
/* Fetch and verify active context. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
BLI_assert(ctx->device);
|
||||
|
||||
/* Ensure vertbuf has been created. */
|
||||
this->bind();
|
||||
BLI_assert(start + len <= alloc_size_);
|
||||
|
||||
/* Create temporary scratch buffer allocation for sub-range of data. */
|
||||
MTLTemporaryBuffer scratch_allocation =
|
||||
ctx->get_scratch_buffer_manager().scratch_buffer_allocate_range_aligned(len, 256);
|
||||
memcpy(scratch_allocation.data, data, len);
|
||||
if ([scratch_allocation.metal_buffer storageMode] == MTLStorageModeManaged) {
|
||||
[scratch_allocation.metal_buffer
|
||||
didModifyRange:NSMakeRange(scratch_allocation.buffer_offset, len)];
|
||||
}
|
||||
id<MTLBuffer> data_buffer = scratch_allocation.metal_buffer;
|
||||
uint64_t data_buffer_offset = scratch_allocation.buffer_offset;
|
||||
|
||||
BLI_assert(vbo_ != nullptr && data != nullptr);
|
||||
BLI_assert((start + len) <= vbo_->get_size());
|
||||
|
||||
/* Fetch destination buffer. */
|
||||
id<MTLBuffer> dst_buffer = vbo_->get_metal_buffer();
|
||||
|
||||
/* Ensure blit command encoder for copying data. */
|
||||
id<MTLBlitCommandEncoder> enc = ctx->main_command_buffer.ensure_begin_blit_encoder();
|
||||
[enc copyFromBuffer:data_buffer
|
||||
sourceOffset:data_buffer_offset
|
||||
toBuffer:dst_buffer
|
||||
destinationOffset:start
|
||||
size:len];
|
||||
|
||||
/* Flush modified buffer back to host buffer, if one exists. */
|
||||
if (dst_buffer.storageMode == MTLStorageModeManaged) {
|
||||
[enc synchronizeResource:dst_buffer];
|
||||
}
|
||||
}
|
||||
|
||||
void MTLVertBuf::bind_as_ssbo(uint binding)
|
||||
{
|
||||
this->flag_used();
|
||||
|
||||
/* Ensure resource is initialized. */
|
||||
this->bind();
|
||||
|
||||
/* Create MTLStorageBuffer to wrap this resource and use conventional binding. */
|
||||
if (ssbo_wrapper_ == nullptr) {
|
||||
ssbo_wrapper_ = new MTLStorageBuf(this, ceil_to_multiple_u(alloc_size_, 16));
|
||||
}
|
||||
ssbo_wrapper_->bind(binding);
|
||||
}
|
||||
|
||||
void MTLVertBuf::bind_as_texture(uint binding)
|
||||
{
|
||||
/* Ensure allocations are ready, and data uploaded. */
|
||||
this->bind();
|
||||
BLI_assert(vbo_ != nullptr);
|
||||
|
||||
/* If vertex buffer updated, release existing texture and re-create. */
|
||||
id<MTLBuffer> buf = this->get_metal_buffer();
|
||||
if (buffer_texture_ != nullptr) {
|
||||
gpu::MTLTexture *mtl_buffer_tex = static_cast<gpu::MTLTexture *>(this->buffer_texture_);
|
||||
id<MTLBuffer> tex_buf = mtl_buffer_tex->get_vertex_buffer();
|
||||
if (tex_buf != buf) {
|
||||
GPU_TEXTURE_FREE_SAFE(buffer_texture_);
|
||||
buffer_texture_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create texture from vertex buffer. */
|
||||
if (buffer_texture_ == nullptr) {
|
||||
buffer_texture_ = GPU_texture_create_from_vertbuf("vertbuf_as_texture", this);
|
||||
}
|
||||
|
||||
/* Verify successful creation and bind. */
|
||||
BLI_assert(buffer_texture_ != nullptr);
|
||||
GPU_texture_bind(buffer_texture_, binding);
|
||||
}
|
||||
|
||||
void MTLVertBuf::read(void *data) const
|
||||
{
|
||||
/* Fetch active context. */
|
||||
MTLContext *ctx = MTLContext::get();
|
||||
BLI_assert(ctx);
|
||||
|
||||
BLI_assert(vbo_ != nullptr);
|
||||
|
||||
if (usage_ != GPU_USAGE_DEVICE_ONLY) {
|
||||
|
||||
/* Ensure data is flushed for host caches. */
|
||||
id<MTLBuffer> source_buffer = vbo_->get_metal_buffer();
|
||||
if (source_buffer.storageMode == MTLStorageModeManaged) {
|
||||
id<MTLBlitCommandEncoder> enc = ctx->main_command_buffer.ensure_begin_blit_encoder();
|
||||
[enc synchronizeResource:source_buffer];
|
||||
}
|
||||
|
||||
/* Ensure GPU has finished operating on commands which may modify data. */
|
||||
GPU_finish();
|
||||
|
||||
/* Simple direct read. */
|
||||
void *host_ptr = vbo_->get_host_ptr();
|
||||
memcpy(data, host_ptr, alloc_size_);
|
||||
}
|
||||
else {
|
||||
/* Copy private data into temporary staging buffer. */
|
||||
gpu::MTLBuffer *dst_tmp_vbo_ = MTLContext::get_global_memory_manager()->allocate(alloc_size_,
|
||||
true);
|
||||
|
||||
id<MTLBuffer> source_buffer = vbo_->get_metal_buffer();
|
||||
id<MTLBuffer> dest_buffer = dst_tmp_vbo_->get_metal_buffer();
|
||||
BLI_assert(source_buffer != nil);
|
||||
BLI_assert(dest_buffer != nil);
|
||||
|
||||
/* Ensure a blit command encoder is active for buffer copy operation. */
|
||||
id<MTLBlitCommandEncoder> enc = ctx->main_command_buffer.ensure_begin_blit_encoder();
|
||||
[enc copyFromBuffer:source_buffer
|
||||
sourceOffset:0
|
||||
toBuffer:dest_buffer
|
||||
destinationOffset:0
|
||||
size:min_ulul([dest_buffer length], [dest_buffer length])];
|
||||
|
||||
/* Flush newly copied data back to host-side buffer, if one exists.
|
||||
* Ensures data and cache coherency for managed MTLBuffers. */
|
||||
if (dest_buffer.storageMode == MTLStorageModeManaged) {
|
||||
[enc synchronizeResource:dest_buffer];
|
||||
}
|
||||
|
||||
/* wait for GPU. */
|
||||
GPU_finish();
|
||||
|
||||
/* Simple direct read. */
|
||||
void *host_ptr = dst_tmp_vbo_->get_host_ptr();
|
||||
memcpy(data, host_ptr, alloc_size_);
|
||||
dst_tmp_vbo_->free();
|
||||
}
|
||||
}
|
||||
|
||||
void MTLVertBuf::wrap_handle(uint64_t handle)
|
||||
{
|
||||
BLI_assert(vbo_ == nullptr);
|
||||
|
||||
/* Attempt to cast to Metal buffer handle. */
|
||||
BLI_assert(handle != 0);
|
||||
id<MTLBuffer> buffer = reinterpret_cast<id<MTLBuffer>>((void *)handle);
|
||||
|
||||
is_wrapper_ = true;
|
||||
vbo_ = new gpu::MTLBuffer(buffer);
|
||||
|
||||
/* We assume the data is already on the device, so no need to allocate or send it. */
|
||||
flag = GPU_VERTBUF_DATA_UPLOADED;
|
||||
}
|
||||
|
||||
void MTLVertBuf::flag_used()
|
||||
{
|
||||
contents_in_flight_ = true;
|
||||
}
|
||||
|
||||
MTLVertexFormat gpu_vertex_format_to_metal(VertAttrType vert_format)
|
||||
{
|
||||
#define CASE(a, b, c, blender_enum, d, e, mtl_vertex_enum, g, h) \
|
||||
case VertAttrType::blender_enum: \
|
||||
return MTLVertexFormat##mtl_vertex_enum;
|
||||
|
||||
#define CASE_DEPRECATED(a, b, c, blender_enum, d, e, mtl_vertex_enum, g, h) \
|
||||
case VertAttrType::blender_enum##_DEPRECATED: \
|
||||
break;
|
||||
|
||||
switch (vert_format) {
|
||||
GPU_VERTEX_FORMAT_EXPAND(CASE)
|
||||
GPU_VERTEX_DEPRECATED_FORMAT_EXPAND(CASE_DEPRECATED)
|
||||
case VertAttrType::Invalid:
|
||||
break;
|
||||
}
|
||||
#undef CASE
|
||||
#undef CASE_DEPRECATED
|
||||
BLI_assert_msg(false, "Unrecognised GPU vertex format!\n");
|
||||
return MTLVertexFormatInvalid;
|
||||
}
|
||||
|
||||
MTLVertexFormat gpu_type_to_metal_vertex_format(const shader::Type type)
|
||||
{
|
||||
using namespace shader;
|
||||
switch (type) {
|
||||
case Type::float_t:
|
||||
return MTLVertexFormatFloat;
|
||||
case Type::float2_t:
|
||||
return MTLVertexFormatFloat2;
|
||||
case Type::float3_t:
|
||||
return MTLVertexFormatFloat3;
|
||||
case Type::float4_t:
|
||||
return MTLVertexFormatFloat4;
|
||||
case Type::float3x3_t:
|
||||
return MTLVertexFormatInvalid;
|
||||
case Type::float4x4_t:
|
||||
return MTLVertexFormatInvalid;
|
||||
case Type::float3_10_10_10_2_t:
|
||||
return MTLVertexFormatInvalid;
|
||||
case Type::uchar_t:
|
||||
return MTLVertexFormatUChar;
|
||||
case Type::uchar2_t:
|
||||
return MTLVertexFormatUChar2;
|
||||
case Type::uchar3_t:
|
||||
return MTLVertexFormatUChar3;
|
||||
case Type::uchar4_t:
|
||||
return MTLVertexFormatUChar4;
|
||||
case Type::char_t:
|
||||
return MTLVertexFormatChar;
|
||||
case Type::char2_t:
|
||||
return MTLVertexFormatChar2;
|
||||
case Type::char3_t:
|
||||
return MTLVertexFormatChar3;
|
||||
case Type::char4_t:
|
||||
return MTLVertexFormatChar4;
|
||||
case Type::int_t:
|
||||
return MTLVertexFormatInt;
|
||||
case Type::int2_t:
|
||||
return MTLVertexFormatInt2;
|
||||
case Type::int3_t:
|
||||
return MTLVertexFormatInt3;
|
||||
case Type::int4_t:
|
||||
return MTLVertexFormatInt4;
|
||||
case Type::uint_t:
|
||||
return MTLVertexFormatUInt;
|
||||
case Type::uint2_t:
|
||||
return MTLVertexFormatUInt2;
|
||||
case Type::uint3_t:
|
||||
return MTLVertexFormatUInt3;
|
||||
case Type::uint4_t:
|
||||
return MTLVertexFormatUInt4;
|
||||
case Type::ushort_t:
|
||||
return MTLVertexFormatUShort;
|
||||
case Type::ushort2_t:
|
||||
return MTLVertexFormatUShort2;
|
||||
case Type::ushort3_t:
|
||||
return MTLVertexFormatUShort3;
|
||||
case Type::ushort4_t:
|
||||
return MTLVertexFormatUShort4;
|
||||
case Type::short_t:
|
||||
return MTLVertexFormatShort;
|
||||
case Type::short2_t:
|
||||
return MTLVertexFormatShort2;
|
||||
case Type::short3_t:
|
||||
return MTLVertexFormatShort3;
|
||||
case Type::short4_t:
|
||||
return MTLVertexFormatShort4;
|
||||
case Type::bool_t:
|
||||
return MTLVertexFormatInvalid;
|
||||
default:
|
||||
BLI_assert(0);
|
||||
return MTLVertexFormatInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::gpu
|
||||
Reference in New Issue
Block a user