3#include <avnd/introspection/gfx.hpp>
5#include <Process/ExecutionContext.hpp>
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>
19#include <score/tools/ThreadPool.hpp>
21#include <ossia/detail/small_flat_map.hpp>
23#include <ossia-qt/invoke.hpp>
25#include <QCoreApplication>
27#include <QtGui/private/qrhi_p.h>
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>
39#include <score_plugin_avnd_export.h>
46 void initWorker(
this auto& self, std::shared_ptr<T>& state)
noexcept
48 if constexpr(avnd::has_worker<T>)
50 auto ptr = QPointer{&self};
51 auto& tq = score::TaskPool::instance();
52 using worker_type =
decltype(state->worker);
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>)
60 worker_type::work(std::forward<
decltype(ff)>(ff)...);
66 auto res = worker_type::work(std::forward<
decltype(ff)>(ff)...);
71 QCoreApplication::instance(),
72 [res = std::move(res), wk_state, ptr]()
mutable {
74 if(
auto state = wk_state.lock())
84template <
typename GpuNodeRenderer,
typename Node>
93 bool can_process_message(std::size_t N)
95 if(mess.input.size() <= N)
98 if(prev_mess.input.size() == mess.input.size())
100 auto& prev = prev_mess.input[N];
101 auto& next = mess.input[N];
102 if(prev.index() == 1 && next.index() == 1)
104 if(ossia::get<ossia::value>(prev) == ossia::get<ossia::value>(next))
113 void operator()(avnd::parameter_port
auto& t,
auto field_index)
115 if(!can_process_message(field_index))
118 if(
auto val = ossia::get_if<ossia::value>(&mess.input[field_index]))
120 oscr::from_ossia_value(t, *val, t.value);
121 if_possible(t.update(state));
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)
130 using node_type = std::remove_cvref_t<
decltype(gpu.node())>;
131 using file_ports = avnd::raw_file_input_introspection<Node>;
133 if(!can_process_message(field_index))
136 auto val = ossia::get_if<ossia::value>(&mess.input[field_index]);
140 static constexpr bool has_text =
requires {
decltype(Field::file)::text; };
141 static constexpr bool has_mmap =
requires {
decltype(Field::file)::mmap; };
144 if(
auto hdl = loadRawfile(*val, ctx, has_text, has_mmap))
146 static constexpr auto N = file_ports::field_index_to_index(NField);
147 if constexpr(avnd::port_can_process<Field>)
151 auto func = executePortPreprocess<Field>(*hdl);
152 const_cast<node_type&
>(gpu.node())
154 state, hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
160 const_cast<node_type&
>(gpu.node())
162 state, hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
168 template <avnd::buffer_port Field, std::
size_t NField>
169 void operator()(Field& t, avnd::field_index<NField> field_index)
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]);
176 node.process(NField, *val);
179 template <avnd::texture_port Field, std::
size_t NField>
180 void operator()(Field& t, avnd::field_index<NField> field_index)
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]);
187 node.process(NField, *val);
190 template <avnd::geometry_port Field, std::
size_t NField>
191 void operator()(Field& t, avnd::field_index<NField> field_index)
193 using node_type = std::remove_cvref_t<
decltype(gpu.node())>;
194 auto& node =
const_cast<node_type&
>(gpu.node());
199 void operator()(
auto& t,
auto field_index) =
delete;
204 template <
typename Self,
typename Node_T>
205 static void processControlIn(
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;
219 std::weak_ptr<Execution::ExecutionCommandQueue> queue;
220 Gfx::exec_controls control_outs;
224 template <
typename Node_T>
225 void processControlOut(Node_T& state)
const noexcept
227 if(!this->control_outs.empty())
229 auto q = this->queue.lock();
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;
249struct SCORE_PLUGIN_AVND_EXPORT GpuNodeElements
251 [[no_unique_address]] oscr::soundfile_storage<T> soundfiles;
253 [[no_unique_address]] oscr::midifile_storage<T> midifiles;
255#if defined(OSCR_HAS_MMAP_FILE_STORAGE)
256 [[no_unique_address]] oscr::raw_file_storage<T> rawfiles;
259 template <std::
size_t N, std::
size_t NField>
261 auto& state,
const std::shared_ptr<oscr::raw_file_data>& hdl,
262 avnd::predicate_index<N>, avnd::field_index<NField>)
265 state, hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
272 :
score::gfx::NodeModel{}
276 virtual ~CustomGfxNodeBase();
284 virtual ~CustomGfxOutputNodeBase();
289struct CustomGpuNodeBase
296 std::weak_ptr<Execution::ExecutionCommandQueue>&& q, Gfx::exec_controls&& ctls,
298 : GpuControlOuts{
std::move(q),
std::move(ctls)}
303 virtual ~CustomGpuNodeBase() =
default;
306 QString vertex, fragment, compute;
311struct SCORE_PLUGIN_AVND_EXPORT CustomGpuOutputNodeBase
317 CustomGpuOutputNodeBase(
318 std::weak_ptr<Execution::ExecutionCommandQueue> q, Gfx::exec_controls&& ctls,
320 virtual ~CustomGpuOutputNodeBase();
323 std::weak_ptr<score::gfx::RenderList> m_renderer{};
324 std::shared_ptr<score::gfx::RenderState> m_renderState{};
326 QString vertex, fragment, compute;
331 void setRenderer(std::shared_ptr<score::gfx::RenderList>)
override;
334 void startRendering()
override;
335 void render()
override;
336 void stopRendering()
override;
337 bool canRender()
const override;
338 void onRendererChange()
override;
342 void destroyOutput()
override;
343 std::shared_ptr<score::gfx::RenderState> renderState()
const override;
345 Configuration configuration() const noexcept override;
348template <typename Node_T, typename Node>
349void prepareNewState(
std::shared_ptr<Node_T>& eff, const Node& parent)
351 if constexpr(avnd::has_worker<Node_T>)
353 parent.initWorker(eff);
355 if constexpr(avnd::has_processor_to_gui_bus<Node_T>)
357 auto& process = parent.processModel;
358 eff->send_message = [ptr = QPointer{&process}](
auto&& b)
mutable {
362 if(ptr && ptr->to_ui)
363 MessageBusSender{ptr->to_ui}(std::move(b));
370 avnd::init_controls(*eff);
372 if constexpr(avnd::can_prepare<Node_T>)
374 if constexpr(avnd::function_reflection<&Node_T::prepare>::count == 1)
376 using prepare_type = avnd::first_argument<&Node_T::prepare>;
378 if_possible(t.instance = parent.instance);
388struct port_to_type_enum
390 template <std::
size_t I, avnd::buffer_port F>
391 constexpr auto operator()(avnd::field_reflection<I, F> p)
393 return score::gfx::Types::Buffer;
396 template <std::
size_t I, avnd::cpu_texture_port F>
397 constexpr auto operator()(avnd::field_reflection<I, F> p)
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;
405 template <std::
size_t I, avnd::gpu_texture_port F>
406 constexpr auto operator()(avnd::field_reflection<I, F> p)
408 return score::gfx::Types::Image;
411 template <std::
size_t I, avnd::sampler_port F>
412 constexpr auto operator()(avnd::field_reflection<I, F> p)
414 return score::gfx::Types::Image;
416 template <std::
size_t I, avnd::image_port F>
417 constexpr auto operator()(avnd::field_reflection<I, F> p)
419 return score::gfx::Types::Image;
421 template <std::
size_t I, avnd::attachment_port F>
422 constexpr auto operator()(avnd::field_reflection<I, F> p)
424 return score::gfx::Types::Image;
427 template <std::
size_t I, avnd::geometry_port F>
428 constexpr auto operator()(avnd::field_reflection<I, F> p)
430 return score::gfx::Types::Geometry;
432 template <std::
size_t I, avnd::mono_audio_port F>
433 constexpr auto operator()(avnd::field_reflection<I, F> p)
435 return score::gfx::Types::Audio;
437 template <std::
size_t I, avnd::poly_audio_port F>
438 constexpr auto operator()(avnd::field_reflection<I, F> p)
440 return score::gfx::Types::Audio;
442 template <std::
size_t I, avnd::
int_parameter F>
443 constexpr auto operator()(avnd::field_reflection<I, F> p)
445 return score::gfx::Types::Int;
447 template <std::
size_t I, avnd::enum_parameter F>
448 constexpr auto operator()(avnd::field_reflection<I, F> p)
450 return score::gfx::Types::Int;
452 template <std::
size_t I, avnd::
float_parameter F>
453 constexpr auto operator()(avnd::field_reflection<I, F> p)
455 return score::gfx::Types::Float;
457 template <std::
size_t I, avnd::parameter_port F>
458 constexpr auto operator()(avnd::field_reflection<I, F> p)
460 using value_type = std::remove_cvref_t<
decltype(F::value)>;
462 if constexpr(std::is_aggregate_v<value_type>)
464 constexpr int sz = boost::pfr::tuple_size_v<value_type>;
465 if constexpr(sz == 2)
467 return score::gfx::Types::Vec2;
469 else if constexpr(sz == 3)
471 return score::gfx::Types::Vec3;
473 else if constexpr(sz == 4)
475 return score::gfx::Types::Vec4;
478 return score::gfx::Types::Empty;
480 template <std::
size_t I,
typename F>
481 constexpr auto operator()(avnd::field_reflection<I, F> p)
483 return score::gfx::Types::Empty;
487template <
typename Node_T>
488inline void initGfxPorts(
auto* self,
auto& input,
auto& output)
490 avnd::input_introspection<Node_T>::for_all(
491 [self, &input]<
typename Field, std::size_t I>(avnd::field_reflection<I, Field> f) {
492 static constexpr auto type = port_to_type_enum{}(f);
495 avnd::output_introspection<Node_T>::for_all(
497 &output]<
typename Field, std::size_t I>(avnd::field_reflection<I, Field> f) {
498 static constexpr auto type = port_to_type_enum{}(f);
506 const auto& inputs = parent.
input;
510 for(
auto& edge : p->edges)
512 auto src_node = edge->source->node;
516 return src_renderer->bufferForOutput(*edge->source);
525static void readbackInputBuffer(
527 , QRhiResourceUpdateBatch& res
529 , QRhiBufferReadbackResult& readback
535 if(
auto buf = getInputBuffer(renderer, parent, port_index))
538 res.readBackBuffer(buf.handle, buf.byte_offset, buf.byte_size, &readback);
542static void recreateOutputBuffer(
546 const auto bytesize = avnd::get_bytesize(cpu_buf);
551 buf.handle = renderer.
state.rhi->newBuffer(
552 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
554 buf.handle->setName(
"GpuUtils::recreateOutputBuffer");
556 buf.byte_size = bytesize;
558 buf.handle->create();
562 cpu_buf.changed =
false;
566 else if(buf.handle->size() != bytesize)
568 buf.handle->destroy();
569 buf.handle->setSize(bytesize);
570 buf.handle->create();
571 buf.byte_size = bytesize;
575static void uploadOutputBuffer(
581 const auto bytesize = avnd::get_bytesize(cpu_buf);
582 recreateOutputBuffer(renderer, cpu_buf, res, rhi_buf);
584 &res, rhi_buf.handle, 0, cpu_buf.byte_size,
585 (
const char*)avnd::get_bytes(cpu_buf));
586 cpu_buf.changed =
false;
590static void uploadOutputBuffer(
594 rhi_buf.handle =
reinterpret_cast<QRhiBuffer*
>(gpu_buf.handle);
595 rhi_buf.byte_size = gpu_buf.byte_size;
596 rhi_buf.byte_offset = gpu_buf.byte_offset;
600struct geometry_inputs_storage;
602struct mesh_input_storage
604 std::vector<QRhiBufferReadbackResult> readbacks;
605 std::vector<QRhiBuffer*> buffers;
607struct geometry_input_storage
609 ossia::geometry_spec spec;
610 std::vector<mesh_input_storage> meshes;
614 requires(avnd::geometry_input_introspection<T>::size > 0)
615struct geometry_inputs_storage<T>
618 static_assert(avnd::geometry_input_introspection<T>::size == 1);
620 geometry_input_storage inputs[avnd::geometry_input_introspection<T>::size];
621 ossia::small_vector<QRhiBuffer*, 4> allocated;
623 void readInputGeometries(
630 avnd::geometry_input_introspection<T>::for_all_n(
631 avnd::get_inputs<T>(state),
632 [&]<
typename Field, std::size_t N>(Field& t, avnd::predicate_index<N> np) {
633 this->inputs[N].spec = spec;
634 this->inputs[N].meshes.resize(1);
637 auto& meshes = this->inputs[N].meshes[0];
639 oscr::meshes_from_ossia(
641 [&](
auto& write_buf,
int buffer_index,
void* data, int64_t bytesize) {
643 SCORE_ASSERT(buffer_index >= 0);
644 if(buffer_index < meshes.readbacks.size())
646 QRhiBuffer* handle = meshes.buffers[buffer_index];
647 write_buf.handle = handle;
648 write_buf.byte_size = handle->size();
650 }, [&](
auto& write_buf,
int buffer_index,
void* handle) {
652 SCORE_ASSERT(buffer_index >= 0);
653 if(buffer_index < meshes.readbacks.size())
656 auto& readback = meshes.readbacks[buffer_index].data;
657 write_buf.raw_data =
reinterpret_cast<unsigned char*
>(readback.data());
658 write_buf.byte_size = readback.size();
664 void inputAboutToFinish(
666 const ossia::geometry_spec& spec,
auto& state,
auto& parent)
668 avnd::geometry_input_introspection<T>::for_all_n2(
669 avnd::get_inputs<T>(state),
670 [&]<
typename Field, std::size_t N, std::size_t NField>(
671 Field& t, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
672 this->inputs[N].spec = spec;
673 this->inputs[N].meshes.resize(1);
676 auto& meshes = this->inputs[N].meshes[0];
677 oscr::meshes_from_ossia(
679 [&](
auto& write_buf,
int buffer_index,
void* data, int64_t bytesize) {
681 if(meshes.buffers.size() <= buffer_index)
683 meshes.buffers.resize(buffer_index + 1);
684 meshes.readbacks.resize(buffer_index + 1);
686 auto buf = renderer.state.rhi->newBuffer(
687 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
689 buf->setName(oscr::getUtf8Name<T>() +
"::" + oscr::getUtf8Name(t));
691 allocated.push_back(buf);
692 meshes.buffers[buffer_index] = buf;
695 res->uploadStaticBuffer(meshes.buffers[buffer_index], 0, bytesize, data);
696 }, [&](
auto& write_buf,
int buffer_index,
void* handle) {
698 if(meshes.readbacks.size() <= buffer_index)
700 meshes.buffers.resize(buffer_index + 1);
701 meshes.readbacks.resize(buffer_index + 1);
704 meshes.readbacks[buffer_index] = {};
705 if(
auto buf =
static_cast<QRhiBuffer*
>(handle))
707 meshes.buffers[buffer_index] = buf;
708 res->readBackBuffer(buf, 0, buf->size(), &meshes.readbacks[buffer_index]);
712 meshes.buffers[buffer_index] = {};
713 meshes.readbacks[buffer_index] = {};
721 for(
auto& buf : allocated)
722 renderer.releaseBuffer(buf);
728 requires(avnd::geometry_input_introspection<T>::size == 0)
729struct geometry_inputs_storage<T>
731 static void readInputBuffers(
auto&&...) { }
733 static void inputAboutToFinish(
auto&&...) { }
737struct buffer_inputs_storage;
740 requires (avnd::buffer_input_introspection<T>::size > 0)
741struct buffer_inputs_storage<T>
744 QRhiBufferReadbackResult
745 m_readbacks[avnd::cpu_buffer_input_introspection<T>::size + 1];
748 void readInputBuffers(
751 if constexpr(avnd::cpu_buffer_input_introspection<T>::size > 0)
756 avnd::cpu_buffer_input_introspection<T>::for_all_n(
757 avnd::get_inputs<T>(state),
758 [&]<
typename Field, std::size_t N>
759 (Field& t, avnd::predicate_index<N> np)
761 auto& readback = m_readbacks[N].data;
762 t.buffer.raw_data =
reinterpret_cast<unsigned char*
>(readback.data());
763 t.buffer.byte_size = readback.size();
764 t.buffer.byte_offset = 0;
765 t.buffer.changed =
true;
769 if constexpr(avnd::gpu_buffer_input_introspection<T>::size > 0)
774 avnd::gpu_buffer_input_introspection<T>::for_all_n2(
775 avnd::get_inputs<T>(state),
776 [&]<
typename Field, std::size_t N, std::size_t NField>(
777 Field& t, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
780 buf = getInputBuffer(renderer, parent, nf);
783 t.buffer.handle = buf.handle;
784 t.buffer.byte_size = buf.byte_size;
785 t.buffer.byte_offset = buf.byte_offset;
791 void inputAboutToFinish(
793 QRhiResourceUpdateBatch*& res,
797 avnd::cpu_buffer_input_introspection<T>::for_all_n2(
798 avnd::get_inputs<T>(state),
799 [&]<
typename Field, std::size_t N, std::size_t NField>
800 (Field& port, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
801 readbackInputBuffer(renderer, *res, parent, m_readbacks[N], nf);
803 avnd::gpu_buffer_input_introspection<T>::for_all_n2(
804 avnd::get_inputs<T>(state),
805 [&]<
typename Field, std::size_t N, std::size_t NField>
806 (Field& port, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
807 m_gpubufs[N] = getInputBuffer(renderer, parent, nf);
813 requires (avnd::buffer_input_introspection<T>::size == 0)
814struct buffer_inputs_storage<T>
816 static void readInputBuffers(
auto&&...)
821 static void inputAboutToFinish(
auto&&...)
833struct buffer_outputs_storage;
836 requires (avnd::buffer_output_introspection<T>::size > 0)
837struct buffer_outputs_storage<T>
839 std::pair<const score::gfx::Port*, MaybeOwnedBuffer>
840 m_buffers[avnd::buffer_output_introspection<T>::size];
842 QRhiResourceUpdateBatch* currentResourceUpdateBatch{};
844 template <
typename Field, std::
size_t N, std::
size_t NField>
845 requires avnd::cpu_buffer<std::decay_t<
decltype(Field::buffer)>>
848 avnd::predicate_index<N> np, avnd::field_index<NField> nf)
850 auto& [gfx_port, buf] = m_buffers[N];
851 gfx_port = parent.
output[nf];
852 buf.handle = renderer.
state.rhi->newBuffer(
853 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer, 1);
854 buf.handle->setName(oscr::getUtf8Name<T>() +
"::" + oscr::getUtf8Name(port));
859 buf.handle->create();
862 = [
this, &renderer, &port](
const char* data, int64_t offset, int64_t bytesize) {
864 SCORE_ASSERT(currentResourceUpdateBatch);
865 auto& [gfx_port, buf] = m_buffers[N];
871 buf.handle = renderer.
state.rhi->newBuffer(
872 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
874 buf.handle->setName(oscr::getUtf8Name<T>() +
"::" + oscr::getUtf8Name(port));
876 buf.byte_size = bytesize;
879 buf.handle->create();
883 buf.handle = renderer.
state.rhi->newBuffer(
884 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
886 buf.handle->setName(oscr::getUtf8Name<T>() +
"::" + oscr::getUtf8Name(port));
891 buf.handle->create();
895 else if(buf.handle->size() != bytesize)
897 buf.handle->destroy();
898 buf.handle->setSize(bytesize);
899 buf.handle->create();
900 buf.byte_size = bytesize;
904 currentResourceUpdateBatch, buf.handle, offset, bytesize, data);
908 template <
typename Field, std::
size_t N, std::
size_t NField>
909 requires avnd::gpu_buffer<std::decay_t<
decltype(Field::buffer)>>
912 avnd::predicate_index<N> np, avnd::field_index<NField> nf)
914 auto& [gfx_port, buf] = m_buffers[N];
915 gfx_port = parent.
output[nf];
916 buf.handle =
reinterpret_cast<QRhiBuffer*
>(port.buffer.handle);
917 buf.byte_size = port.buffer.byte_size;
918 buf.byte_offset = port.buffer.byte_offset;
925 avnd::buffer_output_introspection<T>::for_all_n2(
926 avnd::get_outputs<T>(state), [&]<
typename Field, std::size_t N, std::size_t NField>
927 (Field& port, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
928 SCORE_ASSERT(parent.
output.size() > nf);
929 SCORE_ASSERT(parent.
output[nf]->type == score::gfx::Types::Buffer);
930 using buffer_type = std::decay_t<
decltype(port.buffer)>;
932 if constexpr(avnd::cpu_raw_buffer<buffer_type> &&
requires {
933 port.buffer.upload(
nullptr, 0, 0);
936 createOutput(renderer, parent, port, np, nf);
938 else if constexpr(avnd::gpu_buffer<buffer_type>)
940 createOutput(renderer, parent, port, np, nf);
945 static_assert(std::is_same_v<T, void>,
"unsupported");
950 void prepareUpload(QRhiResourceUpdateBatch& res)
952 currentResourceUpdateBatch = &res;
957 avnd::buffer_output_introspection<T>::for_all_n(
958 avnd::get_outputs<T>(state), [&]<std::size_t N>(
auto& t, avnd::predicate_index<N> idx) {
959 auto& [port, buf] = m_buffers[N];
960 uploadOutputBuffer(renderer, t.buffer, res, buf);
967 for(
auto& [p, buf] : m_buffers)
970 renderer.releaseBuffer(buf.handle);
971 buf.handle =
nullptr;
978 requires (avnd::buffer_output_introspection<T>::size == 0)
979struct buffer_outputs_storage<T>
981 static void init(
auto&&...)
986 static void prepareUpload(
auto&&...)
990 static void upload(
auto&&...)
994 static void release(
auto&&...)
1000template <
typename Tex>
1004 auto& rhi = *renderer.
state.rhi;
1006 if(size.width() > 0 && size.height() > 0)
1008 texture = rhi.newTexture(
1009 gpp::qrhi::textureFormat(texture_spec), size, 1, QRhiTexture::Flag{});
1014 auto sampler = rhi.newSampler(
1015 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
1016 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
1024struct texture_inputs_storage;
1027 requires (avnd::texture_input_introspection<T>::size > 0)
1028struct texture_inputs_storage<T>
1030 ossia::small_flat_map<const score::gfx::Port*, score::gfx::TextureRenderTarget, 2>
1033 QRhiReadbackResult m_readbacks[avnd::texture_input_introspection<T>::size];
1035 template <
typename Tex>
1036 QRhiTexture* createInput(
1040 static constexpr auto flags
1041 = QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource;
1042 QRhiTexture::Format fmt{};
1043 if constexpr(
requires (Tex tex) { tex.format = {}; } && !
requires (Tex tex) { tex.request_format; })
1047 gpp::qrhi::toTextureFormat(fmt, texture_spec);
1051 fmt = gpp::qrhi::textureFormat(texture_spec);
1054 QRhiTexture* texture = renderer.
state.rhi->newTexture(
1055 fmt, spec.size, 1, flags);
1057 SCORE_ASSERT(texture->create());
1066 avnd::texture_input_introspection<T>::for_all_n2(
1067 avnd::get_inputs<T>(*self.state),
1068 [&]<
typename F, std::size_t K, std::size_t N>(F& t, avnd::predicate_index<K>, avnd::field_index<N>) {
1069 auto& parent = self.node();
1070 auto spec = parent.resolveRenderTargetSpecs(N, renderer);
1071 if constexpr(requires {
1076 spec.size.rwidth() = t.request_width;
1077 spec.size.rheight() = t.request_height;
1080 auto tex = createInput(renderer, parent.
input[N], t.texture, spec);
1081 if constexpr(avnd::cpu_texture_port<F>)
1083 t.texture.width = spec.size.width();
1084 t.texture.height = spec.size.height();
1086 else if constexpr(avnd::gpu_texture_port<F>)
1088 t.texture.handle = tex;
1089 t.texture.width = spec.size.width();
1090 t.texture.height = spec.size.height();
1095 bool update(
auto& self,
1099 bool need_update =
false;
1100 avnd::texture_input_introspection<T>::for_all_n2(
1101 avnd::get_inputs<T>(*self.state),
1102 [&]<
typename F, std::size_t K, std::size_t N>(F& t, avnd::predicate_index<K>, avnd::field_index<N>) {
1103 if constexpr(requires {
1108 auto& parent = self.node();
1109 auto port = parent.
input[N];
1113 sz = texture.texture->pixelSize();
1114 if(sz.width() != t.request_width || sz.height() != t.request_height)
1142 void runInitialPasses(
auto& self, QRhi& rhi)
1148 if constexpr(avnd::cpu_texture_input_introspection<T>::size > 0)
1150 avnd::texture_input_introspection<T>::for_all_n(
1151 avnd::get_inputs<T>(*self.state), [&]<
typename F, std::size_t K>(F& t, avnd::predicate_index<K>) {
1152 if constexpr(avnd::cpu_texture_port<F>)
1154 oscr::loadInputTexture(rhi, m_readbacks, t.texture, K);
1164 for(
auto [port, rt] : m_rts)
1169 void inputAboutToFinish(
auto& parent,
const score::gfx::Port& p, QRhiResourceUpdateBatch*& res)
1171 if constexpr(avnd::cpu_texture_input_introspection<T>::size > 0)
1173 const auto& inputs = parent.
input;
1174 auto index_of_port = ossia::find(inputs, &p) - inputs.begin();
1176 auto tex = m_rts[&p].texture;
1177 auto& readback = m_readbacks[index_of_port];
1179 res->readBackTexture(QRhiReadbackDescription{tex}, &readback);
1186 requires (avnd::texture_input_introspection<T>::size == 0)
1187struct texture_inputs_storage<T>
1189 static void init(
auto&&...) { }
1190 static void runInitialPasses(
auto&&...) { }
1191 static void release(
auto&&...) { }
1192 static void inputAboutToFinish(
auto&&...) { }
1197template <avnd::cpu_texture Tex>
1198static QRhiTexture* updateTexture(
auto& self,
score::gfx::RenderList& renderer,
int k,
const Tex& cpu_tex)
1200 auto& [sampler, texture] = self.m_samplers[k];
1203 auto sz = texture->pixelSize();
1204 if(cpu_tex.width == sz.width() && cpu_tex.height == sz.height())
1209 if(cpu_tex.width > 0 && cpu_tex.height > 0)
1211 QRhiTexture* oldtex = texture;
1212 QRhiTexture* newtex = renderer.
state.rhi->newTexture(
1213 gpp::qrhi::textureFormat(cpu_tex), QSize{cpu_tex.width, cpu_tex.height}, 1,
1214 QRhiTexture::Flag{});
1216 for(
auto& [edge, pass] : self.m_p)
1218 score::gfx::replaceTexture(*pass.srb, sampler, newtex);
1223 oldtex->deleteLater();
1230 for(
auto& [edge, pass] : self.m_p)
1232 score::gfx::replaceTexture(*pass.srb, sampler, &renderer.emptyTexture());
1238template <avnd::cpu_texture Tex>
1239static void uploadOutputTexture(
auto& self,
1241 QRhiResourceUpdateBatch* res)
1245 if(
auto texture = updateTexture(self, renderer, k, cpu_tex))
1248 = QByteArray::fromRawData((
const char*)cpu_tex.bytes, cpu_tex.bytesize());
1249 if constexpr(
requires { Tex::RGB; })
1253 const QByteArray rgb = buf;
1255 rgba.resize(cpu_tex.width * cpu_tex.height * 4);
1256 auto src = (
const unsigned char*)rgb.constData();
1257 auto dst = (
unsigned char*)rgba.data();
1258 for(
int rgb_byte = 0, rgba_byte = 0, N = rgb.size(); rgb_byte < N;)
1260 dst[rgba_byte + 0] = src[rgb_byte + 0];
1261 dst[rgba_byte + 1] = src[rgb_byte + 1];
1262 dst[rgba_byte + 2] = src[rgb_byte + 2];
1263 dst[rgba_byte + 3] = 255;
1272 QRhiTextureSubresourceUploadDescription sd(buf);
1273 QRhiTextureUploadDescription desc{QRhiTextureUploadEntry{0, 0, sd}};
1275 res->uploadTexture(texture, desc);
1278 cpu_tex.changed =
false;
1283static const constexpr auto generic_texgen_vs = R
"_(#version 450
1284layout(location = 0) in vec2 position;
1285layout(location = 1) in vec2 texcoord;
1287layout(binding=3) uniform sampler2D y_tex;
1288layout(location = 0) out vec2 v_texcoord;
1290layout(std140, binding = 0) uniform renderer_t {
1291 mat4 clipSpaceCorrMatrix;
1295out gl_PerVertex { vec4 gl_Position; };
1299#if defined(QSHADER_SPIRV) || defined(QSHADER_GLSL)
1300 v_texcoord = vec2(texcoord.x, 1. - texcoord.y);
1302 v_texcoord = texcoord;
1304 gl_Position = renderer.clipSpaceCorrMatrix * vec4(position.xy, 0.0, 1.);
1308static const constexpr auto generic_texgen_fs = R
"_(#version 450
1309layout(location = 0) in vec2 v_texcoord;
1310layout(location = 0) out vec4 fragColor;
1312layout(std140, binding = 0) uniform renderer_t {
1313mat4 clipSpaceCorrMatrix;
1317layout(binding=3) uniform sampler2D y_tex;
1321 fragColor = texture(y_tex, v_texcoord);
1326struct texture_outputs_storage;
1330 requires (avnd::texture_output_introspection<T>::size > 0)
1331struct texture_outputs_storage<T>
1336 self.defaultMeshInit(renderer, mesh, res);
1337 self.processUBOInit(renderer);
1341 std::tie(self.m_vertexS, self.m_fragmentS)
1344 avnd::cpu_texture_output_introspection<T>::for_all(
1345 avnd::get_outputs<T>(*self.state), [&](
auto& t) {
1346 self.m_samplers.push_back(
1347 createOutputTexture(renderer, t.texture, QSize{t.texture.width, t.texture.height}));
1350 self.defaultPassesInit(renderer, mesh);
1353 void runInitialPasses(
auto& self,
1355 QRhiResourceUpdateBatch*& res)
1357 avnd::cpu_texture_output_introspection<T>::for_all_n(
1358 avnd::get_outputs<T>(*self.state), [&]<std::size_t N>(
auto& t, avnd::predicate_index<N>) {
1359 uploadOutputTexture(self, renderer, N, t.texture, res);
1366 for(
auto& [sampl, texture] : self.m_samplers)
1369 texture->deleteLater();
1377 requires (avnd::texture_output_introspection<T>::size == 0)
1378struct texture_outputs_storage<T>
1384 static void runInitialPasses(
auto& self,
1386 QRhiResourceUpdateBatch*& res)
1395struct geometry_outputs_storage;
1398 requires (avnd::geometry_output_introspection<T>::size > 0)
1399struct geometry_outputs_storage<T>
1401 ossia::geometry_spec specs[avnd::geometry_output_introspection<T>::size];
1403 template <avnd::geometry_port Field>
1404 void reload_mesh(Field& ctrl, ossia::geometry_spec& spc)
1406 spc.meshes = std::make_shared<ossia::mesh_list>();
1407 auto& ossia_meshes = *spc.meshes;
1408 if constexpr(avnd::static_geometry_type<Field> || avnd::dynamic_geometry_type<Field>)
1410 ossia_meshes.meshes.resize(1);
1411 load_geometry(ctrl, ossia_meshes.meshes[0]);
1414 avnd::static_geometry_type<
decltype(Field::mesh)>
1415 || avnd::dynamic_geometry_type<
decltype(Field::mesh)>)
1417 ossia_meshes.meshes.resize(1);
1418 load_geometry(ctrl.mesh, ossia_meshes.meshes[0]);
1422 load_geometry(ctrl, ossia_meshes);
1426 template <avnd::geometry_port Field, std::
size_t N>
1429 avnd::predicate_index<N>)
1431 auto edge_sink = edge.sink;
1432 if(
auto pnode = edge_sink->node)
1434 ossia::geometry_spec& spc = specs[N];
1440 reload_mesh(ctrl, spc);
1446 auto& ossia_meshes = *spc.meshes;
1448 bool any_need_reload =
false;
1449 bool any_need_upload =
false;
1450 if constexpr(avnd::static_geometry_type<Field> || avnd::dynamic_geometry_type<Field>)
1452 SCORE_ASSERT(ossia_meshes.meshes.size() == 1);
1453 auto [need_reload, need_upload]
1454 = update_geometry(ctrl, ossia_meshes.meshes[0]);
1455 any_need_reload = need_reload;
1456 any_need_upload = need_upload;
1459 avnd::static_geometry_type<
decltype(Field::mesh)>
1460 || avnd::dynamic_geometry_type<
decltype(Field::mesh)>)
1462 SCORE_ASSERT(ossia_meshes.meshes.size() == 1);
1463 auto [need_reload, need_upload]
1464 = update_geometry(ctrl.mesh, ossia_meshes.meshes[0]);
1465 any_need_reload = need_reload;
1466 any_need_upload = need_upload;
1470 auto [need_reload, need_upload] = update_geometry(ctrl, ossia_meshes);
1471 any_need_reload = need_reload;
1472 any_need_upload = need_upload;
1477 reload_mesh(ctrl, spc);
1481 ctrl.dirty_mesh =
false;
1487 auto rendered_node = pnode->renderedNodes.find(&renderer);
1488 SCORE_ASSERT(rendered_node != pnode->renderedNodes.end());
1490 auto it = std::find(
1491 edge_sink->node->input.begin(), edge_sink->node->input.end(), edge_sink);
1492 SCORE_ASSERT(it != edge_sink->node->input.end());
1493 int n = it - edge_sink->node->input.begin();
1495 rendered_node->second->process(n, spc);
1499 if constexpr(
requires { ctrl.transform; })
1501 if(ctrl.dirty_transform)
1503 ossia::transform3d transform;
1504 std::copy_n(ctrl.transform, std::ssize(ctrl.transform), transform.matrix);
1505 ctrl.dirty_transform =
false;
1507 rendered_node->second->process(n, transform);
1509 pnode->process(n, transform);
1518 avnd::geometry_output_introspection<T>::for_all_n(
1519 avnd::get_outputs(state),
1520 [&](
auto& field,
auto pred) { this->upload(renderer, field, edge, pred); });
1526 requires (avnd::geometry_output_introspection<T>::size == 0)
1527struct geometry_outputs_storage<T>
1529 static void upload(
auto&&...)
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:343
const score::gfx::Mesh & defaultTriangle() const noexcept
A triangle mesh correct for this API.
Definition RenderList.cpp:382
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
void uploadStaticBufferWithStoredData(QRhiResourceUpdateBatch *ub, QRhiBuffer *buf, int offset, int64_t bytesize, const char *data)
Schedule a Static buffer update when we can guarantee the buffer outlives the frame.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:338
std::pair< QShader, QShader > makeShaders(const RenderState &v, QString vert, QString frag)
Get a pair of compiled vertex / fragment shaders from GLSL 4.5 sources.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:394
TextureRenderTarget createRenderTarget(const RenderState &state, QRhiTexture *tex, int samples, bool depth)
Create a render target from a texture.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:10
Base toolkit upon which the software is built.
Definition Application.cpp:111
Definition DocumentContext.hpp:18
Connection between two score::gfx::Port.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:71
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:53
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:26
Useful abstraction for storing all the data related to a render target.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:116