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