Loading...
Searching...
No Matches
ISFVisitors.hpp
1#pragma once
2#include <isf.hpp>
3
4#include <ossia/detail/variant.hpp>
5
6#include <span>
7#include <string_view>
8#include <vector>
9
10namespace score::gfx
11{
12// ---------------------------------------------------------------------------
13// Descriptor port walker
14// ---------------------------------------------------------------------------
15//
16// The single source of truth for "how many input ports / output ports /
17// samplers does each desc.inputs entry produce?", shared by CSF port_indices,
18// RawRaster port_idx, RawRaster bindAuxTexturesInit and IsfBindingsBuilder.
19// Mirrors `isf_input_port_vis` in ISFNode.cpp, which is the actual
20// port-creation code.
21//
22// When a new isf::*_input variant is added, update isf_input_port_vis and the
23// matching `operator()` here -- keep them in lockstep.
25{
26 int inlets{};
27 int outlets{};
28 int samplers{};
30
31 port_counts& operator+=(const port_counts& o) noexcept
32 {
33 inlets += o.inlets;
34 outlets += o.outlets;
35 samplers += o.samplers;
36 return *this;
37 }
38};
39
40// Returns the port_counts contributed by a single input variant. Mirrors
41// isf_input_port_vis (ISFNode.cpp) one-to-one.
43{
44 bool raw_raster{};
45
46 port_counts operator()(const isf::float_input&) const noexcept { return {1, 0, 0}; }
47 port_counts operator()(const isf::long_input&) const noexcept { return {1, 0, 0}; }
48 port_counts operator()(const isf::event_input&) const noexcept { return {1, 0, 0}; }
49 port_counts operator()(const isf::bool_input&) const noexcept { return {1, 0, 0}; }
50 port_counts operator()(const isf::point2d_input&) const noexcept { return {1, 0, 0}; }
51 port_counts operator()(const isf::point3d_input&) const noexcept { return {1, 0, 0}; }
52 port_counts operator()(const isf::color_input&) const noexcept { return {1, 0, 0}; }
53 port_counts operator()(const isf::audio_input&) const noexcept { return {1, 0, 0}; }
54 port_counts operator()(const isf::audioHist_input&) const noexcept{ return {1, 0, 0}; }
55 port_counts operator()(const isf::audioFFT_input&) const noexcept { return {1, 0, 0}; }
56
57 port_counts operator()(const isf::image_input& in) const noexcept
58 {
59 // GrabsFromSource means no own render target, so the matching depth
60 // sampler (image_input.depth==true) is not created in initInputSamplers
61 // either.
62 const bool grabs = (in.dimensions == 3 || in.is_array || in.is_static);
63 const int extra_depth_sampler = (in.depth && !grabs) ? 1 : 0;
64 return {1, 0, 1 + extra_depth_sampler};
65 }
66 port_counts operator()(const isf::cubemap_input&) const noexcept { return {1, 0, 1}; }
67 port_counts operator()(const isf::texture_input&) const noexcept { return {1, 0, 1}; }
68
69 port_counts operator()(const isf::storage_input& in) const noexcept
70 {
71 // read_only: 1 input port (no output, no sampler).
72 // write/read_write: 1 output port; +1 input port if the layout's last
73 // field is a flexible array (synthesized long_input for sizing).
74 if(in.access == "read_only")
75 return {1, 0, 0};
76 port_counts c{0, 1, 0};
77 if(!in.layout.empty()
78 && in.layout.back().type.find("[]") != std::string::npos)
79 c.inlets = 1;
80 return c;
81 }
82
83 port_counts operator()(const isf::uniform_input&) const noexcept
84 {
85 return {1, 0, 0};
86 }
87
88 port_counts operator()(const isf::csf_image_input& in) const noexcept
89 {
90 // read_only: 1 input port; write/read_write: 1 output port (no input).
91 if(in.access == "read_only")
92 return {1, 0, 0};
93 return {0, 1, 0};
94 }
95
96 port_counts operator()(const isf::geometry_input& in) const noexcept
97 {
98 port_counts c{};
99 if(!raw_raster)
100 {
101 if(in.passes_through())
102 {
103 // Pass-through: 1 inlet + 1 outlet
104 c.inlets = 1;
105 c.outlets = 1;
106 }
107 else
108 {
109 if(in.reads_upstream())
110 c.inlets = 1;
111 for(const auto& attr : in.attributes)
112 if(attr.access == "write_only" || attr.access == "read_write")
113 { c.outlets = 1; break; }
114 }
115 }
116 // $USER ports → synthesized long_input each (1 inlet)
117 if(in.vertex_count.find("$USER") != std::string::npos) c.inlets++;
118 if(in.instance_count.find("$USER") != std::string::npos) c.inlets++;
119 for(const auto& aux : in.auxiliary)
120 if(aux.size.find("$USER") != std::string::npos)
121 c.inlets++;
122 return c;
123 }
124};
125
126// Walk desc.inputs once. For each input, the visitor receives:
127// - the isf::input entry
128// - the cumulative port_counts BEFORE this input (so cur.inlets is the
129// index of the first input port this entry creates, if any)
130// - the per-input port_counts delta (how many ports this entry creates)
131// Cumulative state is then advanced before moving on.
132//
133// Callers needing a non-zero starting offset (e.g. RawRaster's port 0 is
134// the implicit Geometry input) can pass it in `start` — its inlets/outlets
135// are accumulated upfront.
136template <typename F>
137inline void walk_descriptor_inputs(
138 const isf::descriptor& desc, port_counts start, F&& fn)
139{
140 port_counts cur = start;
141 for(const auto& inp : desc.inputs)
142 {
143 port_counts delta = ossia::visit(
144 isf_input_port_count_vis{desc.mode == isf::descriptor::RawRaster}, inp.data);
145 fn(inp, cur, delta);
146 cur += delta;
147 }
148}
149
150// Convenience overload: zero starting offset.
151template <typename F>
152inline void walk_descriptor_inputs(const isf::descriptor& desc, F&& fn)
153{
154 walk_descriptor_inputs(desc, port_counts{}, std::forward<F>(fn));
155}
156
157// Input port holding the element count of storage resource `name`'s trailing
158// flexible array: the long_input isf_input_port_vis synthesizes for a written
159// buffer. -1 when there is none, notably for a read_only buffer, whose one
160// inlet is the buffer itself.
161inline int storage_array_size_port(const isf::descriptor& desc, std::string_view name)
162{
163 int port = -1;
164 walk_descriptor_inputs(
165 desc, [&](const isf::input& inp, port_counts cur, port_counts delta) {
166 if(port >= 0 || inp.name != name)
167 return;
168 if(auto* s = ossia::get_if<isf::storage_input>(&inp.data);
169 s && s->access != "read_only" && delta.inlets == 1)
170 port = cur.inlets;
171 });
172 return port;
173}
174
176{
177 int sz{};
178 void operator()(const isf::float_input&) noexcept { sz += 4; }
179
180 void operator()(const isf::long_input&) noexcept { sz += 4; }
181
182 void operator()(const isf::event_input&) noexcept
183 {
184 sz += 4; // bool
185 }
186
187 void operator()(const isf::bool_input&) noexcept
188 {
189 sz += 4; // bool
190 }
191
192 void operator()(const isf::point2d_input&) noexcept
193 {
194 if(sz % 8 != 0)
195 sz += 4;
196 sz += 2 * 4;
197 }
198
199 void operator()(const isf::point3d_input&) noexcept
200 {
201 while(sz % 16 != 0)
202 {
203 sz += 4;
204 }
205 sz += 3 * 4;
206 }
207
208 void operator()(const isf::color_input&) noexcept
209 {
210 while(sz % 16 != 0)
211 {
212 sz += 4;
213 }
214 sz += 4 * 4;
215 }
216
217 void operator()(const isf::image_input&) noexcept { }
218 void operator()(const isf::cubemap_input&) noexcept { }
219
220 void operator()(const isf::audio_input&) noexcept { }
221 void operator()(const isf::audioFFT_input&) noexcept { }
222 void operator()(const isf::audioHist_input&) noexcept { }
223
224 // CSF-specific input handlers
225 void operator()(const isf::storage_input& in) noexcept
226 {
227 // Must match what isf_input_port_vis (ISFNode.cpp) writes into the blob,
228 // and the synthesized "size" int it creates: only a writable buffer whose
229 // layout ends in a flexible-array member. Reserving for every write buffer
230 // would desync this from the port visitor and the generated GLSL
231 // Params/material_t block.
232 if(in.access.contains("write") && !in.layout.empty()
233 && in.layout.back().type.find("[]") != std::string::npos)
234 {
235 (*this)(isf::long_input{});
236 }
237 }
238
239 void operator()(const isf::uniform_input&) noexcept
240 {
241 // UBO inputs are bound from an upstream Buffer port; they do not
242 // contribute to the material UBO size.
243 }
244
245 void operator()(const isf::texture_input in) noexcept { }
246
247 void operator()(const isf::csf_image_input& in) noexcept
248 {
249 // isf_input_port_vis writes nothing into the material blob for write
250 // csf_image inputs (its point2d/long synthesis is commented out), so
251 // reserve nothing here: the size visitor, the port visitor and the
252 // generated uniform block must agree.
253 }
254
255 void operator()(const isf::geometry_input& in) noexcept
256 {
257 // Geometry inputs don't contribute to the material UBO size.
258 // Their buffers are bound separately as SSBOs.
259 // But $USER ports create long_input (int) entries in the material UBO.
260 if(in.vertex_count.find("$USER") != std::string::npos)
261 (*this)(isf::long_input{});
262 if(in.instance_count.find("$USER") != std::string::npos)
263 (*this)(isf::long_input{});
264 for(const auto& aux : in.auxiliary)
265 if(aux.size.find("$USER") != std::string::npos)
266 (*this)(isf::long_input{});
267 }
268};
269
270// The image-like and audio samplers of `desc` in the order libisf declares
271// them: INPUTS order, an image-like input's port_counts::samplers and an
272// audio / audioFFT / audioHist input's one in turn (isf.cpp, the material
273// sampler loop). `image` holds the image-like inputs' samplers in port order,
274// storage images left out; `audio` the audio inputs' in INPUTS order. Any the
275// walk does not account for are appended, so none is lost.
276//
277// Binding the image ones first and the audio ones after, as the renderers
278// did, swapped textures whenever an audio input came before an image input.
279template <typename S>
280std::vector<S> samplersInInputOrder(
281 const isf::descriptor& desc, std::span<const S> image, std::span<const S> audio)
282{
283 std::vector<S> out;
284 out.reserve(image.size() + audio.size());
285 std::size_t i = 0, a = 0;
286 walk_descriptor_inputs(desc, [&](const isf::input& inp, port_counts, port_counts delta) {
287 if(ossia::get_if<isf::audio_input>(&inp.data)
288 || ossia::get_if<isf::audioFFT_input>(&inp.data)
289 || ossia::get_if<isf::audioHist_input>(&inp.data))
290 {
291 if(a < audio.size())
292 out.push_back(audio[a++]);
293 return;
294 }
295 for(int k = 0; k < delta.samplers && i < image.size(); k++)
296 out.push_back(image[i++]);
297 });
298 out.insert(out.end(), image.begin() + i, image.end());
299 out.insert(out.end(), audio.begin() + a, audio.end());
300 return out;
301}
302}
Graphics rendering pipeline for ossia score.
Definition Filter/PreviewWidget.hpp:11
Definition Uniforms.hpp:5
Definition ISFVisitors.hpp:43
Definition ISFVisitors.hpp:176
Definition ISFVisitors.hpp:25
int outlets
score output ports created
Definition ISFVisitors.hpp:27
int samplers
Definition ISFVisitors.hpp:28
int inlets
score input ports created by this desc.inputs entry
Definition ISFVisitors.hpp:26