Blender V4.3
mtl_immediate.mm
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
11#include "BKE_global.hh"
12
13#include "GPU_vertex_format.hh"
15#include "gpu_shader_private.hh"
17
18#include "mtl_context.hh"
19#include "mtl_debug.hh"
20#include "mtl_immediate.hh"
21#include "mtl_primitive.hh"
22#include "mtl_shader.hh"
23
24namespace blender::gpu {
25
27{
28 context_ = ctx;
29}
30
32
34{
35 BLI_assert(!has_begun_);
36
37 /* Determine primitive type. */
38 metal_primitive_type_ = gpu_prim_type_to_metal(this->prim_type);
39 metal_primitive_mode_ = mtl_prim_type_to_topology_class(metal_primitive_type_);
40 has_begun_ = true;
41
42 /* If prim type is line loop, add an extra vertex at the end for placing the closing line,
43 * as metal does not support this primitive type. We treat this as a Line strip with one
44 * extra value. */
45 int vertex_alloc_length = vertex_len;
47 vertex_alloc_length++;
48 }
49
50 /* Allocate a range of data and return host-accessible pointer. */
51 const size_t bytes_needed = vertex_buffer_size(&vertex_format, vertex_alloc_length);
52 current_allocation_ = context_->get_scratchbuffer_manager()
53 .scratch_buffer_allocate_range_aligned(bytes_needed, 256);
54 [current_allocation_.metal_buffer retain];
55 return reinterpret_cast<uchar *>(current_allocation_.data);
56}
57
59{
60 /* Ensure we're between a `imm::begin` / `imm:end` pair. */
61 BLI_assert(has_begun_);
63
64 /* Verify context is valid, vertex data is written and a valid shader is bound. */
65 if (context_ && this->vertex_idx > 0 && this->shader) {
66
67 MTLShader *active_mtl_shader = static_cast<MTLShader *>(unwrap(shader));
68
69 /* Skip draw if Metal shader is not valid. */
70 if (active_mtl_shader == nullptr || !active_mtl_shader->is_valid() ||
71 active_mtl_shader->get_interface() == nullptr)
72 {
73
74 const char *ptr = (active_mtl_shader) ? active_mtl_shader->name_get() : nullptr;
76 "MTLImmediate::end -- cannot perform draw as active shader is NULL or invalid (likely "
77 "unimplemented) (shader %p '%s')",
78 active_mtl_shader,
79 ptr);
80 return;
81 }
82
83 /* Ensure we are inside a render pass and fetch active RenderCommandEncoder. */
84 id<MTLRenderCommandEncoder> rec = context_->ensure_begin_render_pass();
85 BLI_assert(rec != nil);
86
87 /* Fetch active render pipeline state. */
89
90 /* Bind Shader. */
91 GPU_shader_bind(this->shader);
92
93 /* Debug markers for frame-capture and detailed error messages. */
94 if (G.debug & G_DEBUG_GPU) {
95 [rec pushDebugGroup:[NSString
96 stringWithFormat:@"immEnd(verts: %d, shader: %s)",
97 this->vertex_idx,
98 active_mtl_shader->get_interface()->get_name()]];
99 [rec insertDebugSignpost:[NSString stringWithFormat:@"immEnd(verts: %d, shader: %s)",
100 this->vertex_idx,
101 active_mtl_shader->get_interface()
102 ->get_name()]];
103 }
104
105 /* Populate pipeline state vertex descriptor. */
106 MTLStateManager *state_manager = static_cast<MTLStateManager *>(
109 const MTLShaderInterface *interface = active_mtl_shader->get_interface();
110
111 /* Reset vertex descriptor to default state. */
113 desc.vertex_descriptor.total_attributes = interface->get_total_attributes();
114 desc.vertex_descriptor.max_attribute_value = interface->get_total_attributes() - 1;
116
117 for (int i = 0; i < desc.vertex_descriptor.total_attributes; i++) {
118 desc.vertex_descriptor.attributes[i].format = MTLVertexFormatInvalid;
119 }
121 active_mtl_shader->get_uses_ssbo_vertex_fetch();
123
124 /* SSBO Vertex Fetch -- Verify Attributes. */
125 if (active_mtl_shader->get_uses_ssbo_vertex_fetch()) {
126 active_mtl_shader->ssbo_vertex_fetch_bind_attributes_begin();
127
128 /* Disable Indexed rendering in SSBO vertex fetch. */
129 int uniform_ssbo_use_indexed = active_mtl_shader->uni_ssbo_uses_indexed_rendering;
130 BLI_assert_msg(uniform_ssbo_use_indexed != -1,
131 "Expected valid uniform location for ssbo_uses_indexed_rendering.");
132 int uses_indexed_rendering = 0;
133 active_mtl_shader->uniform_int(uniform_ssbo_use_indexed, 1, 1, &uses_indexed_rendering);
134 }
135
136 /* Populate Vertex descriptor and verify attributes.
137 * TODO(Metal): Cache this vertex state based on Vertex format and shaders. */
138 for (int i = 0; i < interface->get_total_attributes(); i++) {
139
140 /* NOTE: Attribute in VERTEX FORMAT does not necessarily share the same array index as
141 * attributes in shader interface. */
142 GPUVertAttr *attr = nullptr;
143 const MTLShaderInputAttribute &mtl_shader_attribute = interface->get_attribute(i);
144
145 /* Scan through vertex_format attributes until one with a name matching the shader interface
146 * is found. */
147 for (uint32_t a_idx = 0; a_idx < this->vertex_format.attr_len && attr == nullptr; a_idx++) {
148 GPUVertAttr *check_attribute = &this->vertex_format.attrs[a_idx];
149
150 /* Attributes can have multiple name aliases associated with them. */
151 for (uint32_t n_idx = 0; n_idx < check_attribute->name_len; n_idx++) {
152 const char *name = GPU_vertformat_attr_name_get(
153 &this->vertex_format, check_attribute, n_idx);
154
155 if (strcmp(name, interface->get_name_at_offset(mtl_shader_attribute.name_offset)) == 0) {
156 attr = check_attribute;
157 break;
158 }
159 }
160 }
161
162 BLI_assert_msg(attr != nullptr,
163 "Could not find expected attribute in immediate mode vertex format.");
164 if (attr == nullptr) {
166 "MTLImmediate::end Could not find matching attribute '%s' from Shader Interface in "
167 "Vertex Format! - TODO: Bind Dummy attribute",
168 interface->get_name_at_offset(mtl_shader_attribute.name_offset));
169 return;
170 }
171
172 /* Determine whether implicit type conversion between input vertex format
173 * and shader interface vertex format is supported. */
174 MTLVertexFormat convertedFormat;
175 bool can_use_implicit_conversion = mtl_convert_vertex_format(
176 mtl_shader_attribute.format,
178 attr->comp_len,
180 &convertedFormat);
181
182 if (can_use_implicit_conversion) {
183 /* Metal API can implicitly convert some formats during vertex assembly:
184 * - Converting from a normalized short2 format to float2
185 * - Type truncation e.g. Float4 to Float2.
186 * - Type expansion from Float3 to Float4.
187 * - NOTE: extra components are filled with the corresponding components of (0,0,0,1).
188 * (See
189 * https://developer.apple.com/documentation/metal/mtlvertexattributedescriptor/1516081-format)
190 */
191 bool is_floating_point_format = (attr->comp_type == GPU_COMP_F32);
192 desc.vertex_descriptor.attributes[i].format = convertedFormat;
194 (is_floating_point_format) ? (GPUVertFetchMode)GPU_FETCH_FLOAT :
196 BLI_assert(convertedFormat != MTLVertexFormatInvalid);
197 }
198 else {
199 /* Some conversions are NOT valid, e.g. Int4 to Float4
200 * - In this case, we need to implement a conversion routine inside the shader.
201 * - This is handled using the format_conversion_mode flag
202 * - This flag is passed into the PSO as a function specialization,
203 * and will generate an appropriate conversion function when reading the vertex attribute
204 * value into local shader storage.
205 * (If no explicit conversion is needed, the function specialize to a pass-through). */
206 MTLVertexFormat converted_format;
207 bool can_convert = mtl_vertex_format_resize(
208 mtl_shader_attribute.format, attr->comp_len, &converted_format);
209 desc.vertex_descriptor.attributes[i].format = (can_convert) ? converted_format :
210 mtl_shader_attribute.format;
212 attr->fetch_mode;
213 BLI_assert(desc.vertex_descriptor.attributes[i].format != MTLVertexFormatInvalid);
214 }
215 /* Using attribute offset in vertex format, as this will be correct */
217 desc.vertex_descriptor.attributes[i].buffer_index = mtl_shader_attribute.buffer_index;
218
219 /* SSBO Vertex Fetch Attribute bind. */
220 if (active_mtl_shader->get_uses_ssbo_vertex_fetch()) {
221 BLI_assert_msg(mtl_shader_attribute.buffer_index == 0,
222 "All attributes should be in buffer index zero");
223 MTLSSBOAttribute ssbo_attr(
224 mtl_shader_attribute.index,
225 mtl_shader_attribute.buffer_index,
226 attr->offset,
227 this->vertex_format.stride,
229 false);
231 ssbo_attr;
233 active_mtl_shader->ssbo_vertex_fetch_bind_attribute(ssbo_attr);
234 }
235 }
236
237 /* Buffer bindings for singular vertex buffer. */
238 desc.vertex_descriptor.buffer_layouts[0].step_function = MTLVertexStepFunctionPerVertex;
242
243 /* Emulate LineLoop using LineStrip. */
244 if (this->prim_type == GPU_PRIM_LINE_LOOP) {
245 /* Patch final vertex of line loop to close. Rendered using LineStrip.
246 * NOTE: vertex_len represents original length, however, allocated Metal
247 * buffer contains space for one extra vertex when LineLoop is used. */
248 uchar *buffer_data = reinterpret_cast<uchar *>(current_allocation_.data);
249 memcpy(buffer_data + (vertex_len)*vertex_format.stride, buffer_data, vertex_format.stride);
250 this->vertex_idx++;
252 }
253
254 /* SSBO Vertex Fetch -- Verify Attributes. */
255 if (active_mtl_shader->get_uses_ssbo_vertex_fetch()) {
256 active_mtl_shader->ssbo_vertex_fetch_bind_attributes_end(rec);
257
258 /* Set Status uniforms. */
259 BLI_assert_msg(active_mtl_shader->uni_ssbo_input_prim_type_loc != -1,
260 "ssbo_input_prim_type uniform location invalid!");
261 BLI_assert_msg(active_mtl_shader->uni_ssbo_input_vert_count_loc != -1,
262 "ssbo_input_vert_count uniform location invalid!");
263 GPU_shader_uniform_int_ex(reinterpret_cast<GPUShader *>(wrap(active_mtl_shader)),
264 active_mtl_shader->uni_ssbo_input_prim_type_loc,
265 1,
266 1,
267 (const int *)(&this->prim_type));
268 GPU_shader_uniform_int_ex(reinterpret_cast<GPUShader *>(wrap(active_mtl_shader)),
269 active_mtl_shader->uni_ssbo_input_vert_count_loc,
270 1,
271 1,
272 (const int *)(&this->vertex_idx));
273 }
274
275 MTLPrimitiveType mtl_prim_type = gpu_prim_type_to_metal(this->prim_type);
276 if (context_->ensure_render_pipeline_state(mtl_prim_type)) {
277
278 /* Issue draw call. */
279 BLI_assert(this->vertex_idx > 0);
280
281 /* Metal API does not support triangle fan, so we can emulate this
282 * input data by generating an index buffer to re-map indices to
283 * a TriangleList.
284 *
285 * NOTE(Metal): Consider caching generated triangle fan index buffers.
286 * For immediate mode, generating these is currently very cheap, as we use
287 * fast scratch buffer allocations. Though we may benefit from caching of
288 * frequently used buffer sizes. */
289 bool rendered = false;
291
292 /* Emulate Tri-fan. */
293 switch (this->prim_type) {
294 case GPU_PRIM_TRI_FAN: {
295 /* Debug safety check for SSBO FETCH MODE. */
296 if (active_mtl_shader->get_uses_ssbo_vertex_fetch()) {
298 false &&
299 "Topology emulation for TriangleFan not supported with SSBO Vertex Fetch mode");
300 }
301
302 /* Prepare Triangle-Fan emulation index buffer on CPU based on number of input
303 * vertices. */
304 uint32_t base_vert_count = this->vertex_idx;
305 uint32_t num_triangles = max_ii(base_vert_count - 2, 0);
306 uint32_t fan_index_count = num_triangles * 3;
307 BLI_assert(num_triangles > 0);
308
309 uint32_t alloc_size = sizeof(uint32_t) * fan_index_count;
310 uint32_t *index_buffer = nullptr;
311
312 MTLTemporaryBuffer allocation =
314 alloc_size, 128);
315 index_buffer = (uint32_t *)allocation.data;
316
317 int a = 0;
318 for (int i = 0; i < num_triangles; i++) {
319 index_buffer[a++] = 0;
320 index_buffer[a++] = i + 1;
321 index_buffer[a++] = i + 2;
322 }
323
324 @autoreleasepool {
325
326 id<MTLBuffer> index_buffer_mtl = nil;
327 uint64_t index_buffer_offset = 0;
328
329 /* Region of scratch buffer used for topology emulation element data.
330 * NOTE(Metal): We do not need to manually flush as the entire scratch
331 * buffer for current command buffer is flushed upon submission. */
332 index_buffer_mtl = allocation.metal_buffer;
333 index_buffer_offset = allocation.buffer_offset;
334
335 /* Set depth stencil state (requires knowledge of primitive type). */
336 context_->ensure_depth_stencil_state(MTLPrimitiveTypeTriangle);
337
338 /* Bind Vertex Buffer. */
340 current_allocation_.metal_buffer, current_allocation_.buffer_offset, 0);
341
342 /* Draw. */
343 [rec drawIndexedPrimitives:MTLPrimitiveTypeTriangle
344 indexCount:fan_index_count
345 indexType:MTLIndexTypeUInt32
346 indexBuffer:index_buffer_mtl
347 indexBufferOffset:index_buffer_offset];
348 context_->main_command_buffer.register_draw_counters(fan_index_count);
349 }
350 rendered = true;
351 } break;
352 default: {
354 } break;
355 }
356 }
357
358 /* If not yet rendered, run through main render path. LineLoop primitive topology emulation
359 * will simply amend original data passed into default rendering path. */
360 if (!rendered) {
361 MTLPrimitiveType primitive_type = metal_primitive_type_;
362 int vertex_count = this->vertex_idx;
363
364 /* Bind Vertex Buffer. */
366 current_allocation_.metal_buffer, current_allocation_.buffer_offset, 0);
367
368 /* Set depth stencil state (requires knowledge of primitive type). */
369 context_->ensure_depth_stencil_state(primitive_type);
370
371 if (active_mtl_shader->get_uses_ssbo_vertex_fetch()) {
372
373 /* Bind Null Buffers for empty/missing bind slots. */
374 id<MTLBuffer> null_buffer = context_->get_null_buffer();
375 BLI_assert(null_buffer != nil);
376 for (int i = 1; i < MTL_SSBO_VERTEX_FETCH_MAX_VBOS; i++) {
377
378 /* We only need to ensure a buffer is bound to the context, its contents do not matter
379 * as it will not be used. */
381 rps.bind_vertex_buffer(null_buffer, 0, i);
382 }
383 }
384
385 /* SSBO vertex fetch - Nullify elements buffer. */
387 nil)
388 {
390 }
391
392 /* Submit draw call with modified vertex count, which reflects vertices per primitive
393 * defined in the USE_SSBO_VERTEX_FETCH `pragma`. */
394 int num_input_primitives = gpu_get_prim_count_from_type(vertex_count, this->prim_type);
395 int output_num_verts = num_input_primitives *
396 active_mtl_shader->get_ssbo_vertex_fetch_output_num_verts();
397#ifndef NDEBUG
400 output_num_verts, active_mtl_shader->get_ssbo_vertex_fetch_output_prim_type()) &&
401 "Output Vertex count is not compatible with the requested output vertex primitive "
402 "type");
403#endif
404 [rec drawPrimitives:active_mtl_shader->get_ssbo_vertex_fetch_output_prim_type()
405 vertexStart:0
406 vertexCount:output_num_verts];
407 context_->main_command_buffer.register_draw_counters(output_num_verts);
408 }
409 else {
410 /* Regular draw. */
411 [rec drawPrimitives:primitive_type vertexStart:0 vertexCount:vertex_count];
412 context_->main_command_buffer.register_draw_counters(vertex_count);
413 }
414 }
415 }
416 if (G.debug & G_DEBUG_GPU) {
417 [rec popDebugGroup];
418 }
419 }
420
421 /* Reset allocation after draw submission. */
422 has_begun_ = false;
423 if (current_allocation_.metal_buffer) {
424 [current_allocation_.metal_buffer release];
425 current_allocation_.metal_buffer = nil;
426 }
427}
428
429} // namespace blender::gpu
@ G_DEBUG_GPU
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
MINLINE int max_ii(int a, int b)
unsigned char uchar
@ GPU_PRIM_TRI_FAN
@ GPU_PRIM_LINE_LOOP
@ GPU_PRIM_NONE
@ GPU_PRIM_LINE_STRIP
int gpu_get_prim_count_from_type(uint vertex_len, GPUPrimType prim_type)
void GPU_shader_uniform_int_ex(GPUShader *shader, int location, int length, int array_size, const int *value)
void GPU_shader_bind(GPUShader *shader)
BLI_INLINE const char * GPU_vertformat_attr_name_get(const GPUVertFormat *format, const GPUVertAttr *attr, uint n_idx)
GPUVertFetchMode
@ GPU_FETCH_FLOAT
@ GPU_FETCH_INT
GPUVertCompType
@ GPU_COMP_F32
struct GPUShader GPUShader
void register_draw_counters(int vertex_submission)
MTLRenderPassState & get_render_pass_state()
bool ensure_render_pipeline_state(MTLPrimitiveType prim_type)
id< MTLRenderCommandEncoder > ensure_begin_render_pass()
static MTLContext * get()
id< MTLBuffer > get_null_buffer()
void ensure_depth_stencil_state(MTLPrimitiveType prim_type)
MTLCommandBufferManager main_command_buffer
MTLScratchBufferManager & get_scratchbuffer_manager()
uchar * begin() override
MTLImmediate(MTLContext *ctx)
void bind_vertex_buffer(id< MTLBuffer > buffer, uint64_t buffer_offset, uint index)
BufferBindingCached cached_vertex_buffer_bindings[MTL_MAX_BUFFER_BINDINGS]
MTLTemporaryBuffer scratch_buffer_allocate_range_aligned(uint64_t alloc_size, uint alignment)
void ssbo_vertex_fetch_bind_attributes_begin()
void ssbo_vertex_fetch_bind_attribute(const MTLSSBOAttribute &ssbo_attr)
int get_ssbo_vertex_fetch_output_num_verts() const override
void uniform_int(int location, int comp_len, int array_size, const int *data) override
MTLPrimitiveType get_ssbo_vertex_fetch_output_prim_type()
static int ssbo_vertex_type_to_attr_type(MTLVertexFormat attribute_type)
MTLShaderInterface * get_interface()
void ssbo_vertex_fetch_bind_attributes_end(id< MTLRenderCommandEncoder > active_encoder)
bool get_uses_ssbo_vertex_fetch() const override
MTLRenderPipelineStateDescriptor & get_pipeline_descriptor()
Definition mtl_state.hh:59
const char *const name_get() const
uint vertex_buffer_size(const GPUVertFormat *format, uint vertex_len)
#define G(x, y, z)
#define MTL_LOG_WARNING(info,...)
Definition mtl_debug.hh:44
#define MTL_LOG_ERROR(info,...)
Definition mtl_debug.hh:36
#define MTL_SSBO_VERTEX_FETCH_MAX_VBOS
#define MTL_SSBO_VERTEX_FETCH_IBO_INDEX
static Context * unwrap(GPUContext *ctx)
bool mtl_vertex_format_resize(MTLVertexFormat mtl_format, uint32_t components, MTLVertexFormat *r_convertedFormat)
static GPUContext * wrap(Context *ctx)
static MTLPrimitiveType gpu_prim_type_to_metal(GPUPrimType prim_type)
static MTLPrimitiveTopologyClass mtl_prim_type_to_topology_class(MTLPrimitiveType prim_type)
static bool mtl_vertex_count_fits_primitive_type(uint32_t vertex_count, MTLPrimitiveType prim_type)
static bool mtl_needs_topology_emulation(GPUPrimType prim_type)
bool mtl_convert_vertex_format(MTLVertexFormat shader_attrib_format, GPUVertCompType component_type, uint32_t component_length, GPUVertFetchMode fetch_mode, MTLVertexFormat *r_convertedFormat)
unsigned int uint32_t
Definition stdint.h:80
unsigned __int64 uint64_t
Definition stdint.h:90
GPUVertAttr attrs[GPU_VERT_ATTR_MAX_LEN]
MTLSSBOAttribute ssbo_attributes[GPU_VERT_ATTR_MAX_LEN]
MTLVertexBufferLayoutDescriptorPSO buffer_layouts[GPU_BATCH_VBO_MAX_LEN+GPU_BATCH_INST_VBO_MAX_LEN]
MTLVertexAttributeDescriptorPSO attributes[GPU_VERT_ATTR_MAX_LEN]
PointerRNA * ptr
Definition wm_files.cc:4126