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