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