VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
metalUniformRingBuffer.h
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025-2026 Arnis Lektauers
3//
4// Created by Arnis Lektauers on 19.02.2026.
5//
6#pragma once
7
8#include <Metal/Metal.hpp>
9#include <dispatch/dispatch.h>
10#include <cassert>
11#include <cstring>
12
13namespace visutwin::canvas
14{
36 {
37 public:
38 static constexpr int kMaxInflightFrames = 3;
39 static constexpr size_t kAlignment = 256; // Metal constant buffer offset alignment
40
47 MetalUniformRingBuffer(MTL::Device* device, size_t maxDrawsPerFrame,
48 size_t uniformStructSize, const char* label = "UniformRing")
49 {
50 _alignedSlotSize = alignUp(uniformStructSize, kAlignment);
51 _regionSize = maxDrawsPerFrame * _alignedSlotSize;
52 _totalSize = kMaxInflightFrames * _regionSize;
53 _maxDrawsPerFrame = maxDrawsPerFrame;
54
55 _buffer = device->newBuffer(_totalSize, MTL::ResourceStorageModeShared);
56 if (_buffer) {
57 _buffer->setLabel(NS::String::string(label, NS::UTF8StringEncoding));
58 _basePtr = static_cast<uint8_t*>(_buffer->contents());
59 }
60
61 _frameSemaphore = dispatch_semaphore_create(kMaxInflightFrames);
62 }
63
65 {
66 if (_buffer) {
67 _buffer->release();
68 _buffer = nullptr;
69 }
70 }
71
72 // Non-copyable, non-movable
77
83 {
84 dispatch_semaphore_wait(_frameSemaphore, DISPATCH_TIME_FOREVER);
85 _frameIndex = (_frameIndex + 1) % kMaxInflightFrames;
86 _drawCount = 0;
87 }
88
96 [[nodiscard]] size_t allocate(const void* data, size_t dataSize)
97 {
98 assert(data != nullptr);
99 assert(dataSize <= _alignedSlotSize && "Data exceeds aligned slot size");
100 assert(_drawCount < _maxDrawsPerFrame && "Ring buffer overflow: too many draws this frame");
101
102 const size_t offset = _frameIndex * _regionSize + _drawCount * _alignedSlotSize;
103 std::memcpy(_basePtr + offset, data, dataSize);
104 ++_drawCount;
105 return offset;
106 }
107
113 void endFrame(MTL::CommandBuffer* commandBuffer)
114 {
115 assert(commandBuffer != nullptr);
116 dispatch_semaphore_t sem = _frameSemaphore;
117 commandBuffer->addCompletedHandler(^(MTL::CommandBuffer*) {
118 dispatch_semaphore_signal(sem);
119 });
120 }
121
122 [[nodiscard]] MTL::Buffer* buffer() const { return _buffer; }
123 [[nodiscard]] size_t alignedSlotSize() const { return _alignedSlotSize; }
124 [[nodiscard]] size_t currentDrawCount() const { return _drawCount; }
125 [[nodiscard]] size_t maxDrawsPerFrame() const { return _maxDrawsPerFrame; }
126 [[nodiscard]] size_t totalSize() const { return _totalSize; }
127
128 private:
129 static size_t alignUp(size_t value, size_t alignment)
130 {
131 return (value + alignment - 1) & ~(alignment - 1);
132 }
133
134 MTL::Buffer* _buffer = nullptr;
135 uint8_t* _basePtr = nullptr;
136 dispatch_semaphore_t _frameSemaphore = nullptr;
137
138 size_t _alignedSlotSize = 0;
139 size_t _regionSize = 0;
140 size_t _totalSize = 0;
141 size_t _maxDrawsPerFrame = 0;
142
143 int _frameIndex = -1; // Will become 0 on first beginFrame()
144 size_t _drawCount = 0;
145 };
146}
MetalUniformRingBuffer(MTL::Device *device, size_t maxDrawsPerFrame, size_t uniformStructSize, const char *label="UniformRing")
MetalUniformRingBuffer(const MetalUniformRingBuffer &)=delete
MetalUniformRingBuffer & operator=(const MetalUniformRingBuffer &)=delete
MetalUniformRingBuffer(MetalUniformRingBuffer &&)=delete
void endFrame(MTL::CommandBuffer *commandBuffer)
MetalUniformRingBuffer & operator=(MetalUniformRingBuffer &&)=delete
size_t allocate(const void *data, size_t dataSize)