VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
metalCoCPass.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025-2026 Arnis Lektauers
3//
4// Circle of Confusion (CoC) pass implementation.
5// Reads the depth buffer and computes per-pixel CoC values for Depth of Field.
6//
7#include "metalCoCPass.h"
8
9#include "metalComposePass.h"
10#include "metalGraphicsDevice.h"
11#include "metalRenderPipeline.h"
12#include "metalTexture.h"
13#include "metalVertexBuffer.h"
22#include "spdlog/spdlog.h"
23
24namespace visutwin::canvas
25{
26 namespace
27 {
28 // CoC (Circle of Confusion) shader — GLSL to Metal.
29 // Reads depth buffer and computes CoC for far and optionally near blur.
30 constexpr const char* COC_SOURCE = R"(
31#include <metal_stdlib>
32using namespace metal;
33
34struct ComposeVertexIn {
35 float3 position [[attribute(0)]];
36 float3 normal [[attribute(1)]];
37 float2 uv0 [[attribute(2)]];
38 float4 tangent [[attribute(3)]];
39 float2 uv1 [[attribute(4)]];
40};
41
42struct CoCVarying {
43 float4 position [[position]];
44 float2 uv;
45};
46
47struct CoCUniforms {
48 float focusDistance;
49 float focusRange;
50 float cameraNear;
51 float cameraFar;
52 float nearBlur;
53};
54
55vertex CoCVarying cocVertex(ComposeVertexIn in [[stage_in]])
56{
57 CoCVarying out;
58 out.position = float4(in.position, 1.0);
59 out.uv = in.uv0;
60 return out;
61}
62
63static inline float getLinearDepth(float rawDepth, float cameraNear, float cameraFar)
64{
65 return (cameraNear * cameraFar) / (cameraFar - rawDepth * (cameraFar - cameraNear));
66}
67
68fragment float4 cocFragment(
69 CoCVarying in [[stage_in]],
70 depth2d<float> depthTexture [[texture(0)]],
71 sampler linearSampler [[sampler(0)]],
72 constant CoCUniforms& uniforms [[buffer(5)]])
73{
74 float2 uv = clamp(in.uv, float2(0.0), float2(1.0));
75 float rawDepth = depthTexture.sample(linearSampler, uv);
76 float linearDepth = getLinearDepth(rawDepth, uniforms.cameraNear, uniforms.cameraFar);
77
78 float cocFar = saturate((linearDepth - uniforms.focusDistance) / uniforms.focusRange);
79
80 if (uniforms.nearBlur > 0.5) {
81 float cocNear = saturate((uniforms.focusDistance - linearDepth) / uniforms.focusRange);
82 return float4(cocFar, cocNear, 0.0, 1.0);
83 }
84 return float4(cocFar, 0.0, 0.0, 1.0);
85}
86)";
87 }
88
90 : _device(device), _composePass(composePass)
91 {
92 }
93
95 {
96 if (_depthStencilState) {
97 _depthStencilState->release();
98 _depthStencilState = nullptr;
99 }
100 }
101
102 void MetalCoCPass::ensureResources()
103 {
104 // Ensure the compose pass's shared vertex buffer/format are created first
105 _composePass->ensureResources();
106
107 if (_shader && _composePass->vertexBuffer() && _composePass->vertexFormat() &&
108 _blendState && _depthState && _depthStencilState) {
109 return;
110 }
111
112 if (!_shader) {
113 ShaderDefinition definition;
114 definition.name = "CoCPass";
115 definition.vshader = "cocVertex";
116 definition.fshader = "cocFragment";
117 _shader = createShader(_device, definition, COC_SOURCE);
118 }
119
120 if (!_blendState) {
121 _blendState = std::make_shared<BlendState>();
122 }
123 if (!_depthState) {
124 _depthState = std::make_shared<DepthState>();
125 }
126 if (!_depthStencilState && _device->raw()) {
127 auto* depthDesc = MTL::DepthStencilDescriptor::alloc()->init();
128 depthDesc->setDepthCompareFunction(MTL::CompareFunctionAlways);
129 depthDesc->setDepthWriteEnabled(false);
130 _depthStencilState = _device->raw()->newDepthStencilState(depthDesc);
131 depthDesc->release();
132 }
133 }
134
135 void MetalCoCPass::execute(MTL::RenderCommandEncoder* encoder,
136 const CoCPassParams& params,
137 MetalRenderPipeline* pipeline, const std::shared_ptr<RenderTarget>& renderTarget,
138 const std::vector<std::shared_ptr<MetalBindGroupFormat>>& bindGroupFormats,
139 MTL::SamplerState* defaultSampler, MTL::DepthStencilState* defaultDepthStencilState)
140 {
141 if (!encoder || !params.depthTexture) {
142 return;
143 }
144
145 ensureResources();
146 if (!_shader || !_composePass->vertexBuffer() || !_composePass->vertexFormat() || !_blendState || !_depthState) {
147 spdlog::warn("[executeCoCPass] missing CoC resources");
148 return;
149 }
150
151 Primitive primitive;
152 primitive.type = PRIMITIVE_TRIANGLES;
153 primitive.base = 0;
154 primitive.count = 3;
155 primitive.indexed = false;
156
157 auto pipelineState = pipeline->get(primitive, _composePass->vertexFormat(), nullptr, -1, _shader, renderTarget,
158 bindGroupFormats, _blendState, _depthState, CullMode::CULLFACE_NONE, false, nullptr, nullptr);
159 if (!pipelineState) {
160 spdlog::warn("[executeCoCPass] failed to get pipeline state");
161 return;
162 }
163
164 auto* vb = dynamic_cast<MetalVertexBuffer*>(_composePass->vertexBuffer().get());
165 if (!vb || !vb->raw()) {
166 spdlog::warn("[executeCoCPass] missing vertex buffer");
167 return;
168 }
169
170 encoder->setRenderPipelineState(pipelineState);
171 encoder->setCullMode(MTL::CullModeNone);
172 encoder->setDepthStencilState(_depthStencilState ? _depthStencilState : defaultDepthStencilState);
173 encoder->setVertexBuffer(vb->raw(), 0, 0);
174
175 auto* depthHw = dynamic_cast<gpu::MetalTexture*>(params.depthTexture->impl());
176 encoder->setFragmentTexture(depthHw ? depthHw->raw() : nullptr, 0);
177 if (defaultSampler) {
178 encoder->setFragmentSamplerState(defaultSampler, 0);
179 }
180
181 // CoCUniforms — all scalars, no float2 alignment issues.
182 struct alignas(16) CoCUniforms
183 {
184 float focusDistance; // offset 0
185 float focusRange; // offset 4
186 float cameraNear; // offset 8
187 float cameraFar; // offset 12
188 float nearBlur; // offset 16
189 } uniforms{};
190
191 uniforms.focusDistance = params.focusDistance;
192 uniforms.focusRange = params.focusRange;
193 uniforms.cameraNear = params.cameraNear;
194 uniforms.cameraFar = params.cameraFar;
195 uniforms.nearBlur = params.nearBlur ? 1.0f : 0.0f;
196 encoder->setFragmentBytes(&uniforms, sizeof(CoCUniforms), 5);
197
198 encoder->drawPrimitives(MTL::PrimitiveTypeTriangle, static_cast<NS::UInteger>(0),
199 static_cast<NS::UInteger>(3));
200 _device->recordDrawCall();
201 }
202}
void execute(MTL::RenderCommandEncoder *encoder, const CoCPassParams &params, MetalRenderPipeline *pipeline, const std::shared_ptr< RenderTarget > &renderTarget, const std::vector< std::shared_ptr< MetalBindGroupFormat > > &bindGroupFormats, MTL::SamplerState *defaultSampler, MTL::DepthStencilState *defaultDepthStencilState)
Execute the CoC pass on the active render command encoder.
MetalCoCPass(MetalGraphicsDevice *device, MetalComposePass *composePass)
std::shared_ptr< VertexFormat > vertexFormat() const
Shared vertex format (full-screen triangle, 14 floats per vertex).
std::shared_ptr< VertexBuffer > vertexBuffer() const
Shared vertex buffer (3-vertex full-screen triangle).
MTL::RenderPipelineState * get(const Primitive &primitive, const std::shared_ptr< VertexFormat > &vertexFormat0, const std::shared_ptr< VertexFormat > &vertexFormat1, int ibFormat, const std::shared_ptr< Shader > &shader, const std::shared_ptr< RenderTarget > &renderTarget, const std::vector< std::shared_ptr< MetalBindGroupFormat > > &bindGroupFormats, const std::shared_ptr< BlendState > &blendState, const std::shared_ptr< DepthState > &depthState, CullMode cullMode, bool stencilEnabled, const std::shared_ptr< StencilParameters > &stencilFront, const std::shared_ptr< StencilParameters > &stencilBack, const std::shared_ptr< VertexFormat > &instancingFormat=nullptr)
gpu::HardwareTexture * impl() const
Definition texture.h:101
std::shared_ptr< Shader > createShader(GraphicsDevice *graphicsDevice, const ShaderDefinition &definition, const std::string &sourceCode)
Definition shader.cpp:39
@ PRIMITIVE_TRIANGLES
Definition mesh.h:23
Describes how vertex and index data should be interpreted for a draw call.
Definition mesh.h:33
PrimitiveType type
Definition mesh.h:34