Blender V4.5
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
10
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{
33 BLI_assert(!has_begun_);
34
35 /* Determine primitive type. */
36 metal_primitive_type_ = gpu_prim_type_to_metal(this->prim_type);
37 metal_primitive_mode_ = mtl_prim_type_to_topology_class(metal_primitive_type_);
38 has_begun_ = true;
39
40 /* If prim type is line loop, add an extra vertex at the end for placing the closing line,
41 * as metal does not support this primitive type. We treat this as a Line strip with one
42 * extra value. */
43 int vertex_alloc_length = vertex_len;
45 vertex_alloc_length++;
46 }
47
48 /* Allocate a range of data and return host-accessible pointer. */
49 const size_t bytes_needed = vertex_buffer_size(&vertex_format, vertex_alloc_length);
50 current_allocation_ = context_->get_scratchbuffer_manager()
51 .scratch_buffer_allocate_range_aligned(bytes_needed, 256);
52 [current_allocation_.metal_buffer retain];
53 return reinterpret_cast<uchar *>(current_allocation_.data);
54}
55
57{
58 /* Ensure we're between a `imm::begin` / `imm:end` pair. */
59 BLI_assert(has_begun_);
61
62 /* Verify context is valid, vertex data is written and a valid shader is bound. */
63 if (context_ && this->vertex_idx > 0 && this->shader) {
64
65 MTLShader *active_mtl_shader = static_cast<MTLShader *>(unwrap(shader));
66
67 /* Skip draw if Metal shader is not valid. */
68 if (active_mtl_shader == nullptr || !active_mtl_shader->is_valid() ||
69 active_mtl_shader->get_interface() == nullptr)
70 {
71
72 const StringRefNull ptr = (active_mtl_shader) ? active_mtl_shader->name_get() : "";
74 "MTLImmediate::end -- cannot perform draw as active shader is NULL or invalid (likely "
75 "unimplemented) (shader %p '%s')",
76 active_mtl_shader,
77 ptr.c_str());
78 return;
79 }
80
81 /* Ensure we are inside a render pass and fetch active RenderCommandEncoder. */
82 id<MTLRenderCommandEncoder> rec = context_->ensure_begin_render_pass();
83 BLI_assert(rec != nil);
84
85 /* Fetch active render pipeline state. */
86 MTLRenderPassState &rps = context_->main_command_buffer.get_render_pass_state();
87
88 /* Bind Shader. */
90
91 /* Debug markers for frame-capture and detailed error messages. */
92 if (G.debug & G_DEBUG_GPU) {
93 [rec pushDebugGroup:[NSString
94 stringWithFormat:@"immEnd(verts: %d, shader: %s)",
95 this->vertex_idx,
96 active_mtl_shader->get_interface()->get_name()]];
97 [rec insertDebugSignpost:[NSString stringWithFormat:@"immEnd(verts: %d, shader: %s)",
98 this->vertex_idx,
99 active_mtl_shader->get_interface()
100 ->get_name()]];
101 }
102
103 /* Populate pipeline state vertex descriptor. */
104 MTLStateManager *state_manager = static_cast<MTLStateManager *>(
107 const MTLShaderInterface *interface = active_mtl_shader->get_interface();
108
109 /* Reset vertex descriptor to default state. */
111 desc.vertex_descriptor.total_attributes = interface->get_total_attributes();
112 desc.vertex_descriptor.max_attribute_value = interface->get_total_attributes() - 1;
114
115 for (int i = 0; i < desc.vertex_descriptor.total_attributes; i++) {
116 desc.vertex_descriptor.attributes[i].format = MTLVertexFormatInvalid;
117 }
118
119 /* Populate Vertex descriptor and verify attributes.
120 * TODO(Metal): Cache this vertex state based on Vertex format and shaders. */
121 for (int i = 0; i < interface->get_total_attributes(); i++) {
122
123 /* NOTE: Attribute in VERTEX FORMAT does not necessarily share the same array index as
124 * attributes in shader interface. */
125 GPUVertAttr *attr = nullptr;
126 const MTLShaderInputAttribute &mtl_shader_attribute = interface->get_attribute(i);
127
128 /* Scan through vertex_format attributes until one with a name matching the shader interface
129 * is found. */
130 for (uint32_t a_idx = 0; a_idx < this->vertex_format.attr_len && attr == nullptr; a_idx++) {
131 GPUVertAttr *check_attribute = &this->vertex_format.attrs[a_idx];
132
133 /* Attributes can have multiple name aliases associated with them. */
134 for (uint32_t n_idx = 0; n_idx < check_attribute->name_len; n_idx++) {
135 const char *name = GPU_vertformat_attr_name_get(
136 &this->vertex_format, check_attribute, n_idx);
137
138 if (strcmp(name, interface->get_name_at_offset(mtl_shader_attribute.name_offset)) == 0) {
139 attr = check_attribute;
140 break;
141 }
142 }
143 }
144
145 BLI_assert_msg(attr != nullptr,
146 "Could not find expected attribute in immediate mode vertex format.");
147 if (attr == nullptr) {
149 "MTLImmediate::end Could not find matching attribute '%s' from Shader Interface in "
150 "Vertex Format! - TODO: Bind Dummy attribute",
151 interface->get_name_at_offset(mtl_shader_attribute.name_offset));
152 return;
153 }
154
155 /* Determine whether implicit type conversion between input vertex format
156 * and shader interface vertex format is supported. */
157 MTLVertexFormat convertedFormat;
158 bool can_use_implicit_conversion = mtl_convert_vertex_format(
159 mtl_shader_attribute.format,
161 attr->comp_len,
163 &convertedFormat);
164
165 if (can_use_implicit_conversion) {
166 /* Metal API can implicitly convert some formats during vertex assembly:
167 * - Converting from a normalized short2 format to float2
168 * - Type truncation e.g. Float4 to Float2.
169 * - Type expansion from Float3 to Float4.
170 * - NOTE: extra components are filled with the corresponding components of (0,0,0,1).
171 * (See
172 * https://developer.apple.com/documentation/metal/mtlvertexattributedescriptor/1516081-format)
173 */
174 bool is_floating_point_format = (attr->comp_type == GPU_COMP_F32);
175 desc.vertex_descriptor.attributes[i].format = convertedFormat;
177 (is_floating_point_format) ? (GPUVertFetchMode)GPU_FETCH_FLOAT :
179 BLI_assert(convertedFormat != MTLVertexFormatInvalid);
180 }
181 else {
182 /* Some conversions are NOT valid, e.g. Int4 to Float4
183 * - In this case, we need to implement a conversion routine inside the shader.
184 * - This is handled using the format_conversion_mode flag
185 * - This flag is passed into the PSO as a function specialization,
186 * and will generate an appropriate conversion function when reading the vertex attribute
187 * value into local shader storage.
188 * (If no explicit conversion is needed, the function specialize to a pass-through). */
189 MTLVertexFormat converted_format = format_resize_comp(mtl_shader_attribute.format,
190 attr->comp_len);
191 desc.vertex_descriptor.attributes[i].format = converted_format;
193 attr->fetch_mode;
194 BLI_assert(desc.vertex_descriptor.attributes[i].format != MTLVertexFormatInvalid);
195 }
196 /* Using attribute offset in vertex format, as this will be correct */
198 desc.vertex_descriptor.attributes[i].buffer_index = mtl_shader_attribute.buffer_index;
199 }
200
201 /* Buffer bindings for singular vertex buffer. */
202 desc.vertex_descriptor.buffer_layouts[0].step_function = MTLVertexStepFunctionPerVertex;
205 BLI_assert(this->vertex_format.stride > 0);
206
207 /* Emulate LineLoop using LineStrip. */
208 if (this->prim_type == GPU_PRIM_LINE_LOOP) {
209 /* Patch final vertex of line loop to close. Rendered using LineStrip.
210 * NOTE: vertex_len represents original length, however, allocated Metal
211 * buffer contains space for one extra vertex when LineLoop is used. */
212 uchar *buffer_data = reinterpret_cast<uchar *>(current_allocation_.data);
213 memcpy(buffer_data + (vertex_len)*vertex_format.stride, buffer_data, vertex_format.stride);
214 this->vertex_idx++;
216 }
217
218 if (unwrap(this->shader)->is_polyline) {
219 context_->get_scratchbuffer_manager().bind_as_ssbo(GPU_SSBO_POLYLINE_POS_BUF_SLOT);
220 context_->get_scratchbuffer_manager().bind_as_ssbo(GPU_SSBO_POLYLINE_COL_BUF_SLOT);
221 context_->get_scratchbuffer_manager().bind_as_ssbo(GPU_SSBO_INDEX_BUF_SLOT);
222 }
223
224 MTLPrimitiveType mtl_prim_type = gpu_prim_type_to_metal(this->prim_type);
225
226 if (context_->ensure_render_pipeline_state(mtl_prim_type)) {
227
228 /* Issue draw call. */
229 BLI_assert(this->vertex_idx > 0);
230
231 /* Metal API does not support triangle fan, so we can emulate this
232 * input data by generating an index buffer to re-map indices to
233 * a TriangleList.
234 *
235 * NOTE(Metal): Consider caching generated triangle fan index buffers.
236 * For immediate mode, generating these is currently very cheap, as we use
237 * fast scratch buffer allocations. Though we may benefit from caching of
238 * frequently used buffer sizes. */
239 bool rendered = false;
241
242 /* Emulate Tri-fan. */
243 switch (this->prim_type) {
244 case GPU_PRIM_TRI_FAN: {
245 /* Prepare Triangle-Fan emulation index buffer on CPU based on number of input
246 * vertices. */
247 uint32_t base_vert_count = this->vertex_idx;
248 uint32_t num_triangles = max_ii(base_vert_count - 2, 0);
249 uint32_t fan_index_count = num_triangles * 3;
250 BLI_assert(num_triangles > 0);
251
252 uint32_t alloc_size = sizeof(uint32_t) * fan_index_count;
253 uint32_t *index_buffer = nullptr;
254
255 MTLTemporaryBuffer allocation =
256 context_->get_scratchbuffer_manager().scratch_buffer_allocate_range_aligned(
257 alloc_size, 128);
258 index_buffer = (uint32_t *)allocation.data;
259
260 int a = 0;
261 for (int i = 0; i < num_triangles; i++) {
262 index_buffer[a++] = 0;
263 index_buffer[a++] = i + 1;
264 index_buffer[a++] = i + 2;
265 }
266
267 @autoreleasepool {
268
269 id<MTLBuffer> index_buffer_mtl = nil;
270 uint64_t index_buffer_offset = 0;
271
272 /* Region of scratch buffer used for topology emulation element data.
273 * NOTE(Metal): We do not need to manually flush as the entire scratch
274 * buffer for current command buffer is flushed upon submission. */
275 index_buffer_mtl = allocation.metal_buffer;
276 index_buffer_offset = allocation.buffer_offset;
277
278 /* Set depth stencil state (requires knowledge of primitive type). */
279 context_->ensure_depth_stencil_state(MTLPrimitiveTypeTriangle);
280
281 /* Bind Vertex Buffer. */
283 current_allocation_.metal_buffer, current_allocation_.buffer_offset, 0);
284
285 /* Draw. */
286 [rec drawIndexedPrimitives:MTLPrimitiveTypeTriangle
287 indexCount:fan_index_count
288 indexType:MTLIndexTypeUInt32
289 indexBuffer:index_buffer_mtl
290 indexBufferOffset:index_buffer_offset];
291 context_->main_command_buffer.register_draw_counters(fan_index_count);
292 }
293 rendered = true;
294 } break;
295 default: {
297 } break;
298 }
299 }
300
301 /* If not yet rendered, run through main render path. LineLoop primitive topology emulation
302 * will simply amend original data passed into default rendering path. */
303 if (!rendered) {
304 MTLPrimitiveType primitive_type = metal_primitive_type_;
305 int vertex_count = this->vertex_idx;
306
307 /* Bind Vertex Buffer. */
309 current_allocation_.metal_buffer, current_allocation_.buffer_offset, 0);
310
311 /* Set depth stencil state (requires knowledge of primitive type). */
312 context_->ensure_depth_stencil_state(primitive_type);
313
314 if (unwrap(this->shader)->is_polyline) {
315 this->polyline_draw_workaround(current_allocation_.buffer_offset);
316 }
317 else {
318 /* Regular draw. */
319 [rec drawPrimitives:primitive_type vertexStart:0 vertexCount:vertex_count];
320 context_->main_command_buffer.register_draw_counters(vertex_count);
321 }
322 }
323 }
324 if (G.debug & G_DEBUG_GPU) {
325 [rec popDebugGroup];
326 }
327
328 if (unwrap(this->shader)->is_polyline) {
329 context_->get_scratchbuffer_manager().unbind_as_ssbo();
330
331 context_->pipeline_state.ssbo_bindings[GPU_SSBO_POLYLINE_POS_BUF_SLOT].ssbo = nil;
332 context_->pipeline_state.ssbo_bindings[GPU_SSBO_POLYLINE_COL_BUF_SLOT].ssbo = nil;
333 context_->pipeline_state.ssbo_bindings[GPU_SSBO_INDEX_BUF_SLOT].ssbo = nil;
334 context_->pipeline_state.ssbo_bindings[GPU_SSBO_POLYLINE_POS_BUF_SLOT].bound = false;
335 context_->pipeline_state.ssbo_bindings[GPU_SSBO_POLYLINE_COL_BUF_SLOT].bound = false;
336 context_->pipeline_state.ssbo_bindings[GPU_SSBO_INDEX_BUF_SLOT].bound = false;
337 }
338 }
339
340 /* Reset allocation after draw submission. */
341 has_begun_ = false;
342 if (current_allocation_.metal_buffer) {
343 [current_allocation_.metal_buffer release];
344 current_allocation_.metal_buffer = nil;
345 }
346}
347
348} // namespace blender::gpu
@ G_DEBUG_GPU
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
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
void GPU_shader_bind(GPUShader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
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
unsigned long long int uint64_t
void polyline_draw_workaround(uint64_t offset)
static MTLContext * get()
uchar * begin() override
MTLImmediate(MTLContext *ctx)
void bind_vertex_buffer(id< MTLBuffer > buffer, uint64_t buffer_offset, uint index)
MTLShaderInterface * get_interface()
MTLRenderPipelineStateDescriptor & get_pipeline_descriptor()
Definition mtl_state.hh:59
StringRefNull name_get() const
#define GPU_SSBO_POLYLINE_POS_BUF_SLOT
#define GPU_SSBO_POLYLINE_COL_BUF_SLOT
#define GPU_SSBO_INDEX_BUF_SLOT
uint vertex_buffer_size(const GPUVertFormat *format, uint vertex_len)
#define G(x, y, z)
#define MTL_LOG_WARNING(info,...)
Definition mtl_debug.hh:42
#define MTL_LOG_ERROR(info,...)
Definition mtl_debug.hh:34
MTLVertexFormat format_resize_comp(MTLVertexFormat mtl_format, uint32_t components)
static Context * unwrap(GPUContext *ctx)
static MTLPrimitiveType gpu_prim_type_to_metal(GPUPrimType prim_type)
static MTLPrimitiveTopologyClass mtl_prim_type_to_topology_class(MTLPrimitiveType prim_type)
MTLBufferRange MTLTemporaryBuffer
bool mtl_convert_vertex_format(MTLVertexFormat shader_attr_format, GPUVertCompType component_type, uint32_t component_len, GPUVertFetchMode fetch_mode, MTLVertexFormat *r_convertedFormat)
static bool mtl_needs_topology_emulation(GPUPrimType prim_type)
MTLVertexBufferLayoutDescriptorPSO buffer_layouts[GPU_BATCH_VBO_MAX_LEN+GPU_BATCH_INST_VBO_MAX_LEN]
MTLVertexAttributeDescriptorPSO attributes[GPU_VERT_ATTR_MAX_LEN]
i
Definition text_draw.cc:230
PointerRNA * ptr
Definition wm_files.cc:4227