VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
metalRenderTarget.cpp
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 22.10.2025.
5//
6#include "metalRenderTarget.h"
7
9#include "metalTexture.h"
10#include "spdlog/spdlog.h"
11
12namespace visutwin::canvas
13{
14 namespace
15 {
16 MTL::Texture* createMultisampledTexture(MetalGraphicsDevice* device, const MTL::PixelFormat format,
17 const int width, const int height, const int samples)
18 {
19 auto* descriptor = MTL::TextureDescriptor::alloc()->init();
20 descriptor->setTextureType(MTL::TextureType2DMultisample);
21 descriptor->setWidth(width);
22 descriptor->setHeight(height);
23 descriptor->setPixelFormat(format);
24 descriptor->setSampleCount(samples);
25 descriptor->setStorageMode(MTL::StorageModePrivate);
26 descriptor->setUsage(MTL::TextureUsageRenderTarget);
27 auto* texture = device->raw()->newTexture(descriptor);
28 descriptor->release();
29 return texture;
30 }
31
32 MTL::Texture* createInternalDepthTexture(MetalGraphicsDevice* device, const MTL::PixelFormat format,
33 const int width, const int height)
34 {
35 auto* descriptor = MTL::TextureDescriptor::alloc()->init();
36 descriptor->setTextureType(MTL::TextureType2D);
37 descriptor->setWidth(width);
38 descriptor->setHeight(height);
39 descriptor->setPixelFormat(format);
40 descriptor->setStorageMode(MTL::StorageModePrivate);
41 descriptor->setUsage(MTL::TextureUsageRenderTarget | MTL::TextureUsageShaderRead);
42 auto* texture = device->raw()->newTexture(descriptor);
43 descriptor->release();
44 return texture;
45 }
46 }
47
49 {
51 multisampledBuffer->release();
52 multisampledBuffer = nullptr;
53 }
54 texture = nullptr;
55 }
56
57 DepthAttachment::DepthAttachment(const MTL::PixelFormat format, const bool hasStencilValue)
58 : pixelFormat(format), hasStencil(hasStencilValue)
59 {
60 }
61
63 {
64 (void)device;
66 depthTexture->release();
67 }
69 depthTexture = nullptr;
70
72 multisampledDepthBuffer->release();
74 }
75 }
76
82
87
89 {
90 if (_colorAttachments.size() != static_cast<size_t>(colorBufferCount())) {
92 return;
93 }
94
95 // Refresh cached MTL::Texture* pointers in case the underlying Texture was
96 // resized (which recreates the MTL::Texture GPU object, invalidating our
97 // cached raw pointer). This is cheaper than calling createFrameBuffers()
98 // because it only updates the pointer, not reallocating attachments.
99 for (int i = 0; i < colorBufferCount(); ++i) {
100 auto* colorBuffer = getColorBuffer(i);
101 if (!colorBuffer || i >= static_cast<int>(_colorAttachments.size()) || !_colorAttachments[i]) continue;
102 auto* hwTexture = dynamic_cast<gpu::MetalTexture*>(colorBuffer->impl());
103 if (hwTexture && hwTexture->raw() && _colorAttachments[i]->texture != hwTexture->raw()) {
104 _colorAttachments[i]->texture = hwTexture->raw();
105 _colorAttachments[i]->pixelFormat = hwTexture->raw()->pixelFormat();
106 }
107 }
108
109 if (hasDepth() && !_depthAttachment) {
111 }
112 }
113
115 {
116 for (const auto& colorAttachment : _colorAttachments) {
117 if (colorAttachment) {
118 colorAttachment->destroy();
119 }
120 }
121 _colorAttachments.clear();
122
123 if (_depthAttachment) {
124 auto* metalDevice = dynamic_cast<MetalGraphicsDevice*>(device());
125 _depthAttachment->destroy(metalDevice);
126 _depthAttachment.reset();
127 }
128 }
129
131 {
132 auto* metalDevice = dynamic_cast<MetalGraphicsDevice*>(device());
133 if (!metalDevice) {
134 spdlog::error("MetalRenderTarget requires MetalGraphicsDevice");
135 return;
136 }
137
139
140 _colorAttachments.resize(colorBufferCount());
141
142 for (int i = 0; i < colorBufferCount(); ++i) {
143 auto* colorBuffer = getColorBuffer(i);
144 if (!colorBuffer) {
145 continue;
146 }
147
148 colorBuffer->upload();
149 auto* hwTexture = dynamic_cast<gpu::MetalTexture*>(colorBuffer->impl());
150 if (!hwTexture || !hwTexture->raw()) {
151 spdlog::warn("MetalRenderTarget color buffer {} has no Metal texture", i);
152 continue;
153 }
154
155 auto colorAttachment = std::make_shared<ColorAttachment>();
156 colorAttachment->texture = hwTexture->raw();
157 colorAttachment->pixelFormat = colorAttachment->texture->pixelFormat();
158
159 if (samples() > 1) {
160 colorAttachment->multisampledBuffer = createMultisampledTexture(
161 metalDevice, colorAttachment->pixelFormat, width(), height(), samples()
162 );
163 }
164
165 _colorAttachments[i] = colorAttachment;
166 }
167
168 if (auto* depthTexture = depthBuffer()) {
169 depthTexture->upload();
170 auto* hwTexture = dynamic_cast<gpu::MetalTexture*>(depthTexture->impl());
171 if (hwTexture && hwTexture->raw()) {
172 const auto depthFormat = hwTexture->raw()->pixelFormat();
173 const bool hasStencil = depthFormat == MTL::PixelFormatDepth32Float_Stencil8;
174 _depthAttachment = std::make_shared<DepthAttachment>(depthFormat, hasStencil);
175 _depthAttachment->depthTexture = hwTexture->raw();
176
177 if (samples() > 1) {
178 _depthAttachment->multisampledDepthBuffer = createMultisampledTexture(
179 metalDevice, depthFormat, width(), height(), samples()
180 );
181 }
182 } else {
183 spdlog::warn("MetalRenderTarget depth buffer has no Metal texture");
184 }
185 } else if (hasDepth()) {
186 const MTL::PixelFormat depthFormat = hasStencil() ? MTL::PixelFormatDepth32Float_Stencil8 : MTL::PixelFormatDepth32Float;
187 _depthAttachment = std::make_shared<DepthAttachment>(depthFormat, hasStencil());
188 _depthAttachment->depthTexture = createInternalDepthTexture(metalDevice, depthFormat, width(), height());
189 _depthAttachment->depthTextureInternal = true;
190
191 if (samples() > 1) {
192 _depthAttachment->multisampledDepthBuffer = createMultisampledTexture(
193 metalDevice, depthFormat, width(), height(), samples()
194 );
195 }
196 }
197 }
198}
DepthAttachment(MTL::PixelFormat format, bool hasStencil)
void destroy(MetalGraphicsDevice *device)
MetalRenderTarget(const RenderTargetOptions &options={})
Texture * getColorBuffer(size_t index) const
GraphicsDevice * device() const
virtual void destroyFrameBuffers()=0
RenderTarget(const RenderTargetOptions &options={})
Texture * depthBuffer() const
virtual void createFrameBuffers()=0
Texture * colorBuffer() const