VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
cameraComponent.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 18.12.2025.
5//
6#include "cameraComponent.h"
7#include <algorithm>
8#include <cassert>
9
10#include "framework/entity.h"
11#include "framework/engine.h"
16#include "scene/graphNode.h"
17
18namespace visutwin::canvas
19{
20 std::vector<CameraComponent*> CameraComponent::_instances;
21
26
28 {
29 const auto it = std::find(_instances.begin(), _instances.end(), this);
30 if (it != _instances.end()) {
31 _instances.erase(it);
32 }
33
34 if (_cameraFrame) {
35 _cameraFrame->destroy();
36 _cameraFrame.reset();
37 }
38
39 if (_camera) {
40 delete _camera;
41 _camera = nullptr;
42 }
43 }
44
46 {
47 if (!_camera) {
48 _camera = new Camera();
49 _camera->setNode(_entity);
50 }
51 }
52
54 {
55 _renderSceneColorMap += enabled ? 1 : -1;
56 assert(_renderSceneColorMap >= 0);
57 if (_renderSceneColorMap < 0) {
58 _renderSceneColorMap = 0;
59 }
60
61 if (const auto* systemPtr = system()) {
62 if (const auto* engine = systemPtr->engine()) {
63 _camera->_enableRenderPassColorGrab(engine->graphicsDevice(), renderSceneColorMap());
64 }
65 }
66 }
67
69 {
70 _renderSceneDepthMap += enabled ? 1 : -1;
71 assert(_renderSceneDepthMap >= 0);
72 if (_renderSceneDepthMap < 0) {
73 _renderSceneDepthMap = 0;
74 }
75
76 if (const auto* systemPtr = system()) {
77 if (const auto* engine = systemPtr->engine()) {
78 _camera->_enableRenderPassDepthGrab(engine->graphicsDevice(), renderSceneDepthMap());
79 }
80 }
81 }
82
84 {
85 if (_dof.enabled == enabled) {
86 return;
87 }
88
89 _dof.enabled = enabled;
90 // Upstream camera-frame parity: DOF uses camera-frame owned scene/depth targets,
91 // not legacy scene grab passes. Do NOT call requestSceneDepthMap() here — it would
92 // cause the DEPTH layer to be treated as a grab pass, splitting the render block and
93 // preventing WORLD actions from reaching the CameraFrame. See comment in setSsaoEnabled().
94
95 if (_cameraFrame) {
96 _cameraFrame->destroy();
97 _cameraFrame.reset();
98 }
99 }
100
102 {
103 if (_taa.enabled == enabled) {
104 return;
105 }
106
107 _taa.enabled = enabled;
108 // Upstream camera-frame parity: TAA uses camera-frame owned scene/depth targets,
109 // not legacy scene grab passes.
110
111 if (_camera) {
112 _camera->setJitter(_taa.enabled ? std::max(_taa.jitter, 0.0f) : 0.0f);
113 }
114
115 if (_cameraFrame) {
116 _cameraFrame->destroy();
117 _cameraFrame.reset();
118 }
119
120 if (!enabled) {
121 _taaPass.reset();
122 }
123
124 // DEVIATION: Do NOT call ensureDofRenderTarget() or updatePostprocessRenderTargetBinding()
125 // here. CameraFrame owns its offscreen targets. See comment in setSsaoEnabled().
126 }
127
129 {
130 if (_ssao.enabled == enabled) {
131 return;
132 }
133
134 _ssao.enabled = enabled;
135 // Upstream camera-frame parity: SSAO uses camera-frame owned scene/depth targets,
136 // not legacy scene grab passes. Do NOT call requestSceneDepthMap() here — it would
137 // cause the DEPTH layer to be treated as a grab pass, splitting the render block and
138 // preventing WORLD actions from reaching the CameraFrame.
139
140 // Force CameraFrame recreation so it picks up the new SSAO state.
141 // Without this, the cached CameraFrame's _options may match (stale ssaoType),
142 // causing needsReset() to return false and leaving broken internal pass state.
143 if (_cameraFrame) {
144 _cameraFrame->destroy();
145 _cameraFrame.reset();
146 }
147
148 // DEVIATION: Do NOT call ensureDofRenderTarget() or updatePostprocessRenderTargetBinding()
149 // here. The CameraFrame creates its own offscreen render targets and clones render
150 // actions to them internally. Setting the camera's render target to an offscreen target
151 // poisons the LayerComposition's cached render actions — when SSAO is later toggled off,
152 // those stale actions still reference the offscreen target, causing the scene to render
153 // to an invisible buffer with no back-buffer pass, starving the ring-buffer semaphores.
154 }
155
156 void CameraComponent::ensureDofRenderTarget()
157 {
158 const auto* componentSystem = system();
159 const auto* app = componentSystem ? componentSystem->engine() : nullptr;
160 const auto graphicsDevice = app ? app->graphicsDevice() : nullptr;
161 if (!graphicsDevice) {
162 return;
163 }
164
165 const auto [width, height] = graphicsDevice->size();
166 const bool needsCreate = !_dofSceneColorTexture || !_dofSceneRenderTarget ||
167 static_cast<int>(_dofSceneColorTexture->width()) != width ||
168 static_cast<int>(_dofSceneColorTexture->height()) != height;
169
170 if (!needsCreate) {
171 return;
172 }
173
174 TextureOptions textureOptions;
175 textureOptions.name = "DofSceneColor";
176 textureOptions.width = std::max(width, 1);
177 textureOptions.height = std::max(height, 1);
178 textureOptions.format = PixelFormat::PIXELFORMAT_RGBA8;
179 textureOptions.mipmaps = false;
180 textureOptions.minFilter = FilterMode::FILTER_LINEAR;
181 textureOptions.magFilter = FilterMode::FILTER_LINEAR;
182 _dofSceneColorTexture = std::make_shared<Texture>(graphicsDevice.get(), textureOptions);
183 _dofSceneColorTexture->setAddressU(AddressMode::ADDRESS_CLAMP_TO_EDGE);
184 _dofSceneColorTexture->setAddressV(AddressMode::ADDRESS_CLAMP_TO_EDGE);
185
186 RenderTargetOptions targetOptions;
187 targetOptions.graphicsDevice = graphicsDevice.get();
188 targetOptions.colorBuffer = _dofSceneColorTexture.get();
189 targetOptions.depth = true;
190 targetOptions.stencil = false;
191 targetOptions.name = "DofSceneTarget";
192 _dofSceneRenderTarget = graphicsDevice->createRenderTarget(targetOptions);
193 }
194
195 void CameraComponent::updatePostprocessRenderTargetBinding() const
196 {
197 if (!_camera) {
198 return;
199 }
200
201 if (requiresPostprocessRenderTarget()) {
202 if (_dofSceneRenderTarget) {
203 _camera->setRenderTarget(_dofSceneRenderTarget);
204 }
205 } else if (_camera->renderTarget() == _dofSceneRenderTarget) {
206 _camera->setRenderTarget(nullptr);
207 }
208 }
209
210 std::shared_ptr<RenderPassTAA> CameraComponent::ensureTaaPass(const std::shared_ptr<GraphicsDevice>& device,
211 Texture* sourceTexture)
212 {
213 if (!device || !sourceTexture || !_taa.enabled) {
214 return nullptr;
215 }
216
217 if (!_taaPass) {
218 _taaPass = std::make_shared<RenderPassTAA>(device, sourceTexture, this);
219 } else {
220 _taaPass->setSourceTexture(sourceTexture);
221 }
222 _taaPass->setHighQuality(_taa.highQuality);
223 return _taaPass;
224 }
225}
CameraComponent(IComponentSystem *system, Entity *entity)
std::shared_ptr< RenderPassTAA > ensureTaaPass(const std::shared_ptr< GraphicsDevice > &device, Texture *sourceTexture)
Perspective or orthographic camera with projection matrix, jitter (TAA), and render target binding.
Definition camera.h:40
Entity * entity() const
Definition component.cpp:16
virtual bool enabled() const
Definition component.h:49
Component(IComponentSystem *system, Entity *entity)
Definition component.cpp:12
IComponentSystem * system() const
Definition component.h:47
ECS entity — a GraphNode that hosts components defining its behavior.
Definition entity.h:32
GPU texture resource supporting 2D, cubemap, volume, and array formats with mipmap management.
Definition texture.h:57