VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
forwardRenderer.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 11.09.2025.
5//
6#include "forwardRenderer.h"
7
8#include <unordered_set>
9
12#include "scene/light.h"
13#include "renderPassForward.h"
15#include "scene/constants.h"
16
17namespace visutwin::canvas
18{
20 {
21 frameGraph->reset();
22
23 if (_scene->clusteredLightingEnabled())
24 {
25 const auto lighting = _scene->lighting();
26 _renderPassUpdateClustered->update(frameGraph, lighting.shadowsEnabled, lighting.cookiesEnabled,
29 }
30 else
31 {
32 // Cull local light shadow maps: allocate shadow maps, position shadow cameras,
33 // compute VP matrices for all shadow-casting local (spot/point) lights.
34 std::vector<Light*> localShadowLights;
35 {
36 for (auto* lightComponent : LightComponent::instances()) {
37 if (!lightComponent || !lightComponent->enabled()) {
38 continue;
39 }
40 if (lightComponent->type() == LightType::LIGHTTYPE_DIRECTIONAL) {
41 continue;
42 }
43 if (!lightComponent->castShadows()) {
44 continue;
45 }
46 Light* sceneLight = lightComponent->light();
47 if (sceneLight) {
48 localShadowLights.push_back(sceneLight);
49 }
50 }
51 if (!localShadowLights.empty()) {
52 _shadowRendererLocal->cullLocalLights(localShadowLights, _device, _ownedShadowMaps);
53 }
54 }
55
56 // Build shadow render passes using the actual shadow-casting lights
57 // (not _localLights which is only populated in the clustered path).
58 _shadowRendererLocal->buildNonClusteredRenderPasses(frameGraph, localShadowLights);
59 }
60
61 // Cull directional shadow maps for each unique camera in the layer composition.
62 // This positions shadow cameras and populates _cameraDirShadowLights.
63 {
64 const auto& actions = layerComposition->renderActions();
65 std::unordered_set<Camera*> culledCameras;
66 for (const auto* action : actions) {
67 if (action && action->camera) {
68 Camera* cam = action->camera->camera();
69 if (cam && culledCameras.insert(cam).second) {
70 cullShadowmaps(cam);
71 }
72 }
73 }
74 }
75
76 if (auto* directionalShadowRenderer = shadowRendererDirectional()) {
77 directionalShadowRenderer->buildNonClusteredRenderPasses(frameGraph, _cameraDirShadowLights);
78 }
79
80 int startIndex = 0;
81 bool newStart = true;
82 RenderTarget* renderTarget = nullptr;
83 const auto& renderActions = layerComposition->renderActions();
84
85 for (int i = startIndex; i < renderActions.size(); i++) {
86 if (auto* renderAction = renderActions[i]; renderAction->useCameraPasses) {
87 // schedule render passes from the camera
88 for (auto renderPass : renderAction->camera->renderPasses()) {
89 if (renderPass) {
90 frameGraph->addRenderPass(renderPass);
91 }
92 };
93 } else {
94 const auto isDepthLayer = renderAction->layer->id() == LAYERID_DEPTH;
95 const auto isGrabPass = isDepthLayer &&
96 (renderAction->camera->renderSceneColorMap() || renderAction->camera->renderSceneDepthMap());
97
98 // start of block of render actions rendering to the same render target
99 if (newStart) {
100 newStart = false;
101 startIndex = i;
102 renderTarget = renderAction->renderTarget.get();
103 }
104
105 // info about the next render action
106 auto* nextRenderAction = (i + 1 < renderActions.size()) ? renderActions[i + 1] : nullptr;
107 const auto isNextLayerDepth = nextRenderAction ? (!nextRenderAction->useCameraPasses && nextRenderAction->layer->id() == LAYERID_DEPTH) : false;
108 const auto isNextLayerGrabPass = isNextLayerDepth &&
109 (renderAction->camera->renderSceneColorMap() || renderAction->camera->renderSceneDepthMap());
110
111 auto* camera = (nextRenderAction && nextRenderAction->camera) ? nextRenderAction->camera->camera() : nullptr;
112 const auto nextNeedDirShadows = nextRenderAction ?
113 (nextRenderAction->firstCameraUse && _cameraDirShadowLights.contains(camera)) : false;
114
115 // The depth layer uses a null render target (separate from the camera's target).
116 // When it is NOT a grab pass it will be skipped as depth-only, so it should not
117 // break the current block — otherwise the camera-frame postprocessing pass ends
118 // up missing the opaque world layer that precedes the depth layer, causing the
119 // scene to render black when TAA/DOF is enabled.
120 const bool nextIsNonGrabDepth = isNextLayerDepth && !isNextLayerGrabPass;
121 const bool rtChanged = nextRenderAction && nextRenderAction->renderTarget.get() != renderTarget && !nextIsNonGrabDepth;
122
123 // end of the block using the same render target if the next render action uses a different render target or needs directional shadows
124 // rendered before it or similar or needs another pass before it.
125 if (!nextRenderAction || rtChanged || nextNeedDirShadows ||
126 isNextLayerGrabPass || isGrabPass) {
127
128 const bool useCameraFrame = renderAction->triggerPostprocess && renderAction->camera &&
129 renderAction->camera->onPostprocessing() != nullptr;
130
131 // render the render actions in the range
132 if (const auto isDepthOnly = isDepthLayer && startIndex == i; !isDepthOnly) {
133 if (useCameraFrame) {
134 std::vector<RenderAction*> blockActions;
135 blockActions.reserve(static_cast<size_t>(i - startIndex + 1));
136 for (int actionIndex = startIndex; actionIndex <= i; ++actionIndex) {
137 auto* blockAction = renderActions[actionIndex];
138 if (blockAction) {
139 blockActions.push_back(blockAction);
140 }
141 }
142 if (!blockActions.empty()) {
143 // Get or create persistent CameraFrame on the camera component.
144 // The CameraFrame manages its own internal offscreen render
145 // targets (scene color, depth, TAA history). Persisting it across
146 // frames avoids reallocating ~56MB of GPU textures per frame and
147 // preserves TAA history for correct temporal accumulation.
148 auto cameraFramePass = renderAction->camera->cameraFrame();
149 if (!cameraFramePass) {
150 cameraFramePass = std::make_shared<RenderPassCameraFrame>(
151 _device, layerComposition, _scene.get(), this, blockActions, renderAction->camera, nullptr);
152 renderAction->camera->setCameraFrame(cameraFramePass);
153 } else {
154 cameraFramePass->updateSourceActions(
155 blockActions, layerComposition, _scene.get(), this, nullptr);
156 }
157 frameGraph->addRenderPass(cameraFramePass);
158 }
159 } else {
160 addMainRenderPass(frameGraph, layerComposition, renderTarget, startIndex, i);
161 }
162 }
163
164 // depth layer triggers grab passes if enabled
165 if (isDepthLayer && !useCameraFrame) {
166 if (renderAction->camera->renderSceneColorMap()) {
167 const auto colorGrabPass = renderAction->camera->camera()->renderPassColorGrab();
168 if (colorGrabPass) {
169 colorGrabPass->setSource(renderAction->renderTarget);
170 frameGraph->addRenderPass(colorGrabPass);
171 }
172 }
173
174 if (renderAction->camera->renderSceneDepthMap()) {
175 const auto depthGrabPass = renderAction->camera->camera()->renderPassDepthGrab();
176 if (depthGrabPass) {
177 frameGraph->addRenderPass(depthGrabPass);
178 }
179 }
180 }
181
182 // postprocessing
183 if (!useCameraFrame && renderAction->triggerPostprocess && renderAction->camera &&
184 renderAction->camera->onPostprocessing()) {
185 auto renderPass = std::make_shared<RenderPassPostprocessing>(_device, this, renderAction);
186 frameGraph->addRenderPass(renderPass);
187 }
188
189 newStart = true;
190 }
191 }
192 }
193 }
194
196 RenderTarget* renderTarget, int startIndex, int endIndex)
197 {
198 if (!frameGraph || !layerComposition) {
199 return;
200 }
201
202 const auto& renderActions = layerComposition->renderActions();
203 if (renderActions.empty() || startIndex < 0 || endIndex < startIndex ||
204 static_cast<size_t>(endIndex) >= renderActions.size()) {
205 return;
206 }
207
208 auto* firstRenderAction = renderActions[startIndex];
209 if (!firstRenderAction || !firstRenderAction->camera) {
210 return;
211 }
212
213 std::shared_ptr<RenderTarget> passTarget = firstRenderAction->renderTarget;
214 if (!passTarget && renderTarget != nullptr) {
215 // Intentional fallback to preserve API shape until render actions are the single source of truth.
216 passTarget = firstRenderAction->camera->camera() ? firstRenderAction->camera->camera()->renderTarget() : nullptr;
217 }
218
219 auto mainPass = std::make_shared<RenderPassForward>(
220 _device, layerComposition, _scene.get(), this
221 );
222 mainPass->init(passTarget);
223
224 for (int i = startIndex; i <= endIndex; ++i) {
225 auto* ra = renderActions[i];
226 if (!ra) {
227 continue;
228 }
229 const auto* cameraForAction = ra->camera;
230 bool hasPreviousForCamera = false;
231 bool hasNextForCamera = false;
232
233 for (int j = startIndex; j < i; ++j) {
234 if (renderActions[j] && renderActions[j]->camera == cameraForAction) {
235 hasPreviousForCamera = true;
236 break;
237 }
238 }
239 for (int j = i + 1; j <= endIndex; ++j) {
240 if (renderActions[j] && renderActions[j]->camera == cameraForAction) {
241 hasNextForCamera = true;
242 break;
243 }
244 }
245
246 ra->firstCameraUse = !hasPreviousForCamera;
247 ra->lastCameraUse = !hasNextForCamera;
248 mainPass->addRenderAction(ra);
249 }
250
251 frameGraph->addRenderPass(mainPass);
252 }
253}
Perspective or orthographic camera with projection matrix, jitter (TAA), and render target binding.
Definition camera.h:40
void buildFrameGraph(FrameGraph *frameGraph, LayerComposition *layerComposition)
void addMainRenderPass(FrameGraph *frameGraph, LayerComposition *layerComposition, RenderTarget *renderTarget, int startIndex, int endIndex)
void addRenderPass(const std::shared_ptr< RenderPass > &renderPass)
const std::vector< RenderAction * > & renderActions()
static const std::vector< LightComponent * > & instances()
Directional, point, spot, or area light with shadow mapping and cookie projection.
Definition light.h:54
std::shared_ptr< Scene > _scene
Definition renderer.h:43
std::vector< Light * > _lights
Definition renderer.h:50
ShadowRendererDirectional * shadowRendererDirectional() const
Definition renderer.h:59
std::shared_ptr< RenderPassUpdateClustered > _renderPassUpdateClustered
Definition renderer.h:45
std::unordered_map< Camera *, std::vector< Light * > > _cameraDirShadowLights
Definition renderer.h:57
std::unique_ptr< ShadowRendererLocal > _shadowRendererLocal
Definition renderer.h:47
std::shared_ptr< GraphicsDevice > _device
Definition renderer.h:41
std::vector< Light * > _localLights
Definition renderer.h:53
void cullShadowmaps(Camera *camera)
Definition renderer.cpp:170
std::vector< std::unique_ptr< ShadowMap > > _ownedShadowMaps
Definition renderer.h:62
constexpr int LAYERID_DEPTH
Definition constants.h:18