Loading...
Searching...
No Matches
score-plugin-avnd/Crousti/GpuNode.hpp
1#pragma once
2
3#if SCORE_PLUGIN_GFX
4#include <Crousti/Concepts.hpp>
5#include <Crousti/GpuUtils.hpp>
6#include <Crousti/Metadatas.hpp>
7#include <Gfx/Graph/NodeRenderer.hpp>
8#include <Gfx/Graph/RenderList.hpp>
9#include <Gfx/Graph/Uniforms.hpp>
10
11// #include <gpp/ports.hpp>
12
13namespace oscr
14{
15
16template <typename Node_T>
17struct CustomGpuRenderer final : score::gfx::NodeRenderer
18{
19 using texture_inputs = avnd::texture_input_introspection<Node_T>;
20 using texture_outputs = avnd::texture_output_introspection<Node_T>;
21 const CustomGpuNodeBase& parent;
22 std::vector<Node_T> states;
23 score::gfx::Message m_last_message{};
24 ossia::small_flat_map<const score::gfx::Port*, score::gfx::TextureRenderTarget, 2>
25 m_rts;
26
27 ossia::time_value m_last_time{-1};
28
29 score::gfx::PassMap m_p;
30
31 QRhiBuffer* m_meshBuffer{};
32 QRhiBuffer* m_idxBuffer{};
33
34 bool m_createdPipeline{};
35
36 int sampler_k = 0;
37 ossia::flat_map<int, QRhiBuffer*> createdUbos;
38 ossia::flat_map<int, QRhiSampler*> createdSamplers;
39 ossia::flat_map<int, QRhiTexture*> createdTexs;
40
41 CustomGpuRenderer(const CustomGpuNodeBase& p)
42 : NodeRenderer{}
43 , parent{p}
44 {
45 }
46
48 renderTargetForInput(const score::gfx::Port& p) override
49 {
50 auto it = m_rts.find(&p);
51 SCORE_ASSERT(it != m_rts.end());
52 return it->second;
53 }
54
55 QRhiTexture* createInput(score::gfx::RenderList& renderer, int k, QSize size)
56 {
57 auto port = parent.input[k];
58 static constexpr auto flags = QRhiTexture::RenderTarget;
59 auto texture = renderer.state.rhi->newTexture(QRhiTexture::RGBA8, size, 1, flags);
60 SCORE_ASSERT(texture->create());
61 m_rts[port]
62 = score::gfx::createRenderTarget(renderer.state, texture, renderer.samples());
63 return texture;
64 }
65
66 template <typename F>
67 QRhiShaderResourceBinding initBinding(score::gfx::RenderList& renderer, F field)
68 {
69 static constexpr auto bindingStages = QRhiShaderResourceBinding::VertexStage
70 | QRhiShaderResourceBinding::FragmentStage;
71 if constexpr(requires { F::ubo; })
72 {
73 auto it = createdUbos.find(F::binding());
74 QRhiBuffer* buffer = it != createdUbos.end() ? it->second : nullptr;
75 return QRhiShaderResourceBinding::uniformBuffer(
76 F::binding(), bindingStages, buffer);
77 }
78 else if constexpr(requires { F::sampler2D; })
79 {
80 auto tex_it = createdTexs.find(F::binding());
81 QRhiTexture* tex
82 = tex_it != createdTexs.end() ? tex_it->second : &renderer.emptyTexture();
83
84 // Samplers are always created by us
85 QRhiSampler* sampler = renderer.state.rhi->newSampler(
86 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
87 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
88 sampler->create();
89 createdSamplers[F::binding()] = sampler;
90
91 return QRhiShaderResourceBinding::sampledTexture(
92 F::binding(), bindingStages, tex, sampler);
93 }
94 else
95 {
96 static_assert(F::nope);
97 throw;
98 }
99 }
100
101 auto initBindings(score::gfx::RenderList& renderer)
102 {
103 auto& rhi = *renderer.state.rhi;
104 // Shader resource bindings
105 auto srb = rhi.newShaderResourceBindings();
106 SCORE_ASSERT(srb);
107
108 QVarLengthArray<QRhiShaderResourceBinding, 8> bindings;
109
110 if constexpr(requires { decltype(Node_T::layout::bindings){}; })
111 {
112 using bindings_type = decltype(Node_T::layout::bindings);
113 boost::pfr::for_each_field(bindings_type{}, [&](auto f) {
114 bindings.push_back(initBinding(renderer, f));
115 });
116 }
117 else if constexpr(requires { sizeof(typename Node_T::layout::bindings); })
118 {
119 using bindings_type = typename Node_T::layout::bindings;
120 boost::pfr::for_each_field(bindings_type{}, [&](auto f) {
121 bindings.push_back(initBinding(renderer, f));
122 });
123 }
124
125 srb->setBindings(bindings.begin(), bindings.end());
126 return srb;
127 }
128
129 QRhiGraphicsPipeline* createRenderPipeline(
131 {
132 auto& rhi = *renderer.state.rhi;
133 auto& mesh = renderer.defaultTriangle();
134 auto ps = rhi.newGraphicsPipeline();
135 ps->setName("createRenderPipeline");
136 SCORE_ASSERT(ps);
137 QRhiGraphicsPipeline::TargetBlend premulAlphaBlend;
138 premulAlphaBlend.enable = true;
139 premulAlphaBlend.srcColor = QRhiGraphicsPipeline::BlendFactor::SrcAlpha;
140 premulAlphaBlend.dstColor = QRhiGraphicsPipeline::BlendFactor::OneMinusSrcAlpha;
141 premulAlphaBlend.srcAlpha = QRhiGraphicsPipeline::BlendFactor::SrcAlpha;
142 premulAlphaBlend.dstAlpha = QRhiGraphicsPipeline::BlendFactor::OneMinusSrcAlpha;
143 ps->setTargetBlends({premulAlphaBlend});
144
145 ps->setSampleCount(1);
146
147 mesh.preparePipeline(*ps);
148
149 auto [v, f]
150 = score::gfx::makeShaders(renderer.state, parent.vertex, parent.fragment);
151 ps->setShaderStages({{QRhiShaderStage::Vertex, v}, {QRhiShaderStage::Fragment, f}});
152
153 SCORE_ASSERT(rt.renderPass);
154 ps->setRenderPassDescriptor(rt.renderPass);
155
156 return ps;
157 }
158
159 void init_input(score::gfx::RenderList& renderer, auto field)
160 {
161 //using input_type = std::decay_t<F>;
162 }
163
164 template <std::size_t Idx, typename F>
165 requires avnd::sampler_port<F>
166 void init_input(score::gfx::RenderList& renderer, avnd::field_reflection<Idx, F> field)
167 {
168 auto tex = createInput(renderer, sampler_k++, renderer.state.renderSize);
169
170 using sampler_type = typename avnd::member_reflection<F::sampler()>::member_type;
171 createdTexs[sampler_type::binding()] = tex;
172 }
173
174 template <std::size_t Idx, typename F>
175 requires avnd::uniform_port<F>
176 void init_input(score::gfx::RenderList& renderer, avnd::field_reflection<Idx, F> field)
177 {
178 using ubo_type = typename avnd::member_reflection<F::uniform()>::class_type;
179
180 // We must mark the UBO to construct.
181 if(createdUbos.find(ubo_type::binding()) != createdUbos.end())
182 return;
183
184 auto ubo = renderer.state.rhi->newBuffer(
185 QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, gpp::std140_size<ubo_type>());
186 ubo->create();
187
188 createdUbos[ubo_type::binding()] = ubo;
189 }
190
191 void init(score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res) override
192 {
193 if constexpr(requires { states[0].prepare(); })
194 {
195 for(auto& state : states)
196 {
197 parent.processControlIn(
198 *this, state, m_last_message, this->parent.last_message, this->parent.m_ctx);
199 state.prepare();
200 }
201 }
202
203 if(!m_meshBuffer)
204 {
205 auto& mesh = renderer.defaultTriangle();
206 auto [mbuffer, ibuffer] = renderer.initMeshBuffer(mesh, res);
207 m_meshBuffer = mbuffer;
208 m_idxBuffer = ibuffer;
209 }
210
211 // Create the global shared inputs
212 avnd::input_introspection<Node_T>::for_all(
213 [this, &renderer](auto f) { init_input(renderer, f); });
214
215 // Create the initial srbs
216 // TODO when implementing multi-pass, we may have to
217 // move this back inside the loop below as they may depend on the pipelines...
218 auto srb = initBindings(renderer);
219
220 // Create the states and pipelines
221 for(score::gfx::Edge* edge : this->parent.output[0]->edges)
222 {
223 auto rt = renderer.renderTargetForOutput(*edge);
224 if(rt.renderTarget)
225 {
226 states.push_back({});
227 prepareNewState(states.back(), parent);
228
229 auto ps = createRenderPipeline(renderer, rt);
230 ps->setShaderResourceBindings(srb);
231
232 m_p.emplace_back(edge, score::gfx::Pipeline{ps, srb});
233
234 // No update step: we can directly create the pipeline here
235 if constexpr(!requires { &Node_T::update; })
236 {
237 SCORE_ASSERT(srb->create());
238 SCORE_ASSERT(ps->create());
239 m_createdPipeline = true;
240 }
241 }
242 }
243 }
244
245 std::vector<QRhiShaderResourceBinding> tmp;
246 void update(score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res) override
247 {
248 // First copy all the "public" uniforms to their space in memory
249 if(states.size() > 0)
250 {
251 auto& state = states[0];
252
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 input_type = std::decay_t<F>;
256 using uniform_type =
257 typename avnd::member_reflection<F::uniform()>::member_type;
258 using ubo_type = typename avnd::member_reflection<F::uniform()>::class_type;
259
260 auto ubo = this->createdUbos.at(ubo_type::binding());
261
262 static constexpr int offset = gpp::std140_offset<F::uniform()>();
263 static constexpr int size = sizeof(uniform_type::value);
264 res.updateDynamicBuffer(ubo, offset, size, &t.value);
265 });
266 }
267
268 if constexpr(requires { &Node_T::update; })
269 {
270 // Then run the update loop, which is a bit complicated
271 // as we have to take into account that buffers could be allocated, freed, etc.
272 // and thus updated in the shader resource bindings
273 SCORE_ASSERT(states.size() == m_p.size());
274 //SCORE_SOFT_ASSERT(state.size() == edges);
275 for(int k = 0; k < states.size(); k++)
276 {
277 auto& state = states[k];
278 auto& pass = m_p[k].second;
279
280 bool srb_touched{false};
281 tmp.assign(pass.srb->cbeginBindings(), pass.srb->cendBindings());
282 for(auto& promise : state.update())
283 {
284 using ret_type = decltype(promise.feedback_value);
285 gpp::qrhi::handle_update<CustomGpuRenderer, ret_type> handler{
286 *this, *renderer.state.rhi, res, tmp, srb_touched};
287 promise.feedback_value = visit(handler, promise.current_command);
288 }
289
290 if(srb_touched)
291 {
292 if(m_createdPipeline)
293 pass.srb->destroy();
294
295 pass.srb->setBindings(tmp.begin(), tmp.end());
296 }
297
298 if(!m_createdPipeline)
299 {
300 SCORE_ASSERT(pass.srb->create());
301 SCORE_ASSERT(pass.pipeline->create());
302 }
303 }
304 m_createdPipeline = true;
305 tmp.clear();
306 }
307 }
308
309 void release(score::gfx::RenderList& r) override
310 {
311 m_createdPipeline = false;
312
313 // Release the object's internal states
314 if constexpr(requires { &Node_T::release; })
315 {
316 for(auto& state : states)
317 {
318 for(auto& promise : state.release())
319 {
320 gpp::qrhi::handle_release handler{*r.state.rhi};
321 visit(handler, promise.current_command);
322 }
323 }
324 }
325 states.clear();
326
327 // Release the allocated mesh buffers
328 m_meshBuffer = nullptr;
329
330 // Release the allocated textures
331 for(auto& [id, tex] : this->createdTexs)
332 tex->deleteLater();
333 this->createdTexs.clear();
334
335 // Release the allocated samplers
336 for(auto& [id, sampl] : this->createdSamplers)
337 sampl->deleteLater();
338 this->createdSamplers.clear();
339
340 // Release the allocated ubos
341 for(auto& [id, ubo] : this->createdUbos)
342 ubo->deleteLater();
343 this->createdUbos.clear();
344
345 // Release the allocated rts
346 // TODO investigate why reference does not work here:
347 for(auto [port, rt] : m_rts)
348 rt.release();
349 m_rts.clear();
350
351 // Release the allocated pipelines
352 for(auto& pass : m_p)
353 pass.second.release();
354 m_p.clear();
355
356 m_meshBuffer = nullptr;
357 m_createdPipeline = false;
358
359 sampler_k = 0;
360 }
361
362 void runInitialPasses(
363 score::gfx::RenderList& renderer, QRhiCommandBuffer& commands,
364 QRhiResourceUpdateBatch*& res, score::gfx::Edge& edge) override
365 {
366 // If we are paused, we don't run the processor implementation.
367 if(parent.last_message.token.date == m_last_time)
368 {
369 return;
370 }
371 m_last_time = parent.last_message.token.date;
372
373 // Apply the controls
374 for(auto& state : states)
375 {
376 this->parent.processControlIn(
377 *this, state, m_last_message, this->parent.last_message, this->parent.m_ctx);
378 }
379 }
380
381 void runRenderPass(
382 score::gfx::RenderList& renderer, QRhiCommandBuffer& commands,
383 score::gfx::Edge& edge) override
384 {
385 auto& mesh = renderer.defaultTriangle();
386 score::gfx::defaultRenderPass(
387 renderer, mesh, {m_meshBuffer, m_idxBuffer}, commands, edge, m_p);
388
389 // Copy the data to the model node
390 if(!this->states.empty())
391 parent.processControlOut(this->states[0]);
392 }
393};
394
395template <typename Node_T>
396struct CustomGpuNode final
397 : CustomGpuNodeBase
398 , GpuNodeElements<Node_T>
399{
400 CustomGpuNode(
401 std::weak_ptr<Execution::ExecutionCommandQueue> q, Gfx::exec_controls ctls, int id,
402 const score::DocumentContext& ctx)
403 : CustomGpuNodeBase{std::move(q), std::move(ctls), ctx}
404 {
405 this->instance = id;
406
407 initGfxPorts<Node_T>(this, this->input, this->output);
408
409 using layout = typename Node_T::layout;
410 static constexpr auto lay = layout{};
411
412 gpp::qrhi::generate_shaders gen;
413 if constexpr(requires { &Node_T::vertex; })
414 {
415 vertex = QString::fromStdString(gen.vertex_shader(lay) + Node_T{}.vertex().data());
416 }
417 else
418 {
419 vertex = gpp::qrhi::DefaultPipeline::vertex();
420 }
421
422 fragment
423 = QString::fromStdString(gen.fragment_shader(lay) + Node_T{}.fragment().data());
424 }
425
427 createRenderer(score::gfx::RenderList& r) const noexcept override
428 {
429 return new CustomGpuRenderer<Node_T>{*this};
430 }
431};
432
433}
434#endif
Renderer for a given node.
Definition NodeRenderer.hpp:11
List of nodes to be rendered to an output.
Definition RenderList.hpp:19
MeshBuffers initMeshBuffer(const Mesh &mesh, QRhiResourceUpdateBatch &res)
Create buffers for a mesh and mark them for upload.
Definition RenderList.cpp:39
const score::gfx::Mesh & defaultTriangle() const noexcept
A triangle mesh correct for this API.
Definition RenderList.cpp:281
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
TextureRenderTarget renderTargetForOutput(const Edge &edge) noexcept
Obtain the texture corresponding to an output port.
Definition RenderList.cpp:164
Definition Factories.hpp:19
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
std::pair< QShader, QShader > makeShaders(const RenderState &v, QString vert, QString frag)
Get a pair of compiled vertex / fragment shaders from GLSL 4.5 sources.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:342
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:51
Useful abstraction for storing a graphics pipeline and associated resource bindings.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:93
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