VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
metalGraphicsDevice.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 13.09.2025.
5//
6#pragma once
7
8#include <array>
9#include <cstdint>
10#include <Metal/Metal.hpp>
11#include <Foundation/NSAutoreleasePool.hpp>
12#include "QuartzCore/CAMetalDrawable.hpp"
13#include "QuartzCore/CAMetalLayer.hpp"
14#include <SDL3/SDL.h>
15
18#include "metalTextureBinder.h"
19#include "metalUniformBinder.h"
23
24namespace visutwin::canvas
25{
26 class Compute;
27 class MetalCoCPass;
28 class MetalComposePass;
30 class MetalDofBlurPass;
31 class MetalLICPass;
34 class MetalSsaoPass;
35 class MetalTaaPass;
40
46 {
47 friend class MetalCoCPass;
48 friend class MetalComposePass;
50 friend class MetalDofBlurPass;
51 friend class MetalLICPass;
54 friend class MetalSsaoPass;
55 friend class MetalTaaPass;
57
58 public:
61
62 void draw(const Primitive& primitive, const std::shared_ptr<IndexBuffer>& indexBuffer = nullptr,
63 int numInstances = 1, int indirectSlot = -1, bool first = true, bool last = true) override;
64 void setTransformUniforms(const Matrix4& viewProjection, const Matrix4& model) override;
65 void setLightingUniforms(const Color& ambientColor, const std::vector<GpuLightData>& lights,
66 const Vector3& cameraPosition, bool enableNormalMaps, float exposure,
67 const FogParams& fogParams = FogParams{}, const ShadowParams& shadowParams = ShadowParams{},
68 int toneMapping = 0) override;
69 void setEnvironmentUniforms(Texture* envAtlas, float skyboxIntensity, float skyboxMip,
70 const Vector3& skyDomeCenter = Vector3(0,0,0), bool isDome = false,
71 Texture* skyboxCubeMap = nullptr) override;
72 void setAtmosphereUniforms(const void* data, size_t size) override;
73
74 [[nodiscard]] MTL::Device* raw() const { return _device; }
75 [[nodiscard]] MTL::CommandQueue* commandQueue() const { return _commandQueue; }
76 [[nodiscard]] CA::MetalDrawable* frameDrawable() const { return _frameDrawable; }
77
78 std::shared_ptr<Shader> createShader(const ShaderDefinition& definition,
79 const std::string& sourceCode = "") override;
80
81 std::unique_ptr<gpu::HardwareTexture> createGPUTexture(Texture* texture) override;
82
83 std::shared_ptr<VertexBuffer> createVertexBuffer(const std::shared_ptr<VertexFormat>& format,
84 int numVertices, const VertexBufferOptions& options = VertexBufferOptions{}) override;
85
88 std::shared_ptr<VertexBuffer> createVertexBufferFromMTLBuffer(
89 const std::shared_ptr<VertexFormat>& format,
90 int numVertices, MTL::Buffer* externalBuffer);
91
92 std::shared_ptr<VertexBuffer> createVertexBufferFromNativeBuffer(
93 const std::shared_ptr<VertexFormat>& format,
94 int numVertices, void* nativeBuffer) override;
95
96 std::shared_ptr<IndexBuffer> createIndexBuffer(IndexFormat format, int numIndices,
97 const std::vector<uint8_t>& data = {}) override;
98 std::shared_ptr<RenderTarget> createRenderTarget(const RenderTargetOptions& options) override;
99 void executeComposePass(const ComposePassParams& params) override;
100 void executeTAAPass(Texture* sourceTexture, Texture* historyTexture, Texture* depthTexture,
101 const Matrix4& viewProjectionPrevious, const Matrix4& viewProjectionInverse,
102 const std::array<float, 4>& jitters, const std::array<float, 4>& cameraParams,
103 bool highQuality, bool historyValid) override;
104 void executeSsaoPass(const SsaoPassParams& params) override;
105 void executeCoCPass(const CoCPassParams& params) override;
106 void executeDofBlurPass(const DofBlurPassParams& params) override;
107 void executeDepthAwareBlurPass(const DepthAwareBlurPassParams& params, bool horizontal) override;
108 bool supportsCompute() const override { return true; }
109 void computeDispatch(const std::vector<Compute*>& computes, const std::string& label = "") override;
110
111 std::pair<int, int> size() const override;
112
113 void setDepthBias(float depthBias, float slopeScale, float clamp) override;
114
115 void startRenderPass(RenderPass* renderPass) override;
116
117 void endRenderPass(RenderPass* renderPass) override;
118
119 void setResolution(int width, int height) override;
120 void setViewport(float x, float y, float w, float h) override;
121 void setScissor(int x, int y, int w, int h) override;
122
125 void setIndirectDrawBuffer(void* nativeBuffer) override;
126
129 void setDynamicBatchPalette(const void* data, size_t size) override;
130
133 void setClusterBuffers(const void* lightData, size_t lightSize,
134 const void* cellData, size_t cellSize) override;
135
136 void setClusterGridParams(const float* boundsMin, const float* boundsRange,
137 const float* cellsCountByBoundsSize,
138 int cellsX, int cellsY, int cellsZ, int maxLightsPerCell,
139 int numClusteredLights) override;
140
141 private:
142 void onFrameStart() override;
143 void onFrameEnd() override;
144
145 int submitVertexBuffer(const std::shared_ptr<VertexBuffer>& vertexBuffer, int slot);
146
147 // Sentinel null shared_ptr used as a const-ref return for empty VB slots,
148 // avoiding shared_ptr copy when checking _vertexBuffers boundaries.
149 static inline const std::shared_ptr<VertexBuffer> _nullVertexBuffer{nullptr};
150
151 SDL_Window* _window;
152
153 MTL::Device* _device;
154 bool _ownsDevice = false;
155 MTL::CommandQueue* _commandQueue;
156
157 CA::MetalLayer* _metalLayer;
158
159 // Pass encoder - can be render or compute pass encoder
160 union {
161 MTL::RenderCommandEncoder* _renderPassEncoder;
162 MTL::ComputeCommandEncoder* _computePassEncoder;
163 };
164
165 // Active command buffer / drawable for the current pass.
166 CA::MetalDrawable* _currentDrawable = nullptr;
167 MTL::CommandBuffer* _commandBuffer = nullptr;
168
169 // Cached drawable for the current frame. Multiple back-buffer render passes
170 // within a single frame must share the same drawable (Metal's nextDrawable()
171 // returns a different drawable each call, unlike WebGL's persistent back buffer).
172 CA::MetalDrawable* _frameDrawable = nullptr;
173
174 MTL::RenderPipelineState* _pipelineState = nullptr;
175 MTL::Buffer* _indirectDrawBuffer = nullptr; // Set by setIndirectDrawBuffer(), consumed by draw()
176
177 // Dynamic batch palette: ring-buffer offset for slot 6.
178 // Set by setDynamicBatchPalette() → allocate from _paletteRing,
179 // consumed (reset to SIZE_MAX) after draw() → setVertexBufferOffset().
180 size_t _pendingPaletteOffset = SIZE_MAX;
181 MTL::SamplerState* _defaultSampler = nullptr;
182 MTL::DepthStencilState* _defaultDepthStencilState = nullptr;
183 MTL::DepthStencilState* _noWriteDepthStencilState = nullptr;
184 MTL::Texture* _backBufferDepthTexture = nullptr;
185 int _backBufferDepthWidth = 0;
186 int _backBufferDepthHeight = 0;
187
188 std::unique_ptr<MetalRenderPipeline> _renderPipeline;
189 std::unique_ptr<MetalComputePipeline> _computePipeline;
190
191 std::vector<std::shared_ptr<MetalBindGroupFormat>> _bindGroupFormats;
192 std::unique_ptr<MetalCoCPass> _cocPass;
193 std::unique_ptr<MetalComposePass> _composePass;
194 std::unique_ptr<MetalDofBlurPass> _dofBlurPass;
195 std::unique_ptr<MetalSsaoPass> _ssaoPass;
196 std::unique_ptr<MetalDepthAwareBlurPass> _blurPassH;
197 std::unique_ptr<MetalDepthAwareBlurPass> _blurPassV;
198 std::unique_ptr<MetalTaaPass> _taaPass;
199
200 // Triple-buffered ring buffers for per-draw uniform data.
201 // Replaces setVertexBytes()/setFragmentBytes() with pre-allocated MTLBuffer
202 // + setVertexBufferOffset() for significantly reduced CPU overhead at scale.
203 std::unique_ptr<MetalUniformRingBuffer> _transformRing; // ModelData (slot 2)
204 std::unique_ptr<MetalUniformRingBuffer> _uniformRing; // MaterialUniforms (slot 3) + LightingUniforms (slot 4)
205 std::unique_ptr<MetalPaletteRingBuffer> _paletteRing; // Dynamic batch palette (slot 6)
206
207 // Uniform packing, ring-buffer allocation, and per-pass deduplication.
208 MetalUniformBinder _uniformBinder;
209
210 // Per-pass texture binding deduplication (slots 0-8 + sampler).
211 MetalTextureBinder _textureBinder;
212
213 // Clustered lighting GPU buffers (fragment slots 7 and 8).
214 MTL::Buffer* _clusterLightBuffer = nullptr;
215 MTL::Buffer* _clusterCellBuffer = nullptr;
216 size_t _clusterLightBufferCapacity = 0;
217 size_t _clusterCellBufferCapacity = 0;
218 bool _clusterBuffersSet = false;
219
220 // Per-frame autorelease pool. Metal-cpp methods like commandBuffer()
221 // return autoreleased objects that accumulate until a pool drains.
222 // Without per-frame draining, memory grows without bound during
223 // continuous rendering (observed as 25 GB leak in long sessions).
224 NS::AutoreleasePool* _framePool = nullptr;
225 };
226}
Abstract GPU interface for resource creation, state management, and draw submission.
void startRenderPass(RenderPass *renderPass) override
void setClusterGridParams(const float *boundsMin, const float *boundsRange, const float *cellsCountByBoundsSize, int cellsX, int cellsY, int cellsZ, int maxLightsPerCell, int numClusteredLights) override
void executeDofBlurPass(const DofBlurPassParams &params) override
void endRenderPass(RenderPass *renderPass) override
void setIndirectDrawBuffer(void *nativeBuffer) override
void computeDispatch(const std::vector< Compute * > &computes, const std::string &label="") override
std::shared_ptr< RenderTarget > createRenderTarget(const RenderTargetOptions &options) override
void executeDepthAwareBlurPass(const DepthAwareBlurPassParams &params, bool horizontal) override
void setDynamicBatchPalette(const void *data, size_t size) override
MTL::ComputeCommandEncoder * _computePassEncoder
void draw(const Primitive &primitive, const std::shared_ptr< IndexBuffer > &indexBuffer=nullptr, int numInstances=1, int indirectSlot=-1, bool first=true, bool last=true) override
void setTransformUniforms(const Matrix4 &viewProjection, const Matrix4 &model) override
std::unique_ptr< gpu::HardwareTexture > createGPUTexture(Texture *texture) override
void executeCoCPass(const CoCPassParams &params) override
void setResolution(int width, int height) override
void setClusterBuffers(const void *lightData, size_t lightSize, const void *cellData, size_t cellSize) override
void executeSsaoPass(const SsaoPassParams &params) override
std::shared_ptr< Shader > createShader(const ShaderDefinition &definition, const std::string &sourceCode="") override
std::shared_ptr< IndexBuffer > createIndexBuffer(IndexFormat format, int numIndices, const std::vector< uint8_t > &data={}) override
MetalGraphicsDevice(const GraphicsDeviceOptions &options)
std::shared_ptr< VertexBuffer > createVertexBufferFromMTLBuffer(const std::shared_ptr< VertexFormat > &format, int numVertices, MTL::Buffer *externalBuffer)
std::shared_ptr< VertexBuffer > createVertexBufferFromNativeBuffer(const std::shared_ptr< VertexFormat > &format, int numVertices, void *nativeBuffer) override
void executeTAAPass(Texture *sourceTexture, Texture *historyTexture, Texture *depthTexture, const Matrix4 &viewProjectionPrevious, const Matrix4 &viewProjectionInverse, const std::array< float, 4 > &jitters, const std::array< float, 4 > &cameraParams, bool highQuality, bool historyValid) override
MTL::RenderCommandEncoder * _renderPassEncoder
CA::MetalDrawable * frameDrawable() const
void setViewport(float x, float y, float w, float h) override
void setDepthBias(float depthBias, float slopeScale, float clamp) override
std::pair< int, int > size() const override
void executeComposePass(const ComposePassParams &params) override
void setAtmosphereUniforms(const void *data, size_t size) override
MTL::CommandQueue * commandQueue() const
std::shared_ptr< VertexBuffer > createVertexBuffer(const std::shared_ptr< VertexFormat > &format, int numVertices, const VertexBufferOptions &options=VertexBufferOptions{}) override
void setScissor(int x, int y, int w, int h) override
void setEnvironmentUniforms(Texture *envAtlas, float skyboxIntensity, float skyboxMip, const Vector3 &skyDomeCenter=Vector3(0, 0, 0), bool isDome=false, Texture *skyboxCubeMap=nullptr) override
void setLightingUniforms(const Color &ambientColor, const std::vector< GpuLightData > &lights, const Vector3 &cameraPosition, bool enableNormalMaps, float exposure, const FogParams &fogParams=FogParams{}, const ShadowParams &shadowParams=ShadowParams{}, int toneMapping=0) override
GPU texture resource supporting 2D, cubemap, volume, and array formats with mipmap management.
Definition texture.h:57
RGBA color with floating-point components in [0, 1].
Definition color.h:18
4x4 column-major transformation matrix with SIMD acceleration.
Definition matrix4.h:31
Describes how vertex and index data should be interpreted for a draw call.
Definition mesh.h:33
3D vector for positions, directions, and normals with multi-backend SIMD acceleration.
Definition vector3.h:29