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_port 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_port 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 SCORE_PLUGIN_AVND_EXPORT 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
326 QString vertex, fragment, compute;
327 score::gfx::Message last_message;
328 void process(score::gfx::Message&& msg) override;
330
331 void setRenderer(std::shared_ptr<score::gfx::RenderList>) override;
332 score::gfx::RenderList* renderer() const override;
333
334 void startRendering() override;
335 void render() override;
336 void stopRendering() override;
337 bool canRender() const override;
338 void onRendererChange() override;
339
340 void createOutput(score::gfx::OutputConfiguration) override;
341
342 void destroyOutput() override;
343 std::shared_ptr<score::gfx::RenderState> renderState() const override;
344
345 Configuration configuration() const noexcept override;
346};
347
348template <typename Node_T, typename Node>
349void prepareNewState(std::shared_ptr<Node_T>& eff, const Node& parent)
350{
351 if constexpr(avnd::has_worker<Node_T>)
352 {
353 parent.initWorker(eff);
354 }
355 if constexpr(avnd::has_processor_to_gui_bus<Node_T>)
356 {
357 auto& process = parent.processModel;
358 eff->send_message = [ptr = QPointer{&process}](auto&& b) mutable {
359 // FIXME right now all the rendering is done in the UI thread, which is very MEH
360 // this->in_edit([&process, bb = std::move(b)]() mutable {
361
362 if(ptr && ptr->to_ui)
363 MessageBusSender{ptr->to_ui}(std::move(b));
364 // });
365 };
366
367 // FIXME GUI -> engine. See executor.hpp
368 }
369
370 avnd::init_controls(*eff);
371
372 if constexpr(avnd::can_prepare<Node_T>)
373 {
374 if constexpr(avnd::function_reflection<&Node_T::prepare>::count == 1)
375 {
376 using prepare_type = avnd::first_argument<&Node_T::prepare>;
377 prepare_type t;
378 if_possible(t.instance = parent.instance);
379 eff->prepare(t);
380 }
381 else
382 {
383 eff->prepare();
384 }
385 }
386}
387
388struct port_to_type_enum
389{
390 template <std::size_t I, avnd::buffer_port F>
391 constexpr auto operator()(avnd::field_reflection<I, F> p)
392 {
393 return score::gfx::Types::Buffer;
394 }
395
396 template <std::size_t I, avnd::cpu_texture_port F>
397 constexpr auto operator()(avnd::field_reflection<I, F> p)
398 {
399 using texture_type = std::remove_cvref_t<decltype(F::texture)>;
400 return (avnd::cpu_fixed_format_texture<texture_type> || avnd::cpu_dynamic_format_texture<texture_type>)
401 ? score::gfx::Types::Image
402 : score::gfx::Types::Buffer;
403 }
404
405 template <std::size_t I, avnd::gpu_texture_port F>
406 constexpr auto operator()(avnd::field_reflection<I, F> p)
407 {
408 return score::gfx::Types::Image;
409 }
410
411 template <std::size_t I, avnd::sampler_port F>
412 constexpr auto operator()(avnd::field_reflection<I, F> p)
413 {
414 return score::gfx::Types::Image;
415 }
416 template <std::size_t I, avnd::image_port F>
417 constexpr auto operator()(avnd::field_reflection<I, F> p)
418 {
419 return score::gfx::Types::Image;
420 }
421 template <std::size_t I, avnd::attachment_port F>
422 constexpr auto operator()(avnd::field_reflection<I, F> p)
423 {
424 return score::gfx::Types::Image;
425 }
426
427 template <std::size_t I, avnd::geometry_port F>
428 constexpr auto operator()(avnd::field_reflection<I, F> p)
429 {
430 return score::gfx::Types::Geometry;
431 }
432 template <std::size_t I, avnd::mono_audio_port F>
433 constexpr auto operator()(avnd::field_reflection<I, F> p)
434 {
435 return score::gfx::Types::Audio;
436 }
437 template <std::size_t I, avnd::poly_audio_port F>
438 constexpr auto operator()(avnd::field_reflection<I, F> p)
439 {
440 return score::gfx::Types::Audio;
441 }
442 template <std::size_t I, avnd::int_parameter F>
443 constexpr auto operator()(avnd::field_reflection<I, F> p)
444 {
445 return score::gfx::Types::Int;
446 }
447 template <std::size_t I, avnd::enum_parameter F>
448 constexpr auto operator()(avnd::field_reflection<I, F> p)
449 {
450 return score::gfx::Types::Int;
451 }
452 template <std::size_t I, avnd::float_parameter F>
453 constexpr auto operator()(avnd::field_reflection<I, F> p)
454 {
455 return score::gfx::Types::Float;
456 }
457 template <std::size_t I, avnd::parameter_port F>
458 constexpr auto operator()(avnd::field_reflection<I, F> p)
459 {
460 using value_type = std::remove_cvref_t<decltype(F::value)>;
461
462 if constexpr(std::is_array_v<value_type>)
463 {
464 static constexpr int sz = sizeof(value_type) / sizeof(value_type{}[0]);
465 if constexpr(sz == 2)
466 {
467 return score::gfx::Types::Vec2;
468 }
469 else if constexpr(sz == 3)
470 {
471 return score::gfx::Types::Vec3;
472 }
473 else if constexpr(sz == 4)
474 {
475 return score::gfx::Types::Vec4;
476 }
477 }
478 else if constexpr(std::is_aggregate_v<value_type>)
479 {
480 static constexpr int sz = avnd::pfr::tuple_size_v<value_type>;
481 if constexpr(sz == 2)
482 {
483 return score::gfx::Types::Vec2;
484 }
485 else if constexpr(sz == 3)
486 {
487 return score::gfx::Types::Vec3;
488 }
489 else if constexpr(sz == 4)
490 {
491 return score::gfx::Types::Vec4;
492 }
493 }
494 return score::gfx::Types::Empty;
495 }
496 template <std::size_t I, typename F>
497 constexpr auto operator()(avnd::field_reflection<I, F> p)
498 {
499 return score::gfx::Types::Empty;
500 }
501};
502
503template <typename Node_T>
504inline void initGfxPorts(auto* self, auto& input, auto& output)
505{
506 avnd::input_introspection<Node_T>::for_all(
507 [self, &input]<typename Field, std::size_t I>(avnd::field_reflection<I, Field> f) {
508 static constexpr auto type = port_to_type_enum{}(f);
509 input.push_back(new score::gfx::Port{self, {}, type, {}, {}});
510 });
511 avnd::output_introspection<Node_T>::for_all(
512 [self,
513 &output]<typename Field, std::size_t I>(avnd::field_reflection<I, Field> f) {
514 static constexpr auto type = port_to_type_enum{}(f);
515 output.push_back(new score::gfx::Port{self, {}, type, {}, {}});
516 });
517}
518
519static score::gfx::BufferView getInputBuffer(
520 score::gfx::RenderList& renderer, const score::gfx::Node& parent, int port_index)
521{
522 const auto& inputs = parent.input;
523 // SCORE_ASSERT(port_index == 0);
524 {
525 score::gfx::Port* p = inputs[port_index];
526 for(auto& edge : p->edges)
527 {
528 auto src_node = edge->source->node;
529 score::gfx::NodeRenderer* src_renderer = src_node->renderedNodes.at(&renderer);
530 if(src_renderer)
531 {
532 return src_renderer->bufferForOutput(*edge->source);
533 }
534 break;
535 }
536 }
537 return {};
538}
539
540
541static void readbackInputBuffer(
542 score::gfx::RenderList& renderer
543 , QRhiResourceUpdateBatch& res
544 , const score::gfx::Node& parent
545 , QRhiBufferReadbackResult& readback
546 , int port_index
547 )
548{
549 // FIXME: instead of doing this we could do the readback in the
550 // producer node and just read its bytearray once...
551 if(auto buf = getInputBuffer(renderer, parent, port_index))
552 {
553 readback = {};
554 res.readBackBuffer(buf.handle, buf.byte_offset, buf.byte_size, &readback);
555 }
556}
557
558static void recreateOutputBuffer(
559 score::gfx::RenderList& renderer, avnd::cpu_buffer auto& cpu_buf,
560 QRhiResourceUpdateBatch& res, score::gfx::BufferView& buf)
561{
562 const auto bytesize = avnd::get_bytesize(cpu_buf);
563 if(!buf.handle)
564 {
565 if(bytesize > 0)
566 {
567 buf.handle = renderer.state.rhi->newBuffer(
568 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
569 bytesize);
570 buf.handle->setName("GpuUtils::recreateOutputBuffer");
571 buf.byte_offset = 0;
572 buf.byte_size = bytesize;
573
574 buf.handle->create();
575 }
576 else
577 {
578 cpu_buf.changed = false;
579 return;
580 }
581 }
582 else if(buf.handle->size() != bytesize)
583 {
584 buf.handle->destroy();
585 buf.handle->setSize(bytesize);
586 buf.handle->create();
587 buf.byte_size = bytesize;
588 }
589}
590
591static void uploadOutputBuffer(
592 score::gfx::RenderList& renderer, avnd::cpu_buffer auto& cpu_buf,
593 QRhiResourceUpdateBatch& res, score::gfx::BufferView& rhi_buf)
594{
595 if(cpu_buf.changed)
596 {
597 recreateOutputBuffer(renderer, cpu_buf, res, rhi_buf);
599 &res, rhi_buf.handle, 0, cpu_buf.byte_size,
600 (const char*)avnd::get_bytes(cpu_buf));
601 cpu_buf.changed = false;
602 }
603}
604
605static void uploadOutputBuffer(
606 score::gfx::RenderList& renderer, avnd::gpu_buffer auto& gpu_buf,
607 QRhiResourceUpdateBatch& res, score::gfx::BufferView& rhi_buf)
608{
609 rhi_buf.handle = reinterpret_cast<QRhiBuffer*>(gpu_buf.handle);
610 rhi_buf.byte_size = gpu_buf.byte_size;
611 rhi_buf.byte_offset = gpu_buf.byte_offset;
612}
613
614template <typename T>
615struct geometry_inputs_storage;
616
617struct mesh_input_storage
618{
619 std::vector<QRhiBufferReadbackResult> readbacks;
620 std::vector<QRhiBuffer*> buffers;
621};
622struct geometry_input_storage
623{
624 ossia::geometry_spec spec;
625 std::vector<mesh_input_storage> meshes;
626};
627
628template <typename T>
629 requires(avnd::geometry_input_introspection<T>::size > 0)
630struct geometry_inputs_storage<T>
631{
632 // FIXME in Gfx/Graph/NodeRenderer.hpp
633 static_assert(avnd::geometry_input_introspection<T>::size == 1);
634
635 geometry_input_storage inputs[avnd::geometry_input_introspection<T>::size];
636 ossia::small_vector<QRhiBuffer*, 4> allocated;
637
638 void readInputGeometries(
639 score::gfx::RenderList& renderer, const ossia::geometry_spec& spec, auto& parent,
640 auto& state)
641 {
642 // Copy the readback output inside the structure
643 // TODO it would be much better to do this inside the readback's
644 // "completed" callback.
645 avnd::geometry_input_introspection<T>::for_all_n(
646 avnd::get_inputs<T>(state),
647 [&]<typename Field, std::size_t N>(Field& t, avnd::predicate_index<N> np) {
648 this->inputs[N].spec = spec; // FIXME multiple geometry input ports
649 this->inputs[N].meshes.resize(1); // FIXME
650
651 // Here we fetch the readbacks results
652 auto& meshes = this->inputs[N].meshes[0];
653
654 oscr::meshes_from_ossia(
655 spec.meshes, t.mesh,
656 [&](auto& write_buf, int buffer_index, void* data, int64_t bytesize) {
657 // CPU input geometry, upload was done before
658 SCORE_ASSERT(buffer_index >= 0);
659 if(buffer_index < meshes.readbacks.size())
660 {
661 QRhiBuffer* handle = meshes.buffers[buffer_index];
662 write_buf.handle = handle;
663 write_buf.byte_size = handle->size();
664 }
665 }, [&](auto& write_buf, int buffer_index, void* handle) {
666 // GPU input buffer, CPU output buffer: need to fetch our readback
667 SCORE_ASSERT(buffer_index >= 0);
668 if(buffer_index < meshes.readbacks.size())
669 {
670 // FIXME investigate why runInitialPasses is called before inputAboutToFinish
671 auto& readback = meshes.readbacks[buffer_index].data;
672 write_buf.raw_data = reinterpret_cast<unsigned char*>(readback.data());
673 write_buf.byte_size = readback.size();
674 }
675 });
676 });
677 }
678
679 void inputAboutToFinish(
680 score::gfx::RenderList& renderer, QRhiResourceUpdateBatch*& res,
681 const ossia::geometry_spec& spec, auto& state, auto& parent)
682 {
683 avnd::geometry_input_introspection<T>::for_all_n2(
684 avnd::get_inputs<T>(state),
685 [&]<typename Field, std::size_t N, std::size_t NField>(
686 Field& t, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
687 this->inputs[N].spec = spec; // FIXME multiple geometry input ports
688 this->inputs[N].meshes.resize(1); // FIXME
689 // Here we request readbacks if necessary
690
691 auto& meshes = this->inputs[N].meshes[0];
692 oscr::meshes_from_ossia(
693 spec.meshes, t.mesh,
694 [&](auto& write_buf, int buffer_index, void* data, int64_t bytesize) {
695 // cpu -> gpu
696 if(meshes.buffers.size() <= buffer_index)
697 {
698 meshes.buffers.resize(buffer_index + 1);
699 meshes.readbacks.resize(buffer_index + 1);
700
701 auto buf = renderer.state.rhi->newBuffer(
702 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
703 bytesize);
704 buf->setName(oscr::getUtf8Name<T>() + "::" + oscr::getUtf8Name(t));
705 buf->create();
706 allocated.push_back(buf);
707 meshes.buffers[buffer_index] = buf;
708 }
709
710 res->uploadStaticBuffer(meshes.buffers[buffer_index], 0, bytesize, data);
711 }, [&](auto& write_buf, int buffer_index, void* handle) {
712 // gpu -> cpu
713 if(meshes.readbacks.size() <= buffer_index)
714 {
715 meshes.buffers.resize(buffer_index + 1);
716 meshes.readbacks.resize(buffer_index + 1);
717 }
718
719 meshes.readbacks[buffer_index] = {};
720 if(auto buf = static_cast<QRhiBuffer*>(handle))
721 {
722 meshes.buffers[buffer_index] = buf;
723 res->readBackBuffer(buf, 0, buf->size(), &meshes.readbacks[buffer_index]);
724 }
725 else
726 {
727 meshes.buffers[buffer_index] = {};
728 meshes.readbacks[buffer_index] = {};
729 }
730 });
731 });
732 }
733
734 void release(score::gfx::RenderList& renderer)
735 {
736 for(auto& buf : allocated)
737 renderer.releaseBuffer(buf);
738 allocated.clear();
739 }
740};
741
742template <typename T>
743 requires(avnd::geometry_input_introspection<T>::size == 0)
744struct geometry_inputs_storage<T>
745{
746 static void readInputBuffers(auto&&...) { }
747
748 static void inputAboutToFinish(auto&&...) { }
749};
750
751template<typename T>
752struct buffer_inputs_storage;
753
754template<typename T>
755 requires (avnd::buffer_input_introspection<T>::size > 0)
756struct buffer_inputs_storage<T>
757{
758 // +1 because of zero-array-size unsupported
759 QRhiBufferReadbackResult
760 m_readbacks[avnd::cpu_buffer_input_introspection<T>::size + 1];
761 score::gfx::BufferView m_gpubufs[avnd::gpu_buffer_input_introspection<T>::size + 1];
762
763 void readInputBuffers(
764 score::gfx::RenderList& renderer, auto& parent, auto& state)
765 {
766 if constexpr(avnd::cpu_buffer_input_introspection<T>::size > 0)
767 {
768 // Copy the readback output inside the structure
769 // TODO it would be much better to do this inside the readback's
770 // "completed" callback.
771 avnd::cpu_buffer_input_introspection<T>::for_all_n(
772 avnd::get_inputs<T>(state),
773 [&]<typename Field, std::size_t N>
774 (Field& t, avnd::predicate_index<N> np)
775 {
776 auto& readback = m_readbacks[N].data;
777 t.buffer.raw_data = reinterpret_cast<unsigned char*>(readback.data());
778 t.buffer.byte_size = readback.size();
779 t.buffer.byte_offset = 0; // FIXME
780 t.buffer.changed = true;
781 });
782 }
783
784 if constexpr(avnd::gpu_buffer_input_introspection<T>::size > 0)
785 {
786 // Copy the readback output inside the structure
787 // TODO it would be much better to do this inside the readback's
788 // "completed" callback.
789 avnd::gpu_buffer_input_introspection<T>::for_all_n2(
790 avnd::get_inputs<T>(state),
791 [&]<typename Field, std::size_t N, std::size_t NField>(
792 Field& t, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
793 score::gfx::BufferView& buf = m_gpubufs[N];
794 if(!buf)
795 buf = getInputBuffer(renderer, parent, nf);
796 if(!buf)
797 return;
798 t.buffer.handle = buf.handle;
799 t.buffer.byte_size = buf.byte_size;
800 t.buffer.byte_offset = buf.byte_offset;
801 // t.buffer.changed = true; FIXME
802 });
803 }
804 }
805
806 void inputAboutToFinish(
807 score::gfx::RenderList& renderer,
808 QRhiResourceUpdateBatch*& res,
809 auto& state,
810 auto& parent)
811 {
812 avnd::cpu_buffer_input_introspection<T>::for_all_n2(
813 avnd::get_inputs<T>(state),
814 [&]<typename Field, std::size_t N, std::size_t NField>
815 (Field& port, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
816 readbackInputBuffer(renderer, *res, parent, m_readbacks[N], nf);
817 });
818 avnd::gpu_buffer_input_introspection<T>::for_all_n2(
819 avnd::get_inputs<T>(state),
820 [&]<typename Field, std::size_t N, std::size_t NField>
821 (Field& port, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
822 m_gpubufs[N] = getInputBuffer(renderer, parent, nf);
823 });
824 }
825};
826
827template<typename T>
828 requires (avnd::buffer_input_introspection<T>::size == 0)
829struct buffer_inputs_storage<T>
830{
831 static void readInputBuffers(auto&&...)
832 {
833
834 }
835
836 static void inputAboutToFinish(auto&&...)
837 {
838
839 }
840};
841
842struct MaybeOwnedBuffer : score::gfx::BufferView
843{
844 bool owned{false};
845};
846
847template<typename T>
848struct buffer_outputs_storage;
849
850template<typename T>
851 requires (avnd::buffer_output_introspection<T>::size > 0)
852struct buffer_outputs_storage<T>
853{
854 std::pair<const score::gfx::Port*, MaybeOwnedBuffer>
855 m_buffers[avnd::buffer_output_introspection<T>::size];
856
857 QRhiResourceUpdateBatch* currentResourceUpdateBatch{};
858
859 template <typename Field, std::size_t N, std::size_t NField>
860 requires avnd::cpu_buffer<std::decay_t<decltype(Field::buffer)>>
861 void createOutput(
862 score::gfx::RenderList& renderer, auto& parent, Field& port,
863 avnd::predicate_index<N> np, avnd::field_index<NField> nf)
864 {
865 auto& [gfx_port, buf] = m_buffers[N];
866 gfx_port = parent.output[nf];
867 buf.handle = renderer.state.rhi->newBuffer(
868 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer, 1);
869 buf.handle->setName(oscr::getUtf8Name<T>() + "::" + oscr::getUtf8Name(port));
870 buf.byte_offset = 0;
871 buf.byte_size = 1;
872 buf.owned = true;
873
874 buf.handle->create();
875
876 port.buffer.upload
877 = [this, &renderer, &port](const char* data, int64_t offset, int64_t bytesize) {
878 // FIXME is offset and bytesize relative to the input or the output data ?
879 SCORE_ASSERT(currentResourceUpdateBatch);
880 auto& [gfx_port, buf] = m_buffers[N];
881
882 if(!buf.handle)
883 {
884 if(bytesize > 0)
885 {
886 buf.handle = renderer.state.rhi->newBuffer(
887 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
888 bytesize);
889 buf.handle->setName(oscr::getUtf8Name<T>() + "::" + oscr::getUtf8Name(port));
890 buf.byte_offset = 0;
891 buf.byte_size = bytesize;
892 buf.owned = true;
893
894 buf.handle->create();
895 }
896 else
897 {
898 buf.handle = renderer.state.rhi->newBuffer(
899 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
900 1);
901 buf.handle->setName(oscr::getUtf8Name<T>() + "::" + oscr::getUtf8Name(port));
902 buf.byte_offset = 0;
903 buf.byte_size = 1;
904 buf.owned = true;
905
906 buf.handle->create();
907 return;
908 }
909 }
910 else if(buf.handle->size() != bytesize)
911 {
912 buf.handle->destroy();
913 buf.handle->setSize(bytesize);
914 buf.handle->create();
915 buf.byte_size = bytesize;
916 }
917
919 currentResourceUpdateBatch, buf.handle, offset, bytesize, data);
920 };
921 }
922
923 template <typename Field, std::size_t N, std::size_t NField>
924 requires avnd::gpu_buffer<std::decay_t<decltype(Field::buffer)>>
925 void createOutput(
926 score::gfx::RenderList& renderer, auto& parent, Field& port,
927 avnd::predicate_index<N> np, avnd::field_index<NField> nf)
928 {
929 auto& [gfx_port, buf] = m_buffers[N];
930 gfx_port = parent.output[nf];
931 buf.handle = reinterpret_cast<QRhiBuffer*>(port.buffer.handle);
932 buf.byte_size = port.buffer.byte_size;
933 buf.byte_offset = port.buffer.byte_offset;
934 buf.owned = false;
935 }
936
937 void init(score::gfx::RenderList& renderer, auto& state, auto& parent)
938 {
939 // Init buffers for the outputs
940 avnd::buffer_output_introspection<T>::for_all_n2(
941 avnd::get_outputs<T>(state), [&]<typename Field, std::size_t N, std::size_t NField>
942 (Field& port, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
943 SCORE_ASSERT(parent.output.size() > nf);
944 SCORE_ASSERT(parent.output[nf]->type == score::gfx::Types::Buffer);
945 using buffer_type = std::decay_t<decltype(port.buffer)>;
946
947 if constexpr(avnd::cpu_raw_buffer<buffer_type> && requires {
948 port.buffer.upload(nullptr, 0, 0);
949 })
950 {
951 createOutput(renderer, parent, port, np, nf);
952 }
953 else if constexpr(avnd::gpu_buffer<buffer_type>)
954 {
955 createOutput(renderer, parent, port, np, nf);
956 }
957 else
958 {
959 // m_buffers[N] = createOutput(renderer, *parent.output[nf], port.buffer);
960 static_assert(std::is_same_v<T, void>, "unsupported");
961 }
962 });
963 }
964
965 void prepareUpload(QRhiResourceUpdateBatch& res)
966 {
967 currentResourceUpdateBatch = &res;
968 }
969
970 void upload(score::gfx::RenderList& renderer, auto& state, QRhiResourceUpdateBatch& res)
971 {
972 avnd::buffer_output_introspection<T>::for_all_n(
973 avnd::get_outputs<T>(state), [&]<std::size_t N>(auto& t, avnd::predicate_index<N> idx) {
974 auto& [port, buf] = m_buffers[N];
975 uploadOutputBuffer(renderer, t.buffer, res, buf);
976 });
977 }
978
979 void release(score::gfx::RenderList& renderer)
980 {
981 // Free outputs
982 for(auto& [p, buf] : m_buffers)
983 {
984 if(buf.owned)
985 renderer.releaseBuffer(buf.handle);
986 buf.handle = nullptr;
987 buf.owned = false;
988 }
989 }
990};
991
992template<typename T>
993 requires (avnd::buffer_output_introspection<T>::size == 0)
994struct buffer_outputs_storage<T>
995{
996 static void init(auto&&...)
997 {
998
999 }
1000
1001 static void prepareUpload(auto&&...)
1002 {
1003 }
1004
1005 static void upload(auto&&...)
1006 {
1007 }
1008
1009 static void release(auto&&...)
1010 {
1011 }
1012};
1013
1014
1015template <typename Tex>
1016static auto
1017createOutputTexture(score::gfx::RenderList& renderer, const Tex& texture_spec, QSize size)
1018{
1019 auto& rhi = *renderer.state.rhi;
1020 QRhiTexture* texture = &renderer.emptyTexture();
1021 if(size.width() > 0 && size.height() > 0)
1022 {
1023 texture = rhi.newTexture(
1024 gpp::qrhi::textureFormat(texture_spec), size, 1, QRhiTexture::Flag{});
1025
1026 texture->create();
1027 }
1028
1029 auto sampler = rhi.newSampler(
1030 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
1031 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
1032
1033 sampler->create();
1034 return score::gfx::Sampler{sampler, texture};
1035}
1036
1037
1038template<typename T>
1039struct texture_inputs_storage;
1040
1041template<typename T>
1042 requires (avnd::texture_input_introspection<T>::size > 0)
1043struct texture_inputs_storage<T>
1044{
1045 ossia::small_flat_map<const score::gfx::Port*, score::gfx::TextureRenderTarget, 2>
1046 m_rts;
1047
1048 QRhiReadbackResult m_readbacks[avnd::texture_input_introspection<T>::size];
1049
1050 template <typename Tex>
1051 QRhiTexture* createInput(
1052 score::gfx::RenderList& renderer, score::gfx::Port* port, Tex& texture_spec,
1054 {
1055 static constexpr auto flags
1056 = QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource;
1057 QRhiTexture::Format fmt{};
1058 if constexpr(requires (Tex tex) { tex.format = {}; } && !requires (Tex tex) { tex.request_format; })
1059 {
1060 // Format freely assignable: we use what the user sets in the GUI
1061 fmt = spec.format;
1062 gpp::qrhi::toTextureFormat(fmt, texture_spec);
1063 }
1064 else
1065 {
1066 fmt = gpp::qrhi::textureFormat(texture_spec);
1067 }
1068
1069 QRhiTexture* texture = renderer.state.rhi->newTexture(
1070 fmt, spec.size, 1, flags);
1071
1072 SCORE_ASSERT(texture->create());
1073 m_rts[port] = score::gfx::createRenderTarget(
1074 renderer.state, texture, renderer.samples(), renderer.requiresDepth(*port));
1075 return texture;
1076 }
1077
1078 void init(auto& self, score::gfx::RenderList& renderer)
1079 {
1080 // Init input render targets
1081 avnd::texture_input_introspection<T>::for_all_n2(
1082 avnd::get_inputs<T>(*self.state),
1083 [&]<typename F, std::size_t K, std::size_t N>(F& t, avnd::predicate_index<K>, avnd::field_index<N>) {
1084 auto& parent = self.node();
1085 auto spec = parent.resolveRenderTargetSpecs(N, renderer);
1086 if constexpr(requires {
1087 t.request_width;
1088 t.request_height;
1089 })
1090 {
1091 spec.size.rwidth() = t.request_width;
1092 spec.size.rheight() = t.request_height;
1093 }
1094
1095 auto tex = createInput(renderer, parent.input[N], t.texture, spec);
1096 if constexpr(avnd::cpu_texture_port<F>)
1097 {
1098 t.texture.width = spec.size.width();
1099 t.texture.height = spec.size.height();
1100 }
1101 else if constexpr(avnd::gpu_texture_port<F>)
1102 {
1103 t.texture.handle = tex;
1104 t.texture.width = spec.size.width();
1105 t.texture.height = spec.size.height();
1106 }
1107 });
1108 }
1109
1110 bool update(auto& self,
1111 score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res)
1112 {
1113#if 0
1114 bool need_update = false;
1115 avnd::texture_input_introspection<T>::for_all_n2(
1116 avnd::get_inputs<T>(*self.state),
1117 [&]<typename F, std::size_t K, std::size_t N>(F& t, avnd::predicate_index<K>, avnd::field_index<N>) {
1118 if constexpr(requires {
1119 t.request_width;
1120 t.request_height;
1121 })
1122 {
1123 auto& parent = self.node();
1124 auto port = parent.input[N];
1125 const score::gfx::TextureRenderTarget& texture = m_rts[port];
1126 QSizeF sz{};
1127 if(texture.texture)
1128 sz = texture.texture->pixelSize();
1129 if(sz.width() != t.request_width || sz.height() != t.request_height)
1130 {
1131 // FIXME right now this doesn't work because
1132 // the render target spec is stored in the node.
1133 // Also the RenderList just recomputes everything anyways,
1134 // so we should just emit a "need to change" signal and abort as
1135 // long as things aren't more optimized and actually follow the graph
1136
1137 // m_rts[port].release();
1138
1139 // auto spec = parent.resolveRenderTargetSpecs(N, renderer);
1140 // spec.size.rwidth() = t.request_width;
1141 // spec.size.rheight() = t.request_height;
1142
1143 // createInput(renderer, port, t.texture, sz);
1144
1145 // t.texture.width = spec.size.width();
1146 // t.texture.height = spec.size.height();
1147 // need_update = true;
1148 //
1149 }
1150 }
1151 });
1152 return need_update;
1153#endif
1154 return false;
1155 }
1156
1157 void runInitialPasses(auto& self, QRhi& rhi)
1158 {
1159 // Fetch input textures (if any)
1160 // Copy the readback output inside the structure
1161 // TODO it would be much better to do this inside the readback's
1162 // "completed" callback.
1163 if constexpr(avnd::cpu_texture_input_introspection<T>::size > 0)
1164 {
1165 avnd::texture_input_introspection<T>::for_all_n(
1166 avnd::get_inputs<T>(*self.state), [&]<typename F, std::size_t K>(F& t, avnd::predicate_index<K>) {
1167 if constexpr(avnd::cpu_texture_port<F>)
1168 {
1169 oscr::loadInputTexture(rhi, m_readbacks, t.texture, K);
1170 }
1171 });
1172 }
1173 }
1174
1175 void release()
1176 {
1177 // Free inputs
1178 // TODO investigate why reference does not work here:
1179 for(auto [port, rt] : m_rts)
1180 rt.release();
1181 m_rts.clear();
1182 }
1183
1184 void inputAboutToFinish(auto& parent, const score::gfx::Port& p, QRhiResourceUpdateBatch*& res)
1185 {
1186 if constexpr(avnd::cpu_texture_input_introspection<T>::size > 0)
1187 {
1188 const auto& inputs = parent.input;
1189 auto index_of_port = ossia::find(inputs, &p) - inputs.begin();
1190 {
1191 auto tex = m_rts[&p].texture;
1192 auto& readback = m_readbacks[index_of_port];
1193 readback = {};
1194 res->readBackTexture(QRhiReadbackDescription{tex}, &readback);
1195 }
1196 }
1197 }
1198
1199};
1200template<typename T>
1201 requires (avnd::texture_input_introspection<T>::size == 0)
1202struct texture_inputs_storage<T>
1203{
1204 static void init(auto&&...) { }
1205 static void runInitialPasses(auto&&...) { }
1206 static void release(auto&&...) { }
1207 static void inputAboutToFinish(auto&&...) { }
1208};
1209
1210
1211
1212template <avnd::cpu_texture Tex>
1213static QRhiTexture* updateTexture(auto& self, score::gfx::RenderList& renderer, int k, const Tex& cpu_tex)
1214{
1215 auto& [sampler, texture] = self.m_samplers[k];
1216 if(texture)
1217 {
1218 auto sz = texture->pixelSize();
1219 if(cpu_tex.width == sz.width() && cpu_tex.height == sz.height())
1220 return texture;
1221 }
1222
1223 // Check the texture size
1224 if(cpu_tex.width > 0 && cpu_tex.height > 0)
1225 {
1226 QRhiTexture* oldtex = texture;
1227 QRhiTexture* newtex = renderer.state.rhi->newTexture(
1228 gpp::qrhi::textureFormat(cpu_tex), QSize{cpu_tex.width, cpu_tex.height}, 1,
1229 QRhiTexture::Flag{});
1230 newtex->create();
1231 for(auto& [edge, pass] : self.m_p)
1232 if(pass.srb)
1233 score::gfx::replaceTexture(*pass.srb, sampler, newtex);
1234 texture = newtex;
1235
1236 if(oldtex && oldtex != &renderer.emptyTexture())
1237 {
1238 oldtex->deleteLater();
1239 }
1240
1241 return newtex;
1242 }
1243 else
1244 {
1245 for(auto& [edge, pass] : self.m_p)
1246 if(pass.srb)
1247 score::gfx::replaceTexture(*pass.srb, sampler, &renderer.emptyTexture());
1248
1249 return &renderer.emptyTexture();
1250 }
1251}
1252
1253template <avnd::cpu_texture Tex>
1254static void uploadOutputTexture(auto& self,
1255 score::gfx::RenderList& renderer, int k, Tex& cpu_tex,
1256 QRhiResourceUpdateBatch* res)
1257{
1258 if(cpu_tex.changed)
1259 {
1260 if(auto texture = updateTexture(self, renderer, k, cpu_tex))
1261 {
1262 if(!cpu_tex.bytes || cpu_tex.bytesize() <= 0)
1263 {
1264 cpu_tex.changed = false;
1265 return;
1266 }
1267
1268 QByteArray buf
1269 = QByteArray::fromRawData((const char*)cpu_tex.bytes, cpu_tex.bytesize());
1270 if constexpr(requires { Tex::RGB; })
1271 {
1272 // RGB -> RGBA
1273 // FIXME other conversions
1274 const QByteArray rgb = buf;
1275 QByteArray rgba;
1276 rgba.resize(cpu_tex.width * cpu_tex.height * 4);
1277 auto src = (const unsigned char*)rgb.constData();
1278 auto dst = (unsigned char*)rgba.data();
1279 for(int rgb_byte = 0, rgba_byte = 0, N = rgb.size(); rgb_byte < N;)
1280 {
1281 dst[rgba_byte + 0] = src[rgb_byte + 0];
1282 dst[rgba_byte + 1] = src[rgb_byte + 1];
1283 dst[rgba_byte + 2] = src[rgb_byte + 2];
1284 dst[rgba_byte + 3] = 255;
1285 rgb_byte += 3;
1286 rgba_byte += 4;
1287 }
1288 buf = rgba;
1289 }
1290
1291 // Upload it (mirroring is done in shader generic_texgen_fs if necessary)
1292 {
1293 QRhiTextureSubresourceUploadDescription sd(buf);
1294 QRhiTextureUploadDescription desc{QRhiTextureUploadEntry{0, 0, sd}};
1295
1296 res->uploadTexture(texture, desc);
1297 }
1298
1299 cpu_tex.changed = false;
1300 }
1301 }
1302}
1303
1304static const constexpr auto generic_texgen_vs = R"_(#version 450
1305layout(location = 0) in vec2 position;
1306layout(location = 1) in vec2 texcoord;
1307
1308layout(binding=3) uniform sampler2D y_tex;
1309layout(location = 0) out vec2 v_texcoord;
1310
1311layout(std140, binding = 0) uniform renderer_t {
1312 mat4 clipSpaceCorrMatrix;
1313 vec2 renderSize;
1314} renderer;
1315
1316out gl_PerVertex { vec4 gl_Position; };
1317
1318void main()
1319{
1320#if defined(QSHADER_SPIRV) || defined(QSHADER_GLSL)
1321 v_texcoord = vec2(texcoord.x, 1. - texcoord.y);
1322#else
1323 v_texcoord = texcoord;
1324#endif
1325 gl_Position = renderer.clipSpaceCorrMatrix * vec4(position.xy, 0.0, 1.);
1326}
1327)_";
1328
1329static const constexpr auto generic_texgen_fs = R"_(#version 450
1330layout(location = 0) in vec2 v_texcoord;
1331layout(location = 0) out vec4 fragColor;
1332
1333layout(std140, binding = 0) uniform renderer_t {
1334mat4 clipSpaceCorrMatrix;
1335vec2 renderSize;
1336} renderer;
1337
1338layout(binding=3) uniform sampler2D y_tex;
1339
1340void main ()
1341{
1342 fragColor = texture(y_tex, v_texcoord);
1343}
1344)_";
1345
1346template<typename T>
1347struct texture_outputs_storage;
1348
1349// If we have texture outs we need the whole rendering infrastructure
1350template<typename T>
1351 requires (avnd::texture_output_introspection<T>::size > 0)
1352struct texture_outputs_storage<T>
1353{
1354 void init(auto& self, score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res)
1355 {
1356 const auto& mesh = renderer.defaultTriangle();
1357 self.defaultMeshInit(renderer, mesh, res);
1358 self.processUBOInit(renderer);
1359 // Not needed here as we do not have a GPU pass:
1360 // this->m_material.init(renderer, this->node.input, this->m_samplers);
1361
1362 std::tie(self.m_vertexS, self.m_fragmentS)
1363 = score::gfx::makeShaders(renderer.state, generic_texgen_vs, generic_texgen_fs);
1364
1365 avnd::cpu_texture_output_introspection<T>::for_all(
1366 avnd::get_outputs<T>(*self.state), [&](auto& t) {
1367 self.m_samplers.push_back(
1368 createOutputTexture(renderer, t.texture, QSize{t.texture.width, t.texture.height}));
1369 });
1370
1371 self.defaultPassesInit(renderer, mesh);
1372 }
1373
1374 void runInitialPasses(auto& self,
1375 score::gfx::RenderList& renderer,
1376 QRhiResourceUpdateBatch*& res)
1377 {
1378 avnd::cpu_texture_output_introspection<T>::for_all_n(
1379 avnd::get_outputs<T>(*self.state), [&]<std::size_t N>(auto& t, avnd::predicate_index<N>) {
1380 uploadOutputTexture(self, renderer, N, t.texture, res);
1381 });
1382 }
1383
1384 void release(auto& self, score::gfx::RenderList& r)
1385 {
1386 // Free outputs
1387 for(auto& [sampl, texture] : self.m_samplers)
1388 {
1389 if(texture != &r.emptyTexture())
1390 texture->deleteLater();
1391 texture = nullptr;
1392 }
1393 }
1394
1395};
1396
1397template<typename T>
1398 requires (avnd::texture_output_introspection<T>::size == 0)
1399struct texture_outputs_storage<T>
1400{
1401 static void init(auto& self, score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res)
1402 {
1403 }
1404
1405 static void runInitialPasses(auto& self,
1406 score::gfx::RenderList& renderer,
1407 QRhiResourceUpdateBatch*& res)
1408 {
1409 }
1410
1411 static void release(auto& self, score::gfx::RenderList& r)
1412 {
1413 }
1414};
1415template<typename T>
1416struct geometry_outputs_storage;
1417
1418template<typename T>
1419 requires (avnd::geometry_output_introspection<T>::size > 0)
1420struct geometry_outputs_storage<T>
1421{
1422 ossia::geometry_spec specs[avnd::geometry_output_introspection<T>::size];
1423
1424 template <avnd::geometry_port Field>
1425 void reload_mesh(Field& ctrl, ossia::geometry_spec& spc)
1426 {
1427 spc.meshes = std::make_shared<ossia::mesh_list>();
1428 auto& ossia_meshes = *spc.meshes;
1429 if constexpr(avnd::static_geometry_type<Field> || avnd::dynamic_geometry_type<Field>)
1430 {
1431 ossia_meshes.meshes.resize(1);
1432 load_geometry(ctrl, ossia_meshes.meshes[0]);
1433 }
1434 else if constexpr(
1435 avnd::static_geometry_type<decltype(Field::mesh)>
1436 || avnd::dynamic_geometry_type<decltype(Field::mesh)>)
1437 {
1438 ossia_meshes.meshes.resize(1);
1439 load_geometry(ctrl.mesh, ossia_meshes.meshes[0]);
1440 }
1441 else
1442 {
1443 load_geometry(ctrl, ossia_meshes);
1444 }
1445 }
1446
1447 template <avnd::geometry_port Field, std::size_t N>
1448 void upload(
1449 score::gfx::RenderList& renderer, Field& ctrl, score::gfx::Edge& edge,
1450 avnd::predicate_index<N>)
1451 {
1452 auto edge_sink = edge.sink;
1453 if(auto pnode = edge_sink->node)
1454 {
1455 ossia::geometry_spec& spc = specs[N];
1456
1457 // 1. Reload mesh
1458 {
1459 if(ctrl.dirty_mesh)
1460 {
1461 reload_mesh(ctrl, spc);
1462 }
1463 else
1464 {
1465 if(spc.meshes)
1466 {
1467 auto& ossia_meshes = *spc.meshes;
1468
1469 bool any_need_reload = false;
1470 bool any_need_upload = false;
1471 if constexpr(avnd::static_geometry_type<Field> || avnd::dynamic_geometry_type<Field>)
1472 {
1473 SCORE_ASSERT(ossia_meshes.meshes.size() == 1);
1474 auto [need_reload, need_upload]
1475 = update_geometry(ctrl, ossia_meshes.meshes[0]);
1476 any_need_reload = need_reload;
1477 any_need_upload = need_upload;
1478 }
1479 else if constexpr(
1480 avnd::static_geometry_type<decltype(Field::mesh)>
1481 || avnd::dynamic_geometry_type<decltype(Field::mesh)>)
1482 {
1483 SCORE_ASSERT(ossia_meshes.meshes.size() == 1);
1484 auto [need_reload, need_upload]
1485 = update_geometry(ctrl.mesh, ossia_meshes.meshes[0]);
1486 any_need_reload = need_reload;
1487 any_need_upload = need_upload;
1488 }
1489 else
1490 {
1491 auto [need_reload, need_upload] = update_geometry(ctrl, ossia_meshes);
1492 any_need_reload = need_reload;
1493 any_need_upload = need_upload;
1494 }
1495
1496 if(any_need_reload)
1497 {
1498 reload_mesh(ctrl, spc);
1499 }
1500 }
1501 }
1502 ctrl.dirty_mesh = false;
1503 }
1504
1505 // 2. Push to next node
1506 // FIXME this should be for the renderer of edge, not the node, since
1507 // geometries can have gpu buffers
1508 auto rendered_node = pnode->renderedNodes.find(&renderer);
1509 SCORE_ASSERT(rendered_node != pnode->renderedNodes.end());
1510
1511 auto it = std::find(
1512 edge_sink->node->input.begin(), edge_sink->node->input.end(), edge_sink);
1513 SCORE_ASSERT(it != edge_sink->node->input.end());
1514 int n = it - edge_sink->node->input.begin();
1515
1516 rendered_node->second->process(n, spc);
1517
1518 // 3. Same for transform3d
1519
1520 if constexpr(requires { ctrl.transform; })
1521 {
1522 if(ctrl.dirty_transform)
1523 {
1524 ossia::transform3d transform;
1525 std::copy_n(ctrl.transform, std::ssize(ctrl.transform), transform.matrix);
1526 ctrl.dirty_transform = false;
1527
1528 rendered_node->second->process(n, transform);
1529 if(auto pnode = dynamic_cast<score::gfx::ProcessNode*>(edge_sink->node))
1530 pnode->process(n, transform);
1531 }
1532 }
1533 }
1534 }
1535
1536 void upload(score::gfx::RenderList& renderer, auto& state, score::gfx::Edge& edge)
1537 {
1538 // FIXME we need something such as port_run_{pre,post}process for GPU nodes
1539 avnd::geometry_output_introspection<T>::for_all_n(
1540 avnd::get_outputs(state),
1541 [&](auto& field, auto pred) { this->upload(renderer, field, edge, pred); });
1542 }
1543};
1544
1545
1546template<typename T>
1547 requires (avnd::geometry_output_introspection<T>::size == 0)
1548struct geometry_outputs_storage<T>
1549{
1550 static void upload(auto&&...)
1551 {
1552
1553 }
1554};
1555}
1556
1557#endif
Root data model for visual nodes.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:74
std::vector< Port * > input
Input ports of that node.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:103
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:109
Common base class for most single-pass, simple nodes.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:203
Renderer for a given node.
Definition NodeRenderer.hpp:11
Base class for sink nodes (QWindow, spout, syphon, NDI output, ...)
Definition OutputNode.hpp:31
Common base class for nodes that map to score processes.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:176
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:467
const score::gfx::Mesh & defaultTriangle() const noexcept
A triangle mesh correct for this API.
Definition RenderList.cpp:506
RenderState & state
RenderState corresponding to this RenderList.
Definition RenderList.hpp:102
QRhiTexture & emptyTexture() const noexcept
Texture to use when a texture is missing.
Definition RenderList.hpp:125
TreeNode< DeviceExplorerNode > Node
Definition DeviceNode.hpp:74
Definition Factories.hpp:19
TextureRenderTarget createRenderTarget(const RenderState &state, QRhiTexture *tex, int samples, bool depth, bool samplableDepth)
Create a render target from a texture.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:16
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:409
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:652
Base toolkit upon which the software is built.
Definition Application.cpp:113
STL namespace.
Definition DocumentContext.hpp:18
Definition Mesh.hpp:15
Connection between two score::gfx::Port.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:78
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:49
Definition OutputNode.hpp:11
Port of a score::gfx::Node.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:57
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:56
Stores a sampler and the texture currently associated with it.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:30
Useful abstraction for storing all the data related to a render target.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:125