VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
layerComposition.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 13.10.2025.
5//
6#include "layerComposition.h"
7
8#include <spdlog/spdlog.h>
10
11namespace visutwin::canvas
12{
14 {
15 clearRenderActions();
16 }
17
18 bool LayerComposition::isSublayerAdded(const std::shared_ptr<Layer>& layer, bool transparent) const {
19 if (const auto& map = transparent ? _layerTransparentIndexMap : _layerOpaqueIndexMap; map.contains(layer)) {
20 spdlog::error("Sublayer {}, transparent: {} is already added.", layer->name(), transparent);
21 return true;
22 }
23 return false;
24 }
25
26 void LayerComposition::pushOpaque(const std::shared_ptr<Layer>& layer) {
27 // add opaque to the end of the array
28 if (isSublayerAdded(layer, false))
29 {
30 return;
31 }
32 _layerList.push_back(layer);
33 _opaqueOrder[layer->id()] = _subLayerList.size();
34 _subLayerList.push_back(false);
35 _subLayerEnabled.push_back(true);
36
37 updateLayerMaps();
38 _dirty = true;
39 fire("add", layer);
40 }
41
42 void LayerComposition::pushTransparent(const std::shared_ptr<Layer>& layer) {
43 // add transparent to the end of the array
44 if (isSublayerAdded(layer, true))
45 {
46 return;
47 }
48 _layerList.push_back(layer);
49 _transparentOrder[layer->id()] = _subLayerList.size();
50 _subLayerList.push_back(true);
51 _subLayerEnabled.push_back(true);
52
53 updateLayerMaps();
54 _dirty = true;
55 fire("add", layer);
56 }
57
58 const std::vector<RenderAction*>& LayerComposition::renderActions()
59 {
60 const auto& cameras = CameraComponent::instances();
61 if (cameras.size() != _lastCameraCount) {
62 _dirty = true;
63 }
64 if (_renderActions.empty() && !cameras.empty()) {
65 _dirty = true;
66 }
67 rebuildRenderActions();
68 return _renderActions;
69 }
70
71 std::shared_ptr<Layer> LayerComposition::getLayerById(const int layerId) const
72 {
73 const auto it = _layerIdMap.find(layerId);
74 return it != _layerIdMap.end() ? it->second : nullptr;
75 }
76
77 std::shared_ptr<Layer> LayerComposition::getLayerByName(const std::string& name) const
78 {
79 const auto it = _layerNameMap.find(name);
80 return it != _layerNameMap.end() ? it->second : nullptr;
81 }
82
83 int LayerComposition::getOpaqueIndex(const std::shared_ptr<Layer>& layer) const
84 {
85 const auto it = _layerOpaqueIndexMap.find(layer);
86 return it != _layerOpaqueIndexMap.end() ? it->second : -1;
87 }
88
89 int LayerComposition::getTransparentIndex(const std::shared_ptr<Layer>& layer) const
90 {
91 const auto it = _layerTransparentIndexMap.find(layer);
92 return it != _layerTransparentIndexMap.end() ? it->second : -1;
93 }
94
95 bool LayerComposition::isEnabled(const Layer* layer, const bool transparent) const
96 {
97 if (!layer || !layer->enabled()) {
98 return false;
99 }
100
101 const auto layerIt = _layerIdMap.find(layer->id());
102 if (layerIt == _layerIdMap.end()) {
103 return false;
104 }
105
106 const int index = transparent ? getTransparentIndex(layerIt->second) : getOpaqueIndex(layerIt->second);
107 if (index < 0 || static_cast<size_t>(index) >= _subLayerEnabled.size()) {
108 return false;
109 }
110
111 return _subLayerEnabled[index];
112 }
113
114 void LayerComposition::updateLayerMaps() {
115 _layerIdMap.clear();
116 _layerNameMap.clear();
117 _layerOpaqueIndexMap.clear();
118 _layerTransparentIndexMap.clear();
119
120 for (size_t i = 0; i < _layerList.size(); i++) {
121 auto& layer = _layerList[i];
122 _layerIdMap[layer->id()] = layer;
123 _layerNameMap[layer->name()] = layer;
124
125 auto& subLayerIndexMap = _subLayerList[i] ? _layerTransparentIndexMap : _layerOpaqueIndexMap;
126 subLayerIndexMap[layer] = i;
127 }
128 }
129
130 void LayerComposition::clearRenderActions()
131 {
132 for (auto* action : _renderActions) {
133 delete action;
134 }
135 _renderActions.clear();
136 }
137
138 void LayerComposition::rebuildRenderActions()
139 {
140 if (!_dirty) {
141 for (const auto& layer : _layerList) {
142 if (layer && layer->dirtyComposition()) {
143 _dirty = true;
144 break;
145 }
146 }
147 }
148
149 if (!_dirty) {
150 return;
151 }
152
153 clearRenderActions();
154
155 const auto& cameras = CameraComponent::instances();
156 if (cameras.empty()) {
157 _lastCameraCount = 0;
158 _dirty = false;
159 return;
160 }
161
162 for (auto* cameraComponent : cameras) {
163 if (!cameraComponent || !cameraComponent->enabled() || !cameraComponent->camera()) {
164 continue;
165 }
166
167 const bool useCameraPasses = !cameraComponent->renderPasses().empty();
168 if (useCameraPasses) {
169 auto* action = new RenderAction();
170 action->camera = cameraComponent;
171 action->useCameraPasses = true;
172 _renderActions.push_back(action);
173 continue;
174 }
175
176 bool firstCameraUse = true;
177 RenderAction* lastRenderAction = nullptr;
178 for (size_t i = 0; i < _layerList.size(); ++i) {
179 if (i >= _subLayerEnabled.size() || !_subLayerEnabled[i]) {
180 continue;
181 }
182
183 const auto& layerRef = _layerList[i];
184 if (!layerRef || !layerRef->enabled() || !cameraComponent->rendersLayer(layerRef->id())) {
185 continue;
186 }
187
188 auto* action = new RenderAction();
189 action->camera = cameraComponent;
190 action->useCameraPasses = useCameraPasses;
191 action->layer = layerRef.get();
192 action->renderTarget = (layerRef->id() == LAYERID_DEPTH)
193 ? nullptr
194 : cameraComponent->camera()->renderTarget();
195 action->firstCameraUse = firstCameraUse;
196 action->triggerPostprocess = false;
197 action->transparent = _subLayerList[i];
198 action->lastCameraUse = false;
199
200 // Match upstream clear behavior: camera clears on first use / first use of target, layer clears always apply.
201 bool usedCameraTarget = false;
202 for (auto existingIt = _renderActions.rbegin(); existingIt != _renderActions.rend(); ++existingIt) {
203 const auto* existing = *existingIt;
204 if (!existing || existing->camera != cameraComponent) {
205 continue;
206 }
207 if (existing->renderTarget == action->renderTarget) {
208 usedCameraTarget = true;
209 break;
210 }
211 }
212 const bool needsCameraClear = firstCameraUse || !usedCameraTarget;
213 if (needsCameraClear || layerRef->clearColorBuffer() || layerRef->clearDepthBuffer() || layerRef->clearStencilBuffer()) {
214 action->setupClears(needsCameraClear ? cameraComponent : nullptr, layerRef.get());
215 }
216
217 action->lastCameraUse = false;
218
219 firstCameraUse = false;
220 lastRenderAction = action;
221 _renderActions.push_back(action);
222 }
223
224 if (lastRenderAction) {
225 lastRenderAction->lastCameraUse = true;
226 // DEVIATION: disablePostEffectsLayer / full camera stack propagation is not ported yet.
227 // Keep parity for default behavior by triggering postprocess on the camera's last render action.
228 lastRenderAction->triggerPostprocess = true;
229 }
230 }
231
232 for (const auto& layer : _layerList) {
233 if (layer) {
234 layer->setDirtyComposition(false);
235 }
236 }
237 _lastCameraCount = cameras.size();
238 _dirty = false;
239 }
240}
static const std::vector< CameraComponent * > & instances()
EventHandler * fire(const std::string &name, Args &&... args)
std::shared_ptr< Layer > getLayerById(int layerId) const
std::shared_ptr< Layer > getLayerByName(const std::string &name) const
const std::vector< RenderAction * > & renderActions()
bool isEnabled(const Layer *layer, bool transparent) const
void pushOpaque(const std::shared_ptr< Layer > &layer)
void pushTransparent(const std::shared_ptr< Layer > &layer)
bool enabled() const
Definition layer.h:54
int id() const
Definition layer.h:26
constexpr int LAYERID_DEPTH
Definition constants.h:18