Loading...
Searching...
No Matches
CpuAnalysisNode.hpp
1#pragma once
2
3#if SCORE_PLUGIN_GFX
4#include <Crousti/GfxNode.hpp>
5
6namespace oscr
7{
8template <typename Node_T>
9 requires(
10 (avnd::texture_output_introspection<Node_T>::size + avnd::buffer_output_introspection<Node_T>::size + avnd::geometry_output_introspection<Node_T>::size + scene_output_introspection<Node_T>::size) == 0
11 && (avnd::gpu_render_target_output_port_output_introspection<Node_T>::size == 0)
12 )
13struct GfxRenderer<Node_T> final
14 : score::gfx::OutputNodeRenderer
15 , GpuRendererFiles<Node_T>
16{
17 std::shared_ptr<Node_T> state;
18 GpuMessageState m_last_message{};
19 ossia::time_value m_last_time{-1};
20
21 AVND_NO_UNIQUE_ADDRESS texture_inputs_storage<Node_T> texture_ins;
22 AVND_NO_UNIQUE_ADDRESS buffer_inputs_storage<Node_T> buffer_ins;
23 AVND_NO_UNIQUE_ADDRESS geometry_inputs_storage<Node_T> geometry_ins;
24 AVND_NO_UNIQUE_ADDRESS scene_inputs_storage<Node_T> scene_ins;
25
26 const GfxNode<Node_T>& node() const noexcept
27 {
28 return static_cast<const GfxNode<Node_T>&>(score::gfx::NodeRenderer::node);
29 }
30
31 GfxRenderer(const GfxNode<Node_T>& p)
32 : score::gfx::OutputNodeRenderer{p}
33 , state{std::make_shared<Node_T>()}
34 {
35 prepareNewState<Node_T>(state, p);
36 }
37
38 using score::gfx::NodeRenderer::process;
39 void process(int32_t port, const ossia::transform3d& v) override
40 {
41 if constexpr(avnd::geometry_input_introspection<Node_T>::size > 0)
42 geometry_ins.setTransform(port, v, *state);
43 score::gfx::OutputNodeRenderer::process(port, v);
44 }
45
47 renderTargetForInput(const score::gfx::Port& p) override
48 {
49 if constexpr(avnd::texture_input_introspection<Node_T>::size > 0)
50 {
51 auto it = texture_ins.m_rts.find(&p);
52 if(it == texture_ins.m_rts.end())
53 return {};
54 return it->second;
55 }
56 return {};
57 }
58
59 // See CpuFilterNode.hpp for the reasoning: init must live in initState
60 // so the incremental edge-rewire path also runs it.
61 void initState(score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res) override
62 {
63 if(m_initialized)
64 return;
65
66 // See CpuFilterNode for the reasoning: optional renderlist
67 // backchannel populated via SFINAE so nodes can reach the
68 // RenderList's GpuResourceRegistry / AssetTable without plumbing.
69 if constexpr(requires { state->renderlist = &renderer; })
70 state->renderlist = &renderer;
71
72 if constexpr(requires { state->prepare(); })
73 {
74 this->node().processControlIn(
75 *this, *state, m_last_message, this->node().last_message, this->node().m_ctx);
76 state->prepare();
77 }
78
79 // Init input render targets
80 if constexpr(avnd::texture_input_introspection<Node_T>::size > 0)
81 texture_ins.init(*this, renderer);
82
83 if_possible(state->init(renderer, res));
84
85 m_initialized = true;
86 }
87
88 void init(score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res) override
89 {
90 initState(renderer, res);
91 }
92
93 void update(
94 score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res,
95 score::gfx::Edge* e) override
96 {
97 auto& parent = node();
98 parent.processControlIn(
99 *this, *state, m_last_message, parent.last_message, parent.m_ctx);
100
101 bool updated = false;
102 if constexpr(avnd::texture_input_introspection<Node_T>::size > 0)
103 {
104 updated |= texture_ins.update(*this, renderer, res);
105 }
106 if_possible(state->update(renderer, res, e));
107
108 if(updated)
109 {
110 // We must notify the graph that the previous nodes have to be recomputed
111 }
112 }
113
114 void releaseState(score::gfx::RenderList& r) override
115 {
116 if(!m_initialized)
117 return;
118
119 if constexpr(avnd::texture_input_introspection<Node_T>::size > 0)
120 texture_ins.release();
121
122 if constexpr(avnd::geometry_input_introspection<Node_T>::size > 0)
123 geometry_ins.release(r);
124
125 if constexpr(scene_input_introspection<Node_T>::size > 0)
126 scene_ins.release(r);
127
128 if constexpr(
129 avnd::texture_input_introspection<Node_T>::size > 0
130 || avnd::texture_output_introspection<Node_T>::size > 0)
131 {
132 // No call-through to GenericNodeRenderer::defaultRelease here:
133 // CpuAnalysisNode's GfxRenderer derives from OutputNodeRenderer,
134 // not GenericNodeRenderer, and OutputNodeRenderer has no
135 // defaultRelease equivalent (it owns no pipeline / passes — it
136 // is a sink, not a node renderer with m_p / m_pipelineCache).
137 // CpuFilterNode's mirror is valid because that GfxRenderer derives
138 // from GenericNodeRenderer.
139 //
140 // If a future CpuAnalysisNode uses textures via OutputNodeRenderer
141 // surfaces, they'll need their own per-storage release path
142 // (texture_ins.release above already handles texture INPUTS).
143 }
144
145 if_possible(state->release(r));
146
147 // Clear the optional renderlist backchannel. Paired with initState;
148 // same SFINAE guard.
149 if constexpr(requires { state->renderlist = nullptr; })
150 state->renderlist = nullptr;
151
152 m_initialized = false;
153 }
154
155 void release(score::gfx::RenderList& r) override
156 {
157 releaseState(r);
158 }
159
160 void inputAboutToFinish(
161 score::gfx::RenderList& renderer, const score::gfx::Port& p,
162 QRhiResourceUpdateBatch*& res) override
163 {
164 // Outer guard includes scene_input_introspection so a node with ONLY
165 // scene inputs (no texture / buffer / geometry) still allocates `res`
166 // — necessary if scene_inputs_storage ever grows an inputAboutToFinish
167 // method (today it's read-only via readInputScenes, but the storage's
168 // lifecycle is part of the new scene_port concept and may evolve).
169 // Without the include, a scene-only sink would silently skip the
170 // res allocation and any future scene-side write would have nowhere
171 // to land.
172 if constexpr(
173 avnd::texture_input_introspection<Node_T>::size > 0
174 || avnd::buffer_input_introspection<Node_T>::size > 0
175 || avnd::geometry_input_introspection<Node_T>::size > 0
176 || scene_input_introspection<Node_T>::size > 0)
177 {
178 res = renderer.state.rhi->nextResourceUpdateBatch();
179
180 if constexpr(avnd::texture_input_introspection<Node_T>::size > 0)
181 texture_ins.inputAboutToFinish(*this, renderer, p, res);
182 if constexpr(avnd::buffer_input_introspection<Node_T>::size > 0)
183 buffer_ins.inputAboutToFinish(renderer, res, *state, this->node());
184 if constexpr(avnd::geometry_input_introspection<Node_T>::size > 0)
185 geometry_ins.inputAboutToFinish(
186 renderer, res, this->geometry, *state, this->node());
187 // No scene_ins.inputAboutToFinish today — the guard is forward-
188 // looking; add the call here when scene_inputs_storage grows one.
189 }
190
191 if_possible(state->inputAboutToFinish(renderer, p, res));
192 }
193
194 void runInitialPasses(
195 score::gfx::RenderList& renderer, QRhiCommandBuffer& commands,
196 QRhiResourceUpdateBatch*& res, score::gfx::Edge& edge) override
197 {
198 auto& parent = this->node();
199 auto& rhi = *renderer.state.rhi;
200
201 // Insert a synchronisation point to allow readbacks to complete
202 rhi.finish();
203
204 // If we are paused, we don't run the processor implementation.
205 if(parent.last_message.message.token.date == m_last_time)
206 return;
207 m_last_time = parent.last_message.message.token.date;
208
209 if constexpr(avnd::texture_input_introspection<Node_T>::size > 0)
210 texture_ins.runInitialPasses(*this, renderer);
211 if constexpr(avnd::buffer_input_introspection<Node_T>::size > 0)
212 buffer_ins.readInputBuffers(renderer, parent, *state);
213 if constexpr(avnd::geometry_input_introspection<Node_T>::size > 0)
214 geometry_ins.readInputGeometries(renderer, this->geometry, parent, *state);
215 if constexpr(scene_input_introspection<Node_T>::size > 0)
216 scene_ins.readInputScenes(*this, *state);
217
218 parent.processControlIn(
219 *this, *state, m_last_message, parent.last_message, parent.m_ctx);
220
221 // Run the processor
222 parent.drainWorker(renderer.dateFromStepClock);
223 if_possible(state->runInitialPasses(renderer, commands, res, edge));
224 if_possible((*state)());
225 parent.clearControlIn(*state);
226
227 // Copy the data to the model node
228 parent.processControlOut(*this->state, renderer, parent);
229 }
230};
231
232template <typename Node_T>
233 requires((avnd::texture_output_introspection<Node_T>::size
234 + avnd::buffer_output_introspection<Node_T>::size
235 + avnd::geometry_output_introspection<Node_T>::size
236 + scene_output_introspection<Node_T>::size)
237 == 0
238 && (avnd::gpu_render_target_output_port_output_introspection<Node_T>::size
239 == 0))
240struct GfxNode<Node_T> final
241 : CustomGpuOutputNodeBase
242 , GpuNodeElements<Node_T>
243{
244 // Renderers are rebuilt during document teardown, after the model is gone.
245 QPointer<oscr::ProcessModel<Node_T>> processModel;
246 GfxNode(
248 std::weak_ptr<Execution::ExecutionCommandQueue> q, Gfx::exec_controls ctls,
249 int64_t id, const score::DocumentContext& ctx)
250 : CustomGpuOutputNodeBase{std::move(q), std::move(ctls), ctx}
251 , processModel{&element}
252 {
253 this->instance = id;
254
255 initGfxPorts<Node_T>(this, this->input, this->output);
256 }
257
259 createRenderer(score::gfx::RenderList& r) const noexcept override
260 {
261 return new GfxRenderer<Node_T>{*this};
262 }
263};
264}
265#endif
Definition score-plugin-avnd/Crousti/ProcessModel.hpp:122
Definition OutputNode.hpp:38
List of nodes to be rendered to an output.
Definition RenderList.hpp:36
Definition Controls.hpp:30
Base toolkit upon which the software is built.
Definition Application.cpp:83
STL namespace.
Definition DocumentContext.hpp:18
Connection between two score::gfx::Port.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:236
Port of a score::gfx::Node.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:215
Useful abstraction for storing all the data related to a render target.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:307