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