Loading...
Searching...
No Matches
GpuUtils.hpp
1#pragma once
2
3#include <avnd/introspection/gfx.hpp>
4#if SCORE_PLUGIN_GFX
5#include <Process/ExecutionContext.hpp>
6
7#include <Crousti/File.hpp>
8#include <Crousti/GppCoroutines.hpp>
9#include <Crousti/GppShaders.hpp>
10#include <Crousti/MessageBus.hpp>
11#include <Crousti/TextureConversion.hpp>
12#include <Crousti/TextureFormat.hpp>
13#include <Gfx/GfxExecNode.hpp>
14#include <Gfx/Graph/Node.hpp>
15#include <Gfx/Graph/OutputNode.hpp>
16#include <Gfx/Graph/RenderList.hpp>
17#include <Gfx/Graph/RenderState.hpp>
18
19#include <score/tools/ThreadPool.hpp>
20
21#include <ossia/detail/small_flat_map.hpp>
22
23#include <ossia-qt/invoke.hpp>
24
25#include <QCoreApplication>
26#include <QTimer>
27#include <QtGui/private/qrhi_p.h>
28
29#include <avnd/binding/ossia/metadatas.hpp>
30#include <avnd/binding/ossia/port_run_postprocess.hpp>
31#include <avnd/binding/ossia/port_run_preprocess.hpp>
32#include <avnd/binding/ossia/soundfiles.hpp>
33#include <avnd/concepts/parameter.hpp>
34#include <avnd/introspection/input.hpp>
35#include <avnd/introspection/output.hpp>
36#include <fmt/format.h>
37#include <gpp/layout.hpp>
38
39#include <score_plugin_avnd_export.h>
40
41namespace oscr
42{
43struct GpuWorker
44{
45 template <typename T>
46 void initWorker(this auto& self, std::shared_ptr<T>& state) noexcept
47 {
48 if constexpr(avnd::has_worker<T>)
49 {
50 auto ptr = QPointer{&self};
51 auto& tq = score::TaskPool::instance();
52 using worker_type = decltype(state->worker);
53
54 auto wk_state = std::weak_ptr{state};
55 state->worker.request = [ptr, &tq, wk_state]<typename... Args>(Args&&... f) {
56 using type_of_result = decltype(worker_type::work(std::forward<Args>(f)...));
57 tq.post([... ff = std::forward<Args>(f), wk_state, ptr]() mutable {
58 if constexpr(std::is_void_v<type_of_result>)
59 {
60 worker_type::work(std::forward<decltype(ff)>(ff)...);
61 }
62 else
63 {
64 // If the worker returns a std::function, it
65 // is to be invoked back in the processor DSP thread
66 auto res = worker_type::work(std::forward<decltype(ff)>(ff)...);
67 if(!res || !ptr)
68 return;
69
70 ossia::qt::run_async(
71 QCoreApplication::instance(),
72 [res = std::move(res), wk_state, ptr]() mutable {
73 if(ptr)
74 if(auto state = wk_state.lock())
75 res(*state);
76 });
77 }
78 });
79 };
80 }
81 }
82};
83
84template <typename GpuNodeRenderer, typename Node>
85struct GpuProcessIns
86{
87 GpuNodeRenderer& gpu;
88 Node& state;
89 const score::gfx::Message& prev_mess;
90 const score::gfx::Message& mess;
91 const score::DocumentContext& ctx;
92
93 bool can_process_message(std::size_t N)
94 {
95 if(mess.input.size() <= N)
96 return false;
97
98 if(prev_mess.input.size() == mess.input.size())
99 {
100 auto& prev = prev_mess.input[N];
101 auto& next = mess.input[N];
102 if(prev.index() == 1 && next.index() == 1)
103 {
104 if(ossia::get<ossia::value>(prev) == ossia::get<ossia::value>(next))
105 {
106 return false;
107 }
108 }
109 }
110 return true;
111 }
112
113 void operator()(avnd::parameter auto& t, auto field_index)
114 {
115 if(!can_process_message(field_index))
116 return;
117
118 if(auto val = ossia::get_if<ossia::value>(&mess.input[field_index]))
119 {
120 oscr::from_ossia_value(t, *val, t.value);
121 if_possible(t.update(state));
122 }
123 }
124
125#if OSCR_HAS_MMAP_FILE_STORAGE
126 template <avnd::raw_file_port Field, std::size_t NField>
127 void operator()(Field& t, avnd::field_index<NField> field_index)
128 {
129 // FIXME we should be loading a file there
130 using node_type = std::remove_cvref_t<decltype(gpu.node())>;
131 using file_ports = avnd::raw_file_input_introspection<Node>;
132
133 if(!can_process_message(field_index))
134 return;
135
136 auto val = ossia::get_if<ossia::value>(&mess.input[field_index]);
137 if(!val)
138 return;
139
140 static constexpr bool has_text = requires { decltype(Field::file)::text; };
141 static constexpr bool has_mmap = requires { decltype(Field::file)::mmap; };
142
143 // First we can load it directly since execution hasn't started yet
144 if(auto hdl = loadRawfile(*val, ctx, has_text, has_mmap))
145 {
146 static constexpr auto N = file_ports::field_index_to_index(NField);
147 if constexpr(avnd::port_can_process<Field>)
148 {
149 // FIXME also do it when we get a run-time message from the exec engine,
150 // OSC, etc
151 auto func = executePortPreprocess<Field>(*hdl);
152 const_cast<node_type&>(gpu.node())
153 .file_loaded(
154 state, hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
155 if(func)
156 func(state);
157 }
158 else
159 {
160 const_cast<node_type&>(gpu.node())
161 .file_loaded(
162 state, hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
163 }
164 }
165 }
166#endif
167
168 template <avnd::buffer_port Field, std::size_t NField>
169 void operator()(Field& t, avnd::field_index<NField> field_index)
170 {
171 using node_type = std::remove_cvref_t<decltype(gpu.node())>;
172 auto& node = const_cast<node_type&>(gpu.node());
173 auto val = ossia::get_if<ossia::render_target_spec>(&mess.input[field_index]);
174 if(!val)
175 return;
176 node.process(NField, *val);
177 }
178
179 template <avnd::texture_port Field, std::size_t NField>
180 void operator()(Field& t, avnd::field_index<NField> field_index)
181 {
182 using node_type = std::remove_cvref_t<decltype(gpu.node())>;
183 auto& node = const_cast<node_type&>(gpu.node());
184 auto val = ossia::get_if<ossia::render_target_spec>(&mess.input[field_index]);
185 if(!val)
186 return;
187 node.process(NField, *val);
188 }
189
190 template <avnd::geometry_port Field, std::size_t NField>
191 void operator()(Field& t, avnd::field_index<NField> field_index)
192 {
193 using node_type = std::remove_cvref_t<decltype(gpu.node())>;
194 auto& node = const_cast<node_type&>(gpu.node());
195
196 // FIXME
197 }
198
199 void operator()(auto& t, auto field_index) = delete;
200};
201
202struct GpuControlIns
203{
204 template <typename Self, typename Node_T>
205 static void processControlIn(
206 Self& self, Node_T& state, score::gfx::Message& renderer_mess,
207 const score::gfx::Message& mess, const score::DocumentContext& ctx) noexcept
208 {
209 // Apply the controls
210 avnd::input_introspection<Node_T>::for_all_n(
211 avnd::get_inputs<Node_T>(state),
212 GpuProcessIns<Self, Node_T>{self, state, renderer_mess, mess, ctx});
213 renderer_mess = mess;
214 }
215};
216
217struct GpuControlOuts
218{
219 std::weak_ptr<Execution::ExecutionCommandQueue> queue;
220 Gfx::exec_controls control_outs;
221
222 int64_t instance{};
223
224 template <typename Node_T>
225 void processControlOut(Node_T& state) const noexcept
226 {
227 if(!this->control_outs.empty())
228 {
229 auto q = this->queue.lock();
230 if(!q)
231 return;
232 auto& qq = *q;
233 int parm_k = 0;
234 avnd::parameter_output_introspection<Node_T>::for_all(
235 avnd::get_outputs(state), [&]<avnd::parameter T>(const T& t) {
236 qq.enqueue([v = oscr::to_ossia_value(t, t.value),
237 port = control_outs[parm_k]]() mutable {
238 std::swap(port->value, v);
239 port->changed = true;
240 });
241
242 parm_k++;
243 });
244 }
245 }
246};
247
248template <typename T>
249struct SCORE_PLUGIN_AVND_EXPORT GpuNodeElements
250{
251 [[no_unique_address]] oscr::soundfile_storage<T> soundfiles;
252
253 [[no_unique_address]] oscr::midifile_storage<T> midifiles;
254
255#if defined(OSCR_HAS_MMAP_FILE_STORAGE)
256 [[no_unique_address]] oscr::raw_file_storage<T> rawfiles;
257#endif
258
259 template <std::size_t N, std::size_t NField>
260 void file_loaded(
261 auto& state, const std::shared_ptr<oscr::raw_file_data>& hdl,
262 avnd::predicate_index<N>, avnd::field_index<NField>)
263 {
264 this->rawfiles.load(
265 state, hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
266 }
267};
268
269struct SCORE_PLUGIN_AVND_EXPORT CustomGfxNodeBase : score::gfx::NodeModel
270{
271 explicit CustomGfxNodeBase(const score::DocumentContext& ctx)
272 : score::gfx::NodeModel{}
273 , m_ctx{ctx}
274 {
275 }
276 virtual ~CustomGfxNodeBase();
277 const score::DocumentContext& m_ctx;
278 score::gfx::Message last_message;
279 void process(score::gfx::Message&& msg) override;
281};
282struct SCORE_PLUGIN_AVND_EXPORT CustomGfxOutputNodeBase : score::gfx::OutputNode
283{
284 virtual ~CustomGfxOutputNodeBase();
285
286 score::gfx::Message last_message;
287 void process(score::gfx::Message&& msg) override;
288};
289struct CustomGpuNodeBase
291 , GpuWorker
292 , GpuControlIns
293 , GpuControlOuts
294{
295 CustomGpuNodeBase(
296 std::weak_ptr<Execution::ExecutionCommandQueue>&& q, Gfx::exec_controls&& ctls,
297 const score::DocumentContext& ctx)
298 : GpuControlOuts{std::move(q), std::move(ctls)}
299 , m_ctx{ctx}
300 {
301 }
302
303 virtual ~CustomGpuNodeBase() = default;
304
305 const score::DocumentContext& m_ctx;
306 QString vertex, fragment, compute;
307 score::gfx::Message last_message;
308 void process(score::gfx::Message&& msg) override;
309};
310
311struct SCORE_PLUGIN_AVND_EXPORT CustomGpuOutputNodeBase
313 , GpuWorker
314 , GpuControlIns
315 , GpuControlOuts
316{
317 CustomGpuOutputNodeBase(
318 std::weak_ptr<Execution::ExecutionCommandQueue> q, Gfx::exec_controls&& ctls,
319 const score::DocumentContext& ctx);
320 virtual ~CustomGpuOutputNodeBase();
321
322 const score::DocumentContext& m_ctx;
323 std::weak_ptr<score::gfx::RenderList> m_renderer{};
324 std::shared_ptr<score::gfx::RenderState> m_renderState{};
325 std::function<void()> m_update;
326
327 QString vertex, fragment, compute;
328 score::gfx::Message last_message;
329 void process(score::gfx::Message&& msg) override;
331
332 void setRenderer(std::shared_ptr<score::gfx::RenderList>) override;
333 score::gfx::RenderList* renderer() const override;
334
335 void startRendering() override;
336 void render() override;
337 void stopRendering() override;
338 bool canRender() const override;
339 void onRendererChange() override;
340
341 void createOutput(
342 score::gfx::GraphicsApi graphicsApi, std::function<void()> onReady,
343 std::function<void()> onUpdate, std::function<void()> onResize) override;
344
345 void destroyOutput() override;
346 std::shared_ptr<score::gfx::RenderState> renderState() const override;
347
348 Configuration configuration() const noexcept override;
349};
350
351template <typename Node_T, typename Node>
352void prepareNewState(std::shared_ptr<Node_T>& eff, const Node& parent)
353{
354 if constexpr(avnd::has_worker<Node_T>)
355 {
356 parent.initWorker(eff);
357 }
358 if constexpr(avnd::has_processor_to_gui_bus<Node_T>)
359 {
360 auto& process = parent.processModel;
361 eff->send_message = [ptr = QPointer{&process}](auto&& b) mutable {
362 // FIXME right now all the rendering is done in the UI thread, which is very MEH
363 // this->in_edit([&process, bb = std::move(b)]() mutable {
364
365 if(ptr && ptr->to_ui)
366 MessageBusSender{ptr->to_ui}(std::move(b));
367 // });
368 };
369
370 // FIXME GUI -> engine. See executor.hpp
371 }
372
373 avnd::init_controls(*eff);
374
375 if constexpr(avnd::can_prepare<Node_T>)
376 {
377 if constexpr(avnd::function_reflection<&Node_T::prepare>::count == 1)
378 {
379 using prepare_type = avnd::first_argument<&Node_T::prepare>;
380 prepare_type t;
381 if_possible(t.instance = parent.instance);
382 eff->prepare(t);
383 }
384 else
385 {
386 eff->prepare();
387 }
388 }
389}
390
391struct port_to_type_enum
392{
393 template <std::size_t I, avnd::buffer_port F>
394 constexpr auto operator()(avnd::field_reflection<I, F> p)
395 {
396 return score::gfx::Types::Buffer;
397 }
398
399 template <std::size_t I, avnd::cpu_texture_port F>
400 constexpr auto operator()(avnd::field_reflection<I, F> p)
401 {
402 using texture_type = std::remove_cvref_t<decltype(F::texture)>;
403 return avnd::cpu_fixed_format_texture<texture_type> ? score::gfx::Types::Image
404 : score::gfx::Types::Buffer;
405 }
406
407 template <std::size_t I, avnd::sampler_port F>
408 constexpr auto operator()(avnd::field_reflection<I, F> p)
409 {
410 return score::gfx::Types::Image;
411 }
412 template <std::size_t I, avnd::image_port F>
413 constexpr auto operator()(avnd::field_reflection<I, F> p)
414 {
415 return score::gfx::Types::Image;
416 }
417 template <std::size_t I, avnd::attachment_port F>
418 constexpr auto operator()(avnd::field_reflection<I, F> p)
419 {
420 return score::gfx::Types::Image;
421 }
422
423 template <std::size_t I, avnd::geometry_port F>
424 constexpr auto operator()(avnd::field_reflection<I, F> p)
425 {
426 return score::gfx::Types::Geometry;
427 }
428 template <std::size_t I, avnd::mono_audio_port F>
429 constexpr auto operator()(avnd::field_reflection<I, F> p)
430 {
431 return score::gfx::Types::Audio;
432 }
433 template <std::size_t I, avnd::poly_audio_port F>
434 constexpr auto operator()(avnd::field_reflection<I, F> p)
435 {
436 return score::gfx::Types::Audio;
437 }
438 template <std::size_t I, avnd::int_parameter F>
439 constexpr auto operator()(avnd::field_reflection<I, F> p)
440 {
441 return score::gfx::Types::Int;
442 }
443 template <std::size_t I, avnd::enum_parameter F>
444 constexpr auto operator()(avnd::field_reflection<I, F> p)
445 {
446 return score::gfx::Types::Int;
447 }
448 template <std::size_t I, avnd::float_parameter F>
449 constexpr auto operator()(avnd::field_reflection<I, F> p)
450 {
451 return score::gfx::Types::Float;
452 }
453 template <std::size_t I, avnd::parameter F>
454 constexpr auto operator()(avnd::field_reflection<I, F> p)
455 {
456 using value_type = std::remove_cvref_t<decltype(F::value)>;
457
458 if constexpr(std::is_aggregate_v<value_type>)
459 {
460 constexpr int sz = boost::pfr::tuple_size_v<value_type>;
461 if constexpr(sz == 2)
462 {
463 return score::gfx::Types::Vec2;
464 }
465 else if constexpr(sz == 3)
466 {
467 return score::gfx::Types::Vec3;
468 }
469 else if constexpr(sz == 4)
470 {
471 return score::gfx::Types::Vec4;
472 }
473 }
474 return score::gfx::Types::Empty;
475 }
476 template <std::size_t I, typename F>
477 constexpr auto operator()(avnd::field_reflection<I, F> p)
478 {
479 return score::gfx::Types::Empty;
480 }
481};
482
483template <typename Node_T>
484inline void initGfxPorts(auto* self, auto& input, auto& output)
485{
486 avnd::input_introspection<Node_T>::for_all(
487 [self, &input]<typename Field, std::size_t I>(avnd::field_reflection<I, Field> f) {
488 static constexpr auto type = port_to_type_enum{}(f);
489 input.push_back(new score::gfx::Port{self, {}, type, {}});
490 });
491 avnd::output_introspection<Node_T>::for_all(
492 [self,
493 &output]<typename Field, std::size_t I>(avnd::field_reflection<I, Field> f) {
494 static constexpr auto type = port_to_type_enum{}(f);
495 output.push_back(new score::gfx::Port{self, {}, type, {}});
496 });
497}
498
499static score::gfx::BufferView getInputBuffer(
500 score::gfx::RenderList& renderer, const score::gfx::Node& parent, int port_index)
501{
502 const auto& inputs = parent.input;
503 // SCORE_ASSERT(port_index == 0);
504 {
505 score::gfx::Port* p = inputs[port_index];
506 for(auto& edge : p->edges)
507 {
508 auto src_node = edge->source->node;
509 score::gfx::NodeRenderer* src_renderer = src_node->renderedNodes.at(&renderer);
510 if(src_renderer)
511 {
512 return src_renderer->bufferForOutput(*edge->source);
513 }
514 break;
515 }
516 }
517 return {};
518}
519
520
521static void readbackInputBuffer(
522 score::gfx::RenderList& renderer
523 , QRhiResourceUpdateBatch& res
524 , const score::gfx::Node& parent
525 , QRhiBufferReadbackResult& readback
526 , int port_index
527 )
528{
529 // FIXME: instead of doing this we could do the readback in the
530 // producer node and just read its bytearray once...
531 if(auto buf = getInputBuffer(renderer, parent, port_index))
532 {
533 readback = {};
534 res.readBackBuffer(buf.handle, buf.byte_offset, buf.byte_size, &readback);
535 }
536}
537
538static void recreateOutputBuffer(
539 score::gfx::RenderList& renderer, avnd::cpu_buffer auto& cpu_buf,
540 QRhiResourceUpdateBatch& res, score::gfx::BufferView& buf)
541{
542 const auto bytesize = avnd::get_bytesize(cpu_buf);
543 if(!buf.handle)
544 {
545 if(bytesize > 0)
546 {
547 buf.handle = renderer.state.rhi->newBuffer(
548 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
549 bytesize);
550 buf.handle->setName("GpuUtils::recreateOutputBuffer");
551 buf.byte_offset = 0;
552 buf.byte_size = bytesize;
553
554 buf.handle->create();
555 }
556 else
557 {
558 cpu_buf.changed = false;
559 return;
560 }
561 }
562 else if(buf.handle->size() != bytesize)
563 {
564 buf.handle->destroy();
565 buf.handle->setSize(bytesize);
566 buf.handle->create();
567 buf.byte_size = bytesize;
568 }
569}
570
571static void uploadOutputBuffer(
572 score::gfx::RenderList& renderer, avnd::cpu_buffer auto& cpu_buf,
573 QRhiResourceUpdateBatch& res, score::gfx::BufferView& rhi_buf)
574{
575 if(cpu_buf.changed)
576 {
577 const auto bytesize = avnd::get_bytesize(cpu_buf);
578 recreateOutputBuffer(renderer, cpu_buf, res, rhi_buf);
580 &res, rhi_buf.handle, 0, cpu_buf.byte_size,
581 (const char*)avnd::get_bytes(cpu_buf));
582 cpu_buf.changed = false;
583 }
584}
585
586static void uploadOutputBuffer(
587 score::gfx::RenderList& renderer, avnd::gpu_buffer auto& gpu_buf,
588 QRhiResourceUpdateBatch& res, score::gfx::BufferView& rhi_buf)
589{
590 rhi_buf.handle = reinterpret_cast<QRhiBuffer*>(gpu_buf.handle);
591 rhi_buf.byte_size = gpu_buf.byte_size;
592 rhi_buf.byte_offset = gpu_buf.byte_offset;
593}
594
595template <typename T>
596struct geometry_inputs_storage;
597
598struct mesh_input_storage
599{
600 std::vector<QRhiBufferReadbackResult> readbacks;
601 std::vector<QRhiBuffer*> buffers;
602};
603struct geometry_input_storage
604{
605 ossia::geometry_spec spec;
606 std::vector<mesh_input_storage> meshes;
607};
608
609template <typename T>
610 requires(avnd::geometry_input_introspection<T>::size > 0)
611struct geometry_inputs_storage<T>
612{
613 // FIXME in Gfx/Graph/NodeRenderer.hpp
614 static_assert(avnd::geometry_input_introspection<T>::size == 1);
615
616 geometry_input_storage inputs[avnd::geometry_input_introspection<T>::size];
617 ossia::small_vector<QRhiBuffer*, 4> allocated;
618
619 void readInputGeometries(
620 score::gfx::RenderList& renderer, const ossia::geometry_spec& spec, auto& parent,
621 auto& state)
622 {
623 // Copy the readback output inside the structure
624 // TODO it would be much better to do this inside the readback's
625 // "completed" callback.
626 avnd::geometry_input_introspection<T>::for_all_n(
627 avnd::get_inputs<T>(state),
628 [&]<typename Field, std::size_t N>(Field& t, avnd::predicate_index<N> np) {
629 this->inputs[N].spec = spec; // FIXME multiple geometry input ports
630 this->inputs[N].meshes.resize(1); // FIXME
631
632 // Here we fetch the readbacks results
633 auto& meshes = this->inputs[N].meshes[0];
634
635 oscr::meshes_from_ossia(
636 spec.meshes, t.mesh,
637 [&](auto& write_buf, int buffer_index, void* data, int64_t bytesize) {
638 // CPU input geometry, upload was done before
639 SCORE_ASSERT(buffer_index >= 0);
640 if(buffer_index < meshes.readbacks.size())
641 {
642 QRhiBuffer* handle = meshes.buffers[buffer_index];
643 write_buf.handle = handle;
644 write_buf.byte_size = handle->size();
645 }
646 }, [&](auto& write_buf, int buffer_index, void* handle) {
647 // GPU input buffer, CPU output buffer: need to fetch our readback
648 SCORE_ASSERT(buffer_index >= 0);
649 if(buffer_index < meshes.readbacks.size())
650 {
651 // FIXME investigate why runInitialPasses is called before inputAboutToFinish
652 auto& readback = meshes.readbacks[buffer_index].data;
653 write_buf.raw_data = reinterpret_cast<unsigned char*>(readback.data());
654 write_buf.byte_size = readback.size();
655 }
656 });
657 });
658 }
659
660 void inputAboutToFinish(
661 score::gfx::RenderList& renderer, QRhiResourceUpdateBatch*& res,
662 const ossia::geometry_spec& spec, auto& state, auto& parent)
663 {
664 avnd::geometry_input_introspection<T>::for_all_n2(
665 avnd::get_inputs<T>(state),
666 [&]<typename Field, std::size_t N, std::size_t NField>(
667 Field& t, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
668 this->inputs[N].spec = spec; // FIXME multiple geometry input ports
669 this->inputs[N].meshes.resize(1); // FIXME
670 // Here we request readbacks if necessary
671
672 auto& meshes = this->inputs[N].meshes[0];
673 oscr::meshes_from_ossia(
674 spec.meshes, t.mesh,
675 [&](auto& write_buf, int buffer_index, void* data, int64_t bytesize) {
676 // cpu -> gpu
677 if(meshes.buffers.size() <= buffer_index)
678 {
679 meshes.buffers.resize(buffer_index + 1);
680 meshes.readbacks.resize(buffer_index + 1);
681
682 auto buf = renderer.state.rhi->newBuffer(
683 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
684 bytesize);
685 buf->setName(oscr::getUtf8Name<T>() + "::" + oscr::getUtf8Name(t));
686 buf->create();
687 allocated.push_back(buf);
688 meshes.buffers[buffer_index] = buf;
689 }
690
691 res->uploadStaticBuffer(meshes.buffers[buffer_index], 0, bytesize, data);
692 }, [&](auto& write_buf, int buffer_index, void* handle) {
693 // gpu -> cpu
694 if(meshes.readbacks.size() <= buffer_index)
695 {
696 meshes.buffers.resize(buffer_index + 1);
697 meshes.readbacks.resize(buffer_index + 1);
698 }
699
700 meshes.readbacks[buffer_index] = {};
701 if(auto buf = static_cast<QRhiBuffer*>(handle))
702 {
703 meshes.buffers[buffer_index] = buf;
704 res->readBackBuffer(buf, 0, buf->size(), &meshes.readbacks[buffer_index]);
705 }
706 else
707 {
708 meshes.buffers[buffer_index] = {};
709 meshes.readbacks[buffer_index] = {};
710 }
711 });
712 });
713 }
714
715 void release(score::gfx::RenderList& renderer)
716 {
717 for(auto& buf : allocated)
718 renderer.releaseBuffer(buf);
719 allocated.clear();
720 }
721};
722
723template <typename T>
724 requires(avnd::geometry_input_introspection<T>::size == 0)
725struct geometry_inputs_storage<T>
726{
727 static void readInputBuffers(auto&&...) { }
728
729 static void inputAboutToFinish(auto&&...) { }
730};
731
732template<typename T>
733struct buffer_inputs_storage;
734
735template<typename T>
736 requires (avnd::buffer_input_introspection<T>::size > 0)
737struct buffer_inputs_storage<T>
738{
739 // +1 because of zero-array-size unsupported
740 QRhiBufferReadbackResult
741 m_readbacks[avnd::cpu_buffer_input_introspection<T>::size + 1];
742 score::gfx::BufferView m_gpubufs[avnd::gpu_buffer_input_introspection<T>::size + 1];
743
744 void readInputBuffers(
745 score::gfx::RenderList& renderer, auto& parent, auto& state)
746 {
747 if constexpr(avnd::cpu_buffer_input_introspection<T>::size > 0)
748 {
749 // Copy the readback output inside the structure
750 // TODO it would be much better to do this inside the readback's
751 // "completed" callback.
752 avnd::cpu_buffer_input_introspection<T>::for_all_n(
753 avnd::get_inputs<T>(state),
754 [&]<typename Field, std::size_t N>
755 (Field& t, avnd::predicate_index<N> np)
756 {
757 auto& readback = m_readbacks[N].data;
758 t.buffer.raw_data = reinterpret_cast<unsigned char*>(readback.data());
759 t.buffer.byte_size = readback.size();
760 t.buffer.byte_offset = 0; // FIXME
761 t.buffer.changed = true;
762 });
763 }
764
765 if constexpr(avnd::gpu_buffer_input_introspection<T>::size > 0)
766 {
767 // Copy the readback output inside the structure
768 // TODO it would be much better to do this inside the readback's
769 // "completed" callback.
770 avnd::gpu_buffer_input_introspection<T>::for_all_n2(
771 avnd::get_inputs<T>(state),
772 [&]<typename Field, std::size_t N, std::size_t NField>(
773 Field& t, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
774 score::gfx::BufferView& buf = m_gpubufs[N];
775 if(!buf)
776 buf = getInputBuffer(renderer, parent, nf);
777 if(!buf)
778 return;
779 t.buffer.handle = buf.handle;
780 t.buffer.byte_size = buf.byte_size;
781 t.buffer.byte_offset = buf.byte_offset;
782 // t.buffer.changed = true; FIXME
783 });
784 }
785 }
786
787 void inputAboutToFinish(
788 score::gfx::RenderList& renderer,
789 QRhiResourceUpdateBatch*& res,
790 auto& state,
791 auto& parent)
792 {
793 avnd::cpu_buffer_input_introspection<T>::for_all_n2(
794 avnd::get_inputs<T>(state),
795 [&]<typename Field, std::size_t N, std::size_t NField>
796 (Field& port, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
797 readbackInputBuffer(renderer, *res, parent, m_readbacks[N], nf);
798 });
799 avnd::gpu_buffer_input_introspection<T>::for_all_n2(
800 avnd::get_inputs<T>(state),
801 [&]<typename Field, std::size_t N, std::size_t NField>
802 (Field& port, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
803 m_gpubufs[N] = getInputBuffer(renderer, parent, nf);
804 });
805 }
806};
807
808template<typename T>
809 requires (avnd::buffer_input_introspection<T>::size == 0)
810struct buffer_inputs_storage<T>
811{
812 static void readInputBuffers(auto&&...)
813 {
814
815 }
816
817 static void inputAboutToFinish(auto&&...)
818 {
819
820 }
821};
822
823struct MaybeOwnedBuffer : score::gfx::BufferView
824{
825 bool owned{false};
826};
827
828template<typename T>
829struct buffer_outputs_storage;
830
831template<typename T>
832 requires (avnd::buffer_output_introspection<T>::size > 0)
833struct buffer_outputs_storage<T>
834{
835 std::pair<const score::gfx::Port*, MaybeOwnedBuffer>
836 m_buffers[avnd::buffer_output_introspection<T>::size];
837
838 QRhiResourceUpdateBatch* currentResourceUpdateBatch{};
839
840 template <typename Field, std::size_t N, std::size_t NField>
841 requires avnd::cpu_buffer<std::decay_t<decltype(Field::buffer)>>
842 void createOutput(
843 score::gfx::RenderList& renderer, auto& parent, Field& port,
844 avnd::predicate_index<N> np, avnd::field_index<NField> nf)
845 {
846 auto& [gfx_port, buf] = m_buffers[N];
847 gfx_port = parent.output[nf];
848 buf.handle = renderer.state.rhi->newBuffer(
849 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer, 1);
850 buf.handle->setName(oscr::getUtf8Name<T>() + "::" + oscr::getUtf8Name(port));
851 buf.byte_offset = 0;
852 buf.byte_size = 1;
853 buf.owned = true;
854
855 buf.handle->create();
856
857 port.buffer.upload
858 = [this, &renderer, &port](const char* data, int64_t offset, int64_t bytesize) {
859 // FIXME is offset and bytesize relative to the input or the output data ?
860 SCORE_ASSERT(currentResourceUpdateBatch);
861 auto& [gfx_port, buf] = m_buffers[N];
862
863 if(!buf.handle)
864 {
865 if(bytesize > 0)
866 {
867 buf.handle = renderer.state.rhi->newBuffer(
868 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
869 bytesize);
870 buf.handle->setName(oscr::getUtf8Name<T>() + "::" + oscr::getUtf8Name(port));
871 buf.byte_offset = 0;
872 buf.byte_size = bytesize;
873 buf.owned = true;
874
875 buf.handle->create();
876 }
877 else
878 {
879 buf.handle = renderer.state.rhi->newBuffer(
880 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
881 1);
882 buf.handle->setName(oscr::getUtf8Name<T>() + "::" + oscr::getUtf8Name(port));
883 buf.byte_offset = 0;
884 buf.byte_size = 1;
885 buf.owned = true;
886
887 buf.handle->create();
888 return;
889 }
890 }
891 else if(buf.handle->size() != bytesize)
892 {
893 buf.handle->destroy();
894 buf.handle->setSize(bytesize);
895 buf.handle->create();
896 buf.byte_size = bytesize;
897 }
898
900 currentResourceUpdateBatch, buf.handle, offset, bytesize, data);
901 };
902 }
903
904 template <typename Field, std::size_t N, std::size_t NField>
905 requires avnd::gpu_buffer<std::decay_t<decltype(Field::buffer)>>
906 void createOutput(
907 score::gfx::RenderList& renderer, auto& parent, Field& port,
908 avnd::predicate_index<N> np, avnd::field_index<NField> nf)
909 {
910 auto& [gfx_port, buf] = m_buffers[N];
911 gfx_port = parent.output[nf];
912 buf.handle = reinterpret_cast<QRhiBuffer*>(port.buffer.handle);
913 buf.byte_size = port.buffer.byte_size;
914 buf.byte_offset = port.buffer.byte_offset;
915 buf.owned = false;
916 }
917
918 void init(score::gfx::RenderList& renderer, auto& state, auto& parent)
919 {
920 // Init buffers for the outputs
921 avnd::buffer_output_introspection<T>::for_all_n2(
922 avnd::get_outputs<T>(state), [&]<typename Field, std::size_t N, std::size_t NField>
923 (Field& port, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
924 SCORE_ASSERT(parent.output.size() > nf);
925 SCORE_ASSERT(parent.output[nf]->type == score::gfx::Types::Buffer);
926 using buffer_type = std::decay_t<decltype(port.buffer)>;
927
928 if constexpr(avnd::cpu_raw_buffer<buffer_type> && requires {
929 port.buffer.upload(nullptr, 0, 0);
930 })
931 {
932 createOutput(renderer, parent, port, np, nf);
933 }
934 else if constexpr(avnd::gpu_buffer<buffer_type>)
935 {
936 createOutput(renderer, parent, port, np, nf);
937 }
938 else
939 {
940 // m_buffers[N] = createOutput(renderer, *parent.output[nf], port.buffer);
941 static_assert(std::is_same_v<T, void>, "unsupported");
942 }
943 });
944 }
945
946 void prepareUpload(QRhiResourceUpdateBatch& res)
947 {
948 currentResourceUpdateBatch = &res;
949 }
950
951 void upload(score::gfx::RenderList& renderer, auto& state, QRhiResourceUpdateBatch& res)
952 {
953 avnd::buffer_output_introspection<T>::for_all_n(
954 avnd::get_outputs<T>(state), [&]<std::size_t N>(auto& t, avnd::predicate_index<N> idx) {
955 auto& [port, buf] = m_buffers[N];
956 uploadOutputBuffer(renderer, t.buffer, res, buf);
957 });
958 }
959
960 void release(score::gfx::RenderList& renderer)
961 {
962 // Free outputs
963 for(auto& [p, buf] : m_buffers)
964 {
965 if(buf.owned)
966 renderer.releaseBuffer(buf.handle);
967 buf.handle = nullptr;
968 buf.owned = false;
969 }
970 }
971};
972
973template<typename T>
974 requires (avnd::buffer_output_introspection<T>::size == 0)
975struct buffer_outputs_storage<T>
976{
977 static void init(auto&&...)
978 {
979
980 }
981
982 static void prepareUpload(auto&&...)
983 {
984 }
985
986 static void upload(auto&&...)
987 {
988 }
989
990 static void release(auto&&...)
991 {
992 }
993};
994
995
996template <typename Tex>
997static auto
998createOutputTexture(score::gfx::RenderList& renderer, const Tex& texture_spec, QSize size)
999{
1000 auto& rhi = *renderer.state.rhi;
1001 QRhiTexture* texture = &renderer.emptyTexture();
1002 if(size.width() > 0 && size.height() > 0)
1003 {
1004 texture = rhi.newTexture(
1005 gpp::qrhi::textureFormat(texture_spec), size, 1, QRhiTexture::Flag{});
1006
1007 texture->create();
1008 }
1009
1010 auto sampler = rhi.newSampler(
1011 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
1012 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
1013
1014 sampler->create();
1015 return score::gfx::Sampler{sampler, texture};
1016}
1017
1018
1019template<typename T>
1020struct texture_inputs_storage;
1021
1022template<typename T>
1023 requires (avnd::texture_input_introspection<T>::size > 0)
1024struct texture_inputs_storage<T>
1025{
1026 ossia::small_flat_map<const score::gfx::Port*, score::gfx::TextureRenderTarget, 2>
1027 m_rts;
1028
1029 QRhiReadbackResult m_readbacks[avnd::texture_input_introspection<T>::size];
1030
1031 template <typename Tex>
1032 void createInput(
1033 score::gfx::RenderList& renderer, score::gfx::Port* port, const Tex& texture_spec,
1035 {
1036 static constexpr auto flags
1037 = QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource;
1038 auto texture = renderer.state.rhi->newTexture(
1039 gpp::qrhi::textureFormat(texture_spec), spec.size, 1, flags);
1040 SCORE_ASSERT(texture->create());
1041 m_rts[port] = score::gfx::createRenderTarget(
1042 renderer.state, texture, renderer.samples(), renderer.requiresDepth(*port));
1043 }
1044
1045 void init(auto& self, score::gfx::RenderList& renderer)
1046 {
1047 // Init input render targets
1048 avnd::cpu_texture_input_introspection<T>::for_all_n(
1049 avnd::get_inputs<T>(*self.state),
1050 [&]<typename F, std::size_t K>(F& t, avnd::predicate_index<K>) {
1051 // FIXME k isn't the port index, it's the texture port index
1052 auto& parent = self.node();
1053 auto spec = parent.resolveRenderTargetSpecs(K, renderer);
1054 if constexpr(requires {
1055 t.request_width;
1056 t.request_height;
1057 })
1058 {
1059 spec.size.rwidth() = t.request_width;
1060 spec.size.rheight() = t.request_height;
1061 }
1062
1063 createInput(renderer, parent.input[K], t.texture, spec);
1064
1065 if constexpr(avnd::cpu_fixed_format_texture<decltype(t.texture)>)
1066 {
1067 t.texture.width = spec.size.width();
1068 t.texture.height = spec.size.height();
1069 }
1070 });
1071 }
1072
1073 void runInitialPasses(auto& self, QRhi& rhi)
1074 {
1075 // Fetch input textures (if any)
1076 // Copy the readback output inside the structure
1077 // TODO it would be much better to do this inside the readback's
1078 // "completed" callback.
1079 avnd::cpu_texture_input_introspection<T>::for_all_n(
1080 avnd::get_inputs<T>(*self.state), [&]<std::size_t K>(auto& t, avnd::predicate_index<K>) {
1081 oscr::loadInputTexture(rhi, m_readbacks, t.texture, K);
1082 });
1083 }
1084
1085 void release()
1086 {
1087 // Free inputs
1088 // TODO investigate why reference does not work here:
1089 for(auto [port, rt] : m_rts)
1090 rt.release();
1091 m_rts.clear();
1092 }
1093
1094 void inputAboutToFinish(auto& parent, const score::gfx::Port& p, QRhiResourceUpdateBatch*& res)
1095 {
1096 const auto& inputs = parent.input;
1097 auto index_of_port = ossia::find(inputs, &p) - inputs.begin();
1098 {
1099 auto tex = m_rts[&p].texture;
1100 auto& readback = m_readbacks[index_of_port];
1101 readback = {};
1102 res->readBackTexture(QRhiReadbackDescription{tex}, &readback);
1103 }
1104 }
1105
1106};
1107template<typename T>
1108 requires (avnd::texture_input_introspection<T>::size == 0)
1109struct texture_inputs_storage<T>
1110{
1111 static void init(auto&&...) { }
1112 static void runInitialPasses(auto&&...) { }
1113 static void release(auto&&...) { }
1114 static void inputAboutToFinish(auto&&...) { }
1115};
1116
1117
1118
1119template <avnd::cpu_texture Tex>
1120static QRhiTexture* updateTexture(auto& self, score::gfx::RenderList& renderer, int k, const Tex& cpu_tex)
1121{
1122 auto& [sampler, texture] = self.m_samplers[k];
1123 if(texture)
1124 {
1125 auto sz = texture->pixelSize();
1126 if(cpu_tex.width == sz.width() && cpu_tex.height == sz.height())
1127 return texture;
1128 }
1129
1130 // Check the texture size
1131 if(cpu_tex.width > 0 && cpu_tex.height > 0)
1132 {
1133 QRhiTexture* oldtex = texture;
1134 QRhiTexture* newtex = renderer.state.rhi->newTexture(
1135 gpp::qrhi::textureFormat(cpu_tex), QSize{cpu_tex.width, cpu_tex.height}, 1,
1136 QRhiTexture::Flag{});
1137 newtex->create();
1138 for(auto& [edge, pass] : self.m_p)
1139 if(pass.srb)
1140 score::gfx::replaceTexture(*pass.srb, sampler, newtex);
1141 texture = newtex;
1142
1143 if(oldtex && oldtex != &renderer.emptyTexture())
1144 {
1145 oldtex->deleteLater();
1146 }
1147
1148 return newtex;
1149 }
1150 else
1151 {
1152 for(auto& [edge, pass] : self.m_p)
1153 if(pass.srb)
1154 score::gfx::replaceTexture(*pass.srb, sampler, &renderer.emptyTexture());
1155
1156 return &renderer.emptyTexture();
1157 }
1158}
1159
1160template <avnd::cpu_texture Tex>
1161static void uploadOutputTexture(auto& self,
1162 score::gfx::RenderList& renderer, int k, Tex& cpu_tex,
1163 QRhiResourceUpdateBatch* res)
1164{
1165 if(cpu_tex.changed)
1166 {
1167 if(auto texture = updateTexture(self, renderer, k, cpu_tex))
1168 {
1169 QByteArray buf
1170 = QByteArray::fromRawData((const char*)cpu_tex.bytes, cpu_tex.bytesize());
1171 if constexpr(requires { Tex::RGB; })
1172 {
1173 // RGB -> RGBA
1174 // FIXME other conversions
1175 const QByteArray rgb = buf;
1176 QByteArray rgba;
1177 rgba.resize(cpu_tex.width * cpu_tex.height * 4);
1178 auto src = (const unsigned char*)rgb.constData();
1179 auto dst = (unsigned char*)rgba.data();
1180 for(int rgb_byte = 0, rgba_byte = 0, N = rgb.size(); rgb_byte < N;)
1181 {
1182 dst[rgba_byte + 0] = src[rgb_byte + 0];
1183 dst[rgba_byte + 1] = src[rgb_byte + 1];
1184 dst[rgba_byte + 2] = src[rgb_byte + 2];
1185 dst[rgba_byte + 3] = 255;
1186 rgb_byte += 3;
1187 rgba_byte += 4;
1188 }
1189 buf = rgba;
1190 }
1191
1192 // Upload it (mirroring is done in shader generic_texgen_fs if necessary)
1193 {
1194 QRhiTextureSubresourceUploadDescription sd(buf);
1195 QRhiTextureUploadDescription desc{QRhiTextureUploadEntry{0, 0, sd}};
1196
1197 res->uploadTexture(texture, desc);
1198 }
1199
1200 cpu_tex.changed = false;
1201 }
1202 }
1203}
1204
1205static const constexpr auto generic_texgen_vs = R"_(#version 450
1206layout(location = 0) in vec2 position;
1207layout(location = 1) in vec2 texcoord;
1208
1209layout(binding=3) uniform sampler2D y_tex;
1210layout(location = 0) out vec2 v_texcoord;
1211
1212layout(std140, binding = 0) uniform renderer_t {
1213 mat4 clipSpaceCorrMatrix;
1214 vec2 renderSize;
1215} renderer;
1216
1217out gl_PerVertex { vec4 gl_Position; };
1218
1219void main()
1220{
1221#if defined(QSHADER_SPIRV) || defined(QSHADER_GLSL)
1222 v_texcoord = vec2(texcoord.x, 1. - texcoord.y);
1223#else
1224 v_texcoord = texcoord;
1225#endif
1226 gl_Position = renderer.clipSpaceCorrMatrix * vec4(position.xy, 0.0, 1.);
1227}
1228)_";
1229
1230static const constexpr auto generic_texgen_fs = R"_(#version 450
1231layout(location = 0) in vec2 v_texcoord;
1232layout(location = 0) out vec4 fragColor;
1233
1234layout(std140, binding = 0) uniform renderer_t {
1235mat4 clipSpaceCorrMatrix;
1236vec2 renderSize;
1237} renderer;
1238
1239layout(binding=3) uniform sampler2D y_tex;
1240
1241void main ()
1242{
1243 fragColor = texture(y_tex, v_texcoord);
1244}
1245)_";
1246
1247template<typename T>
1248struct texture_outputs_storage;
1249
1250// If we have texture outs we need the whole rendering infrastructure
1251template<typename T>
1252 requires (avnd::texture_output_introspection<T>::size > 0)
1253struct texture_outputs_storage<T>
1254{
1255 void init(auto& self, score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res)
1256 {
1257 const auto& mesh = renderer.defaultTriangle();
1258 self.defaultMeshInit(renderer, mesh, res);
1259 self.processUBOInit(renderer);
1260 // Not needed here as we do not have a GPU pass:
1261 // this->m_material.init(renderer, this->node.input, this->m_samplers);
1262
1263 std::tie(self.m_vertexS, self.m_fragmentS)
1264 = score::gfx::makeShaders(renderer.state, generic_texgen_vs, generic_texgen_fs);
1265
1266 avnd::cpu_texture_output_introspection<T>::for_all(
1267 avnd::get_outputs<T>(*self.state), [&](auto& t) {
1268 self.m_samplers.push_back(
1269 createOutputTexture(renderer, t.texture, QSize{t.texture.width, t.texture.height}));
1270 });
1271
1272 self.defaultPassesInit(renderer, mesh);
1273 }
1274
1275 void runInitialPasses(auto& self,
1276 score::gfx::RenderList& renderer,
1277 QRhiResourceUpdateBatch*& res)
1278 {
1279 avnd::cpu_texture_output_introspection<T>::for_all_n(
1280 avnd::get_outputs<T>(*self.state), [&]<std::size_t N>(auto& t, avnd::predicate_index<N>) {
1281 uploadOutputTexture(self, renderer, N, t.texture, res);
1282 });
1283 }
1284
1285 void release(auto& self, score::gfx::RenderList& r)
1286 {
1287 // Free outputs
1288 for(auto& [sampl, texture] : self.m_samplers)
1289 {
1290 if(texture != &r.emptyTexture())
1291 texture->deleteLater();
1292 texture = nullptr;
1293 }
1294 }
1295
1296};
1297
1298template<typename T>
1299 requires (avnd::texture_output_introspection<T>::size == 0)
1300struct texture_outputs_storage<T>
1301{
1302 static void init(auto& self, score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res)
1303 {
1304 }
1305
1306 static void runInitialPasses(auto& self,
1307 score::gfx::RenderList& renderer,
1308 QRhiResourceUpdateBatch*& res)
1309 {
1310 }
1311
1312 static void release(auto& self, score::gfx::RenderList& r)
1313 {
1314 }
1315};
1316template<typename T>
1317struct geometry_outputs_storage;
1318
1319template<typename T>
1320 requires (avnd::geometry_output_introspection<T>::size > 0)
1321struct geometry_outputs_storage<T>
1322{
1323 ossia::geometry_spec specs[avnd::geometry_output_introspection<T>::size];
1324
1325 template <avnd::geometry_port Field>
1326 void reload_mesh(Field& ctrl, ossia::geometry_spec& spc)
1327 {
1328 spc.meshes = std::make_shared<ossia::mesh_list>();
1329 auto& ossia_meshes = *spc.meshes;
1330 if constexpr(avnd::static_geometry_type<Field> || avnd::dynamic_geometry_type<Field>)
1331 {
1332 ossia_meshes.meshes.resize(1);
1333 load_geometry(ctrl, ossia_meshes.meshes[0]);
1334 }
1335 else if constexpr(
1336 avnd::static_geometry_type<decltype(Field::mesh)>
1337 || avnd::dynamic_geometry_type<decltype(Field::mesh)>)
1338 {
1339 ossia_meshes.meshes.resize(1);
1340 load_geometry(ctrl.mesh, ossia_meshes.meshes[0]);
1341 }
1342 else
1343 {
1344 load_geometry(ctrl, ossia_meshes);
1345 }
1346 }
1347
1348 template <avnd::geometry_port Field, std::size_t N>
1349 void upload(
1350 score::gfx::RenderList& renderer, Field& ctrl, score::gfx::Edge& edge,
1351 avnd::predicate_index<N>)
1352 {
1353 auto edge_sink = edge.sink;
1354 if(auto pnode = edge_sink->node)
1355 {
1356 ossia::geometry_spec& spc = specs[N];
1357
1358 // 1. Reload mesh
1359 {
1360 if(ctrl.dirty_mesh)
1361 {
1362 reload_mesh(ctrl, spc);
1363 }
1364 else
1365 {
1366 if(spc.meshes)
1367 {
1368 auto& ossia_meshes = *spc.meshes;
1369
1370 bool any_need_reload = false;
1371 bool any_need_upload = false;
1372 if constexpr(avnd::static_geometry_type<Field> || avnd::dynamic_geometry_type<Field>)
1373 {
1374 SCORE_ASSERT(ossia_meshes.meshes.size() == 1);
1375 auto [need_reload, need_upload]
1376 = update_geometry(ctrl, ossia_meshes.meshes[0]);
1377 any_need_reload = need_reload;
1378 any_need_upload = need_upload;
1379 }
1380 else if constexpr(
1381 avnd::static_geometry_type<decltype(Field::mesh)>
1382 || avnd::dynamic_geometry_type<decltype(Field::mesh)>)
1383 {
1384 SCORE_ASSERT(ossia_meshes.meshes.size() == 1);
1385 auto [need_reload, need_upload]
1386 = update_geometry(ctrl.mesh, ossia_meshes.meshes[0]);
1387 any_need_reload = need_reload;
1388 any_need_upload = need_upload;
1389 }
1390 else
1391 {
1392 auto [need_reload, need_upload] = update_geometry(ctrl, ossia_meshes);
1393 any_need_reload = need_reload;
1394 any_need_upload = need_upload;
1395 }
1396
1397 if(any_need_reload)
1398 {
1399 reload_mesh(ctrl, spc);
1400 }
1401 }
1402 }
1403 ctrl.dirty_mesh = false;
1404 }
1405
1406 // 2. Push to next node
1407 // FIXME this should be for the renderer of edge, not the node, since
1408 // geometries can have gpu buffers
1409 auto rendered_node = pnode->renderedNodes.find(&renderer);
1410 SCORE_ASSERT(rendered_node != pnode->renderedNodes.end());
1411
1412 auto it = std::find(
1413 edge_sink->node->input.begin(), edge_sink->node->input.end(), edge_sink);
1414 SCORE_ASSERT(it != edge_sink->node->input.end());
1415 int n = it - edge_sink->node->input.begin();
1416
1417 rendered_node->second->process(n, spc);
1418
1419 // 3. Same for transform3d
1420
1421 if constexpr(requires { ctrl.transform; })
1422 {
1423 if(ctrl.dirty_transform)
1424 {
1425 ossia::transform3d transform;
1426 std::copy_n(ctrl.transform, std::ssize(ctrl.transform), transform.matrix);
1427 ctrl.dirty_transform = false;
1428
1429 if(auto pnode = dynamic_cast<score::gfx::ProcessNode*>(edge_sink->node))
1430 pnode->process(n, transform);
1431 }
1432 }
1433 }
1434 }
1435
1436 void upload(score::gfx::RenderList& renderer, auto& state, score::gfx::Edge& edge)
1437 {
1438 // FIXME we need something such as port_run_{pre,post}process for GPU nodes
1439 avnd::geometry_output_introspection<T>::for_all_n(
1440 avnd::get_outputs(state),
1441 [&](auto& field, auto pred) { this->upload(renderer, field, edge, pred); });
1442 }
1443};
1444
1445
1446template<typename T>
1447 requires (avnd::geometry_output_introspection<T>::size == 0)
1448struct geometry_outputs_storage<T>
1449{
1450 static void upload(auto&&...)
1451 {
1452
1453 }
1454};
1455}
1456
1457#endif
Root data model for visual nodes.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:75
std::vector< Port * > input
Input ports of that node.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:104
virtual void process(Message &&msg)
Process a message from the execution engine.
Definition Node.cpp:25
ossia::small_pod_vector< Port *, 1 > output
Output ports of that node.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:110
Common base class for most single-pass, simple nodes.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:204
Renderer for a given node.
Definition NodeRenderer.hpp:11
Base class for sink nodes (QWindow, spout, syphon, NDI output, ...)
Definition OutputNode.hpp:24
Common base class for nodes that map to score processes.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:177
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
const score::gfx::Mesh & defaultTriangle() const noexcept
A triangle mesh correct for this API.
Definition RenderList.cpp:383
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
TreeNode< DeviceExplorerNode > Node
Definition DeviceNode.hpp:74
Definition Factories.hpp:19
GraphicsApi
Available graphics APIs to use.
Definition RenderState.hpp:20
void uploadStaticBufferWithStoredData(QRhiResourceUpdateBatch *ub, QRhiBuffer *buf, int offset, int64_t bytesize, const char *data)
Schedule a Static buffer update when we can guarantee the buffer outlives the frame.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:338
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
Base toolkit upon which the software is built.
Definition Application.cpp:97
STL namespace.
Definition DocumentContext.hpp:18
Definition Mesh.hpp:15
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
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:57
Stores a sampler and the texture currently associated with it.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:26