Blender V4.3
mtl_drawlist.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
12#include "BLI_assert.h"
13
14#include "GPU_batch.hh"
15#include "mtl_common.hh"
16#include "mtl_drawlist.hh"
17#include "mtl_primitive.hh"
18
19using namespace blender::gpu;
20
21namespace blender::gpu {
22
23/* Indirect draw call structure for reference. */
24/* MTLDrawPrimitivesIndirectArguments --
25 * https://developer.apple.com/documentation/metal/mtldrawprimitivesindirectarguments?language=objc
26 */
27/* struct MTLDrawPrimitivesIndirectArguments {
28 * uint32_t vertexCount;
29 * uint32_t instanceCount;
30 * uint32_t vertexStart;
31 * uint32_t baseInstance;
32 * }; */
33
34/* MTLDrawIndexedPrimitivesIndirectArguments --
35 * https://developer.apple.com/documentation/metal/mtldrawindexedprimitivesindirectarguments?language=objc
36 */
37/* struct MTLDrawIndexedPrimitivesIndirectArguments {
38 * uint32_t indexCount;
39 * uint32_t instanceCount;
40 * uint32_t indexStart;
41 * uint32_t baseVertex;
42 * uint32_t baseInstance;
43 * }; */
44
45#define MDI_ENABLED (buffer_size_ != 0)
46#define MDI_DISABLED (buffer_size_ == 0)
47#define MDI_INDEXED (base_index_ != UINT_MAX)
48
50{
51 BLI_assert(length > 0);
52 batch_ = nullptr;
53 command_len_ = 0;
54 base_index_ = 0;
55 command_offset_ = 0;
56 data_size_ = 0;
57 buffer_size_ = sizeof(MTLDrawIndexedPrimitivesIndirectArguments) * length;
58 data_ = (void *)MEM_mallocN(buffer_size_, __func__);
59}
60
62{
63 if (data_) {
64 MEM_freeN(data_);
65 data_ = nullptr;
66 }
67}
68
69void MTLDrawList::init()
70{
72 BLI_assert(ctx);
74 BLI_assert(data_ == nullptr);
76
77 batch_ = nullptr;
78 command_len_ = 0;
79 BLI_assert(data_);
80
81 command_offset_ = 0;
82}
83
84void MTLDrawList::append(Batch *gpu_batch, int i_first, int i_count)
85{
86 /* Fallback when MultiDrawIndirect is not supported/enabled. */
87 MTLShader *shader = static_cast<MTLShader *>(unwrap(gpu_batch->shader));
88 bool requires_ssbo = (shader->get_uses_ssbo_vertex_fetch());
89 bool requires_emulation = mtl_needs_topology_emulation(gpu_batch->prim_type);
90 if (MDI_DISABLED || requires_ssbo || requires_emulation) {
91 GPU_batch_draw_advanced(gpu_batch, 0, 0, i_first, i_count);
92 return;
93 }
94
95 if (data_ == nullptr) {
96 this->init();
97 }
98 BLI_assert(data_);
99
100 MTLBatch *mtl_batch = static_cast<MTLBatch *>(gpu_batch);
101 BLI_assert(mtl_batch);
102 if (mtl_batch != batch_) {
103 /* Submit existing calls. */
104 this->submit();
105
106 /* Begin new batch. */
107 batch_ = mtl_batch;
108
109 /* Cached for faster access. */
110 MTLIndexBuf *el = batch_->elem_();
111 base_index_ = el ? el->index_base_ : UINT_MAX;
112 v_first_ = el ? el->index_start_ : 0;
113 v_count_ = el ? el->index_len_ : batch_->verts_(0)->vertex_len;
114 }
115
116 if (v_count_ == 0) {
117 /* Nothing to draw. */
118 return;
119 }
120
121 if (MDI_INDEXED) {
122 MTLDrawIndexedPrimitivesIndirectArguments *cmd =
123 reinterpret_cast<MTLDrawIndexedPrimitivesIndirectArguments *>((char *)data_ +
124 command_offset_);
125 cmd->indexStart = v_first_;
126 cmd->indexCount = v_count_;
127 cmd->instanceCount = i_count;
128 cmd->baseVertex = base_index_;
129 cmd->baseInstance = i_first;
130 }
131 else {
132 MTLDrawPrimitivesIndirectArguments *cmd =
133 reinterpret_cast<MTLDrawPrimitivesIndirectArguments *>((char *)data_ + command_offset_);
134 cmd->vertexStart = v_first_;
135 cmd->vertexCount = v_count_;
136 cmd->instanceCount = i_count;
137 cmd->baseInstance = i_first;
138 }
139
140 size_t command_size = MDI_INDEXED ? sizeof(MTLDrawIndexedPrimitivesIndirectArguments) :
141 sizeof(MTLDrawPrimitivesIndirectArguments);
142
143 command_offset_ += command_size;
144 command_len_++;
145
146 /* Check if we can fit at least one other command. */
147 if (command_offset_ + command_size > buffer_size_) {
148 this->submit();
149 }
150
151 return;
152}
153
155{
156 /* Metal does not support MDI from the host side, but we still benefit from only executing the
157 * batch bind a single time, rather than per-draw.
158 * NOTE(Metal): Consider using #MTLIndirectCommandBuffer to achieve similar behavior. */
159 if (command_len_ == 0) {
160 return;
161 }
162
163 /* Something's wrong if we get here without MDI support. */
165 BLI_assert(data_);
166
167 /* Host-side MDI Currently unsupported on Metal. */
168 bool can_use_MDI = false;
169
170 /* Verify context. */
172 BLI_assert(ctx);
173
174 /* Execute indirect draw calls. */
175 MTLShader *shader = static_cast<MTLShader *>(unwrap(batch_->shader));
176 bool SSBO_MODE = (shader->get_uses_ssbo_vertex_fetch());
177 if (SSBO_MODE) {
178 can_use_MDI = false;
179 BLI_assert(false);
180 return;
181 }
182
183 /* Heuristic to determine whether using indirect drawing is more efficient. */
184 size_t command_size = MDI_INDEXED ? sizeof(MTLDrawIndexedPrimitivesIndirectArguments) :
185 sizeof(MTLDrawPrimitivesIndirectArguments);
186 const bool is_finishing_a_buffer = (command_offset_ + command_size > buffer_size_);
187 can_use_MDI = can_use_MDI && (is_finishing_a_buffer || command_len_ > 2);
188
189 /* Bind Batch to setup render pipeline state. */
190 BLI_assert(batch_ != nullptr);
191 id<MTLRenderCommandEncoder> rec = batch_->bind(0);
192 if (rec == nil) {
193 BLI_assert_msg(false, "A RenderCommandEncoder should always be available!\n");
194
195 /* Unbind batch. */
196 batch_->unbind(rec);
197 return;
198 }
199
200 /* Common properties. */
201 MTLPrimitiveType mtl_prim_type = gpu_prim_type_to_metal(batch_->prim_type);
202
203 /* Execute multi-draw indirect. */
204 if (can_use_MDI && false) {
205 /* Metal Doesn't support MDI -- Singular Indirect draw calls are supported,
206 * but Multi-draw is not.
207 * TODO(Metal): Consider using #IndirectCommandBuffers to provide similar
208 * behavior. */
209 }
210 else {
211
212 /* Execute draws manually. */
213 if (MDI_INDEXED) {
214 MTLDrawIndexedPrimitivesIndirectArguments *cmd =
215 (MTLDrawIndexedPrimitivesIndirectArguments *)data_;
216 MTLIndexBuf *mtl_elem = static_cast<MTLIndexBuf *>(
217 reinterpret_cast<IndexBuf *>(batch_->elem));
218 BLI_assert(mtl_elem);
219 MTLIndexType index_type = MTLIndexBuf::gpu_index_type_to_metal(mtl_elem->index_type_);
220 uint32_t index_size = (mtl_elem->index_type_ == GPU_INDEX_U16) ? 2 : 4;
221 uint32_t v_first_ofs = (mtl_elem->index_start_ * index_size);
222 uint32_t index_count = cmd->indexCount;
223
224 /* Fetch index buffer. May return an index buffer of a differing format,
225 * if index buffer optimization is used. In these cases, mtl_prim_type and
226 * index_count get updated with the new properties. */
227 GPUPrimType final_prim_type = batch_->prim_type;
228 id<MTLBuffer> index_buffer = mtl_elem->get_index_buffer(final_prim_type, index_count);
229 BLI_assert(index_buffer != nil);
230
231 /* Final primitive type. */
232 mtl_prim_type = gpu_prim_type_to_metal(final_prim_type);
233
234 if (index_buffer != nil) {
235
236 /* Set depth stencil state (requires knowledge of primitive type). */
237 ctx->ensure_depth_stencil_state(mtl_prim_type);
238
239 for (int i = 0; i < command_len_; i++, cmd++) {
240 [rec drawIndexedPrimitives:mtl_prim_type
241 indexCount:index_count
242 indexType:index_type
243 indexBuffer:index_buffer
244 indexBufferOffset:v_first_ofs
245 instanceCount:cmd->instanceCount
246 baseVertex:cmd->baseVertex
247 baseInstance:cmd->baseInstance];
248 ctx->main_command_buffer.register_draw_counters(cmd->indexCount * cmd->instanceCount);
249 }
250 }
251 else {
252 BLI_assert_msg(false, "Index buffer does not have backing Metal buffer");
253 }
254 }
255 else {
256 MTLDrawPrimitivesIndirectArguments *cmd = (MTLDrawPrimitivesIndirectArguments *)data_;
257
258 /* Verify if topology emulation is required. */
259 if (mtl_needs_topology_emulation(batch_->prim_type)) {
260 BLI_assert_msg(false, "topology emulation cases should use fallback.");
261 }
262 else {
263
264 /* Set depth stencil state (requires knowledge of primitive type). */
265 ctx->ensure_depth_stencil_state(mtl_prim_type);
266
267 for (int i = 0; i < command_len_; i++, cmd++) {
268 [rec drawPrimitives:mtl_prim_type
269 vertexStart:cmd->vertexStart
270 vertexCount:cmd->vertexCount
271 instanceCount:cmd->instanceCount
272 baseInstance:cmd->baseInstance];
273 ctx->main_command_buffer.register_draw_counters(cmd->vertexCount * cmd->instanceCount);
274 }
275 }
276 }
277 }
278
279 /* Unbind batch. */
280 batch_->unbind(rec);
281
282 /* Reset command offsets. */
283 command_len_ = 0;
284 command_offset_ = 0;
285
286 /* Avoid keeping reference to the batch. */
287 batch_ = nullptr;
288}
289
290} // namespace blender::gpu
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
#define UNUSED_VARS_NDEBUG(...)
void GPU_batch_draw_advanced(blender::gpu::Batch *batch, int vertex_first, int vertex_count, int instance_first, int instance_count)
GPUPrimType
GPUIndexBufType index_type_
MTLIndexBuf * elem_() const
Definition mtl_batch.hh:100
MTLVertBuf * verts_(const int index) const
Definition mtl_batch.hh:104
id< MTLRenderCommandEncoder > bind(uint v_count)
Definition mtl_batch.mm:411
void unbind(id< MTLRenderCommandEncoder > rec)
Definition mtl_batch.mm:596
void register_draw_counters(int vertex_submission)
static MTLContext * get()
void ensure_depth_stencil_state(MTLPrimitiveType prim_type)
MTLCommandBufferManager main_command_buffer
void append(Batch *batch, int i_first, int i_count) override
static MTLIndexType gpu_index_type_to_metal(GPUIndexBufType type)
id< MTLBuffer > get_index_buffer(GPUPrimType &in_out_primitive_type, uint &in_out_v_count)
#define UINT_MAX
Definition hash_md5.cc:44
void *(* MEM_mallocN)(size_t len, const char *str)
Definition mallocn.cc:44
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
#define MDI_INDEXED
#define MDI_ENABLED
#define MDI_DISABLED
static Context * unwrap(GPUContext *ctx)
static MTLPrimitiveType gpu_prim_type_to_metal(GPUPrimType prim_type)
static bool mtl_needs_topology_emulation(GPUPrimType prim_type)
unsigned int uint32_t
Definition stdint.h:80