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