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