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