Blender V5.0
mtl_storage_buffer.mm
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2022 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_string.h"
10#include "BLI_time.h"
11
12#include "GPU_state.hh"
13#include "gpu_backend.hh"
15
16#include "mtl_backend.hh"
17#include "mtl_context.hh"
18#include "mtl_debug.hh"
19#include "mtl_index_buffer.hh"
20#include "mtl_memory.hh"
21#include "mtl_storage_buffer.hh"
22#include "mtl_uniform_buffer.hh"
23#include "mtl_vertex_buffer.hh"
24#include <cstdio>
25
26namespace blender::gpu {
27
28/* -------------------------------------------------------------------- */
31
33{
34 usage_ = GPU_USAGE_STREAM;
35 storage_source_ = MTL_STORAGE_BUF_TYPE_DEFAULT;
37}
38
41{
42 usage_ = usage;
43 /* Do not create SSBO MTL buffer here to allow allocation from any thread. */
44 storage_source_ = MTL_STORAGE_BUF_TYPE_DEFAULT;
45 metal_buffer_ = nullptr;
46}
47
49 : StorageBuf(size, "UniformBuffer_as_SSBO")
50{
51 usage_ = GPU_USAGE_DYNAMIC;
52 storage_source_ = MTL_STORAGE_BUF_TYPE_UNIFORMBUF;
53 uniform_buffer_ = uniform_buf;
54 BLI_assert(uniform_buffer_ != nullptr);
55}
56
58 : StorageBuf(size, "VertexBuffer_as_SSBO")
59{
60 usage_ = GPU_USAGE_DYNAMIC;
61 storage_source_ = MTL_STORAGE_BUF_TYPE_VERTBUF;
62 vertex_buffer_ = vert_buf;
63 BLI_assert(vertex_buffer_ != nullptr);
64}
65
67 : StorageBuf(size, "IndexBuffer_as_SSBO")
68{
69 usage_ = GPU_USAGE_DYNAMIC;
70 storage_source_ = MTL_STORAGE_BUF_TYPE_INDEXBUF;
71 index_buffer_ = index_buf;
72 BLI_assert(index_buffer_ != nullptr);
73}
74
76 : StorageBuf(size, "Texture_as_SSBO")
77{
78 usage_ = GPU_USAGE_DYNAMIC;
79 storage_source_ = MTL_STORAGE_BUF_TYPE_TEXTURE;
81 BLI_assert(texture_ != nullptr);
82}
83
85{
86 if (storage_source_ == MTL_STORAGE_BUF_TYPE_DEFAULT) {
87 if (metal_buffer_ != nullptr) {
88 metal_buffer_->free();
89 metal_buffer_ = nullptr;
90 }
91 has_data_ = false;
92 }
93
94 if (gpu_write_fence_ != nil) {
95 [gpu_write_fence_ release];
96 gpu_write_fence_ = nil;
97 }
98
99 /* Ensure SSBO is not bound to active CTX.
100 * SSBO bindings are reset upon Context-switch so we do not need
101 * to check deactivated context's. */
103 if (ctx) {
104 for (int i = 0; i < MTL_MAX_BUFFER_BINDINGS; i++) {
106 if (slot.bound && slot.ssbo == this) {
107 slot.bound = false;
108 slot.ssbo = nullptr;
109 }
110 }
111 }
112}
113
115
116/* -------------------------------------------------------------------- */
119
121{
122 /* We only need to initialize the storage buffer for default buffer types. */
123 if (storage_source_ != MTL_STORAGE_BUF_TYPE_DEFAULT) {
124 return;
125 }
126 BLI_assert(this);
128
129 /* Allocate MTL buffer */
131 BLI_assert(ctx);
132 BLI_assert(ctx->device);
134
136 size_in_bytes_, (usage_ == GPU_USAGE_DEVICE_ONLY) ? false : true);
137
138#ifndef NDEBUG
139 metal_buffer_->set_label([NSString stringWithFormat:@"Storage Buffer %s", name_]);
140#endif
141 BLI_assert(metal_buffer_ != nullptr);
142 BLI_assert(metal_buffer_->get_metal_buffer() != nil);
143
144 has_data_ = false;
145}
146
148{
149 /* We only need to initialize the storage buffer for default buffer types. */
150 if (storage_source_ != MTL_STORAGE_BUF_TYPE_DEFAULT) {
151 return;
152 }
153
154 /* For device-only Storage buffers, update private resource via staging buffer in command
155 * stream. */
156 bool device_only = (usage_ == GPU_USAGE_DEVICE_ONLY);
157 bool do_upload_data = (data != nullptr);
158
159 /* If host-visible, upload data to new buffer, as previous data may still be in-use by executing
160 * GPU commands. */
161 if (!device_only && do_upload_data) {
162 if (metal_buffer_ != nullptr) {
163 metal_buffer_->free();
164 metal_buffer_ = nullptr;
165 }
166 }
167
168 /* Ensure buffer has been allocated. */
169 if (metal_buffer_ == nullptr) {
170 init();
171 }
172
173 BLI_assert(do_upload_data);
174 if (do_upload_data) {
175 if (device_only) {
176
177 /* Fetch active context. */
179 BLI_assert(ctx);
180
181 /* Prepare staging buffer. */
183 size_in_bytes_, true);
184 memcpy(staging_buf->get_host_ptr(), data, size_in_bytes_);
185 staging_buf->flush_range(0, size_in_bytes_);
186 id<MTLBuffer> staging_buf_mtl = staging_buf->get_metal_buffer();
187 BLI_assert(staging_buf_mtl != nil);
188
189 /* Ensure destination buffer. */
190 id<MTLBuffer> dst_buf = this->metal_buffer_->get_metal_buffer();
191 BLI_assert(dst_buf != nil);
192
193 id<MTLBlitCommandEncoder> blit_encoder =
195 [blit_encoder copyFromBuffer:staging_buf_mtl
196 sourceOffset:0
197 toBuffer:dst_buf
198 destinationOffset:0
200 staging_buf->free();
201 }
202 else {
203 /* Upload data. */
204 BLI_assert(data != nullptr);
205 BLI_assert(!(metal_buffer_->get_resource_options() & MTLResourceStorageModePrivate));
207 BLI_assert(size_in_bytes_ <= [metal_buffer_->get_metal_buffer() length]);
208 memcpy(metal_buffer_->get_host_ptr(), data, size_in_bytes_);
209 metal_buffer_->flush_range(0, size_in_bytes_);
210 }
211 has_data_ = true;
212 }
213}
214
216
217/* -------------------------------------------------------------------- */
220
222{
223 if (slot >= MTL_MAX_BUFFER_BINDINGS) {
224 fprintf(
225 stderr,
226 "Error: Trying to bind \"%s\" ssbo to slot %d which is above the reported limit of %d.\n",
227 name_,
228 slot,
230 BLI_assert(false);
231 return;
232 }
233
234 if (metal_buffer_ == nullptr) {
235 this->init();
236 }
237
238 if (data_ != nullptr) {
239 this->update(data_);
241 }
242
243 /* Bind current UBO to active context. */
245 BLI_assert(ctx);
246
247 MTLStorageBufferBinding &ctx_ssbo_bind_slot = ctx->pipeline_state.ssbo_bindings[slot];
248 ctx_ssbo_bind_slot.ssbo = this;
249 ctx_ssbo_bind_slot.bound = true;
250
251 bind_slot_ = slot;
252 bound_ctx_ = ctx;
253}
254
256{
257 /* Unbind in debug mode to validate missing binds.
258 * Otherwise, only perform a full unbind upon destruction
259 * to ensure no lingering references. */
260#ifndef NDEBUG
261 if (true)
262#else
263 if (G.debug & G_DEBUG_GPU)
264#endif
265 {
266 if (bound_ctx_ != nullptr && bind_slot_ > -1) {
267 MTLStorageBufferBinding &ctx_ssbo_bind_slot =
268 bound_ctx_->pipeline_state.ssbo_bindings[bind_slot_];
269 if (ctx_ssbo_bind_slot.bound && ctx_ssbo_bind_slot.ssbo == this) {
270 ctx_ssbo_bind_slot.bound = false;
271 ctx_ssbo_bind_slot.ssbo = nullptr;
272 }
273 }
274 }
275
276 /* Reset bind index. */
277 bind_slot_ = -1;
278 bound_ctx_ = nullptr;
279}
280
281void MTLStorageBuf::clear(uint32_t clear_value)
282{
283 /* Fetch active context. */
285 BLI_assert_msg(ctx, "Clears should always be performed while a valid context exists.");
286
287 if (metal_buffer_ == nullptr) {
288 this->init();
289 }
290
291 if (ctx) {
292 /* If all 4 bytes within clear value are equal, use the builtin fast-path for clearing. */
293 uint clear_byte = clear_value & 0xFF;
294 bool clear_value_bytes_equal = (clear_byte == ((clear_value >> 8) & 0xFF)) &&
295 (clear_byte == ((clear_value >> 16) & 0xFF)) &&
296 (clear_byte == ((clear_value >> 24) & 0xFF));
297 if (clear_value_bytes_equal) {
298 id<MTLBlitCommandEncoder> blit_encoder =
300 [blit_encoder fillBuffer:metal_buffer_->get_metal_buffer()
301 range:NSMakeRange(0, size_in_bytes_)
302 value:clear_byte];
303 }
304 else {
305 /* We need a special compute routine to update 32 bit values efficiently. */
306 id<MTLComputePipelineState> pso = ctx->get_compute_utils().get_buffer_clear_pso();
307 id<MTLComputeCommandEncoder> compute_encoder =
309
311 cs.bind_pso(pso);
312 cs.bind_compute_bytes(&clear_value, sizeof(uint32_t), 0);
313 cs.bind_compute_buffer(metal_buffer_->get_metal_buffer(), 0, 1);
314 [compute_encoder dispatchThreads:MTLSizeMake(size_in_bytes_ / sizeof(uint32_t), 1, 1)
315 threadsPerThreadgroup:MTLSizeMake(128, 1, 1)];
316 }
317 }
318}
319
320void MTLStorageBuf::copy_sub(VertBuf *src_, uint dst_offset, uint src_offset, uint copy_size)
321{
322 MTLVertBuf *src = static_cast<MTLVertBuf *>(src_);
323 MTLStorageBuf *dst = this;
324
325 if (dst->metal_buffer_ == nullptr) {
326 dst->init();
327 }
328 if (copy_size == 0) {
329 return;
330 }
331 if (src->vbo_ == nullptr) {
332 src->bind();
333 }
334
335 /* Fetch active context. */
337 BLI_assert(ctx);
338
339 /* Fetch Metal buffers. */
340 id<MTLBuffer> src_buf = src->vbo_->get_metal_buffer();
341 id<MTLBuffer> dst_buf = dst->metal_buffer_->get_metal_buffer();
342 BLI_assert(src_buf != nil);
343 BLI_assert(dst_buf != nil);
344
345 id<MTLBlitCommandEncoder> blit_encoder = ctx->main_command_buffer.ensure_begin_blit_encoder();
346 [blit_encoder copyFromBuffer:src_buf
347 sourceOffset:src_offset
348 toBuffer:dst_buf
349 destinationOffset:dst_offset
350 size:copy_size];
351}
352
354{
355 bool device_only = (usage_ == GPU_USAGE_DEVICE_ONLY);
356 BLI_assert_msg(!device_only,
357 "Storage buffers with usage GPU_USAGE_DEVICE_ONLY cannot have their data "
358 "synchronized to the host.");
359 if (device_only) {
360 return;
361 }
362
364 BLI_assert(ctx);
365
366 if (gpu_write_fence_ == nil) {
367 gpu_write_fence_ = [ctx->device newSharedEvent];
368 }
369
370 if (metal_buffer_ == nullptr) {
371 this->init();
372 }
373
374 /* For discrete memory systems, explicitly flush GPU-resident memory back to host. */
375 id<MTLBuffer> storage_buf_mtl = this->metal_buffer_->get_metal_buffer();
376 if (storage_buf_mtl.storageMode == MTLStorageModeManaged) {
377 id<MTLBlitCommandEncoder> blit_encoder = ctx->main_command_buffer.ensure_begin_blit_encoder();
378 [blit_encoder synchronizeResource:storage_buf_mtl];
379 }
380
381 /* Encode event signal and flush command buffer to ensure GPU work is in the pipeline for future
382 * reads. */
383 ctx->main_command_buffer.encode_signal_event(gpu_write_fence_, ++host_read_signal_value_);
384 GPU_flush();
385}
386
388{
389 if (data == nullptr) {
390 return;
391 }
392
393 if (metal_buffer_ == nullptr) {
394 this->init();
395 }
396
397 /* Device-only storage buffers cannot be read directly and require staging.
398 * This path should only be used for unit testing. */
399 bool device_only = (usage_ == GPU_USAGE_DEVICE_ONLY);
400 if (device_only) {
402 /* Fetch active context. */
404 BLI_assert(ctx);
405
406 /* Prepare staging buffer. */
408 true);
409 id<MTLBuffer> staging_buf_mtl = staging_buf->get_metal_buffer();
410 BLI_assert(staging_buf_mtl != nil);
411
412 /* Ensure destination buffer. */
413 id<MTLBuffer> storage_buf_mtl = this->metal_buffer_->get_metal_buffer();
414 BLI_assert(storage_buf_mtl != nil);
415
416 id<MTLBlitCommandEncoder> blit_encoder = ctx->main_command_buffer.ensure_begin_blit_encoder();
417 [blit_encoder copyFromBuffer:storage_buf_mtl
418 sourceOffset:0
419 toBuffer:staging_buf_mtl
420 destinationOffset:0
422 if (staging_buf_mtl.storageMode == MTLStorageModeManaged) {
423 [blit_encoder synchronizeResource:staging_buf_mtl];
424 }
425
426 /* Device-only reads will always stall the GPU pipe. */
427 GPU_finish();
429 "Device-only storage buffer being read. This will stall the GPU pipeline. Ensure this "
430 "path is only used in testing.");
431
432 /* Read contents back to data. */
433 memcpy(data, staging_buf->get_host_ptr(), size_in_bytes_);
434 staging_buf->free();
435 }
436 else {
438 /* If we have a synchronization event from a prior memory sync, ensure memory is fully synced.
439 * Otherwise, assume read is synchronous and stall until in-flight work is complete. */
440 if (gpu_write_fence_ != nil) {
441 /* Ensure the GPU updates are visible to the host before reading. */
442 while (gpu_write_fence_.signaledValue < host_read_signal_value_) {
444 }
445 }
446 else {
447 /* In the case of unified memory. Wait for all pending operation. */
448 GPU_finish();
449 }
450
451 /* Managed buffers need to be explicitly flushed back to host. */
452 if (metal_buffer_->get_resource_options() & MTLResourceStorageModeManaged) {
453 /* Fetch active context. */
455 BLI_assert(ctx);
456
457 /* Ensure GPU updates are flushed back to CPU. */
458 id<MTLBlitCommandEncoder> blit_encoder =
460 [blit_encoder synchronizeResource:metal_buffer_->get_metal_buffer()];
461
462 /* Wait for the blit to finish. */
463 GPU_finish();
464 }
465
466 /* Read data. NOTE: Unless explicitly synchronized with GPU work, results may not be ready. */
467 memcpy(data, metal_buffer_->get_host_ptr(), size_in_bytes_);
468 }
469}
470
472{
473
474 gpu::MTLBuffer *source_buffer = nullptr;
475 switch (storage_source_) {
476 /* Default SSBO buffer comes from own allocation. */
477 case MTL_STORAGE_BUF_TYPE_DEFAULT: {
478 /* NOTE: We should always ensure that the data is primed prior to requiring fetching of the
479 * buffer. If get_metal_buffer is called during a resource bind phase, invoking a blit
480 * command encoder to upload data would override the active encoder state being prepared.
481 * Resource generation and data upload should happen earlier as a resource is bound. */
482 BLI_assert_msg(metal_buffer_ != nullptr,
483 "Storage Buffer backing resource does not yet exist. Ensure the resource is "
484 "bound with data before the calling code requires its underlying MTLBuffer.");
486 data_ == nullptr,
487 "Storage Buffer backing resource data has not yet been uploaded. Ensure the resource is "
488 "bound with data before the calling code requires its underlying MTLBuffer.");
489 source_buffer = metal_buffer_;
490 } break;
491 /* SSBO buffer comes from Uniform Buffer. */
492 case MTL_STORAGE_BUF_TYPE_UNIFORMBUF: {
493 source_buffer = uniform_buffer_->metal_buffer_;
494 } break;
495 /* SSBO buffer comes from Vertex Buffer. */
496 case MTL_STORAGE_BUF_TYPE_VERTBUF: {
497 source_buffer = vertex_buffer_->vbo_;
498 } break;
499 /* SSBO buffer comes from Index Buffer. */
500 case MTL_STORAGE_BUF_TYPE_INDEXBUF: {
501 source_buffer = index_buffer_->ibo_;
502 } break;
503 /* SSBO buffer comes from Texture. */
504 case MTL_STORAGE_BUF_TYPE_TEXTURE: {
506 /* Fetch metal texture to ensure it has been initialized. */
507 id<MTLTexture> tex = texture_->get_metal_handle_base();
508 BLI_assert(tex != nil);
510 source_buffer = texture_->backing_buffer_;
511 }
512 }
513
514 /* Return Metal allocation handle and flag as used. */
515 BLI_assert(source_buffer != nullptr);
516 source_buffer->debug_ensure_used();
517 return source_buffer->get_metal_buffer();
518}
519
521{
522 BLI_assert(this);
523 return size_in_bytes_;
524}
525
526} // namespace blender::gpu
@ G_DEBUG_GPU
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
unsigned int uint
Platform independent time functions.
void BLI_time_sleep_ms(int ms)
Definition time.cc:133
#define UNUSED_VARS_NDEBUG(...)
void GPU_flush()
Definition gpu_state.cc:305
void GPU_finish()
Definition gpu_state.cc:310
@ GPU_USAGE_STREAM
@ GPU_USAGE_DYNAMIC
@ GPU_USAGE_DEVICE_ONLY
#define MEM_SAFE_FREE(v)
BMesh const char void * data
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
gpu::MTLBuffer * allocate(uint64_t size, bool cpu_visible)
gpu::MTLBuffer * allocate_aligned(uint64_t size, uint alignment, bool cpu_visible)
void * get_host_ptr() const
void flush_range(uint64_t offset, uint64_t length)
id< MTLBuffer > get_metal_buffer() const
void encode_signal_event(id< MTLEvent > event, uint64_t value)
id< MTLComputeCommandEncoder > ensure_begin_compute_encoder()
id< MTLBlitCommandEncoder > ensure_begin_blit_encoder()
void bind_compute_bytes(const void *bytes, uint64_t length, uint index)
void bind_compute_buffer(id< MTLBuffer > buffer, uint64_t buffer_offset, uint index)
void bind_pso(id< MTLComputePipelineState > pso)
id< MTLComputePipelineState > get_buffer_clear_pso()
MTLContextComputeUtils & get_compute_utils()
static MTLContext * get()
MTLContextGlobalShaderPipelineState pipeline_state
MTLCommandBufferManager main_command_buffer
static MTLBufferPool * get_global_memory_manager()
void read(void *data) override
void clear(uint32_t clear_value) override
void copy_sub(VertBuf *src, uint dst_offset, uint src_offset, uint copy_size) override
void update(const void *data) override
MTLStorageBuf(size_t size, GPUUsageType usage, const char *name)
void bind(int slot) override
StorageBuf(size_t size, const char *name)
float length(VecOp< float, D >) RET
TEX_TEMPLATE DataVec texture(T, FltCoord, float=0.0f) RET
#define G(x, y, z)
#define MTL_MAX_BUFFER_BINDINGS
#define MTL_LOG_WARNING(info,...)
Definition mtl_debug.hh:42
const char * name
MTLStorageBufferBinding ssbo_bindings[MTL_MAX_BUFFER_BINDINGS]
i
Definition text_draw.cc:230