Loading...
Searching...
No Matches
CpuAnalysisNode.hpp
1#pragma once
2
3#if SCORE_PLUGIN_GFX
4#include <Crousti/GfxNode.hpp>
5
6namespace oscr
7{
8
9template <typename Node_T>
10 requires(
11 avnd::texture_input_introspection<Node_T>::size > 0
12 && avnd::texture_output_introspection<Node_T>::size == 0)
13struct GfxRenderer<Node_T> final : score::gfx::OutputNodeRenderer
14{
15 using texture_inputs = avnd::texture_input_introspection<Node_T>;
16 Node_T state;
17 score::gfx::Message m_last_message{};
18 ossia::small_flat_map<const score::gfx::Port*, score::gfx::TextureRenderTarget, 2>
19 m_rts;
20
21 std::vector<QRhiReadbackResult> m_readbacks;
22 ossia::time_value m_last_time{-1};
23
24 const GfxNode<Node_T>& node() const noexcept
25 {
26 return static_cast<const GfxNode<Node_T>&>(score::gfx::NodeRenderer::node);
27 }
28 GfxRenderer(const GfxNode<Node_T>& p)
29 : score::gfx::OutputNodeRenderer{p}
30 , m_readbacks(texture_inputs::size)
31 {
32 prepareNewState(state, p);
33 }
34
36 renderTargetForInput(const score::gfx::Port& p) override
37 {
38 auto it = m_rts.find(&p);
39 SCORE_ASSERT(it != m_rts.end());
40 return it->second;
41 }
42
43 template <typename Tex>
44 void createInput(
45 score::gfx::RenderList& renderer, int k, const Tex& texture_spec,
47 {
48 auto port = this->node().input[k];
49 static constexpr auto flags
50 = QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource;
51 auto texture = renderer.state.rhi->newTexture(
52 gpp::qrhi::textureFormat<Tex>(), spec.size, 1, flags);
53 SCORE_ASSERT(texture->create());
55 renderer.state, texture, renderer.samples(), renderer.requiresDepth());
56 }
57
58 QRhiTexture* texture(int k) const noexcept
59 {
60 auto port = this->node().input[k];
61 auto it = m_rts.find(port);
62 SCORE_ASSERT(it != m_rts.end());
63 SCORE_ASSERT(it->second.texture);
64 return it->second.texture;
65 }
66
67 void loadInputTexture(QRhi& rhi, avnd::cpu_texture auto& cpu_tex, int k)
68 {
69 auto& buf = m_readbacks[k].data;
70 if(buf.size() != 4 * cpu_tex.width * cpu_tex.height)
71 {
72 cpu_tex.bytes = nullptr;
73 }
74 else
75 {
76 cpu_tex.bytes = reinterpret_cast<unsigned char*>(buf.data());
77
78 if(rhi.isYUpInFramebuffer())
79 if(cpu_tex.width * cpu_tex.height > 0)
80 inplaceMirror(cpu_tex.bytes, cpu_tex.width, cpu_tex.height);
81
82 cpu_tex.changed = true;
83 }
84 }
85
86 void init(score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res) override
87 {
88 if constexpr(requires { state.prepare(); })
89 {
90 this->node().processControlIn(
91 *this, state, m_last_message, this->node().last_message, this->node().m_ctx);
92 state.prepare();
93 }
94
95 // Init input render targets
96 int k = 0;
97 avnd::cpu_texture_input_introspection<Node_T>::for_all(
98 avnd::get_inputs<Node_T>(state), [&]<typename F>(F& t) {
99 // FIXME k isn't the port index, it's the texture port index
100 auto spec = this->node().resolveRenderTargetSpecs(k, renderer);
101 if constexpr(requires {
102 t.request_width;
103 t.request_height;
104 })
105 {
106 spec.size.rwidth() = t.request_width;
107 spec.size.rheight() = t.request_height;
108 }
109 createInput(renderer, k, t.texture, spec);
110 if constexpr(avnd::cpu_fixed_format_texture<decltype(t.texture)>)
111 {
112 t.texture.width = spec.size.width();
113 t.texture.height = spec.size.height();
114 }
115 k++;
116 });
117 }
118
119 void update(
120 score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res,
121 score::gfx::Edge* edge) override
122 {
123 bool updated = false;
124 /*
125 int k = 0;
126 avnd::cpu_texture_input_introspection<Node_T>::for_all(
127 avnd::get_inputs<Node_T>(state), [&]<typename F>(F& t) {
128 if constexpr(requires {
129 t.request_width;
130 t.request_height;
131 })
132 {
133 const auto tex = this->texture(k)->pixelSize();
134 if(tex.width() != t.request_width || tex.height() != t.request_height)
135 {
136 QSize sz{t.request_width, t.request_height};
137
138 // Release
139 auto port = parent.input[k];
140
141 m_rts[port].release();
142 createInput(renderer, k, t.texture, sz);
143
144 t.texture.width = sz.width();
145 t.texture.height = sz.height();
146
147 updated = true;
148 }
149 }
150 k++;
151 });
152*/
153 if(updated)
154 {
155 // We must notify the graph that the previous nodes have to be recomputed
156 }
157 }
158
159 void release(score::gfx::RenderList& r) override
160 {
161 // Free inputs
162 // TODO investigate why reference does not work here:
163 for(auto [port, rt] : m_rts)
164 rt.release();
165 m_rts.clear();
166 }
167
168 void inputAboutToFinish(
169 score::gfx::RenderList& renderer, const score::gfx::Port& p,
170 QRhiResourceUpdateBatch*& res) override
171 {
172 auto& parent = this->node();
173 res = renderer.state.rhi->nextResourceUpdateBatch();
174 const auto& inputs = parent.input;
175 auto index_of_port = ossia::find(inputs, &p) - inputs.begin();
176 SCORE_ASSERT(index_of_port == 0);
177 {
178 auto tex = m_rts[&p].texture;
179 auto& readback = m_readbacks[index_of_port];
180 readback = {};
181 res->readBackTexture(QRhiReadbackDescription{tex}, &readback);
182 }
183 }
184
185 void runInitialPasses(
186 score::gfx::RenderList& renderer, QRhiCommandBuffer& commands,
187 QRhiResourceUpdateBatch*& res, score::gfx::Edge& edge) override
188 {
189 auto& parent = this->node();
190 auto& rhi = *renderer.state.rhi;
191
192 // Insert a synchronisation point to allow readbacks to complete
193 rhi.finish();
194
195 // If we are paused, we don't run the processor implementation.
196 if(parent.last_message.token.date == m_last_time)
197 {
198 return;
199 }
200 m_last_time = parent.last_message.token.date;
201
202 // Fetch input textures (if any)
203 {
204 // Copy the readback output inside the structure
205 // TODO it would be much better to do this inside the readback's
206 // "completed" callback.
207 int k = 0;
208 avnd::cpu_texture_input_introspection<Node_T>::for_all(
209 avnd::get_inputs<Node_T>(state), [&](auto& t) {
210 loadInputTexture(rhi, t.texture, k);
211 k++;
212 });
213 }
214
215 parent.processControlIn(
216 *this, state, m_last_message, parent.last_message, parent.m_ctx);
217
218 // Run the processor
219 state();
220
221 // Copy the data to the model node
222 parent.processControlOut(this->state);
223
224 // Copy the geometry
225 // FIXME we need something such as port_run_{pre,post}process for GPU nodes
226 avnd::geometry_output_introspection<Node_T>::for_all_n2(
227 state.outputs, [&]<std::size_t F, std::size_t P>(
228 auto& t, avnd::predicate_index<P>, avnd::field_index<F>) {
229 postprocess_geometry(t);
230 });
231 }
232 template <avnd::geometry_port Field>
233 void postprocess_geometry(Field& ctrl)
234 {
235 using namespace avnd;
236 bool mesh_dirty{};
237 bool tform_dirty{};
238 mesh_dirty = ctrl.dirty_mesh;
239
240 if(ctrl.dirty_mesh)
241 {
242 auto meshes = std::make_shared<ossia::mesh_list>();
243 auto& ossia_meshes = *meshes;
244 if constexpr(static_geometry_type<Field> || dynamic_geometry_type<Field>)
245 {
246 ossia_meshes.meshes.resize(1);
247 oscr::load_geometry(ctrl, ossia_meshes.meshes[0]);
248 }
249 else if constexpr(
250 static_geometry_type<decltype(Field::mesh)>
251 || dynamic_geometry_type<decltype(Field::mesh)>)
252 {
253 ossia_meshes.meshes.resize(1);
254 oscr::load_geometry(ctrl.mesh, ossia_meshes.meshes[0]);
255 }
256 else
257 {
258 oscr::load_geometry(ctrl, ossia_meshes);
259 }
260 }
261 ctrl.dirty_mesh = false;
262
263 // if constexpr(requires { ctrl.transform; })
264 // {
265 // if(ctrl.dirty_transform)
266 // {
267 // std::copy_n(
268 // ctrl.transform, std::ssize(ctrl.transform), port.data.transform.matrix);
269 // tform_dirty = true;
270 // ctrl.dirty_transform = false;
271 // }
272 // }
273 //
274 // port.data.flags = {};
275 // if(mesh_dirty)
276 // port.data.flags = port.data.flags | ossia::geometry_port::dirty_meshes;
277 // if(tform_dirty)
278 // port.data.flags = port.data.flags | ossia::geometry_port::dirty_transform;
279 }
280};
281
282template <typename Node_T>
283 requires(avnd::texture_input_introspection<Node_T>::size > 0
284 && avnd::texture_output_introspection<Node_T>::size == 0)
285struct GfxNode<Node_T> final
286 : CustomGpuOutputNodeBase
287 , GpuNodeElements<Node_T>
288{
289 oscr::ProcessModel<Node_T>& processModel;
290 GfxNode(
292 std::weak_ptr<Execution::ExecutionCommandQueue> q, Gfx::exec_controls ctls, int id,
293 const score::DocumentContext& ctx)
294 : CustomGpuOutputNodeBase{std::move(q), std::move(ctls), ctx}
295 , processModel{element}
296 {
297 this->instance = id;
298
299 initGfxPorts<Node_T>(this, this->input, this->output);
300 }
301
303 createRenderer(score::gfx::RenderList& r) const noexcept override
304 {
305 return new GfxRenderer<Node_T>{*this};
306 }
307};
308}
309#endif
Definition score-plugin-avnd/Crousti/ProcessModel.hpp:79
Definition OutputNode.hpp:11
List of nodes to be rendered to an output.
Definition RenderList.hpp:19
bool requiresDepth() const noexcept
Whether this list of rendering actions requires depth testing at all.
Definition RenderList.hpp:137
RenderState & state
RenderState corresponding to this RenderList.
Definition RenderList.hpp:89
Definition Factories.hpp:19
TextureRenderTarget createRenderTarget(const RenderState &state, QRhiTexture *tex, int samples, bool depth)
Create a render target from a texture.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:10
Base toolkit upon which the software is built.
Definition Application.cpp:97
STL namespace.
Definition DocumentContext.hpp:18
Connection between two score::gfx::Port.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:66
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:50
Port of a score::gfx::Node.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:48
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:57
Useful abstraction for storing all the data related to a render target.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:111