Loading...
Searching...
No Matches
GpuComputeNode.hpp
1#pragma once
2
3#if SCORE_PLUGIN_GFX
4#include <Process/ExecutionContext.hpp>
5
6#include <Crousti/Concepts.hpp>
7#include <Crousti/GpuUtils.hpp>
8#include <Crousti/Metadatas.hpp>
9#include <Gfx/GfxExecNode.hpp>
10#include <Gfx/Graph/NodeRenderer.hpp>
11#include <Gfx/Graph/RenderList.hpp>
12#include <Gfx/Graph/Uniforms.hpp>
13
14namespace oscr
15{
16// Compute nodes that do not have any texture output so that they can get pulled
17// must drive things internally instead
18template <typename Node_T>
19using ComputeNodeBaseType = std::conditional_t<
20 avnd::gpu_image_output_introspection<Node_T>::size != 0, CustomGpuNodeBase,
21 CustomGpuOutputNodeBase>;
22template <typename Node_T>
23using ComputeRendererBaseType = std::conditional_t<
24 avnd::gpu_image_output_introspection<Node_T>::size != 0, score::gfx::NodeRenderer,
26
27template <typename Node_T>
28struct GpuComputeNode final : ComputeNodeBaseType<Node_T>
29{
30 GpuComputeNode(
31 std::weak_ptr<Execution::ExecutionCommandQueue> q, Gfx::exec_controls ctls, int id,
32 const score::DocumentContext& ctx)
33 : ComputeNodeBaseType<Node_T>{std::move(q), std::move(ctls), ctx}
34 {
35 this->instance = id;
36
37 initGfxPorts<Node_T>(this, this->input, this->output);
38 using layout = typename Node_T::layout;
39 static constexpr auto lay = layout{};
40
41 gpp::qrhi::generate_shaders gen;
42 this->compute
43 = QString::fromStdString(gen.compute_shader(lay) + Node_T{}.compute().data());
44 }
45
47 createRenderer(score::gfx::RenderList& r) const noexcept override;
48};
49
50template <typename Node_T>
51struct GpuComputeRenderer final : ComputeRendererBaseType<Node_T>
52{
53 using texture_inputs = avnd::gpu_image_input_introspection<Node_T>;
54 using texture_outputs = avnd::gpu_image_output_introspection<Node_T>;
55 const GpuComputeNode<Node_T>& parent;
56 Node_T state;
57 score::gfx::Message m_last_message{};
58 ossia::small_flat_map<const score::gfx::Port*, score::gfx::TextureRenderTarget, 2>
59 m_rts;
60
61 ossia::time_value m_last_time{-1};
62
63 QRhiShaderResourceBindings* m_srb{};
64 QRhiComputePipeline* m_pipeline{};
65
66 bool m_createdPipeline{};
67
68 int sampler_k = 0;
69 int ubo_k = 0;
70 ossia::flat_map<int, QRhiBuffer*> createdUbos;
71 ossia::flat_map<int, QRhiTexture*> createdTexs;
72
73#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
74 std::vector<QRhiBufferReadbackResult*> bufReadbacks;
75 void addReadback(QRhiBufferReadbackResult* r) { bufReadbacks.push_back(r); }
76#endif
77
78 std::vector<QRhiReadbackResult*> texReadbacks;
79 void addReadback(QRhiReadbackResult* r) { texReadbacks.push_back(r); }
80 GpuComputeRenderer(const GpuComputeNode<Node_T>& p)
81 : ComputeRendererBaseType<Node_T>{}
82 , parent{p}
83 {
84 prepareNewState(state, parent);
85 }
86
88 renderTargetForInput(const score::gfx::Port& p) override
89 {
90 auto it = m_rts.find(&p);
91 SCORE_ASSERT(it != m_rts.end());
92 return it->second;
93 }
94
95 QRhiTexture* createInput(
96 score::gfx::RenderList& renderer, int k, QRhiTexture::Format fmt, QSize size)
97 {
98 auto port = parent.input[k];
99 static constexpr auto flags
100 = QRhiTexture::RenderTarget | QRhiTexture::UsedWithLoadStore;
101 auto texture = renderer.state.rhi->newTexture(fmt, size, 1, flags);
102 SCORE_ASSERT(texture->create());
103 m_rts[port]
104 = score::gfx::createRenderTarget(renderer.state, texture, renderer.samples());
105 return texture;
106 }
107
108 template <typename F>
109 QRhiShaderResourceBinding initBinding(score::gfx::RenderList& renderer, F field)
110 {
111 static constexpr auto bindingStages = QRhiShaderResourceBinding::ComputeStage;
112 if constexpr(requires { F::ubo; })
113 {
114 auto it = createdUbos.find(F::binding());
115 QRhiBuffer* buffer = it != createdUbos.end() ? it->second : nullptr;
116 return QRhiShaderResourceBinding::uniformBuffer(
117 F::binding(), bindingStages, buffer);
118 }
119 else if constexpr(requires { F::image2D; })
120 {
121 auto tex_it = createdTexs.find(F::binding());
122 QRhiTexture* tex
123 = tex_it != createdTexs.end() ? tex_it->second : &renderer.emptyTexture();
124
125 if constexpr(
126 requires { F::load; } && requires { F::store; })
127 return QRhiShaderResourceBinding::imageLoadStore(
128 F::binding(), bindingStages, tex, 0);
129 else if constexpr(requires { F::readonly; })
130 return QRhiShaderResourceBinding::imageLoad(F::binding(), bindingStages, tex, 0);
131 else if constexpr(requires { F::writeonly; })
132 return QRhiShaderResourceBinding::imageStore(
133 F::binding(), bindingStages, tex, 0);
134 else
135 static_assert(F::load || F::store);
136 }
137 else if constexpr(requires { F::buffer; })
138 {
139 auto it = createdUbos.find(F::binding());
140 QRhiBuffer* buf = it != createdUbos.end() ? it->second : nullptr;
141
142 if constexpr(
143 requires { F::load; } && requires { F::store; })
144 return QRhiShaderResourceBinding::bufferLoadStore(
145 F::binding(), bindingStages, buf);
146 else if constexpr(requires { F::load; })
147 return QRhiShaderResourceBinding::bufferLoad(F::binding(), bindingStages, buf);
148 else if constexpr(requires { F::store; })
149 return QRhiShaderResourceBinding::bufferStore(F::binding(), bindingStages, buf);
150 else
151 static_assert(F::load || F::store);
152 }
153 else
154 {
155 static_assert(F::nope);
156 throw;
157 }
158 }
159
160 auto initBindings(score::gfx::RenderList& renderer)
161 {
162 auto& rhi = *renderer.state.rhi;
163 // Shader resource bindings
164 auto srb = rhi.newShaderResourceBindings();
165 SCORE_ASSERT(srb);
166
167 QVarLengthArray<QRhiShaderResourceBinding, 8> bindings;
168
169 using bindings_type = decltype(Node_T::layout::bindings);
170 boost::pfr::for_each_field(
171 bindings_type{}, [&](auto f) { bindings.push_back(initBinding(renderer, f)); });
172
173 srb->setBindings(bindings.begin(), bindings.end());
174 return srb;
175 }
176
177 QRhiComputePipeline* createComputePipeline(score::gfx::RenderList& renderer)
178 {
179 auto& rhi = *renderer.state.rhi;
180 auto compute = rhi.newComputePipeline();
181 auto cs = score::gfx::makeCompute(renderer.state, parent.compute);
182 compute->setShaderStage(QRhiShaderStage(QRhiShaderStage::Compute, cs));
183
184 return compute;
185 }
186
187 void init_input(score::gfx::RenderList& renderer, auto field)
188 {
189 //using input_type = std::decay_t<F>;
190 }
191
192 template <std::size_t Idx, typename F>
193 requires avnd::image_port<F>
194 void init_input(score::gfx::RenderList& renderer, avnd::field_reflection<Idx, F> field)
195 {
196 using bindings_type = decltype(Node_T::layout::bindings);
197 using image_type = std::decay_t<decltype(bindings_type{}.*F::image())>;
198 auto tex = createInput(
199 renderer, sampler_k++, gpp::qrhi::textureFormat<image_type>(),
200 renderer.state.renderSize);
201
202 using sampler_type = typename avnd::member_reflection<F::image()>::member_type;
203 createdTexs[sampler_type::binding()] = tex;
204 }
205
206 template <std::size_t Idx, typename F>
207 requires avnd::uniform_port<F>
208 void init_input(score::gfx::RenderList& renderer, avnd::field_reflection<Idx, F> field)
209 {
210 using ubo_type = typename avnd::member_reflection<F::uniform()>::class_type;
211
212 // We must mark the UBO to construct.
213 if(createdUbos.find(ubo_type::binding()) != createdUbos.end())
214 return;
215
216 auto ubo = renderer.state.rhi->newBuffer(
217 QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, gpp::std140_size<ubo_type>());
218 ubo->create();
219
220 createdUbos[ubo_type::binding()] = ubo;
221 }
222
223 void init(score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res) override
224 {
225 if constexpr(requires { state.prepare(); })
226 {
227 parent.processControlIn(
228 *this, state, m_last_message, this->parent.last_message, this->parent.m_ctx);
229 state.prepare();
230 }
231
232 // Create the global shared inputs
233 avnd::input_introspection<Node_T>::for_all(
234 [this, &renderer](auto f) { init_input(renderer, f); });
235
236 m_srb = initBindings(renderer);
237 m_pipeline = createComputePipeline(renderer);
238 m_pipeline->setShaderResourceBindings(m_srb);
239
240 // No update step: we can directly create the pipeline here
241 if constexpr(!requires { &Node_T::update; })
242 {
243 SCORE_ASSERT(m_srb->create());
244 SCORE_ASSERT(m_pipeline->create());
245 m_createdPipeline = true;
246 }
247 }
248
249 std::vector<QRhiShaderResourceBinding> tmp;
250 void update(score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res) override
251 {
252 // First copy all the "public" uniforms to their space in memory
253 avnd::gpu_uniform_introspection<Node_T>::for_all(
254 avnd::get_inputs<Node_T>(state), [&]<avnd::uniform_port F>(const F& t) {
255 using uniform_type =
256 typename avnd::member_reflection<F::uniform()>::member_type;
257 using ubo_type = typename avnd::member_reflection<F::uniform()>::class_type;
258
259 auto ubo = this->createdUbos.at(ubo_type::binding());
260
261 static constexpr int offset = gpp::std140_offset<F::uniform()>();
262 static constexpr int size = sizeof(uniform_type::value);
263 res.updateDynamicBuffer(ubo, offset, size, &t.value);
264 });
265
266 // Then run the update loop, which is a bit complicated
267 // as we have to take into account that buffers could be allocated, freed, etc.
268 // and thus updated in the shader resource bindings
269
270 if constexpr(requires { &Node_T::update; })
271 {
272 bool srb_touched{false};
273 tmp.assign(m_srb->cbeginBindings(), m_srb->cendBindings());
274 for(auto& promise : state.update())
275 {
276 using ret_type = decltype(promise.feedback_value);
277 gpp::qrhi::handle_update<GpuComputeRenderer, ret_type> handler{
278 *this, *renderer.state.rhi, res, tmp, srb_touched};
279 promise.feedback_value = visit(handler, promise.current_command);
280 }
281
282 if(srb_touched)
283 {
284 if(m_createdPipeline)
285 m_srb->destroy();
286 m_srb->setBindings(tmp.begin(), tmp.end());
287 }
288
289 /*
290 qDebug() << srb_touched << m_createdPipeline;
291 for(auto& b : tmp) {
292 const QRhiShaderResourceBinding::Data& dat = *b.data();
293 switch(dat.type) {
294 case QRhiShaderResourceBinding::UniformBuffer:
295 qDebug() << "ubo: " << dat.binding << (void*) dat.u.ubuf.buf;
296 break;
297 case QRhiShaderResourceBinding::ImageLoad:
298 case QRhiShaderResourceBinding::ImageStore:
299 case QRhiShaderResourceBinding::ImageLoadStore:
300 qDebug() << "image: " << dat.binding << (void*) dat.u.simage.tex;
301 break;
302 case QRhiShaderResourceBinding::BufferLoad:
303 case QRhiShaderResourceBinding::BufferStore:
304 case QRhiShaderResourceBinding::BufferLoadStore:
305 qDebug() << "buffer: " << dat.binding << (void*) dat.u.sbuf.buf;
306 break;
307 default:
308 qDebug() << "WRTF: " << dat.binding;
309 }
310 }
311 */
312
313 if(!m_createdPipeline)
314 {
315 SCORE_ASSERT(m_srb->create());
316 SCORE_ASSERT(m_pipeline->create());
317 m_createdPipeline = true;
318 }
319 tmp.clear();
320 }
321 }
322
323 void release(score::gfx::RenderList& r) override
324 {
325 m_createdPipeline = false;
326
327 // Release the object's internal states
328 if constexpr(requires { &Node_T::release; })
329 {
330 for(auto& promise : state.release())
331 {
332 gpp::qrhi::handle_release handler{*r.state.rhi};
333 visit(handler, promise.current_command);
334 }
335 state.release();
336 }
337
338 // Release the allocated textures
339 for(auto& [id, tex] : this->createdTexs)
340 tex->deleteLater();
341 this->createdTexs.clear();
342
343 // Release the allocated ubos
344 for(auto& [id, ubo] : this->createdUbos)
345 ubo->deleteLater();
346 this->createdUbos.clear();
347
348 // Release the allocated rts
349 // TODO investigate why reference does not work here:
350 // for(auto [port, rt] : m_rts)
351 // rt.release();
352 m_rts.clear();
353
354 // Release the allocated pipelines
355 if(m_srb)
356 m_srb->deleteLater();
357 if(m_pipeline)
358 m_pipeline->deleteLater();
359 m_srb = nullptr;
360 m_pipeline = nullptr;
361
362 m_createdPipeline = false;
363
364 sampler_k = 0;
365 ubo_k = 0;
366 }
367
368 void runCompute(
369 score::gfx::RenderList& renderer, QRhiCommandBuffer& cb,
370 QRhiResourceUpdateBatch*& res)
371 {
372 // If we are paused, we don't run the processor implementation.
373 // if(parent.last_message.token.date == m_last_time) {
374 // return;
375 // }
376 // m_last_time = parent.last_message.token.date;
377
378 // Apply the controls
379 parent.processControlIn(
380 *this, this->state, m_last_message, this->parent.last_message,
381 this->parent.m_ctx);
382
383 // Run the compute shader
384 {
385 SCORE_ASSERT(this->m_pipeline);
386 SCORE_ASSERT(this->m_pipeline->shaderResourceBindings());
387 for(auto& promise : this->state.dispatch())
388 {
389 using ret_type = decltype(promise.feedback_value);
390 gpp::qrhi::handle_dispatch<GpuComputeRenderer, ret_type> handler{
391 *this, *renderer.state.rhi, cb, res, *this->m_pipeline};
392 promise.feedback_value = visit(handler, promise.current_command);
393 }
394 }
395
396 // Clear the readbacks
397#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
398 for(auto rb : this->bufReadbacks)
399 delete rb;
400 this->bufReadbacks.clear();
401#endif
402 for(auto rb : this->texReadbacks)
403 delete rb;
404 this->texReadbacks.clear();
405
406 // Copy the data to the model node
407 parent.processControlOut(this->state);
408 }
409
410 void runInitialPasses(
411 score::gfx::RenderList& renderer, QRhiCommandBuffer& cb,
412 QRhiResourceUpdateBatch*& res, score::gfx::Edge& edge) override
413 {
414 runCompute(renderer, cb, res);
415 }
416
417 void runRenderPass(
418 score::gfx::RenderList& renderer, QRhiCommandBuffer& commands,
419 score::gfx::Edge& edge) override
420 {
421 }
422};
423
424template <typename Node_T>
426GpuComputeNode<Node_T>::createRenderer(score::gfx::RenderList& r) const noexcept
427{
428 return new GpuComputeRenderer<Node_T>{*this};
429};
430
431}
432#endif
Renderer for a given node.
Definition NodeRenderer.hpp:11
Definition OutputNode.hpp:11
List of nodes to be rendered to an output.
Definition RenderList.hpp:19
RenderState & state
RenderState corresponding to this RenderList.
Definition RenderList.hpp:89
QRhiTexture & emptyTexture() const noexcept
Texture to use when a texture is missing.
Definition RenderList.hpp:112
Definition Factories.hpp:19
QShader makeCompute(const RenderState &v, QString compute)
Compile a compute shader.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:369
TextureRenderTarget createRenderTarget(const RenderState &state, QRhiTexture *tex, int samples)
Create a render target from a texture.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:10
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:51
Port of a score::gfx::Node.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:48
Useful abstraction for storing all the data related to a render target.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:111