Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -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 &params [[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
}

View File

@@ -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 &params [[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
}

View File

@@ -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;
}

View File

@@ -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()

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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()

View File

@@ -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);
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View File

@@ -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

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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

View 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

View 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

View File

@@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

View 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

View 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