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() != cpu_tex.bytesize())
71 {
72 cpu_tex.bytes = nullptr;
73 }
74 else
75 {
76 cpu_tex.bytes = reinterpret_cast<unsigned char*>(buf.data());
77
78 if(rhi.isYUpInNDC())
79 if(cpu_tex.width * cpu_tex.height > 0)
80 inplaceMirror(
81 cpu_tex.bytes, cpu_tex.width, cpu_tex.height, cpu_tex.bytes_per_pixel);
82
83 cpu_tex.changed = true;
84 }
85 }
86
87 void init(score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res) override
88 {
89 if constexpr(requires { state.prepare(); })
90 {
91 this->node().processControlIn(
92 *this, state, m_last_message, this->node().last_message, this->node().m_ctx);
93 state.prepare();
94 }
95
96 // Init input render targets
97 int k = 0;
98 avnd::cpu_texture_input_introspection<Node_T>::for_all(
99 avnd::get_inputs<Node_T>(state), [&]<typename F>(F& t) {
100 // FIXME k isn't the port index, it's the texture port index
101 auto spec = this->node().resolveRenderTargetSpecs(k, renderer);
102 if constexpr(requires {
103 t.request_width;
104 t.request_height;
105 })
106 {
107 spec.size.rwidth() = t.request_width;
108 spec.size.rheight() = t.request_height;
109 }
110 createInput(renderer, k, t.texture, spec);
111 if constexpr(avnd::cpu_fixed_format_texture<decltype(t.texture)>)
112 {
113 t.texture.width = spec.size.width();
114 t.texture.height = spec.size.height();
115 }
116 k++;
117 });
118 }
119
120 void update(
121 score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res,
122 score::gfx::Edge* edge) override
123 {
124 bool updated = false;
125 /*
126 int k = 0;
127 avnd::cpu_texture_input_introspection<Node_T>::for_all(
128 avnd::get_inputs<Node_T>(state), [&]<typename F>(F& t) {
129 if constexpr(requires {
130 t.request_width;
131 t.request_height;
132 })
133 {
134 const auto tex = this->texture(k)->pixelSize();
135 if(tex.width() != t.request_width || tex.height() != t.request_height)
136 {
137 QSize sz{t.request_width, t.request_height};
138
139 // Release
140 auto port = parent.input[k];
141
142 m_rts[port].release();
143 createInput(renderer, k, t.texture, sz);
144
145 t.texture.width = sz.width();
146 t.texture.height = sz.height();
147
148 updated = true;
149 }
150 }
151 k++;
152 });
153*/
154 if(updated)
155 {
156 // We must notify the graph that the previous nodes have to be recomputed
157 }
158 }
159
160 void release(score::gfx::RenderList& r) override
161 {
162 // Free inputs
163 // TODO investigate why reference does not work here:
164 for(auto [port, rt] : m_rts)
165 rt.release();
166 m_rts.clear();
167 }
168
169 void inputAboutToFinish(
170 score::gfx::RenderList& renderer, const score::gfx::Port& p,
171 QRhiResourceUpdateBatch*& res) override
172 {
173 auto& parent = this->node();
174 res = renderer.state.rhi->nextResourceUpdateBatch();
175 const auto& inputs = parent.input;
176 auto index_of_port = ossia::find(inputs, &p) - inputs.begin();
177 SCORE_ASSERT(index_of_port == 0);
178 {
179 auto tex = m_rts[&p].texture;
180 auto& readback = m_readbacks[index_of_port];
181 readback = {};
182 res->readBackTexture(QRhiReadbackDescription{tex}, &readback);
183 }
184 }
185
186 void runInitialPasses(
187 score::gfx::RenderList& renderer, QRhiCommandBuffer& commands,
188 QRhiResourceUpdateBatch*& res, score::gfx::Edge& edge) override
189 {
190 auto& parent = this->node();
191 auto& rhi = *renderer.state.rhi;
192
193 // Insert a synchronisation point to allow readbacks to complete
194 rhi.finish();
195
196 // If we are paused, we don't run the processor implementation.
197 if(parent.last_message.token.date == m_last_time)
198 {
199 return;
200 }
201 m_last_time = parent.last_message.token.date;
202
203 // Fetch input textures (if any)
204 {
205 // Copy the readback output inside the structure
206 // TODO it would be much better to do this inside the readback's
207 // "completed" callback.
208 int k = 0;
209 avnd::cpu_texture_input_introspection<Node_T>::for_all(
210 avnd::get_inputs<Node_T>(state), [&](auto& t) {
211 loadInputTexture(rhi, t.texture, k);
212 k++;
213 });
214 }
215
216 parent.processControlIn(
217 *this, state, m_last_message, parent.last_message, parent.m_ctx);
218
219 // Run the processor
220 state();
221
222 // Copy the data to the model node
223 parent.processControlOut(this->state);
224
225 // Copy the geometry
226 // FIXME we need something such as port_run_{pre,post}process for GPU nodes
227 avnd::geometry_output_introspection<Node_T>::for_all_n2(
228 state.outputs, [&]<std::size_t F, std::size_t P>(
229 auto& t, avnd::predicate_index<P>, avnd::field_index<F>) {
230 postprocess_geometry(t);
231 });
232 }
233 template <avnd::geometry_port Field>
234 void postprocess_geometry(Field& ctrl)
235 {
236 using namespace avnd;
237 // bool mesh_dirty{};
238 // bool tform_dirty{};
239 // mesh_dirty = ctrl.dirty_mesh;
240
241 if(ctrl.dirty_mesh)
242 {
243 auto meshes = std::make_shared<ossia::mesh_list>();
244 auto& ossia_meshes = *meshes;
245 if constexpr(static_geometry_type<Field> || dynamic_geometry_type<Field>)
246 {
247 ossia_meshes.meshes.resize(1);
248 oscr::load_geometry(ctrl, ossia_meshes.meshes[0]);
249 }
250 else if constexpr(
251 static_geometry_type<decltype(Field::mesh)>
252 || dynamic_geometry_type<decltype(Field::mesh)>)
253 {
254 ossia_meshes.meshes.resize(1);
255 oscr::load_geometry(ctrl.mesh, ossia_meshes.meshes[0]);
256 }
257 else
258 {
259 oscr::load_geometry(ctrl, ossia_meshes);
260 }
261 }
262 ctrl.dirty_mesh = false;
263
264 // if constexpr(requires { ctrl.transform; })
265 // {
266 // if(ctrl.dirty_transform)
267 // {
268 // std::copy_n(
269 // ctrl.transform, std::ssize(ctrl.transform), port.data.transform.matrix);
270 // tform_dirty = true;
271 // ctrl.dirty_transform = false;
272 // }
273 // }
274 //
275 // port.data.flags = {};
276 // if(mesh_dirty)
277 // port.data.flags = port.data.flags | ossia::geometry_port::dirty_meshes;
278 // if(tform_dirty)
279 // port.data.flags = port.data.flags | ossia::geometry_port::dirty_transform;
280 }
281};
282
283template <typename Node_T>
284 requires(avnd::texture_input_introspection<Node_T>::size > 0
285 && avnd::texture_output_introspection<Node_T>::size == 0)
286struct GfxNode<Node_T> final
287 : CustomGpuOutputNodeBase
288 , GpuNodeElements<Node_T>
289{
290 oscr::ProcessModel<Node_T>& processModel;
291 GfxNode(
293 std::weak_ptr<Execution::ExecutionCommandQueue> q, Gfx::exec_controls ctls, int id,
294 const score::DocumentContext& ctx)
295 : CustomGpuOutputNodeBase{std::move(q), std::move(ctls), ctx}
296 , processModel{element}
297 {
298 this->instance = id;
299
300 initGfxPorts<Node_T>(this, this->input, this->output);
301 }
302
304 createRenderer(score::gfx::RenderList& r) const noexcept override
305 {
306 return new GfxRenderer<Node_T>{*this};
307 }
308};
309}
310#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