Blender V5.0
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 *>(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++) {
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(mtl_shader_attribute.format,
159 attr->type.comp_type(),
160 attr->type.comp_len(),
161 attr->type.fetch_mode(),
162 &convertedFormat);
163
164 if (can_use_implicit_conversion) {
165 /* Metal API can implicitly convert some formats during vertex assembly:
166 * - Converting from a normalized short2 format to float2
167 * - Type truncation e.g. Float4 to Float2.
168 * - Type expansion from Float3 to Float4.
169 * - NOTE: extra components are filled with the corresponding components of (0,0,0,1).
170 * (See
171 * https://developer.apple.com/documentation/metal/mtlvertexattributedescriptor/1516081-format)
172 */
173 bool is_floating_point_format = is_fetch_float(attr->type.format);
174 desc.vertex_descriptor.attributes[i].format = convertedFormat;
176 (is_floating_point_format) ? (GPUVertFetchMode)GPU_FETCH_FLOAT :
178 BLI_assert(convertedFormat != MTLVertexFormatInvalid);
179 }
180 else {
181 /* Some conversions are NOT valid, e.g. Int4 to Float4
182 * - In this case, we need to implement a conversion routine inside the shader.
183 * - This is handled using the format_conversion_mode flag
184 * - This flag is passed into the PSO as a function specialization,
185 * and will generate an appropriate conversion function when reading the vertex attribute
186 * value into local shader storage.
187 * (If no explicit conversion is needed, the function specialize to a pass-through). */
188 MTLVertexFormat converted_format = format_resize_comp(mtl_shader_attribute.format,
189 attr->type.comp_len());
190 desc.vertex_descriptor.attributes[i].format = converted_format;
192 BLI_assert(desc.vertex_descriptor.attributes[i].format != MTLVertexFormatInvalid);
193 }
194 /* Using attribute offset in vertex format, as this will be correct */
196 desc.vertex_descriptor.attributes[i].buffer_index = mtl_shader_attribute.buffer_index;
197 }
198
199 /* Buffer bindings for singular vertex buffer. */
200 desc.vertex_descriptor.buffer_layouts[0].step_function = MTLVertexStepFunctionPerVertex;
203 BLI_assert(this->vertex_format.stride > 0);
204
205 /* Emulate LineLoop using LineStrip. */
206 if (this->prim_type == GPU_PRIM_LINE_LOOP) {
207 /* Patch final vertex of line loop to close. Rendered using LineStrip.
208 * NOTE: vertex_len represents original length, however, allocated Metal
209 * buffer contains space for one extra vertex when LineLoop is used. */
210 uchar *buffer_data = reinterpret_cast<uchar *>(current_allocation_.data);
211 memcpy(buffer_data + (vertex_len)*vertex_format.stride, buffer_data, vertex_format.stride);
212 this->vertex_idx++;
214 }
215
216 if (this->shader->is_polyline) {
217 context_->get_scratchbuffer_manager().bind_as_ssbo(GPU_SSBO_POLYLINE_POS_BUF_SLOT);
218 context_->get_scratchbuffer_manager().bind_as_ssbo(GPU_SSBO_POLYLINE_COL_BUF_SLOT);
219 context_->get_scratchbuffer_manager().bind_as_ssbo(GPU_SSBO_INDEX_BUF_SLOT);
220 }
221
222 MTLPrimitiveType mtl_prim_type = gpu_prim_type_to_metal(this->prim_type);
223
224 if (context_->ensure_render_pipeline_state(mtl_prim_type)) {
225
226 /* Issue draw call. */
227 BLI_assert(this->vertex_idx > 0);
228
229 /* Metal API does not support triangle fan, so we can emulate this
230 * input data by generating an index buffer to re-map indices to
231 * a TriangleList.
232 *
233 * NOTE(Metal): Consider caching generated triangle fan index buffers.
234 * For immediate mode, generating these is currently very cheap, as we use
235 * fast scratch buffer allocations. Though we may benefit from caching of
236 * frequently used buffer sizes. */
237 bool rendered = false;
239
240 /* Emulate Tri-fan. */
241 switch (this->prim_type) {
242 case GPU_PRIM_TRI_FAN: {
243 /* Prepare Triangle-Fan emulation index buffer on CPU based on number of input
244 * vertices. */
245 uint32_t base_vert_count = this->vertex_idx;
246 uint32_t num_triangles = max_ii(base_vert_count - 2, 0);
247 uint32_t fan_index_count = num_triangles * 3;
248 BLI_assert(num_triangles > 0);
249
250 uint32_t alloc_size = sizeof(uint32_t) * fan_index_count;
251 uint32_t *index_buffer = nullptr;
252
253 MTLTemporaryBuffer allocation =
254 context_->get_scratchbuffer_manager().scratch_buffer_allocate_range_aligned(
255 alloc_size, 128);
256 index_buffer = (uint32_t *)allocation.data;
257
258 int a = 0;
259 for (int i = 0; i < num_triangles; i++) {
260 index_buffer[a++] = 0;
261 index_buffer[a++] = i + 1;
262 index_buffer[a++] = i + 2;
263 }
264
265 @autoreleasepool {
266
267 id<MTLBuffer> index_buffer_mtl = nil;
268 uint64_t index_buffer_offset = 0;
269
270 /* Region of scratch buffer used for topology emulation element data.
271 * NOTE(Metal): We do not need to manually flush as the entire scratch
272 * buffer for current command buffer is flushed upon submission. */
273 index_buffer_mtl = allocation.metal_buffer;
274 index_buffer_offset = allocation.buffer_offset;
275
276 /* Set depth stencil state (requires knowledge of primitive type). */
277 context_->ensure_depth_stencil_state(MTLPrimitiveTypeTriangle);
278
279 /* Bind Vertex Buffer. */
281 current_allocation_.metal_buffer, current_allocation_.buffer_offset, 0);
282
283 /* Draw. */
284 [rec drawIndexedPrimitives:MTLPrimitiveTypeTriangle
285 indexCount:fan_index_count
286 indexType:MTLIndexTypeUInt32
287 indexBuffer:index_buffer_mtl
288 indexBufferOffset:index_buffer_offset];
289 context_->main_command_buffer.register_draw_counters(fan_index_count);
290 }
291 rendered = true;
292 } break;
293 default: {
295 } break;
296 }
297 }
298
299 /* If not yet rendered, run through main render path. LineLoop primitive topology emulation
300 * will simply amend original data passed into default rendering path. */
301 if (!rendered) {
302 MTLPrimitiveType primitive_type = metal_primitive_type_;
303 int vertex_count = this->vertex_idx;
304
305 /* Bind Vertex Buffer. */
307 current_allocation_.metal_buffer, current_allocation_.buffer_offset, 0);
308
309 /* Set depth stencil state (requires knowledge of primitive type). */
310 context_->ensure_depth_stencil_state(primitive_type);
311
312 if (this->shader->is_polyline) {
313 this->polyline_draw_workaround(current_allocation_.buffer_offset);
314 }
315 else {
316 /* Regular draw. */
317 [rec drawPrimitives:primitive_type vertexStart:0 vertexCount:vertex_count];
318 context_->main_command_buffer.register_draw_counters(vertex_count);
319 }
320 }
321 }
322 if (G.debug & G_DEBUG_GPU) {
323 [rec popDebugGroup];
324 }
325
326 if (this->shader->is_polyline) {
327 context_->get_scratchbuffer_manager().unbind_as_ssbo();
328
329 context_->pipeline_state.ssbo_bindings[GPU_SSBO_POLYLINE_POS_BUF_SLOT].ssbo = nil;
330 context_->pipeline_state.ssbo_bindings[GPU_SSBO_POLYLINE_COL_BUF_SLOT].ssbo = nil;
331 context_->pipeline_state.ssbo_bindings[GPU_SSBO_INDEX_BUF_SLOT].ssbo = nil;
332 context_->pipeline_state.ssbo_bindings[GPU_SSBO_POLYLINE_POS_BUF_SLOT].bound = false;
333 context_->pipeline_state.ssbo_bindings[GPU_SSBO_POLYLINE_COL_BUF_SLOT].bound = false;
334 context_->pipeline_state.ssbo_bindings[GPU_SSBO_INDEX_BUF_SLOT].bound = false;
335 }
336 }
337
338 /* Reset allocation after draw submission. */
339 has_begun_ = false;
340 if (current_allocation_.metal_buffer) {
341 [current_allocation_.metal_buffer release];
342 current_allocation_.metal_buffer = nil;
343 }
344}
345
346} // 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(blender::gpu::Shader *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
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 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)
bool is_fetch_float(VertAttrType attr_type)
const char * name
blender::gpu::VertAttrType format
GPUVertFetchMode fetch_mode() const
GPUVertCompType comp_type() const
struct GPUVertAttr::Type type
MTLVertexAttributeDescriptorPSO attributes[GPU_VERT_ATTR_MAX_LEN]
MTLVertexBufferLayoutDescriptorPSO buffer_layouts[GPU_BATCH_VBO_MAX_LEN]
i
Definition text_draw.cc:230
PointerRNA * ptr
Definition wm_files.cc:4238