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/SceneConcepts.hpp>
12#include <Crousti/TextureConversion.hpp>
13#include <Crousti/TextureFormat.hpp>
14#include <Crousti/WorkerBinding.hpp>
15#include <Gfx/GfxExecNode.hpp>
16#include <Gfx/Graph/Node.hpp>
17#include <Gfx/Graph/OutputNode.hpp>
18#include <Gfx/Graph/RenderList.hpp>
19#include <Gfx/Graph/RenderState.hpp>
21#include <score/tools/ThreadPool.hpp>
23#include <ossia/detail/hash_map.hpp>
24#include <ossia/detail/lockfree_queue.hpp>
25#include <ossia/detail/small_flat_map.hpp>
26#include <ossia/detail/small_vector.hpp>
28#include <ossia-qt/invoke.hpp>
30#include <QCoreApplication>
32#include <QtGui/private/qrhi_p.h>
34#include <avnd/binding/ossia/metadatas.hpp>
35#include <avnd/binding/ossia/port_run_postprocess.hpp>
36#include <avnd/binding/ossia/port_run_preprocess.hpp>
37#include <avnd/binding/ossia/soundfiles.hpp>
38#include <avnd/concepts/parameter.hpp>
39#include <avnd/introspection/input.hpp>
40#include <avnd/introspection/output.hpp>
41#include <fmt/format.h>
42#include <gpp/layout.hpp>
43#include <halp/texture.hpp>
45#include <score_plugin_avnd_export.h>
70 std::function<void()> apply;
72 ossia::mpmc_queue<Result> queue;
73 std::atomic<uint64_t> pushed{};
74 std::atomic<int> inflight{};
76 void push(std::function<
void()> f)
78 queue.enqueue(Result{pushed.fetch_add(1, std::memory_order_relaxed), std::move(f)});
83 ossia::small_vector<Result, 4> batch;
85 while(queue.try_dequeue(r))
86 batch.push_back(std::move(r));
87 std::ranges::sort(batch, {}, &Result::order);
88 for(
auto& res : batch)
98 for(
int n = inflight.load(); n > 0; n = inflight.load())
100 if(queue.size_approx() == 0)
111 std::weak_ptr<WorkerResults> results;
115 std::weak_ptr<WorkerResults> results;
116 explicit Job(std::weak_ptr<WorkerResults> r) noexcept
117 : results{std::move(r)}
119 if(
auto p = results.lock())
120 p->inflight.fetch_add(1);
122 Job(Job&& other)
noexcept =
default;
123 Job& operator=(Job&&) =
delete;
126 if(
auto p = results.lock())
128 p->inflight.fetch_sub(1);
129 p->inflight.notify_all();
133 template <
typename F>
134 void operator()(F&& apply)
136 auto r = results.lock();
139 r->push(std::forward<F>(apply));
140 ossia::qt::run_async(QCoreApplication::instance(), [wr = results] {
141 if(
auto r = wr.lock())
146 Job begin() const noexcept {
return Job{results}; }
151 void drainWorker(
bool settle =
false)
const
156 m_workerResults->settle();
158 m_workerResults->drain();
161 template <
typename T>
162 void initWorker(
this auto& self, std::shared_ptr<T>& state)
noexcept
164 if constexpr(avnd::has_worker<T>)
166 if(!self.m_workerResults)
167 self.m_workerResults = std::make_shared<WorkerResults>();
169 bind_worker(*state, std::weak_ptr{state}, Delivery{self.m_workerResults});
173 mutable std::shared_ptr<WorkerResults> m_workerResults;
176#if defined(OSCR_HAS_MMAP_FILE_STORAGE)
183struct GpuRendererFiles
185 template <std::
size_t N, std::
size_t NField>
187 T& state,
const std::shared_ptr<oscr::raw_file_data>& hdl,
188 avnd::predicate_index<N> pred, avnd::field_index<NField> field)
190 m_rawfiles[&state].load(state, hdl, pred, field);
193 void releaseFiles() noexcept { m_rawfiles.clear(); }
196 ossia::hash_map<const T*, oscr::raw_file_storage<T>> m_rawfiles;
200 requires(avnd::raw_file_input_introspection<T>::size == 0)
201struct GpuRendererFiles<T>
203 void releaseFiles() noexcept { }
207struct GpuRendererFiles
209 void releaseFiles() noexcept { }
220struct SCORE_PLUGIN_AVND_EXPORT GpuMessageState
223 std::vector<uint32_t> generation;
228template <
typename GpuNodeRenderer,
typename Node>
232 GpuNodeRenderer& gpu, Node& state,
const GpuMessageState& prev,
236 , prev_mess{prev.message}
238 , prev_gen{prev.generation}
239 , gen{cur.generation}
244 GpuNodeRenderer& gpu;
248 const std::vector<uint32_t>& prev_gen;
249 const std::vector<uint32_t>& gen;
252 bool can_process_message(std::size_t N)
254 if(mess.input.size() <= N)
257 if(prev_mess.input.size() == mess.input.size())
259 auto& prev = prev_mess.input[N];
260 auto& next = mess.input[N];
261 if(prev.index() == 1 && next.index() == 1)
263 if(ossia::get<ossia::value>(prev) == ossia::get<ossia::value>(next))
275 bool received_event(std::size_t N)
const noexcept
277 if(mess.input.size() <= N || gen.size() <= N)
279 return prev_gen.size() <= N || prev_gen[N] != gen[N];
282 template <avnd::parameter_port Field, std::
size_t NField>
283 void operator()(Field& t, avnd::field_index<NField> field_index)
285 if constexpr(avnd::optional_ish<
decltype(Field::value)>)
288 if(!received_event(NField))
291 else if(!can_process_message(field_index))
296 if(
auto val = ossia::get_if<ossia::value>(&mess.input[field_index]))
298 oscr::from_ossia_value(t, *val, t.value);
299 if_possible(t.update(state));
303#if OSCR_HAS_MMAP_FILE_STORAGE
304 template <avnd::raw_file_port Field, std::
size_t NField>
305 void operator()(Field& t, avnd::field_index<NField> field_index)
308 using file_ports = avnd::raw_file_input_introspection<Node>;
310 if(!can_process_message(field_index))
313 auto val = ossia::get_if<ossia::value>(&mess.input[field_index]);
317 static constexpr bool has_text =
requires {
decltype(Field::file)::text; };
318 static constexpr bool has_mmap =
requires {
decltype(Field::file)::mmap; };
321 if(
auto hdl = loadRawfile(*val, ctx, has_text, has_mmap))
323 static constexpr auto N = file_ports::field_index_to_index(NField);
324 if constexpr(avnd::port_can_process<Field>)
328 auto func = executePortPreprocess<Field>(*hdl);
330 state, hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
337 state, hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
343 template <avnd::buffer_port Field, std::
size_t NField>
344 void operator()(Field& t, avnd::field_index<NField> field_index)
346 if(!can_process_message(field_index))
349 using node_type = std::remove_cvref_t<
decltype(gpu.node())>;
350 auto& node =
const_cast<node_type&
>(gpu.node());
351 if(field_index >= mess.input.size())
353 auto val = ossia::get_if<ossia::render_target_spec>(&mess.input[field_index]);
359 template <avnd::texture_port Field, std::
size_t NField>
360 void operator()(Field& t, avnd::field_index<NField> field_index)
362 if(!can_process_message(field_index))
365 using node_type = std::remove_cvref_t<
decltype(gpu.node())>;
366 auto& node =
const_cast<node_type&
>(gpu.node());
367 if(field_index >= mess.input.size())
369 auto val = ossia::get_if<ossia::render_target_spec>(&mess.input[field_index]);
375 template <avnd::geometry_port Field, std::
size_t NField>
376 void operator()(Field& t, avnd::field_index<NField> field_index)
385 template <scene_port Field, std::
size_t NField>
386 void operator()(Field& t, avnd::field_index<NField> field_index)
392 void operator()(
auto& t,
auto field_index) =
delete;
397 template <
typename Self,
typename Node_T>
398 static void processControlIn(
399 Self& self, Node_T& state, GpuMessageState& renderer_mess,
403 avnd::input_introspection<Node_T>::for_all_n(
404 avnd::get_inputs<Node_T>(state),
405 GpuProcessIns<Self, Node_T>{self, state, renderer_mess, mess, ctx});
406 renderer_mess = mess;
412 template <
typename Node_T>
413 static void clearControlIn(Node_T& state)
noexcept
415 avnd::parameter_input_introspection<Node_T>::for_all(
416 avnd::get_inputs<Node_T>(state), []<
typename Field>(Field& t) {
417 if constexpr(avnd::optional_ish<
decltype(Field::value)>)
425 std::weak_ptr<Execution::ExecutionCommandQueue> queue;
426 Gfx::exec_controls control_outs;
430 template <
typename Node_T>
431 void processControlOut(Node_T& state)
const noexcept
433 if(!this->control_outs.empty())
435 auto q = this->queue.lock();
440 avnd::parameter_output_introspection<Node_T>::for_all(
441 avnd::get_outputs(state), [&]<avnd::parameter_port T>(
const T& t) {
442 qq.enqueue([v = oscr::to_ossia_value(t, t.value),
443 port = control_outs[parm_k]]()
mutable {
444 std::swap(port->value, v);
445 port->changed = true;
455struct SCORE_PLUGIN_AVND_EXPORT GpuNodeElements
457 [[no_unique_address]] oscr::soundfile_storage<T> soundfiles;
459 [[no_unique_address]] oscr::midifile_storage<T> midifiles;
467 :
score::gfx::NodeModel{}
471 virtual ~CustomGfxNodeBase();
473 GpuMessageState last_message;
479 virtual ~CustomGfxOutputNodeBase();
481 GpuMessageState last_message;
484struct SCORE_PLUGIN_AVND_EXPORT CustomGpuNodeBase
491 std::weak_ptr<Execution::ExecutionCommandQueue>&& q, Gfx::exec_controls&& ctls,
493 : GpuControlOuts{
std::move(q),
std::move(ctls)}
498 virtual ~CustomGpuNodeBase() =
default;
501 QString vertex, fragment, compute;
502 GpuMessageState last_message;
506struct SCORE_PLUGIN_AVND_EXPORT CustomGpuOutputNodeBase
515 static constexpr QSize defaultRenderSize{200, 200};
517 CustomGpuOutputNodeBase(
518 std::weak_ptr<Execution::ExecutionCommandQueue> q, Gfx::exec_controls&& ctls,
520 virtual ~CustomGpuOutputNodeBase();
523 std::weak_ptr<score::gfx::RenderList> m_renderer{};
524 std::shared_ptr<score::gfx::RenderState> m_renderState{};
526 QString vertex, fragment, compute;
527 GpuMessageState last_message;
531 void setRenderer(std::shared_ptr<score::gfx::RenderList>)
override;
534 void startRendering()
override;
535 void render()
override;
536 void stopRendering()
override;
537 bool canRender()
const override;
538 void onRendererChange()
override;
542 void destroyOutput()
override;
543 std::shared_ptr<score::gfx::RenderState> renderState()
const override;
545 Configuration configuration() const noexcept override;
548template <typename Node_T, typename Node>
549void prepareNewState(
std::shared_ptr<Node_T>& eff, const Node& parent)
551 if constexpr(avnd::has_worker<Node_T>)
553 parent.initWorker(eff);
555 if constexpr(avnd::has_processor_to_gui_bus<Node_T>)
557 auto& process = parent.processModel;
558 eff->send_message = [ptr = QPointer{&process}](
auto&& b)
mutable {
562 if(ptr && ptr->to_ui)
563 MessageBusSender{ptr->to_ui}(std::move(b));
570 avnd::init_controls(*eff);
572 if constexpr(avnd::can_prepare<Node_T>)
574 if constexpr(avnd::function_reflection<&Node_T::prepare>::count == 1)
576 using prepare_type = avnd::first_argument<&Node_T::prepare>;
578 if_possible(t.instance = parent.instance);
588struct port_to_type_enum
590 template <std::
size_t I, avnd::buffer_port F>
591 constexpr auto operator()(avnd::field_reflection<I, F> p)
593 return score::gfx::Types::Buffer;
596 template <std::
size_t I, avnd::cpu_texture_port F>
597 constexpr auto operator()(avnd::field_reflection<I, F> p)
599 using texture_type = std::remove_cvref_t<
decltype(F::texture)>;
600 return (avnd::cpu_fixed_format_texture<texture_type> || avnd::cpu_dynamic_format_texture<texture_type>)
601 ? score::gfx::Types::Image
602 : score::gfx::Types::Buffer;
605 template <std::
size_t I, avnd::gpu_texture_port F>
606 constexpr auto operator()(avnd::field_reflection<I, F> p)
608 return score::gfx::Types::Image;
611 template <std::
size_t I, avnd::sampler_port F>
612 constexpr auto operator()(avnd::field_reflection<I, F> p)
614 return score::gfx::Types::Image;
616 template <std::
size_t I, avnd::image_port F>
617 constexpr auto operator()(avnd::field_reflection<I, F> p)
619 return score::gfx::Types::Image;
621 template <std::
size_t I, avnd::attachment_port F>
622 constexpr auto operator()(avnd::field_reflection<I, F> p)
624 return score::gfx::Types::Image;
626 template <std::
size_t I, avnd::gpu_render_target_output_port F>
627 constexpr auto operator()(avnd::field_reflection<I, F> p)
629 return score::gfx::Types::Image;
632 template <std::
size_t I, avnd::geometry_port F>
633 constexpr auto operator()(avnd::field_reflection<I, F> p)
635 return score::gfx::Types::Geometry;
638 template <std::
size_t I, scene_port F>
639 requires(!avnd::geometry_port<F>)
640 constexpr auto operator()(avnd::field_reflection<I, F> p)
642 return score::gfx::Types::Geometry;
644 template <std::
size_t I, avnd::mono_audio_port F>
645 constexpr auto operator()(avnd::field_reflection<I, F> p)
647 return score::gfx::Types::Audio;
649 template <std::
size_t I, avnd::poly_audio_port F>
650 constexpr auto operator()(avnd::field_reflection<I, F> p)
652 return score::gfx::Types::Audio;
654 template <std::
size_t I, avnd::
int_parameter F>
655 constexpr auto operator()(avnd::field_reflection<I, F> p)
657 return score::gfx::Types::Int;
659 template <std::
size_t I, avnd::enum_parameter F>
660 constexpr auto operator()(avnd::field_reflection<I, F> p)
662 return score::gfx::Types::Int;
664 template <std::
size_t I, avnd::
float_parameter F>
665 constexpr auto operator()(avnd::field_reflection<I, F> p)
667 return score::gfx::Types::Float;
669 template <std::
size_t I, avnd::parameter_port F>
670 constexpr auto operator()(avnd::field_reflection<I, F> p)
672 using value_type = std::remove_cvref_t<
decltype(F::value)>;
674 if constexpr(std::is_array_v<value_type>)
676 static constexpr int sz =
sizeof(value_type) /
sizeof(value_type{}[0]);
677 if constexpr(sz == 2)
679 return score::gfx::Types::Vec2;
681 else if constexpr(sz == 3)
683 return score::gfx::Types::Vec3;
685 else if constexpr(sz == 4)
687 return score::gfx::Types::Vec4;
690 else if constexpr(std::is_aggregate_v<value_type>)
692 static constexpr int sz = avnd::pfr::tuple_size_v<value_type>;
693 if constexpr(sz == 2)
695 return score::gfx::Types::Vec2;
697 else if constexpr(sz == 3)
699 return score::gfx::Types::Vec3;
701 else if constexpr(sz == 4)
703 return score::gfx::Types::Vec4;
706 return score::gfx::Types::Empty;
708 template <std::
size_t I,
typename F>
709 constexpr auto operator()(avnd::field_reflection<I, F> p)
711 return score::gfx::Types::Empty;
723template <
typename Field>
724constexpr score::gfx::Flag port_flags_for_field() noexcept
726 if constexpr(avnd::gpu_texture_port<Field>)
728 constexpr auto kind = halp::texture_kind_of<Field>();
729 constexpr bool nonD2 = (kind != halp::texture_kind::texture_2d);
730 constexpr bool depth = halp::samplable_depth_of<Field>();
732 !(single_cable_port<Field> && (depth || nonD2)),
733 "single_cable is only supported on 2D texture inputs without depth");
734 if constexpr(single_cable_port<Field>)
735 return score::gfx::Flag::SingleCable;
736 else if constexpr(nonD2 && depth)
737 return score::gfx::Flag::GrabsFromSource | score::gfx::Flag::SamplableDepth;
738 else if constexpr(nonD2)
739 return score::gfx::Flag::GrabsFromSource;
740 else if constexpr(depth)
741 return score::gfx::Flag::SamplableDepth;
743 return score::gfx::Flag{};
749inline constexpr halp::gpu_texture::depth_format_t qrhiToHalpDepthFormat(
750 QRhiTexture::Format f)
noexcept
752 using D = halp::gpu_texture::depth_format_t;
755 case QRhiTexture::D16:
return D::D16;
756 case QRhiTexture::D24:
return D::D24;
757 case QRhiTexture::D24S8:
return D::D24S8;
758 case QRhiTexture::D32F:
return D::D32F;
764template <
typename Node_T>
765inline void initGfxPorts(
auto* self,
auto& input,
auto& output)
767 avnd::input_introspection<Node_T>::for_all(
768 [self, &input]<
typename Field, std::size_t I>(avnd::field_reflection<I, Field> f) {
769 static constexpr auto type = port_to_type_enum{}(f);
770 static constexpr auto flags = port_flags_for_field<Field>();
773 avnd::output_introspection<Node_T>::for_all(
775 &output]<
typename Field, std::size_t I>(avnd::field_reflection<I, Field> f) {
776 static constexpr auto type = port_to_type_enum{}(f);
784 output.push_back(
new score::gfx::Port{self, {}, type, score::gfx::Flag{}, {}});
791 const auto& inputs = parent.
input;
795 for(
auto& edge : p->edges)
797 auto src_node = edge->source->node;
801 return src_renderer->bufferForOutput(*edge->source);
810static void readbackInputBuffer(
812 , QRhiResourceUpdateBatch& res
814 , QRhiBufferReadbackResult& readback
820 if(
auto buf = getInputBuffer(renderer, parent, port_index))
823 res.readBackBuffer(buf.handle, buf.byte_offset, buf.byte_size, &readback);
827static void recreateOutputBuffer(
831 const auto bytesize = avnd::get_bytesize(cpu_buf);
836 buf.handle = renderer.
state.rhi->newBuffer(
840 QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer),
842 buf.handle->setName(
"GpuUtils::recreateOutputBuffer");
844 buf.byte_size = bytesize;
846 buf.handle->create();
850 cpu_buf.changed =
false;
854 else if(buf.handle->size() != bytesize)
856 buf.handle->destroy();
857 buf.handle->setSize(bytesize);
858 buf.handle->create();
859 buf.byte_size = bytesize;
863static void uploadOutputBuffer(
869 recreateOutputBuffer(renderer, cpu_buf, res, rhi_buf);
871 &res, rhi_buf.handle, 0, cpu_buf.byte_size,
872 (
const char*)avnd::get_bytes(cpu_buf));
873 cpu_buf.changed =
false;
877static void uploadOutputBuffer(
881 rhi_buf.handle =
reinterpret_cast<QRhiBuffer*
>(gpu_buf.handle);
882 rhi_buf.byte_size = gpu_buf.byte_size;
883 rhi_buf.byte_offset = gpu_buf.byte_offset;
887struct geometry_inputs_storage;
889struct mesh_input_storage
891 std::vector<QRhiBufferReadbackResult> readbacks;
892 std::vector<QRhiBuffer*> buffers;
894struct geometry_input_storage
896 ossia::geometry_spec spec;
897 std::vector<mesh_input_storage> meshes;
901 requires(avnd::geometry_input_introspection<T>::size > 0)
902struct geometry_inputs_storage<T>
905 static_assert(avnd::geometry_input_introspection<T>::size == 1);
907 geometry_input_storage inputs[avnd::geometry_input_introspection<T>::size];
908 ossia::small_vector<QRhiBuffer*, 4> allocated;
910 struct pending_transform
912 ossia::transform3d value;
915 pending_transform transforms[avnd::geometry_input_introspection<T>::size];
917 void setTransform(int32_t port,
const ossia::transform3d& v,
auto& state)
919 avnd::geometry_input_introspection<T>::for_all_n2(
920 avnd::get_inputs<T>(state),
921 [&]<
typename Field, std::size_t N, std::size_t NField>(
922 Field&, avnd::predicate_index<N>, avnd::field_index<NField>) {
923 if(int32_t(NField) == port)
924 transforms[N] = {v,
true};
928 void readInputGeometries(
935 avnd::geometry_input_introspection<T>::for_all_n(
936 avnd::get_inputs<T>(state),
937 [&]<
typename Field, std::size_t N>(Field& t, avnd::predicate_index<N> np) {
938 this->inputs[N].spec = spec;
939 this->inputs[N].meshes.resize(1);
942 auto& meshes = this->inputs[N].meshes[0];
944 oscr::meshes_from_ossia(
946 [&](
auto& write_buf,
int buffer_index,
void* data, int64_t bytesize) {
948 SCORE_ASSERT(buffer_index >= 0);
949 if(buffer_index < meshes.readbacks.size())
951 QRhiBuffer* handle = meshes.buffers[buffer_index];
952 write_buf.handle = handle;
953 write_buf.byte_size = handle->size();
955 }, [&](
auto& write_buf,
int buffer_index,
void* handle) {
957 SCORE_ASSERT(buffer_index >= 0);
958 if(buffer_index < meshes.readbacks.size())
961 auto& readback = meshes.readbacks[buffer_index].data;
962 write_buf.raw_data =
reinterpret_cast<unsigned char*
>(readback.data());
963 write_buf.byte_size = readback.size();
967 if constexpr(
requires {
972 auto& pending = transforms[N];
975 static_assert(std::extent_v<std::remove_cvref_t<
decltype(t.transform)>> == 16);
976 std::copy_n(pending.value.matrix, 16, t.transform);
977 pending.dirty =
false;
978 t.dirty_transform =
true;
982 t.dirty_transform =
false;
986 if constexpr(
requires { t.mesh.buffers[0].handle; })
988 if(spec.meshes && !spec.meshes->meshes.empty())
990 const auto& src = spec.meshes->meshes[0].buffers;
992 = std::min({src.size(), t.mesh.buffers.size(), meshes.buffers.size()});
993 for(std::size_t i = 0; i < n; i++)
995 if(!ossia::get_if<ossia::geometry::cpu_buffer>(&src[i].data))
997 if(QRhiBuffer* handle = meshes.buffers[i])
999 t.mesh.buffers[i].handle = handle;
1000 t.mesh.buffers[i].byte_size = handle->size();
1008 void inputAboutToFinish(
1010 const ossia::geometry_spec& spec,
auto& state,
auto& parent)
1012 avnd::geometry_input_introspection<T>::for_all_n2(
1013 avnd::get_inputs<T>(state),
1014 [&]<
typename Field, std::size_t N, std::size_t NField>(
1015 Field& t, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
1016 this->inputs[N].spec = spec;
1017 this->inputs[N].meshes.resize(1);
1020 auto& meshes = this->inputs[N].meshes[0];
1021 auto upload = [&](
int buffer_index,
const void* data, int64_t bytesize) {
1022 if(meshes.buffers.size() <= std::size_t(buffer_index))
1024 meshes.buffers.resize(buffer_index + 1);
1025 meshes.readbacks.resize(buffer_index + 1);
1027 QRhiBuffer*& buf = meshes.buffers[buffer_index];
1028 if(!buf || !ossia::contains(allocated, buf))
1030 buf = renderer.
state.rhi->newBuffer(
1033 *renderer.
state.rhi,
1034 QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer),
1036 buf->setName(oscr::getUtf8Name<T>() +
"::" + oscr::getUtf8Name(t));
1038 allocated.push_back(buf);
1040 else if(buf->size() < bytesize)
1043 buf->setSize(bytesize);
1047 res->uploadStaticBuffer(buf, 0, bytesize, data);
1049 oscr::meshes_from_ossia(
1050 spec.meshes, t.mesh,
1051 [&](
auto& write_buf,
int buffer_index,
void* data, int64_t bytesize) {
1053 upload(buffer_index, data, bytesize);
1054 }, [&](
auto& write_buf,
int buffer_index,
void* handle) {
1056 if(meshes.readbacks.size() <= buffer_index)
1058 meshes.buffers.resize(buffer_index + 1);
1059 meshes.readbacks.resize(buffer_index + 1);
1062 meshes.readbacks[buffer_index] = {};
1063 if(
auto buf =
static_cast<QRhiBuffer*
>(handle))
1065 meshes.buffers[buffer_index] = buf;
1066 res->readBackBuffer(buf, 0, buf->size(), &meshes.readbacks[buffer_index]);
1070 meshes.buffers[buffer_index] = {};
1071 meshes.readbacks[buffer_index] = {};
1078 if constexpr(
requires { t.mesh.buffers[0].handle; })
1080 if(spec.meshes && !spec.meshes->meshes.empty())
1082 const auto& src = spec.meshes->meshes[0].buffers;
1083 for(std::size_t i = 0; i < src.size(); i++)
1085 auto* cpu = ossia::get_if<ossia::geometry::cpu_buffer>(&src[i].data);
1086 if(!cpu || !cpu->raw_data || cpu->byte_size <= 0)
1088 if(i < meshes.buffers.size() && meshes.buffers[i]
1089 && ossia::contains(allocated, meshes.buffers[i]))
1091 upload(
int(i), cpu->raw_data.get(), cpu->byte_size);
1100 for(
auto& buf : allocated)
1101 renderer.releaseBuffer(buf);
1103 for(
auto& in : inputs)
1108template <
typename T>
1109 requires(avnd::geometry_input_introspection<T>::size == 0)
1110struct geometry_inputs_storage<T>
1112 static void readInputGeometries(
auto&&...) { }
1114 static void inputAboutToFinish(
auto&&...) { }
1116 static void release(
auto&&...) { }
1120struct buffer_inputs_storage;
1123 requires (avnd::buffer_input_introspection<T>::size > 0)
1124struct buffer_inputs_storage<T>
1127 QRhiBufferReadbackResult
1128 m_readbacks[avnd::cpu_buffer_input_introspection<T>::size + 1];
1131 void readInputBuffers(
1134 if constexpr(avnd::cpu_buffer_input_introspection<T>::size > 0)
1139 avnd::cpu_buffer_input_introspection<T>::for_all_n(
1140 avnd::get_inputs<T>(state),
1141 [&]<
typename Field, std::size_t N>
1142 (Field& t, avnd::predicate_index<N> np)
1144 auto& readback = m_readbacks[N].data;
1145 t.buffer.raw_data =
reinterpret_cast<unsigned char*
>(readback.data());
1146 t.buffer.byte_size = readback.size();
1147 t.buffer.byte_offset = 0;
1148 t.buffer.changed =
true;
1152 if constexpr(avnd::gpu_buffer_input_introspection<T>::size > 0)
1157 avnd::gpu_buffer_input_introspection<T>::for_all_n2(
1158 avnd::get_inputs<T>(state),
1159 [&]<
typename Field, std::size_t N, std::size_t NField>(
1160 Field& t, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
1163 buf = getInputBuffer(renderer, parent, nf);
1166 t.buffer.handle = buf.handle;
1167 t.buffer.byte_size = buf.byte_size;
1168 t.buffer.byte_offset = buf.byte_offset;
1174 void inputAboutToFinish(
1176 QRhiResourceUpdateBatch*& res,
1180 avnd::cpu_buffer_input_introspection<T>::for_all_n2(
1181 avnd::get_inputs<T>(state),
1182 [&]<
typename Field, std::size_t N, std::size_t NField>
1183 (Field& port, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
1184 readbackInputBuffer(renderer, *res, parent, m_readbacks[N], nf);
1186 avnd::gpu_buffer_input_introspection<T>::for_all_n2(
1187 avnd::get_inputs<T>(state),
1188 [&]<
typename Field, std::size_t N, std::size_t NField>
1189 (Field& port, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
1190 m_gpubufs[N] = getInputBuffer(renderer, parent, nf);
1196 requires (avnd::buffer_input_introspection<T>::size == 0)
1197struct buffer_inputs_storage<T>
1199 static void readInputBuffers(
auto&&...)
1204 static void inputAboutToFinish(
auto&&...)
1216struct buffer_outputs_storage;
1219 requires (avnd::buffer_output_introspection<T>::size > 0)
1220struct buffer_outputs_storage<T>
1222 std::pair<const score::gfx::Port*, MaybeOwnedBuffer>
1223 m_buffers[avnd::buffer_output_introspection<T>::size];
1225 QRhiResourceUpdateBatch* currentResourceUpdateBatch{};
1231 template <
typename Field, std::
size_t N, std::
size_t NField>
1232 requires avnd::cpu_buffer<std::decay_t<
decltype(Field::buffer)>>
1235 avnd::predicate_index<N> np, avnd::field_index<NField> nf)
1237 auto& [gfx_port, buf] = m_buffers[N];
1238 gfx_port = parent.
output[nf];
1239 buf.handle = renderer.
state.rhi->newBuffer(
1242 *renderer.
state.rhi,
1243 QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer), 1);
1244 buf.handle->setName(oscr::getUtf8Name<T>() +
"::" + oscr::getUtf8Name(port));
1245 buf.byte_offset = 0;
1249 buf.handle->create();
1251 m_renderer = &renderer;
1252 bindUpload(port, np);
1262 template <
typename Field, std::
size_t N>
1263 void bindUpload(Field& port, avnd::predicate_index<N>)
1266 = [
this, &port](
const char* data, int64_t offset, int64_t bytesize) {
1268 SCORE_ASSERT(currentResourceUpdateBatch);
1269 SCORE_ASSERT(m_renderer);
1270 auto& rhi = *m_renderer->state.rhi;
1271 auto& [gfx_port, buf] = m_buffers[N];
1277 buf.handle = rhi.newBuffer(
1280 rhi, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer),
1282 buf.handle->setName(oscr::getUtf8Name<T>() +
"::" + oscr::getUtf8Name(port));
1283 buf.byte_offset = 0;
1284 buf.byte_size = bytesize;
1287 buf.handle->create();
1291 buf.handle = rhi.newBuffer(
1294 rhi, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer),
1296 buf.handle->setName(oscr::getUtf8Name<T>() +
"::" + oscr::getUtf8Name(port));
1297 buf.byte_offset = 0;
1301 buf.handle->create();
1305 else if(buf.handle->size() != bytesize)
1307 buf.handle->destroy();
1308 buf.handle->setSize(bytesize);
1309 buf.handle->create();
1310 buf.byte_size = bytesize;
1314 currentResourceUpdateBatch, buf.handle, offset, bytesize, data);
1320 void bindUploads(
auto& state)
1322 avnd::buffer_output_introspection<T>::for_all_n(
1323 avnd::get_outputs<T>(state),
1324 [
this]<
typename Field, std::size_t N>(Field& port, avnd::predicate_index<N> np) {
1325 if constexpr(avnd::cpu_buffer<std::decay_t<
decltype(Field::buffer)>> &&
requires {
1326 port.buffer.upload(
nullptr, 0, 0);
1329 bindUpload(port, np);
1334 template <
typename Field, std::
size_t N, std::
size_t NField>
1335 requires avnd::gpu_buffer<std::decay_t<
decltype(Field::buffer)>>
1338 avnd::predicate_index<N> np, avnd::field_index<NField> nf)
1340 auto& [gfx_port, buf] = m_buffers[N];
1341 gfx_port = parent.
output[nf];
1342 buf.handle =
reinterpret_cast<QRhiBuffer*
>(port.buffer.handle);
1343 buf.byte_size = port.buffer.byte_size;
1344 buf.byte_offset = port.buffer.byte_offset;
1351 avnd::buffer_output_introspection<T>::for_all_n2(
1352 avnd::get_outputs<T>(state), [&]<
typename Field, std::size_t N, std::size_t NField>
1353 (Field& port, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
1354 SCORE_ASSERT(parent.
output.size() > nf);
1355 SCORE_ASSERT(parent.
output[nf]->type == score::gfx::Types::Buffer);
1356 using buffer_type = std::decay_t<
decltype(port.buffer)>;
1358 if constexpr(avnd::cpu_raw_buffer<buffer_type> &&
requires {
1359 port.buffer.upload(
nullptr, 0, 0);
1362 createOutput(renderer, parent, port, np, nf);
1364 else if constexpr(avnd::gpu_buffer<buffer_type>)
1366 createOutput(renderer, parent, port, np, nf);
1371 static_assert(std::is_same_v<T, void>,
"unsupported");
1376 void prepareUpload(QRhiResourceUpdateBatch& res)
1378 currentResourceUpdateBatch = &res;
1383 avnd::buffer_output_introspection<T>::for_all_n(
1384 avnd::get_outputs<T>(state), [&]<std::size_t N>(
auto& t, avnd::predicate_index<N> idx) {
1385 auto& [port, buf] = m_buffers[N];
1386 uploadOutputBuffer(renderer, t.buffer, res, buf);
1393 for(
auto& [p, buf] : m_buffers)
1397 buf.handle =
nullptr;
1404 requires (avnd::buffer_output_introspection<T>::size == 0)
1405struct buffer_outputs_storage<T>
1407 static void init(
auto&&...)
1412 static void prepareUpload(
auto&&...)
1416 static void bindUploads(
auto&&...)
1420 static void upload(
auto&&...)
1424 static void release(
auto&&...)
1430template <
typename Tex>
1434 auto& rhi = *renderer.
state.rhi;
1436 if(size.width() > 0 && size.height() > 0)
1438 texture = rhi.newTexture(
1439 gpp::qrhi::textureFormat(texture_spec), size, 1, QRhiTexture::Flag{});
1444 auto sampler = rhi.newSampler(
1445 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
1446 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
1454struct texture_inputs_storage;
1457 requires (avnd::texture_input_introspection<T>::size > 0)
1458struct texture_inputs_storage<T>
1460 ossia::small_flat_map<const score::gfx::Port*, score::gfx::TextureRenderTarget, 2>
1462 ossia::small_flat_map<const score::gfx::Port*, QRhiSampler*, 2> m_samplers;
1464 QRhiReadbackResult m_readbacks[avnd::texture_input_introspection<T>::size];
1465 bool m_readbackPremultiplied[avnd::texture_input_introspection<T>::size]{};
1467 template <
typename Tex>
1468 QRhiTexture* createInput(
1471 bool mipmapped =
false)
1473 QRhiTexture::Flags flags
1474 = QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource;
1476 flags |= QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips;
1477 QRhiTexture::Format fmt{};
1478 if constexpr(
requires (Tex tex) { tex.format = {}; } && !
requires (Tex tex) { tex.request_format; })
1482 gpp::qrhi::toTextureFormat(fmt, texture_spec);
1486 fmt = gpp::qrhi::textureFormat(texture_spec);
1489 QRhiTexture* texture = renderer.
state.rhi->newTexture(
1490 fmt, spec.size, 1, flags);
1492 SCORE_ASSERT(texture->create());
1497 const bool wantsDepth = renderer.
requiresDepth(*port) || wantsSamplableDepth;
1499 renderer.
state, texture, renderer.samples(),
1500 wantsDepth, wantsSamplableDepth);
1507 avnd::texture_input_introspection<T>::for_all_n2(
1508 avnd::get_inputs<T>(*self.state),
1509 [&]<
typename F, std::size_t K, std::size_t N>(F& t, avnd::predicate_index<K>, avnd::field_index<N>) {
1516 if constexpr(avnd::gpu_texture_port<F>
1517 && halp::texture_kind_of<F>() != halp::texture_kind::texture_2d)
1519 t.texture.kind = halp::texture_kind_of<F>();
1525 auto& parent = self.node();
1527 if constexpr(
requires {
1532 spec.size.rwidth() = t.request_width;
1533 spec.size.rheight() = t.request_height;
1536 constexpr bool wantsSamplableDepth
1537 = avnd::gpu_texture_port<F> && halp::samplable_depth_of<F>();
1538 if constexpr(avnd::gpu_texture_port<F>)
1540 if(!single_cable_port<F> || singleCableNeedsTarget(renderer, *parent.
input[N]))
1542 renderer, parent.
input[N], t.texture, spec, wantsSamplableDepth,
1543 spec.mipmap_mode != QRhiSampler::None);
1544 auto* sampler = renderer.
state.rhi->newSampler(
1545 spec.mag_filter, spec.min_filter, spec.mipmap_mode, spec.address_u,
1546 spec.address_v, spec.address_w);
1547 sampler->setName(
"texture_inputs_storage::sampler");
1549 m_samplers[parent.
input[N]] = sampler;
1553 createInput(renderer, parent.
input[N], t.texture, spec, wantsSamplableDepth);
1555 if constexpr(avnd::cpu_texture_port<F>)
1557 t.texture.width = spec.size.width();
1558 t.texture.height = spec.size.height();
1562 refreshGpuInputs(self, renderer);
1565 static std::pair<bool, QRhiTexture*> upstreamTexture(
1570 QRhiTexture* direct =
nullptr;
1571 for(
auto* edge : port.edges)
1573 if(!edge || !edge->source || !edge->source->node)
1575 auto& rendered = edge->source->node->renderedNodes;
1576 auto it = rendered.find(&renderer);
1577 if(it == rendered.end() || !it->second)
1580 direct = it->second->textureForOutput(*edge->source);
1584 return {wired > 0, direct};
1590 for(
auto* edge : port.edges)
1592 if(!edge || !edge->source || !edge->source->node)
1594 auto& rendered = edge->source->node->renderedNodes;
1595 auto it = rendered.find(&renderer);
1596 if(it == rendered.end() || !it->second)
1598 return it->second->textureForOutput(*edge->source);
1606 return !port.edges.empty() && !singleCableTexture(renderer, port);
1612 for(
auto* edge : port.edges)
1614 if(!edge || !edge->source || !edge->source->node)
1616 auto& rendered = edge->source->node->renderedNodes;
1617 if(
auto it = rendered.find(&renderer);
1618 it != rendered.end() && it->second && it->second->hasOutputPassForEdge(*edge))
1626 if(
auto it = m_rts.find(port); it != m_rts.end())
1628 it->second.release();
1633 QRhiSampler* refreshSampler(
1637 auto it = m_samplers.find(port);
1638 if(it == m_samplers.end() || !it->second)
1640 auto* sampler = it->second;
1641 const auto spec = node.resolveRenderTargetSpecs(index, renderer);
1642 if(sampler->magFilter() != spec.mag_filter || sampler->minFilter() != spec.min_filter
1643 || sampler->mipmapMode() != spec.mipmap_mode
1644 || sampler->addressU() != spec.address_u || sampler->addressV() != spec.address_v
1645 || sampler->addressW() != spec.address_w)
1648 sampler->setMagFilter(spec.mag_filter);
1649 sampler->setMinFilter(spec.min_filter);
1650 sampler->setMipmapMode(spec.mipmap_mode);
1651 sampler->setAddressU(spec.address_u);
1652 sampler->setAddressV(spec.address_v);
1653 sampler->setAddressW(spec.address_w);
1659 static void describeTexture(halp::gpu_texture& dst, QRhiTexture& tex)
1661 const auto sz = tex.pixelSize();
1663 dst.width = sz.width();
1664 dst.height = sz.height();
1665 gpp::qrhi::toTextureFormat(tex.format(), dst);
1666 const auto flags = tex.flags();
1667 if(flags & QRhiTexture::CubeMap)
1669 dst.kind = halp::texture_kind::cubemap;
1670 dst.layers_or_depth = 6;
1672 else if(flags & QRhiTexture::ThreeDimensional)
1674 dst.kind = halp::texture_kind::texture_3d;
1675 dst.layers_or_depth = tex.depth();
1677 else if(flags & QRhiTexture::TextureArray)
1679 dst.kind = halp::texture_kind::texture_array;
1680 dst.layers_or_depth = tex.arraySize();
1684 dst.kind = halp::texture_kind::texture_2d;
1685 dst.layers_or_depth = 1;
1691 avnd::texture_input_introspection<T>::for_all_n2(
1692 avnd::get_inputs<T>(*self.state),
1693 [&]<
typename F, std::size_t K, std::size_t N>(
1694 F& t, avnd::predicate_index<K>, avnd::field_index<N>) {
1696 avnd::gpu_texture_port<F>
1697 && halp::texture_kind_of<F>() == halp::texture_kind::texture_2d)
1699 constexpr bool wantsSamplableDepth = halp::samplable_depth_of<F>();
1700 auto* port = self.node().input[N];
1701 auto& tex = t.texture;
1702 const auto rt_it = m_rts.find(port);
1703 const bool mipmapped
1704 = rt_it != m_rts.end() && rt_it->second.texture
1705 && rt_it->second.texture->flags().testFlag(QRhiTexture::MipMapped);
1706 tex.sampler_handle = refreshSampler(self.node(), N, renderer, port);
1708 QRhiTexture* src = nullptr;
1709 if constexpr(single_cable_port<F>)
1711 src = singleCableTexture(renderer, *port);
1714 if(rt_it != m_rts.end() && !composited(renderer, *port))
1715 releaseTarget(port);
1717 else if(!port->edges.empty() && rt_it != m_rts.end())
1719 src = rt_it->second.texture;
1722 else if(auto [wired, direct]
1723 = upstreamTexture(renderer, self.node(), N, *port, mipmapped);
1726 if constexpr(!wantsSamplableDepth)
1728 if(!src && rt_it != m_rts.end())
1729 src = rt_it->second.texture;
1734 tex.handle = nullptr;
1737 tex.kind = halp::texture_kind::texture_2d;
1738 tex.layers_or_depth = 1;
1739 if constexpr(wantsSamplableDepth)
1740 tex.depth_handle = nullptr;
1744 describeTexture(tex, *src);
1745 if constexpr(wantsSamplableDepth)
1747 auto* depth = rt_it->second.depthTexture;
1748 tex.depth_handle = depth;
1750 tex.depth_format = qrhiToHalpDepthFormat(depth->format());
1756 bool update(
auto& self,
1759 avnd::texture_input_introspection<T>::for_all_n2(
1760 avnd::get_inputs<T>(*self.state),
1761 [&]<
typename F, std::size_t K, std::size_t N>(
1762 F& t, avnd::predicate_index<K>, avnd::field_index<N>) {
1763 if constexpr(single_cable_port<F> && avnd::gpu_texture_port<F>)
1765 auto& parent = self.node();
1766 auto* port = parent.input[N];
1767 if(m_rts.find(port) != m_rts.end() || !singleCableNeedsTarget(renderer, *port))
1769 auto spec = renderer.resolveInputRenderTargetSpecs(parent, N);
1770 if constexpr(requires {
1775 spec.size.rwidth() = t.request_width;
1776 spec.size.rheight() = t.request_height;
1779 renderer, port, t.texture, spec, false,
1780 spec.mipmap_mode != QRhiSampler::None);
1781 for(auto* edge : port->edges)
1783 auto& rendered = edge->source->node->renderedNodes;
1784 if(auto it = rendered.find(&renderer); it != rendered.end() && it->second)
1786 it->second->removeOutputPass(renderer, *edge);
1787 it->second->addOutputPass(renderer, *edge, res);
1793 bool need_update =
false;
1794 avnd::texture_input_introspection<T>::for_all_n2(
1795 avnd::get_inputs<T>(*self.state),
1796 [&]<
typename F, std::size_t K, std::size_t N>(F& t, avnd::predicate_index<K>, avnd::field_index<N>) {
1797 if constexpr(requires {
1802 auto& parent = self.node();
1803 auto port = parent.
input[N];
1807 sz = texture.texture->pixelSize();
1808 if(sz.width() != t.request_width || sz.height() != t.request_height)
1836 template <
typename F>
1837 static constexpr bool reads_upstream_directly() noexcept
1839 using Tex = std::decay_t<
decltype(F::texture)>;
1840 return avnd::cpu_texture_port<F>
1841 &&
requires(Tex tex) { tex.format = {}; }
1842 && !
requires(Tex tex) { tex.request_format; }
1843 && !
requires(F f) { f.request_width; };
1848 auto& rhi = *renderer.
state.rhi;
1849 refreshGpuInputs(self, renderer);
1855 if constexpr(avnd::cpu_texture_input_introspection<T>::size > 0)
1857 avnd::texture_input_introspection<T>::for_all_n(
1858 avnd::get_inputs<T>(*self.state), [&]<
typename F, std::size_t K>(F& t, avnd::predicate_index<K>) {
1859 if constexpr(avnd::cpu_texture_port<F>)
1861 if constexpr(reads_upstream_directly<F>())
1863 const auto& rb = m_readbacks[K];
1864 if(!rb.pixelSize.isEmpty())
1866 t.texture.width = rb.pixelSize.width();
1867 t.texture.height = rb.pixelSize.height();
1868 gpp::qrhi::toTextureFormat(rb.format, t.texture);
1871 if(auto& rb = m_readbacks[K]; m_readbackPremultiplied[K] && !rb.data.isEmpty())
1873 gpp::qrhi::unpremultiply(
1874 rb.format, rb.data, rb.pixelSize.width(), rb.pixelSize.height());
1875 m_readbackPremultiplied[K] = false;
1877 oscr::loadInputTexture(rhi, m_readbacks, t.texture, K);
1887 for(
auto [port, rt] : m_rts)
1890 for(
auto [port, sampler] : m_samplers)
1891 sampler->deleteLater();
1895 void inputAboutToFinish(
1897 QRhiResourceUpdateBatch*& res)
1899 if constexpr(avnd::cpu_texture_input_introspection<T>::size > 0)
1901 const auto& inputs = self.node().input;
1902 avnd::texture_input_introspection<T>::for_all_n2(
1903 avnd::get_inputs<T>(*self.state),
1904 [&]<
typename F, std::size_t K, std::size_t N>(
1905 F&, avnd::predicate_index<K>, avnd::field_index<N>) {
1906 if constexpr(avnd::cpu_texture_port<F>)
1908 if(N >= inputs.size() || inputs[N] != &p)
1910 auto rt_it = m_rts.find(&p);
1911 if(rt_it == m_rts.end())
1913 QRhiTexture* tex = rt_it->second.texture;
1914 if constexpr(reads_upstream_directly<F>())
1916 auto [wired, direct] = upstreamTexture(renderer, self.node(), N, p);
1917 if(direct && direct->sampleCount() <= 1
1918 && (direct->flags() & QRhiTexture::UsedAsTransferSource)
1919 && !(direct->flags()
1920 & (QRhiTexture::CubeMap | QRhiTexture::ThreeDimensional
1921 | QRhiTexture::TextureArray)))
1926 auto& readback = m_readbacks[K];
1928 m_readbackPremultiplied[K] = tex == rt_it->second.texture;
1929 res->readBackTexture(QRhiReadbackDescription{tex}, &readback);
1937 requires (avnd::texture_input_introspection<T>::size == 0)
1938struct texture_inputs_storage<T>
1940 static void init(
auto&&...) { }
1941 static void runInitialPasses(
auto&&...) { }
1942 static void release(
auto&&...) { }
1943 static void inputAboutToFinish(
auto&&...) { }
1948template <avnd::cpu_texture Tex>
1949static QRhiTexture* updateTexture(
auto& self,
score::gfx::RenderList& renderer,
int k,
const Tex& cpu_tex)
1951 auto& [sampler, texture, fb_] = self.m_samplers[k];
1954 auto sz = texture->pixelSize();
1955 if(cpu_tex.width == sz.width() && cpu_tex.height == sz.height())
1960 if(cpu_tex.width > 0 && cpu_tex.height > 0)
1962 QRhiTexture* oldtex = texture;
1963 QRhiTexture* newtex = renderer.
state.rhi->newTexture(
1964 gpp::qrhi::textureFormat(cpu_tex), QSize{cpu_tex.width, cpu_tex.height}, 1,
1965 QRhiTexture::Flag{});
1967 for(
auto& [edge, pass] : self.m_p)
1969 score::gfx::replaceTexture(*pass.p.srb, sampler, newtex);
1974 oldtex->deleteLater();
1981 for(
auto& [edge, pass] : self.m_p)
1983 score::gfx::replaceTexture(*pass.p.srb, sampler, &renderer.emptyTexture());
1989template <avnd::cpu_texture Tex>
1990static void uploadOutputTexture(
auto& self,
1992 QRhiResourceUpdateBatch* res)
1996 if(
auto texture = updateTexture(self, renderer, k, cpu_tex))
1998 if(!cpu_tex.bytes || cpu_tex.bytesize() <= 0)
2000 cpu_tex.changed =
false;
2005 = QByteArray::fromRawData((
const char*)cpu_tex.bytes, cpu_tex.bytesize());
2006 if constexpr(
requires { Tex::RGB; })
2010 const QByteArray rgb = buf;
2012 rgba.resize(cpu_tex.width * cpu_tex.height * 4);
2013 auto src = (
const unsigned char*)rgb.constData();
2014 auto dst = (
unsigned char*)rgba.data();
2015 for(
int rgb_byte = 0, rgba_byte = 0, N = rgb.size(); rgb_byte < N;)
2017 dst[rgba_byte + 0] = src[rgb_byte + 0];
2018 dst[rgba_byte + 1] = src[rgb_byte + 1];
2019 dst[rgba_byte + 2] = src[rgb_byte + 2];
2020 dst[rgba_byte + 3] = 255;
2029 QRhiTextureSubresourceUploadDescription sd(buf);
2030 QRhiTextureUploadDescription desc{QRhiTextureUploadEntry{0, 0, sd}};
2032 res->uploadTexture(texture, desc);
2035 cpu_tex.changed =
false;
2040static const constexpr auto generic_texgen_vs = R
"_(#version 450
2041layout(location = 0) in vec2 position;
2042layout(location = 1) in vec2 texcoord;
2044layout(binding=3) uniform sampler2D y_tex;
2045layout(location = 0) out vec2 v_texcoord;
2047layout(std140, binding = 0) uniform renderer_t {
2048 mat4 clipSpaceCorrMatrix;
2052out gl_PerVertex { vec4 gl_Position; };
2056#if defined(QSHADER_SPIRV) || defined(QSHADER_GLSL)
2057 v_texcoord = vec2(texcoord.x, 1. - texcoord.y);
2059 v_texcoord = texcoord;
2061 gl_Position = renderer.clipSpaceCorrMatrix * vec4(position.xy, 0.0, 1.);
2065static const constexpr auto generic_texgen_fs = R
"_(#version 450
2066layout(location = 0) in vec2 v_texcoord;
2067layout(location = 0) out vec4 fragColor;
2069layout(std140, binding = 0) uniform renderer_t {
2070mat4 clipSpaceCorrMatrix;
2074layout(binding=3) uniform sampler2D y_tex;
2078 fragColor = texture(y_tex, v_texcoord);
2083struct texture_outputs_storage;
2087 requires (avnd::texture_output_introspection<T>::size > 0)
2088struct texture_outputs_storage<T>
2090 static bool drawnOutputPremultiplied(
auto& self)
noexcept
2092 bool premultiplied =
false;
2093 const auto first = [&]<
typename F, std::size_t N>(F&, avnd::predicate_index<N>) {
2094 if constexpr(N == 0)
2095 premultiplied =
requires { F::premultiplied; };
2097 if constexpr(avnd::cpu_texture_output_introspection<T>::size > 0)
2098 avnd::cpu_texture_output_introspection<T>::for_all_n(
2099 avnd::get_outputs<T>(*self.state), first);
2101 avnd::gpu_texture_output_introspection<T>::for_all_n(
2102 avnd::get_outputs<T>(*self.state), first);
2103 return premultiplied;
2109 self.defaultMeshInit(renderer, mesh, res);
2110 self.processUBOInit(renderer);
2114 std::tie(self.m_vertexS, self.m_fragmentS)
2117 avnd::cpu_texture_output_introspection<T>::for_all(
2118 avnd::get_outputs<T>(*self.state), [&](
auto& t) {
2119 self.m_samplers.push_back(
2120 createOutputTexture(renderer, t.texture, QSize{t.texture.width, t.texture.height}));
2123 gpu_first = self.m_samplers.size();
2124 avnd::gpu_texture_output_introspection<T>::for_all(
2125 avnd::get_outputs<T>(*self.state), [&](
auto&) {
2126 auto sampler = renderer.state.rhi->newSampler(
2127 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
2128 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
2130 self.m_samplers.push_back(score::gfx::Sampler{sampler, nullptr});
2133 self.m_outputPremultiplied = drawnOutputPremultiplied(self);
2134 self.defaultPassesInit(renderer, mesh);
2139 for(
auto* port : self.node().output)
2141 if(!port || port->type != score::gfx::Types::Image)
2143 for(
auto* edge : port->edges)
2144 if(!self.hasOutputPassForEdge(*edge))
2145 self.addOutputPass(renderer, *edge, res);
2149 void runInitialPasses(
auto& self,
2151 QRhiResourceUpdateBatch*& res)
2153 avnd::cpu_texture_output_introspection<T>::for_all_n(
2154 avnd::get_outputs<T>(*self.state), [&]<std::size_t N>(
auto& t, avnd::predicate_index<N>) {
2155 uploadOutputTexture(self, renderer, N, t.texture, res);
2158 std::size_t k = gpu_first;
2159 avnd::gpu_texture_output_introspection<T>::for_all(
2160 avnd::get_outputs<T>(*self.state), [&](
auto& t) {
2161 auto* tex = static_cast<QRhiTexture*>(t.texture.handle);
2164 & (QRhiTexture::CubeMap | QRhiTexture::ThreeDimensional
2165 | QRhiTexture::TextureArray)))
2167 auto& sampler = self.m_samplers[k];
2168 if(tex != sampler.texture)
2170 sampler.texture = tex;
2171 for(auto& [edge, pass] : self.m_p)
2173 if(!pass.p.srb || !edge)
2177 const auto bound = self.samplersForOutputEdge(*edge);
2178 if(bound.size() == 1)
2180 if(bound.data() == &sampler)
2181 score::gfx::replaceTexture(
2182 *pass.p.srb, 3, tex ? tex : &renderer.emptyTexture());
2186 score::gfx::replaceTexture(
2187 *pass.p.srb, int(3 + k), tex ? tex : &renderer.emptyTexture());
2197 for(std::size_t i = 0; i < self.m_samplers.size(); i++)
2199 auto& texture = self.m_samplers[i].texture;
2201 texture->deleteLater();
2206 std::size_t gpu_first{};
2211 requires (avnd::texture_output_introspection<T>::size == 0)
2212struct texture_outputs_storage<T>
2218 static void runInitialPasses(
auto& self,
2220 QRhiResourceUpdateBatch*& res)
2229struct geometry_outputs_storage;
2232 requires (avnd::geometry_output_introspection<T>::size > 0)
2233struct geometry_outputs_storage<T>
2235 ossia::geometry_spec specs[avnd::geometry_output_introspection<T>::size];
2237 struct sent_transform
2239 std::optional<ossia::transform3d> value;
2240 std::vector<std::pair<const score::gfx::Edge*, const score::gfx::NodeRenderer*>>
2243 sent_transform transforms[avnd::geometry_output_introspection<T>::size];
2245 template <avnd::geometry_port Field>
2246 void reload_mesh(Field& ctrl, ossia::geometry_spec& spc)
2248 spc.meshes = std::make_shared<ossia::mesh_list>();
2249 auto& ossia_meshes = *spc.meshes;
2250 if constexpr(avnd::static_geometry_type<Field> || avnd::dynamic_geometry_type<Field>)
2252 ossia_meshes.meshes.resize(1);
2253 load_geometry(ctrl, ossia_meshes.meshes[0]);
2256 avnd::static_geometry_type<
decltype(Field::mesh)>
2257 || avnd::dynamic_geometry_type<
decltype(Field::mesh)>)
2259 ossia_meshes.meshes.resize(1);
2260 load_geometry(ctrl.mesh, ossia_meshes.meshes[0]);
2264 load_geometry(ctrl, ossia_meshes);
2268 template <avnd::geometry_port Field, std::
size_t N>
2271 avnd::predicate_index<N>)
2273 auto edge_sink = edge.sink;
2274 if(
auto pnode = edge_sink->node)
2276 ossia::geometry_spec& spc = specs[N];
2282 reload_mesh(ctrl, spc);
2288 auto& ossia_meshes = *spc.meshes;
2290 bool any_need_reload =
false;
2291 bool any_need_upload =
false;
2292 if constexpr(avnd::static_geometry_type<Field> || avnd::dynamic_geometry_type<Field>)
2294 SCORE_ASSERT(ossia_meshes.meshes.size() == 1);
2295 auto [need_reload, need_upload]
2296 = update_geometry(ctrl, ossia_meshes.meshes[0]);
2297 any_need_reload = need_reload;
2298 any_need_upload = need_upload;
2301 avnd::static_geometry_type<
decltype(Field::mesh)>
2302 || avnd::dynamic_geometry_type<
decltype(Field::mesh)>)
2304 SCORE_ASSERT(ossia_meshes.meshes.size() == 1);
2305 auto [need_reload, need_upload]
2306 = update_geometry(ctrl.mesh, ossia_meshes.meshes[0]);
2307 any_need_reload = need_reload;
2308 any_need_upload = need_upload;
2312 auto [need_reload, need_upload] = update_geometry(ctrl, ossia_meshes);
2313 any_need_reload = need_reload;
2314 any_need_upload = need_upload;
2319 reload_mesh(ctrl, spc);
2323 ctrl.dirty_mesh =
false;
2329 auto rendered_node = pnode->renderedNodes.find(&renderer);
2330 SCORE_ASSERT(rendered_node != pnode->renderedNodes.end());
2332 auto it = std::find(
2333 edge_sink->node->input.begin(), edge_sink->node->input.end(), edge_sink);
2334 SCORE_ASSERT(it != edge_sink->node->input.end());
2335 int n = it - edge_sink->node->input.begin();
2337 rendered_node->second->process(n, spc, edge.source);
2341 if constexpr(
requires { ctrl.transform; })
2343 auto& sent = transforms[N];
2344 if(ctrl.dirty_transform)
2346 sent.value.emplace();
2347 std::copy_n(ctrl.transform, std::ssize(ctrl.transform), sent.value->matrix);
2348 sent.receivers.clear();
2349 ctrl.dirty_transform =
false;
2352 const std::pair<const score::gfx::Edge*, const score::gfx::NodeRenderer*>
2353 receiver{&edge, rendered_node->second};
2354 if(sent.value && !ossia::contains(sent.receivers, receiver))
2356 sent.receivers.push_back(receiver);
2357 rendered_node->second->process(n, *sent.value);
2359 pnode->process(n, *sent.value);
2368 avnd::geometry_output_introspection<T>::for_all_n(
2369 avnd::get_outputs(state),
2370 [&](
auto& field,
auto pred) { this->upload(renderer, field, edge, pred); });
2381 requires (avnd::geometry_output_introspection<T>::size == 0)
2382struct geometry_outputs_storage<T>
2384 static void upload(
auto&&...)
2388 static void release(
auto&&...) noexcept { }
2395template <
typename Field>
2396using is_scene_port_t = boost::mp11::mp_bool<scene_port<Field>>;
2398template <
typename T>
2399using scene_output_introspection =
2400 avnd::predicate_introspection<typename avnd::outputs_type<T>::type, is_scene_port_t>;
2402template <
typename T>
2403using scene_input_introspection =
2404 avnd::predicate_introspection<typename avnd::inputs_type<T>::type, is_scene_port_t>;
2412template <
typename T>
2413struct scene_inputs_storage;
2415template <
typename T>
2416 requires(scene_input_introspection<T>::size > 0)
2417struct scene_inputs_storage<T>
2421 ossia::small_vector<std::pair<const ossia::scene_state*, int64_t>, 4> inputs;
2422 ossia::scene_spec merged;
2424 port_cache m_cache[scene_input_introspection<T>::size];
2426 static ossia::scene_spec
2429 ossia::small_vector<std::pair<const ossia::scene_state*, int64_t>, 4> sig;
2430 ossia::small_vector<ossia::scene_spec, 4> scenes;
2432 sig.push_back({s.state.get(), s.state->version});
2433 scenes.push_back(s);
2441 if(scenes.size() == 1)
2446 if(sig == cache.inputs && cache.merged.state)
2447 return cache.merged;
2449 cache.inputs.assign(sig.begin(), sig.end());
2450 cache.merged = ossia::merge_scenes(
2451 std::span<const ossia::scene_spec>{scenes.data(), scenes.size()});
2452 return cache.merged;
2457 if constexpr(scene_input_introspection<T>::size == 1)
2459 scene_input_introspection<T>::for_all(
2460 avnd::get_inputs<T>(state), [&](
auto& field) { field.scene = renderer.
scene; });
2463 scene_input_introspection<T>::for_all_n2(
2464 avnd::get_inputs<T>(state),
2465 [&]<
typename F, std::size_t K, std::size_t N>(
2466 F& field, avnd::predicate_index<K>, avnd::field_index<N>) {
2467 field.scene = sceneOnPort(renderer,
int(N), m_cache[K]);
2473 for(
auto& c : m_cache)
2478template <
typename T>
2479 requires(scene_input_introspection<T>::size == 0)
2480struct scene_inputs_storage<T>
2482 static void readInputScenes(
auto&&...) { }
2483 static void release(
auto&&...) { }
2486template <
typename T>
2487struct scene_outputs_storage;
2489template <
typename T>
2490 requires(scene_output_introspection<T>::size > 0)
2491struct scene_outputs_storage<T>
2493 template <scene_port Field, std::
size_t N>
2496 avnd::predicate_index<N>)
2503 if(!ctrl.scene.state)
2506 auto* edge_sink = edge.sink;
2507 if(!edge_sink || !edge_sink->node)
2511 if(rendered_node == edge_sink->node->renderedNodes.end())
2514 auto it = std::find(
2515 edge_sink->node->input.begin(), edge_sink->node->input.end(), edge_sink);
2516 if(it == edge_sink->node->input.end())
2518 int n = it - edge_sink->node->input.begin();
2525 rendered_node->second->process(n, ctrl.scene, edge.source);
2527 if constexpr(
requires { ctrl.dirty; })
2533 scene_output_introspection<T>::for_all_n(
2534 avnd::get_outputs(state),
2535 [&](
auto& field,
auto pred) { this->upload(renderer, field, edge, pred); });
2547template <
typename T>
2548 requires(scene_output_introspection<T>::size == 0)
2549struct scene_outputs_storage<T>
2551 static void upload(
auto&&...) { }
2552 static void release(
auto&&...) noexcept { }
Root data model for visual nodes.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:76
std::vector< Port * > input
Input ports of that node.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:105
bool hasExplicitRenderTargetSpecs(int32_t port) const noexcept
Whether the user set a size or a format on a texture inlet.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:184
ossia::flat_map< RenderList *, score::gfx::NodeRenderer * > renderedNodes
Map associating each RenderList to a Renderer for this model.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:116
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:111
Common base class for most single-pass, simple nodes.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:279
Renderer for a given node.
Definition NodeRenderer.hpp:11
ossia::scene_spec scene
The scene to use (when receiving scene_spec data).
Definition NodeRenderer.hpp:215
void forEachSceneOnPort(int32_t port, F &&fn) const
Definition NodeRenderer.hpp:172
Base class for sink nodes (QWindow, spout, syphon, NDI output, ...)
Definition OutputNode.hpp:55
Common base class for nodes that map to score processes.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:216
List of nodes to be rendered to an output.
Definition RenderList.hpp:33
RenderTargetSpecs resolveInputRenderTargetSpecs(const Node &node, int32_t port) noexcept
The render target specs of a texture inlet, with its size resolved.
Definition RenderList.cpp:366
bool requiresDepth(const score::gfx::Port &p) const noexcept
Whether this list of rendering actions requires depth testing at all.
Definition RenderList.cpp:1709
const score::gfx::Mesh & defaultTriangle() const noexcept
A triangle mesh correct for this API.
Definition RenderList.cpp:1748
RenderState & state
RenderState corresponding to this RenderList.
Definition RenderList.hpp:274
QRhiTexture & emptyTexture() const noexcept
Texture to use when a texture is missing (2D)
Definition RenderList.hpp:297
void releaseBuffer(QRhiBuffer *buf)
Retire a buffer this render list's node owns.
Definition RenderList.cpp:1084
TreeNode< DeviceExplorerNode > Node
Definition DeviceNode.hpp:74
void settle(::State::Address &a, const score::DocumentContext &ctx)
Definition ScriptableReference.cpp:121
Definition Controls.hpp:27
void bind_worker(Object &obj, std::weak_ptr< Object > obj_wp, Delivery deliver)
Definition WorkerBinding.hpp:31
std::pair< QShader, QShader > makeShaders(const RenderState &v, QString vert, QString frag, int multiViewCount)
Get a pair of compiled vertex / fragment shaders from GLSL 4.5 sources.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:1359
TextureRenderTarget createRenderTarget(const RenderState &state, QRhiTexture *tex, int samples, bool depth, bool samplableDepth)
Create a render target from a texture.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:68
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:933
QRhiBuffer::UsageFlags compatibleBufferUsage(QRhi &rhi, QRhiBuffer::UsageFlags usage) noexcept
Drop a StorageBuffer usage the backend cannot actually honour.
Definition RenderState.hpp:223
Base toolkit upon which the software is built.
Definition Application.cpp:108
Definition DocumentContext.hpp:18
Connection between two score::gfx::Port.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:220
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:51
Definition OutputNode.hpp:16
Port of a score::gfx::Node.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:199
score::gfx::Node * node
Parent node of the port.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:201
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:58
Stores a sampler and the texture currently associated with it.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:164
Useful abstraction for storing all the data related to a render target.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:279