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