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 <Gfx/GfxExecNode.hpp>
15#include <Gfx/Graph/Node.hpp>
16#include <Gfx/Graph/OutputNode.hpp>
17#include <Gfx/Graph/RenderList.hpp>
18#include <Gfx/Graph/RenderState.hpp>
20#include <score/tools/ThreadPool.hpp>
22#include <ossia/detail/hash_map.hpp>
23#include <ossia/detail/small_flat_map.hpp>
25#include <ossia-qt/invoke.hpp>
27#include <QCoreApplication>
29#include <QtGui/private/qrhi_p.h>
31#include <avnd/binding/ossia/metadatas.hpp>
32#include <avnd/binding/ossia/port_run_postprocess.hpp>
33#include <avnd/binding/ossia/port_run_preprocess.hpp>
34#include <avnd/binding/ossia/soundfiles.hpp>
35#include <avnd/concepts/parameter.hpp>
36#include <avnd/introspection/input.hpp>
37#include <avnd/introspection/output.hpp>
38#include <fmt/format.h>
39#include <gpp/layout.hpp>
40#include <halp/texture.hpp>
42#include <score_plugin_avnd_export.h>
49 void initWorker(
this auto& self, std::shared_ptr<T>& state)
noexcept
51 if constexpr(avnd::has_worker<T>)
53 auto ptr = QPointer{&self};
54 auto& tq = score::TaskPool::instance();
55 using worker_type =
decltype(state->worker);
57 auto wk_state = std::weak_ptr{state};
58 state->worker.request = [ptr, &tq, wk_state]<
typename... Args>(Args&&... f) {
59 using type_of_result =
decltype(worker_type::work(std::forward<Args>(f)...));
60 tq.post([... ff = std::forward<Args>(f), wk_state, ptr]()
mutable {
61 if constexpr(std::is_void_v<type_of_result>)
63 worker_type::work(std::forward<
decltype(ff)>(ff)...);
69 auto res = worker_type::work(std::forward<
decltype(ff)>(ff)...);
74 QCoreApplication::instance(),
75 [res = std::move(res), wk_state, ptr]()
mutable {
77 if(
auto state = wk_state.lock())
87#if defined(OSCR_HAS_MMAP_FILE_STORAGE)
94struct GpuRendererFiles
96 template <std::
size_t N, std::
size_t NField>
98 T& state,
const std::shared_ptr<oscr::raw_file_data>& hdl,
99 avnd::predicate_index<N> pred, avnd::field_index<NField> field)
101 m_rawfiles[&state].load(state, hdl, pred, field);
104 void releaseFiles() noexcept { m_rawfiles.clear(); }
107 ossia::hash_map<const T*, oscr::raw_file_storage<T>> m_rawfiles;
111 requires(avnd::raw_file_input_introspection<T>::size == 0)
112struct GpuRendererFiles<T>
114 void releaseFiles() noexcept { }
118struct GpuRendererFiles
120 void releaseFiles() noexcept { }
124template <
typename GpuNodeRenderer,
typename Node>
127 GpuNodeRenderer& gpu;
133 bool can_process_message(std::size_t N)
135 if(mess.input.size() <= N)
138 if(prev_mess.input.size() == mess.input.size())
140 auto& prev = prev_mess.input[N];
141 auto& next = mess.input[N];
142 if(prev.index() == 1 && next.index() == 1)
144 if(ossia::get<ossia::value>(prev) == ossia::get<ossia::value>(next))
155 bool is_new_message() const noexcept
157 return prev_mess.node_id != mess.node_id || prev_mess.token.date != mess.token.date
158 || prev_mess.input.size() != mess.input.size();
161 template <avnd::parameter_port Field, std::
size_t NField>
162 void operator()(Field& t, avnd::field_index<NField> field_index)
164 if constexpr(avnd::optional_ish<
decltype(Field::value)>)
169 if(mess.input.size() <= NField || !is_new_message())
172 else if(!can_process_message(field_index))
177 if(
auto val = ossia::get_if<ossia::value>(&mess.input[field_index]))
179 oscr::from_ossia_value(t, *val, t.value);
180 if_possible(t.update(state));
184#if OSCR_HAS_MMAP_FILE_STORAGE
185 template <avnd::raw_file_port Field, std::
size_t NField>
186 void operator()(Field& t, avnd::field_index<NField> field_index)
189 using file_ports = avnd::raw_file_input_introspection<Node>;
191 if(!can_process_message(field_index))
194 auto val = ossia::get_if<ossia::value>(&mess.input[field_index]);
198 static constexpr bool has_text =
requires {
decltype(Field::file)::text; };
199 static constexpr bool has_mmap =
requires {
decltype(Field::file)::mmap; };
202 if(
auto hdl = loadRawfile(*val, ctx, has_text, has_mmap))
204 static constexpr auto N = file_ports::field_index_to_index(NField);
205 if constexpr(avnd::port_can_process<Field>)
209 auto func = executePortPreprocess<Field>(*hdl);
211 state, hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
218 state, hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
224 template <avnd::buffer_port Field, std::
size_t NField>
225 void operator()(Field& t, avnd::field_index<NField> field_index)
227 if(!can_process_message(field_index))
230 using node_type = std::remove_cvref_t<
decltype(gpu.node())>;
231 auto& node =
const_cast<node_type&
>(gpu.node());
232 if(field_index >= mess.input.size())
234 auto val = ossia::get_if<ossia::render_target_spec>(&mess.input[field_index]);
237 node.process(NField, *val);
240 template <avnd::texture_port Field, std::
size_t NField>
241 void operator()(Field& t, avnd::field_index<NField> field_index)
243 if(!can_process_message(field_index))
246 using node_type = std::remove_cvref_t<
decltype(gpu.node())>;
247 auto& node =
const_cast<node_type&
>(gpu.node());
248 if(field_index >= mess.input.size())
250 auto val = ossia::get_if<ossia::render_target_spec>(&mess.input[field_index]);
253 node.process(NField, *val);
256 template <avnd::geometry_port Field, std::
size_t NField>
257 void operator()(Field& t, avnd::field_index<NField> field_index)
266 template <scene_port Field, std::
size_t NField>
267 void operator()(Field& t, avnd::field_index<NField> field_index)
273 void operator()(
auto& t,
auto field_index) =
delete;
278 template <
typename Self,
typename Node_T>
279 static void processControlIn(
284 avnd::input_introspection<Node_T>::for_all_n(
285 avnd::get_inputs<Node_T>(state),
286 GpuProcessIns<Self, Node_T>{self, state, renderer_mess, mess, ctx});
287 renderer_mess = mess;
293 template <
typename Node_T>
294 static void clearControlIn(Node_T& state)
noexcept
296 avnd::parameter_input_introspection<Node_T>::for_all(
297 avnd::get_inputs<Node_T>(state), []<
typename Field>(Field& t) {
298 if constexpr(avnd::optional_ish<
decltype(Field::value)>)
306 std::weak_ptr<Execution::ExecutionCommandQueue> queue;
307 Gfx::exec_controls control_outs;
311 template <
typename Node_T>
312 void processControlOut(Node_T& state)
const noexcept
314 if(!this->control_outs.empty())
316 auto q = this->queue.lock();
321 avnd::parameter_output_introspection<Node_T>::for_all(
322 avnd::get_outputs(state), [&]<avnd::parameter_port T>(
const T& t) {
323 qq.enqueue([v = oscr::to_ossia_value(t, t.value),
324 port = control_outs[parm_k]]()
mutable {
325 std::swap(port->value, v);
326 port->changed = true;
336struct SCORE_PLUGIN_AVND_EXPORT GpuNodeElements
338 [[no_unique_address]] oscr::soundfile_storage<T> soundfiles;
340 [[no_unique_address]] oscr::midifile_storage<T> midifiles;
348 :
score::gfx::NodeModel{}
352 virtual ~CustomGfxNodeBase();
360 virtual ~CustomGfxOutputNodeBase();
365struct SCORE_PLUGIN_AVND_EXPORT CustomGpuNodeBase
372 std::weak_ptr<Execution::ExecutionCommandQueue>&& q, Gfx::exec_controls&& ctls,
374 : GpuControlOuts{
std::move(q),
std::move(ctls)}
379 virtual ~CustomGpuNodeBase() =
default;
382 QString vertex, fragment, compute;
387struct SCORE_PLUGIN_AVND_EXPORT CustomGpuOutputNodeBase
396 static constexpr QSize defaultRenderSize{200, 200};
398 CustomGpuOutputNodeBase(
399 std::weak_ptr<Execution::ExecutionCommandQueue> q, Gfx::exec_controls&& ctls,
401 virtual ~CustomGpuOutputNodeBase();
404 std::weak_ptr<score::gfx::RenderList> m_renderer{};
405 std::shared_ptr<score::gfx::RenderState> m_renderState{};
407 QString vertex, fragment, compute;
412 void setRenderer(std::shared_ptr<score::gfx::RenderList>)
override;
415 void startRendering()
override;
416 void render()
override;
417 void stopRendering()
override;
418 bool canRender()
const override;
419 void onRendererChange()
override;
423 void destroyOutput()
override;
424 std::shared_ptr<score::gfx::RenderState> renderState()
const override;
426 Configuration configuration() const noexcept override;
429template <typename Node_T, typename Node>
430void prepareNewState(
std::shared_ptr<Node_T>& eff, const Node& parent)
432 if constexpr(avnd::has_worker<Node_T>)
434 parent.initWorker(eff);
436 if constexpr(avnd::has_processor_to_gui_bus<Node_T>)
438 auto& process = parent.processModel;
439 eff->send_message = [ptr = QPointer{&process}](
auto&& b)
mutable {
443 if(ptr && ptr->to_ui)
444 MessageBusSender{ptr->to_ui}(std::move(b));
451 avnd::init_controls(*eff);
453 if constexpr(avnd::can_prepare<Node_T>)
455 if constexpr(avnd::function_reflection<&Node_T::prepare>::count == 1)
457 using prepare_type = avnd::first_argument<&Node_T::prepare>;
459 if_possible(t.instance = parent.instance);
469struct port_to_type_enum
471 template <std::
size_t I, avnd::buffer_port F>
472 constexpr auto operator()(avnd::field_reflection<I, F> p)
474 return score::gfx::Types::Buffer;
477 template <std::
size_t I, avnd::cpu_texture_port F>
478 constexpr auto operator()(avnd::field_reflection<I, F> p)
480 using texture_type = std::remove_cvref_t<
decltype(F::texture)>;
481 return (avnd::cpu_fixed_format_texture<texture_type> || avnd::cpu_dynamic_format_texture<texture_type>)
482 ? score::gfx::Types::Image
483 : score::gfx::Types::Buffer;
486 template <std::
size_t I, avnd::gpu_texture_port F>
487 constexpr auto operator()(avnd::field_reflection<I, F> p)
489 return score::gfx::Types::Image;
492 template <std::
size_t I, avnd::sampler_port F>
493 constexpr auto operator()(avnd::field_reflection<I, F> p)
495 return score::gfx::Types::Image;
497 template <std::
size_t I, avnd::image_port F>
498 constexpr auto operator()(avnd::field_reflection<I, F> p)
500 return score::gfx::Types::Image;
502 template <std::
size_t I, avnd::attachment_port F>
503 constexpr auto operator()(avnd::field_reflection<I, F> p)
505 return score::gfx::Types::Image;
507 template <std::
size_t I, avnd::gpu_render_target_output_port F>
508 constexpr auto operator()(avnd::field_reflection<I, F> p)
510 return score::gfx::Types::Image;
513 template <std::
size_t I, avnd::geometry_port F>
514 constexpr auto operator()(avnd::field_reflection<I, F> p)
516 return score::gfx::Types::Geometry;
519 template <std::
size_t I, scene_port F>
520 requires(!avnd::geometry_port<F>)
521 constexpr auto operator()(avnd::field_reflection<I, F> p)
523 return score::gfx::Types::Geometry;
525 template <std::
size_t I, avnd::mono_audio_port F>
526 constexpr auto operator()(avnd::field_reflection<I, F> p)
528 return score::gfx::Types::Audio;
530 template <std::
size_t I, avnd::poly_audio_port F>
531 constexpr auto operator()(avnd::field_reflection<I, F> p)
533 return score::gfx::Types::Audio;
535 template <std::
size_t I, avnd::
int_parameter F>
536 constexpr auto operator()(avnd::field_reflection<I, F> p)
538 return score::gfx::Types::Int;
540 template <std::
size_t I, avnd::enum_parameter F>
541 constexpr auto operator()(avnd::field_reflection<I, F> p)
543 return score::gfx::Types::Int;
545 template <std::
size_t I, avnd::
float_parameter F>
546 constexpr auto operator()(avnd::field_reflection<I, F> p)
548 return score::gfx::Types::Float;
550 template <std::
size_t I, avnd::parameter_port F>
551 constexpr auto operator()(avnd::field_reflection<I, F> p)
553 using value_type = std::remove_cvref_t<
decltype(F::value)>;
555 if constexpr(std::is_array_v<value_type>)
557 static constexpr int sz =
sizeof(value_type) /
sizeof(value_type{}[0]);
558 if constexpr(sz == 2)
560 return score::gfx::Types::Vec2;
562 else if constexpr(sz == 3)
564 return score::gfx::Types::Vec3;
566 else if constexpr(sz == 4)
568 return score::gfx::Types::Vec4;
571 else if constexpr(std::is_aggregate_v<value_type>)
573 static constexpr int sz = avnd::pfr::tuple_size_v<value_type>;
574 if constexpr(sz == 2)
576 return score::gfx::Types::Vec2;
578 else if constexpr(sz == 3)
580 return score::gfx::Types::Vec3;
582 else if constexpr(sz == 4)
584 return score::gfx::Types::Vec4;
587 return score::gfx::Types::Empty;
589 template <std::
size_t I,
typename F>
590 constexpr auto operator()(avnd::field_reflection<I, F> p)
592 return score::gfx::Types::Empty;
604template <
typename Field>
605constexpr score::gfx::Flag port_flags_for_field() noexcept
607 if constexpr(avnd::gpu_texture_port<Field>)
609 constexpr auto kind = halp::texture_kind_of<Field>();
610 constexpr bool nonD2 = (kind != halp::texture_kind::texture_2d);
611 constexpr bool depth = halp::samplable_depth_of<Field>();
612 if constexpr(nonD2 && depth)
613 return score::gfx::Flag::GrabsFromSource | score::gfx::Flag::SamplableDepth;
614 else if constexpr(nonD2)
615 return score::gfx::Flag::GrabsFromSource;
616 else if constexpr(depth)
617 return score::gfx::Flag::SamplableDepth;
619 return score::gfx::Flag{};
625inline constexpr halp::gpu_texture::depth_format_t qrhiToHalpDepthFormat(
626 QRhiTexture::Format f)
noexcept
628 using D = halp::gpu_texture::depth_format_t;
631 case QRhiTexture::D16:
return D::D16;
632 case QRhiTexture::D24:
return D::D24;
633 case QRhiTexture::D24S8:
return D::D24S8;
634 case QRhiTexture::D32F:
return D::D32F;
640template <
typename Node_T>
641inline void initGfxPorts(
auto* self,
auto& input,
auto& output)
643 avnd::input_introspection<Node_T>::for_all(
644 [self, &input]<
typename Field, std::size_t I>(avnd::field_reflection<I, Field> f) {
645 static constexpr auto type = port_to_type_enum{}(f);
646 static constexpr auto flags = port_flags_for_field<Field>();
649 avnd::output_introspection<Node_T>::for_all(
651 &output]<
typename Field, std::size_t I>(avnd::field_reflection<I, Field> f) {
652 static constexpr auto type = port_to_type_enum{}(f);
660 output.push_back(
new score::gfx::Port{self, {}, type, score::gfx::Flag{}, {}});
667 const auto& inputs = parent.
input;
671 for(
auto& edge : p->edges)
673 auto src_node = edge->source->node;
677 return src_renderer->bufferForOutput(*edge->source);
686static void readbackInputBuffer(
688 , QRhiResourceUpdateBatch& res
690 , QRhiBufferReadbackResult& readback
696 if(
auto buf = getInputBuffer(renderer, parent, port_index))
699 res.readBackBuffer(buf.handle, buf.byte_offset, buf.byte_size, &readback);
703static void recreateOutputBuffer(
707 const auto bytesize = avnd::get_bytesize(cpu_buf);
712 buf.handle = renderer.
state.rhi->newBuffer(
716 QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer),
718 buf.handle->setName(
"GpuUtils::recreateOutputBuffer");
720 buf.byte_size = bytesize;
722 buf.handle->create();
726 cpu_buf.changed =
false;
730 else if(buf.handle->size() != bytesize)
732 buf.handle->destroy();
733 buf.handle->setSize(bytesize);
734 buf.handle->create();
735 buf.byte_size = bytesize;
739static void uploadOutputBuffer(
745 recreateOutputBuffer(renderer, cpu_buf, res, rhi_buf);
747 &res, rhi_buf.handle, 0, cpu_buf.byte_size,
748 (
const char*)avnd::get_bytes(cpu_buf));
749 cpu_buf.changed =
false;
753static void uploadOutputBuffer(
757 rhi_buf.handle =
reinterpret_cast<QRhiBuffer*
>(gpu_buf.handle);
758 rhi_buf.byte_size = gpu_buf.byte_size;
759 rhi_buf.byte_offset = gpu_buf.byte_offset;
763struct geometry_inputs_storage;
765struct mesh_input_storage
767 std::vector<QRhiBufferReadbackResult> readbacks;
768 std::vector<QRhiBuffer*> buffers;
770struct geometry_input_storage
772 ossia::geometry_spec spec;
773 std::vector<mesh_input_storage> meshes;
777 requires(avnd::geometry_input_introspection<T>::size > 0)
778struct geometry_inputs_storage<T>
781 static_assert(avnd::geometry_input_introspection<T>::size == 1);
783 geometry_input_storage inputs[avnd::geometry_input_introspection<T>::size];
784 ossia::small_vector<QRhiBuffer*, 4> allocated;
786 void readInputGeometries(
793 avnd::geometry_input_introspection<T>::for_all_n(
794 avnd::get_inputs<T>(state),
795 [&]<
typename Field, std::size_t N>(Field& t, avnd::predicate_index<N> np) {
796 this->inputs[N].spec = spec;
797 this->inputs[N].meshes.resize(1);
800 auto& meshes = this->inputs[N].meshes[0];
802 oscr::meshes_from_ossia(
804 [&](
auto& write_buf,
int buffer_index,
void* data, int64_t bytesize) {
806 SCORE_ASSERT(buffer_index >= 0);
807 if(buffer_index < meshes.readbacks.size())
809 QRhiBuffer* handle = meshes.buffers[buffer_index];
810 write_buf.handle = handle;
811 write_buf.byte_size = handle->size();
813 }, [&](
auto& write_buf,
int buffer_index,
void* handle) {
815 SCORE_ASSERT(buffer_index >= 0);
816 if(buffer_index < meshes.readbacks.size())
819 auto& readback = meshes.readbacks[buffer_index].data;
820 write_buf.raw_data =
reinterpret_cast<unsigned char*
>(readback.data());
821 write_buf.byte_size = readback.size();
827 void inputAboutToFinish(
829 const ossia::geometry_spec& spec,
auto& state,
auto& parent)
831 avnd::geometry_input_introspection<T>::for_all_n2(
832 avnd::get_inputs<T>(state),
833 [&]<
typename Field, std::size_t N, std::size_t NField>(
834 Field& t, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
835 this->inputs[N].spec = spec;
836 this->inputs[N].meshes.resize(1);
839 auto& meshes = this->inputs[N].meshes[0];
840 oscr::meshes_from_ossia(
842 [&](
auto& write_buf,
int buffer_index,
void* data, int64_t bytesize) {
844 if(meshes.buffers.size() <= buffer_index)
846 meshes.buffers.resize(buffer_index + 1);
847 meshes.readbacks.resize(buffer_index + 1);
849 auto buf = renderer.state.rhi->newBuffer(
851 score::gfx::compatibleBufferUsage(
853 QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer),
855 buf->setName(oscr::getUtf8Name<T>() +
"::" + oscr::getUtf8Name(t));
857 allocated.push_back(buf);
858 meshes.buffers[buffer_index] = buf;
860 else if(
auto* existing = meshes.buffers[buffer_index];
861 existing && existing->size() < bytesize)
864 existing->setSize(bytesize);
868 res->uploadStaticBuffer(meshes.buffers[buffer_index], 0, bytesize, data);
869 }, [&](
auto& write_buf,
int buffer_index,
void* handle) {
871 if(meshes.readbacks.size() <= buffer_index)
873 meshes.buffers.resize(buffer_index + 1);
874 meshes.readbacks.resize(buffer_index + 1);
877 meshes.readbacks[buffer_index] = {};
878 if(
auto buf =
static_cast<QRhiBuffer*
>(handle))
880 meshes.buffers[buffer_index] = buf;
881 res->readBackBuffer(buf, 0, buf->size(), &meshes.readbacks[buffer_index]);
885 meshes.buffers[buffer_index] = {};
886 meshes.readbacks[buffer_index] = {};
894 for(
auto& buf : allocated)
895 renderer.releaseBuffer(buf);
901 requires(avnd::geometry_input_introspection<T>::size == 0)
902struct geometry_inputs_storage<T>
904 static void readInputGeometries(
auto&&...) { }
906 static void inputAboutToFinish(
auto&&...) { }
908 static void release(
auto&&...) { }
912struct buffer_inputs_storage;
915 requires (avnd::buffer_input_introspection<T>::size > 0)
916struct buffer_inputs_storage<T>
919 QRhiBufferReadbackResult
920 m_readbacks[avnd::cpu_buffer_input_introspection<T>::size + 1];
923 void readInputBuffers(
926 if constexpr(avnd::cpu_buffer_input_introspection<T>::size > 0)
931 avnd::cpu_buffer_input_introspection<T>::for_all_n(
932 avnd::get_inputs<T>(state),
933 [&]<
typename Field, std::size_t N>
934 (Field& t, avnd::predicate_index<N> np)
936 auto& readback = m_readbacks[N].data;
937 t.buffer.raw_data =
reinterpret_cast<unsigned char*
>(readback.data());
938 t.buffer.byte_size = readback.size();
939 t.buffer.byte_offset = 0;
940 t.buffer.changed =
true;
944 if constexpr(avnd::gpu_buffer_input_introspection<T>::size > 0)
949 avnd::gpu_buffer_input_introspection<T>::for_all_n2(
950 avnd::get_inputs<T>(state),
951 [&]<
typename Field, std::size_t N, std::size_t NField>(
952 Field& t, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
955 buf = getInputBuffer(renderer, parent, nf);
958 t.buffer.handle = buf.handle;
959 t.buffer.byte_size = buf.byte_size;
960 t.buffer.byte_offset = buf.byte_offset;
966 void inputAboutToFinish(
968 QRhiResourceUpdateBatch*& res,
972 avnd::cpu_buffer_input_introspection<T>::for_all_n2(
973 avnd::get_inputs<T>(state),
974 [&]<
typename Field, std::size_t N, std::size_t NField>
975 (Field& port, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
976 readbackInputBuffer(renderer, *res, parent, m_readbacks[N], nf);
978 avnd::gpu_buffer_input_introspection<T>::for_all_n2(
979 avnd::get_inputs<T>(state),
980 [&]<
typename Field, std::size_t N, std::size_t NField>
981 (Field& port, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
982 m_gpubufs[N] = getInputBuffer(renderer, parent, nf);
988 requires (avnd::buffer_input_introspection<T>::size == 0)
989struct buffer_inputs_storage<T>
991 static void readInputBuffers(
auto&&...)
996 static void inputAboutToFinish(
auto&&...)
1008struct buffer_outputs_storage;
1011 requires (avnd::buffer_output_introspection<T>::size > 0)
1012struct buffer_outputs_storage<T>
1014 std::pair<const score::gfx::Port*, MaybeOwnedBuffer>
1015 m_buffers[avnd::buffer_output_introspection<T>::size];
1017 QRhiResourceUpdateBatch* currentResourceUpdateBatch{};
1023 template <
typename Field, std::
size_t N, std::
size_t NField>
1024 requires avnd::cpu_buffer<std::decay_t<
decltype(Field::buffer)>>
1027 avnd::predicate_index<N> np, avnd::field_index<NField> nf)
1029 auto& [gfx_port, buf] = m_buffers[N];
1030 gfx_port = parent.
output[nf];
1031 buf.handle = renderer.
state.rhi->newBuffer(
1034 *renderer.
state.rhi,
1035 QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer), 1);
1036 buf.handle->setName(oscr::getUtf8Name<T>() +
"::" + oscr::getUtf8Name(port));
1037 buf.byte_offset = 0;
1041 buf.handle->create();
1043 m_renderer = &renderer;
1044 bindUpload(port, np);
1054 template <
typename Field, std::
size_t N>
1055 void bindUpload(Field& port, avnd::predicate_index<N>)
1058 = [
this, &port](
const char* data, int64_t offset, int64_t bytesize) {
1060 SCORE_ASSERT(currentResourceUpdateBatch);
1061 SCORE_ASSERT(m_renderer);
1062 auto& rhi = *m_renderer->state.rhi;
1063 auto& [gfx_port, buf] = m_buffers[N];
1069 buf.handle = rhi.newBuffer(
1072 rhi, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer),
1074 buf.handle->setName(oscr::getUtf8Name<T>() +
"::" + oscr::getUtf8Name(port));
1075 buf.byte_offset = 0;
1076 buf.byte_size = bytesize;
1079 buf.handle->create();
1083 buf.handle = rhi.newBuffer(
1086 rhi, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer),
1088 buf.handle->setName(oscr::getUtf8Name<T>() +
"::" + oscr::getUtf8Name(port));
1089 buf.byte_offset = 0;
1093 buf.handle->create();
1097 else if(buf.handle->size() != bytesize)
1099 buf.handle->destroy();
1100 buf.handle->setSize(bytesize);
1101 buf.handle->create();
1102 buf.byte_size = bytesize;
1106 currentResourceUpdateBatch, buf.handle, offset, bytesize, data);
1112 void bindUploads(
auto& state)
1114 avnd::buffer_output_introspection<T>::for_all_n(
1115 avnd::get_outputs<T>(state),
1116 [
this]<
typename Field, std::size_t N>(Field& port, avnd::predicate_index<N> np) {
1117 if constexpr(avnd::cpu_buffer<std::decay_t<
decltype(Field::buffer)>> &&
requires {
1118 port.buffer.upload(
nullptr, 0, 0);
1121 bindUpload(port, np);
1126 template <
typename Field, std::
size_t N, std::
size_t NField>
1127 requires avnd::gpu_buffer<std::decay_t<
decltype(Field::buffer)>>
1130 avnd::predicate_index<N> np, avnd::field_index<NField> nf)
1132 auto& [gfx_port, buf] = m_buffers[N];
1133 gfx_port = parent.
output[nf];
1134 buf.handle =
reinterpret_cast<QRhiBuffer*
>(port.buffer.handle);
1135 buf.byte_size = port.buffer.byte_size;
1136 buf.byte_offset = port.buffer.byte_offset;
1143 avnd::buffer_output_introspection<T>::for_all_n2(
1144 avnd::get_outputs<T>(state), [&]<
typename Field, std::size_t N, std::size_t NField>
1145 (Field& port, avnd::predicate_index<N> np, avnd::field_index<NField> nf) {
1146 SCORE_ASSERT(parent.
output.size() > nf);
1147 SCORE_ASSERT(parent.
output[nf]->type == score::gfx::Types::Buffer);
1148 using buffer_type = std::decay_t<
decltype(port.buffer)>;
1150 if constexpr(avnd::cpu_raw_buffer<buffer_type> &&
requires {
1151 port.buffer.upload(
nullptr, 0, 0);
1154 createOutput(renderer, parent, port, np, nf);
1156 else if constexpr(avnd::gpu_buffer<buffer_type>)
1158 createOutput(renderer, parent, port, np, nf);
1163 static_assert(std::is_same_v<T, void>,
"unsupported");
1168 void prepareUpload(QRhiResourceUpdateBatch& res)
1170 currentResourceUpdateBatch = &res;
1175 avnd::buffer_output_introspection<T>::for_all_n(
1176 avnd::get_outputs<T>(state), [&]<std::size_t N>(
auto& t, avnd::predicate_index<N> idx) {
1177 auto& [port, buf] = m_buffers[N];
1178 uploadOutputBuffer(renderer, t.buffer, res, buf);
1185 for(
auto& [p, buf] : m_buffers)
1188 renderer.releaseBuffer(buf.handle);
1189 buf.handle =
nullptr;
1196 requires (avnd::buffer_output_introspection<T>::size == 0)
1197struct buffer_outputs_storage<T>
1199 static void init(
auto&&...)
1204 static void prepareUpload(
auto&&...)
1208 static void bindUploads(
auto&&...)
1212 static void upload(
auto&&...)
1216 static void release(
auto&&...)
1222template <
typename Tex>
1226 auto& rhi = *renderer.
state.rhi;
1228 if(size.width() > 0 && size.height() > 0)
1230 texture = rhi.newTexture(
1231 gpp::qrhi::textureFormat(texture_spec), size, 1, QRhiTexture::Flag{});
1236 auto sampler = rhi.newSampler(
1237 QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
1238 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
1246struct texture_inputs_storage;
1249 requires (avnd::texture_input_introspection<T>::size > 0)
1250struct texture_inputs_storage<T>
1252 ossia::small_flat_map<const score::gfx::Port*, score::gfx::TextureRenderTarget, 2>
1255 QRhiReadbackResult m_readbacks[avnd::texture_input_introspection<T>::size];
1257 template <
typename Tex>
1258 QRhiTexture* createInput(
1262 static constexpr auto flags
1263 = QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource;
1264 QRhiTexture::Format fmt{};
1265 if constexpr(
requires (Tex tex) { tex.format = {}; } && !
requires (Tex tex) { tex.request_format; })
1269 gpp::qrhi::toTextureFormat(fmt, texture_spec);
1273 fmt = gpp::qrhi::textureFormat(texture_spec);
1276 QRhiTexture* texture = renderer.
state.rhi->newTexture(
1277 fmt, spec.size, 1, flags);
1279 SCORE_ASSERT(texture->create());
1284 const bool wantsDepth = renderer.
requiresDepth(*port) || wantsSamplableDepth;
1286 renderer.
state, texture, renderer.samples(),
1287 wantsDepth, wantsSamplableDepth);
1294 avnd::texture_input_introspection<T>::for_all_n2(
1295 avnd::get_inputs<T>(*self.state),
1296 [&]<
typename F, std::size_t K, std::size_t N>(F& t, avnd::predicate_index<K>, avnd::field_index<N>) {
1303 if constexpr(avnd::gpu_texture_port<F>
1304 && halp::texture_kind_of<F>() != halp::texture_kind::texture_2d)
1306 t.texture.kind = halp::texture_kind_of<F>();
1312 auto& parent = self.node();
1313 auto spec = parent.resolveRenderTargetSpecs(N, renderer);
1314 if constexpr(
requires {
1319 spec.size.rwidth() = t.request_width;
1320 spec.size.rheight() = t.request_height;
1323 constexpr bool wantsSamplableDepth
1324 = avnd::gpu_texture_port<F> && halp::samplable_depth_of<F>();
1325 auto tex = createInput(
1326 renderer, parent.
input[N], t.texture, spec, wantsSamplableDepth);
1327 if constexpr(avnd::cpu_texture_port<F>)
1329 t.texture.width = spec.size.width();
1330 t.texture.height = spec.size.height();
1332 else if constexpr(avnd::gpu_texture_port<F>)
1334 t.texture.handle = tex;
1335 t.texture.width = spec.size.width();
1336 t.texture.height = spec.size.height();
1337 if constexpr(wantsSamplableDepth)
1342 const auto& rt = m_rts[parent.
input[N]];
1343 t.texture.depth_handle = rt.depthTexture;
1345 t.texture.depth_format = qrhiToHalpDepthFormat(rt.depthTexture->format());
1351 bool update(
auto& self,
1355 bool need_update =
false;
1356 avnd::texture_input_introspection<T>::for_all_n2(
1357 avnd::get_inputs<T>(*self.state),
1358 [&]<
typename F, std::size_t K, std::size_t N>(F& t, avnd::predicate_index<K>, avnd::field_index<N>) {
1359 if constexpr(requires {
1364 auto& parent = self.node();
1365 auto port = parent.
input[N];
1369 sz = texture.texture->pixelSize();
1370 if(sz.width() != t.request_width || sz.height() != t.request_height)
1398 void runInitialPasses(
auto& self, QRhi& rhi)
1404 if constexpr(avnd::cpu_texture_input_introspection<T>::size > 0)
1406 avnd::texture_input_introspection<T>::for_all_n(
1407 avnd::get_inputs<T>(*self.state), [&]<
typename F, std::size_t K>(F& t, avnd::predicate_index<K>) {
1408 if constexpr(avnd::cpu_texture_port<F>)
1410 oscr::loadInputTexture(rhi, m_readbacks, t.texture, K);
1420 for(
auto [port, rt] : m_rts)
1425 void inputAboutToFinish(
auto& parent,
const score::gfx::Port& p, QRhiResourceUpdateBatch*& res)
1427 if constexpr(avnd::cpu_texture_input_introspection<T>::size > 0)
1429 const auto& inputs = parent.
input;
1430 auto index_of_port = ossia::find(inputs, &p) - inputs.begin();
1432 auto tex = m_rts[&p].texture;
1433 auto& readback = m_readbacks[index_of_port];
1435 res->readBackTexture(QRhiReadbackDescription{tex}, &readback);
1442 requires (avnd::texture_input_introspection<T>::size == 0)
1443struct texture_inputs_storage<T>
1445 static void init(
auto&&...) { }
1446 static void runInitialPasses(
auto&&...) { }
1447 static void release(
auto&&...) { }
1448 static void inputAboutToFinish(
auto&&...) { }
1453template <avnd::cpu_texture Tex>
1454static QRhiTexture* updateTexture(
auto& self,
score::gfx::RenderList& renderer,
int k,
const Tex& cpu_tex)
1456 auto& [sampler, texture, fb_] = self.m_samplers[k];
1459 auto sz = texture->pixelSize();
1460 if(cpu_tex.width == sz.width() && cpu_tex.height == sz.height())
1465 if(cpu_tex.width > 0 && cpu_tex.height > 0)
1467 QRhiTexture* oldtex = texture;
1468 QRhiTexture* newtex = renderer.
state.rhi->newTexture(
1469 gpp::qrhi::textureFormat(cpu_tex), QSize{cpu_tex.width, cpu_tex.height}, 1,
1470 QRhiTexture::Flag{});
1472 for(
auto& [edge, pass] : self.m_p)
1474 score::gfx::replaceTexture(*pass.p.srb, sampler, newtex);
1479 oldtex->deleteLater();
1486 for(
auto& [edge, pass] : self.m_p)
1488 score::gfx::replaceTexture(*pass.p.srb, sampler, &renderer.emptyTexture());
1494template <avnd::cpu_texture Tex>
1495static void uploadOutputTexture(
auto& self,
1497 QRhiResourceUpdateBatch* res)
1501 if(
auto texture = updateTexture(self, renderer, k, cpu_tex))
1503 if(!cpu_tex.bytes || cpu_tex.bytesize() <= 0)
1505 cpu_tex.changed =
false;
1510 = QByteArray::fromRawData((
const char*)cpu_tex.bytes, cpu_tex.bytesize());
1511 if constexpr(
requires { Tex::RGB; })
1515 const QByteArray rgb = buf;
1517 rgba.resize(cpu_tex.width * cpu_tex.height * 4);
1518 auto src = (
const unsigned char*)rgb.constData();
1519 auto dst = (
unsigned char*)rgba.data();
1520 for(
int rgb_byte = 0, rgba_byte = 0, N = rgb.size(); rgb_byte < N;)
1522 dst[rgba_byte + 0] = src[rgb_byte + 0];
1523 dst[rgba_byte + 1] = src[rgb_byte + 1];
1524 dst[rgba_byte + 2] = src[rgb_byte + 2];
1525 dst[rgba_byte + 3] = 255;
1534 QRhiTextureSubresourceUploadDescription sd(buf);
1535 QRhiTextureUploadDescription desc{QRhiTextureUploadEntry{0, 0, sd}};
1537 res->uploadTexture(texture, desc);
1540 cpu_tex.changed =
false;
1545static const constexpr auto generic_texgen_vs = R
"_(#version 450
1546layout(location = 0) in vec2 position;
1547layout(location = 1) in vec2 texcoord;
1549layout(binding=3) uniform sampler2D y_tex;
1550layout(location = 0) out vec2 v_texcoord;
1552layout(std140, binding = 0) uniform renderer_t {
1553 mat4 clipSpaceCorrMatrix;
1557out gl_PerVertex { vec4 gl_Position; };
1561#if defined(QSHADER_SPIRV) || defined(QSHADER_GLSL)
1562 v_texcoord = vec2(texcoord.x, 1. - texcoord.y);
1564 v_texcoord = texcoord;
1566 gl_Position = renderer.clipSpaceCorrMatrix * vec4(position.xy, 0.0, 1.);
1570static const constexpr auto generic_texgen_fs = R
"_(#version 450
1571layout(location = 0) in vec2 v_texcoord;
1572layout(location = 0) out vec4 fragColor;
1574layout(std140, binding = 0) uniform renderer_t {
1575mat4 clipSpaceCorrMatrix;
1579layout(binding=3) uniform sampler2D y_tex;
1583 fragColor = texture(y_tex, v_texcoord);
1588struct texture_outputs_storage;
1592 requires (avnd::texture_output_introspection<T>::size > 0)
1593struct texture_outputs_storage<T>
1598 self.defaultMeshInit(renderer, mesh, res);
1599 self.processUBOInit(renderer);
1603 std::tie(self.m_vertexS, self.m_fragmentS)
1606 avnd::cpu_texture_output_introspection<T>::for_all(
1607 avnd::get_outputs<T>(*self.state), [&](
auto& t) {
1608 self.m_samplers.push_back(
1609 createOutputTexture(renderer, t.texture, QSize{t.texture.width, t.texture.height}));
1612 self.defaultPassesInit(renderer, mesh);
1615 void runInitialPasses(
auto& self,
1617 QRhiResourceUpdateBatch*& res)
1619 avnd::cpu_texture_output_introspection<T>::for_all_n(
1620 avnd::get_outputs<T>(*self.state), [&]<std::size_t N>(
auto& t, avnd::predicate_index<N>) {
1621 uploadOutputTexture(self, renderer, N, t.texture, res);
1628 for(
auto& [sampl, texture, fb_] : self.m_samplers)
1631 texture->deleteLater();
1639 requires (avnd::texture_output_introspection<T>::size == 0)
1640struct texture_outputs_storage<T>
1646 static void runInitialPasses(
auto& self,
1648 QRhiResourceUpdateBatch*& res)
1657struct geometry_outputs_storage;
1660 requires (avnd::geometry_output_introspection<T>::size > 0)
1661struct geometry_outputs_storage<T>
1663 ossia::geometry_spec specs[avnd::geometry_output_introspection<T>::size];
1665 template <avnd::geometry_port Field>
1666 void reload_mesh(Field& ctrl, ossia::geometry_spec& spc)
1668 spc.meshes = std::make_shared<ossia::mesh_list>();
1669 auto& ossia_meshes = *spc.meshes;
1670 if constexpr(avnd::static_geometry_type<Field> || avnd::dynamic_geometry_type<Field>)
1672 ossia_meshes.meshes.resize(1);
1673 load_geometry(ctrl, ossia_meshes.meshes[0]);
1676 avnd::static_geometry_type<
decltype(Field::mesh)>
1677 || avnd::dynamic_geometry_type<
decltype(Field::mesh)>)
1679 ossia_meshes.meshes.resize(1);
1680 load_geometry(ctrl.mesh, ossia_meshes.meshes[0]);
1684 load_geometry(ctrl, ossia_meshes);
1688 template <avnd::geometry_port Field, std::
size_t N>
1691 avnd::predicate_index<N>)
1693 auto edge_sink = edge.sink;
1694 if(
auto pnode = edge_sink->node)
1696 ossia::geometry_spec& spc = specs[N];
1702 reload_mesh(ctrl, spc);
1708 auto& ossia_meshes = *spc.meshes;
1710 bool any_need_reload =
false;
1711 bool any_need_upload =
false;
1712 if constexpr(avnd::static_geometry_type<Field> || avnd::dynamic_geometry_type<Field>)
1714 SCORE_ASSERT(ossia_meshes.meshes.size() == 1);
1715 auto [need_reload, need_upload]
1716 = update_geometry(ctrl, ossia_meshes.meshes[0]);
1717 any_need_reload = need_reload;
1718 any_need_upload = need_upload;
1721 avnd::static_geometry_type<
decltype(Field::mesh)>
1722 || avnd::dynamic_geometry_type<
decltype(Field::mesh)>)
1724 SCORE_ASSERT(ossia_meshes.meshes.size() == 1);
1725 auto [need_reload, need_upload]
1726 = update_geometry(ctrl.mesh, ossia_meshes.meshes[0]);
1727 any_need_reload = need_reload;
1728 any_need_upload = need_upload;
1732 auto [need_reload, need_upload] = update_geometry(ctrl, ossia_meshes);
1733 any_need_reload = need_reload;
1734 any_need_upload = need_upload;
1739 reload_mesh(ctrl, spc);
1743 ctrl.dirty_mesh =
false;
1749 auto rendered_node = pnode->renderedNodes.find(&renderer);
1750 SCORE_ASSERT(rendered_node != pnode->renderedNodes.end());
1752 auto it = std::find(
1753 edge_sink->node->input.begin(), edge_sink->node->input.end(), edge_sink);
1754 SCORE_ASSERT(it != edge_sink->node->input.end());
1755 int n = it - edge_sink->node->input.begin();
1757 rendered_node->second->process(n, spc, edge.source);
1761 if constexpr(
requires { ctrl.transform; })
1763 if(ctrl.dirty_transform)
1765 ossia::transform3d transform;
1766 std::copy_n(ctrl.transform, std::ssize(ctrl.transform), transform.matrix);
1767 ctrl.dirty_transform =
false;
1769 rendered_node->second->process(n, transform);
1771 pnode->process(n, transform);
1780 avnd::geometry_output_introspection<T>::for_all_n(
1781 avnd::get_outputs(state),
1782 [&](
auto& field,
auto pred) { this->upload(renderer, field, edge, pred); });
1793 requires (avnd::geometry_output_introspection<T>::size == 0)
1794struct geometry_outputs_storage<T>
1796 static void upload(
auto&&...)
1800 static void release(
auto&&...) noexcept { }
1807template <
typename Field>
1808using is_scene_port_t = boost::mp11::mp_bool<scene_port<Field>>;
1810template <
typename T>
1811using scene_output_introspection =
1812 avnd::predicate_introspection<typename avnd::outputs_type<T>::type, is_scene_port_t>;
1814template <
typename T>
1815using scene_input_introspection =
1816 avnd::predicate_introspection<typename avnd::inputs_type<T>::type, is_scene_port_t>;
1822template <
typename T>
1823struct scene_inputs_storage;
1825template <
typename T>
1826 requires(scene_input_introspection<T>::size > 0)
1827struct scene_inputs_storage<T>
1829 void readInputScenes(
const ossia::scene_spec& scene,
auto& state)
1831 scene_input_introspection<T>::for_all(
1832 avnd::get_inputs<T>(state), [&](
auto& field) { field.scene = scene; });
1838template <
typename T>
1839 requires(scene_input_introspection<T>::size == 0)
1840struct scene_inputs_storage<T>
1842 static void readInputScenes(
auto&&...) { }
1843 static void release(
auto&&...) { }
1846template <
typename T>
1847struct scene_outputs_storage;
1849template <
typename T>
1850 requires(scene_output_introspection<T>::size > 0)
1851struct scene_outputs_storage<T>
1853 template <scene_port Field, std::
size_t N>
1856 avnd::predicate_index<N>)
1863 if(!ctrl.scene.state)
1866 auto* edge_sink = edge.sink;
1867 if(!edge_sink || !edge_sink->node)
1871 if(rendered_node == edge_sink->node->renderedNodes.end())
1874 auto it = std::find(
1875 edge_sink->node->input.begin(), edge_sink->node->input.end(), edge_sink);
1876 if(it == edge_sink->node->input.end())
1878 int n = it - edge_sink->node->input.begin();
1885 rendered_node->second->process(n, ctrl.scene, edge.source);
1887 if constexpr(
requires { ctrl.dirty; })
1893 scene_output_introspection<T>::for_all_n(
1894 avnd::get_outputs(state),
1895 [&](
auto& field,
auto pred) { this->upload(renderer, field, edge, pred); });
1907template <
typename T>
1908 requires(scene_output_introspection<T>::size == 0)
1909struct scene_outputs_storage<T>
1911 static void upload(
auto&&...) { }
1912 static void release(
auto&&...) noexcept { }
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
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:114
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:228
Renderer for a given node.
Definition NodeRenderer.hpp:11
Base class for sink nodes (QWindow, spout, syphon, NDI output, ...)
Definition OutputNode.hpp:54
Common base class for nodes that map to score processes.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:201
List of nodes to be rendered to an output.
Definition RenderList.hpp:30
bool requiresDepth(const score::gfx::Port &p) const noexcept
Whether this list of rendering actions requires depth testing at all.
Definition RenderList.cpp:945
const score::gfx::Mesh & defaultTriangle() const noexcept
A triangle mesh correct for this API.
Definition RenderList.cpp:984
RenderState & state
RenderState corresponding to this RenderList.
Definition RenderList.hpp:169
QRhiTexture & emptyTexture() const noexcept
Texture to use when a texture is missing (2D)
Definition RenderList.hpp:192
TreeNode< DeviceExplorerNode > Node
Definition DeviceNode.hpp:74
Definition Controls.hpp:27
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:1238
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:60
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:751
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:117
Definition DocumentContext.hpp:18
Connection between two score::gfx::Port.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:103
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:49
Definition OutputNode.hpp:15
Port of a score::gfx::Node.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:82
score::gfx::Node * node
Parent node of the port.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:84
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:47
Useful abstraction for storing all the data related to a render target.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:152