Loading...
Searching...
No Matches
ISFProcess.hpp
1#pragma once
2
3#include <Process/Dataflow/WidgetInlets.hpp>
4#include <Process/ProcessFactory.hpp>
5
6#include <Explorer/DocumentPlugin/DeviceDocumentPlugin.hpp>
7
8#include <Gfx/TexturePort.hpp>
9#include <Gfx/WindowDevice.hpp>
10
11#include <score/tools/File.hpp>
12
13#include <ossia/detail/flat_map.hpp>
14
15#include <QFile>
16
17#include <isf.hpp>
18
19#include <algorithm>
20
21namespace Gfx
22{
24{
25 template <typename T>
26 static Process::Descriptor descriptorFromISFFile(QString path)
27 {
29 if(path.isEmpty())
30 return base;
31
32 QFile f{path};
33 if(!f.open(QIODevice::ReadOnly))
34 return base;
35
36 try
37 {
38 auto [_, desc] = isf::parser::parse_isf_header(score::readFileAsString(f));
39 if(desc.credits.starts_with("Automatically converted from "))
40 desc.credits = desc.credits.substr(strlen("Automatically converted from "));
41 else if(desc.credits.starts_with("by "))
42 desc.credits = desc.credits.substr(strlen("by "));
43 if(!desc.credits.empty())
44 base.author = QString::fromStdString(desc.credits);
45 if(!desc.description.empty())
46 base.description = QString::fromStdString(desc.description);
47 for(auto& cat : desc.categories)
48 base.tags.push_back(QString::fromStdString(cat));
49 }
50 catch(...)
51 {
52 }
53
54 return base;
55 }
56
57 template <typename T>
58 static void setupISFModelPorts(
59 T& self, const isf::descriptor& desc,
60 const ossia::flat_map<QString, ossia::value>& previous_values)
61 {
62 /*
63 {
64 auto& [shader, error] = score::gfx::ShaderCache::get(
65 m_processedProgram.vertex.toLatin1(), QShader::Stage::VertexStage);
66 SCORE_ASSERT(error.isEmpty());
67 }
68 {
69 auto& [shader, error] = score::gfx::ShaderCache::get(
70 m_processedProgram.fragment.toLatin1(), QShader::Stage::FragmentStage);
71 SCORE_ASSERT(error.isEmpty());
72 }
73 */
74
75 int i = 0;
76 using namespace isf;
77
78 struct input_vis
79 {
80 const ossia::flat_map<QString, ossia::value>& previous_values;
81 const isf::input& input;
82 const int i;
83 T& self;
84 // Outlet id allocator for write-access storage / image inputs. Starts at
85 // a high base so it never collides with inlet ids (input index `i`), the
86 // default "Texture Out" outlet (id 1), or the MRT outlet base (10000).
87 int& outlet_id;
88
89 Process::Inlet* operator()(const float_input& v)
90 {
91 auto nm = QString::fromStdString(input.name);
92 auto port = new Process::FloatSlider(
93 v.min, v.max, v.def, nm, Id<Process::Port>(i), &self);
94
95 self.m_inlets.push_back(port);
96 if(auto it = previous_values.find(nm);
97 it != previous_values.end()
98 && it->second.get_type() == ossia::val_type::FLOAT)
99 port->setValue(it->second);
100
101 self.controlAdded(port->id());
102 return port;
103 }
104
105 Process::Inlet* operator()(const long_input& v)
106 {
107 auto nm = QString::fromStdString(input.name);
108
109 // Numeric mode: MIN/MAX set and no VALUES/LABELS → IntSpinBox
110 if(v.values.empty() && v.min && v.max)
111 {
112 auto port = new Process::IntSpinBox(
113 *v.min, *v.max, (int)v.def, nm, Id<Process::Port>(i), &self);
114
115 if(auto it = previous_values.find(nm);
116 it != previous_values.end()
117 && it->second.get_type() == port->value().get_type())
118 port->setValue(it->second);
119
120 self.m_inlets.push_back(port);
121 self.controlAdded(port->id());
122 return port;
123 }
124
125 // Enum mode: VALUES/LABELS → ComboBox
126 std::vector<std::pair<QString, ossia::value>> alternatives;
127 if(v.labels.size() == v.values.size())
128 {
129 for(std::size_t value_idx = 0; value_idx < v.values.size(); value_idx++)
130 {
131 auto& val = v.values[value_idx];
132 if(auto int_ptr = ossia::get_if<int64_t>(&val))
133 {
134 alternatives.emplace_back(
135 QString::fromStdString(v.labels[value_idx]), int(*int_ptr));
136 }
137 else if(auto dbl_ptr = ossia::get_if<double>(&val))
138 {
139 alternatives.emplace_back(
140 QString::fromStdString(v.labels[value_idx]), int(*dbl_ptr));
141 }
142 else
143 {
144 alternatives.emplace_back(
145 QString::fromStdString(v.labels[value_idx]), int(value_idx));
146 }
147 }
148 }
149 else
150 {
151 for(std::size_t value_idx = 0; value_idx < v.values.size(); value_idx++)
152 {
153 auto& val = v.values[value_idx];
154 if(auto int_ptr = ossia::get_if<int64_t>(&val))
155 {
156 alternatives.emplace_back(QString::number(*int_ptr), int(*int_ptr));
157 }
158 else if(auto dbl_ptr = ossia::get_if<double>(&val))
159 {
160 alternatives.emplace_back(QString::number(*dbl_ptr), int(*dbl_ptr));
161 }
162 else if(auto str_ptr = ossia::get_if<std::string>(&val))
163 {
164 alternatives.emplace_back(
165 QString::fromStdString(*str_ptr), int(value_idx));
166 }
167 }
168 }
169
170 if(alternatives.empty())
171 {
172 alternatives.emplace_back("0", 0);
173 alternatives.emplace_back("1", 1);
174 alternatives.emplace_back("2", 2);
175 }
176
177 // ComboBox::init expects the value to be initially selected, not an
178 // index, while libisf's `v.def` is the index into values for enum
179 // mode: look up the alternative at v.def and forward its value.
180 // CSF/Process.cpp and GeometryFilter/Process.cpp do the same.
181 const std::size_t def_idx
182 = std::min<std::size_t>(v.def, alternatives.size() - 1);
183 const ossia::value& init_value = alternatives[def_idx].second;
184
185 auto port = new Process::ComboBox(
186 std::move(alternatives), init_value, nm, Id<Process::Port>(i), &self);
187
188 if(auto it = previous_values.find(nm);
189 it != previous_values.end()
190 && it->second.get_type() == port->value().get_type())
191 port->setValue(it->second);
192
193 self.m_inlets.push_back(port);
194 self.controlAdded(port->id());
195 return port;
196 }
197
198 Process::Inlet* operator()(const event_input& v)
199 {
200 auto nm = QString::fromStdString(input.name);
201 auto port = new Process::Button(nm, Id<Process::Port>(i), &self);
202
203 self.m_inlets.push_back(port);
204 self.controlAdded(port->id());
205 return port;
206 }
207
208 Process::Inlet* operator()(const bool_input& v)
209 {
210 auto nm = QString::fromStdString(input.name);
211 auto port = new Process::Toggle(v.def, nm, Id<Process::Port>(i), &self);
212
213 if(auto it = previous_values.find(nm);
214 it != previous_values.end()
215 && it->second.get_type() == port->value().get_type())
216 port->setValue(it->second);
217
218 self.m_inlets.push_back(port);
219 self.controlAdded(port->id());
220 return port;
221 }
222
223 Process::Inlet* operator()(const point2d_input& v)
224 {
225 auto nm = QString::fromStdString(input.name);
226 ossia::vec2f min{-100., -100.};
227 ossia::vec2f max{100., 100.};
228 ossia::vec2f init{0.0, 0.0};
229 if(v.def)
230 std::copy_n(v.def->begin(), 2, init.begin());
231 if(v.min)
232 std::copy_n(v.min->begin(), 2, min.begin());
233 if(v.max)
234 std::copy_n(v.max->begin(), 2, max.begin());
235 auto port = new Process::XYSpinboxes{
236 min, max, init, false, nm, Id<Process::Port>(i), &self};
237
238 auto& ctx = score::IDocument::documentContext(self);
239 auto& device_plug = ctx.template plugin<Explorer::DeviceDocumentPlugin>();
240 const Device::DeviceList& list = device_plug.list();
241 QString firstWindowDeviceName;
242 for(auto dev : list.devices())
243 {
244 if(auto win = qobject_cast<WindowDevice*>(dev))
245 {
246 firstWindowDeviceName = win->name();
247 break;
248 }
249 }
250
251 if(!firstWindowDeviceName.isEmpty())
252 {
253 if(nm.contains("iMouse"))
254 port->setAddress(
256 State::Address{firstWindowDeviceName, {"cursor", "absolute"}}});
257 else if(nm.contains("mouse", Qt::CaseInsensitive))
258 port->setAddress(
260 State::Address{firstWindowDeviceName, {"cursor", "gl"}}});
261 }
262
263 if(auto it = previous_values.find(nm);
264 it != previous_values.end()
265 && it->second.get_type() == port->value().get_type())
266 port->setValue(it->second);
267
268 self.m_inlets.push_back(port);
269 self.controlAdded(port->id());
270 return port;
271 }
272
273 Process::Inlet* operator()(const point3d_input& v)
274 {
275 auto nm = QString::fromStdString(input.name);
276 ossia::vec3f min{-100., -100., -100.};
277 ossia::vec3f max{100., 100., 100.};
278 ossia::vec3f init{0., 0., 0.};
279 if(v.def)
280 std::copy_n(v.def->begin(), 3, init.begin());
281 if(v.min)
282 std::copy_n(v.min->begin(), 3, min.begin());
283 if(v.max)
284 std::copy_n(v.max->begin(), 3, max.begin());
285 auto port = new Process::XYZSpinboxes{
286 min, max, init, false, nm, Id<Process::Port>(i), &self};
287
288 if(auto it = previous_values.find(nm);
289 it != previous_values.end()
290 && it->second.get_type() == port->value().get_type())
291 port->setValue(it->second);
292
293 self.m_inlets.push_back(port);
294 self.controlAdded(port->id());
295 return port;
296 }
297
298 Process::Inlet* operator()(const color_input& v)
299 {
300 auto nm = QString::fromStdString(input.name);
301 ossia::vec4f init{0.5, 0.5, 0.5, 1.};
302 if(v.def)
303 {
304 std::copy_n(v.def->begin(), 4, init.begin());
305 }
306 auto port = new Process::HSVSlider(
307 init, QString::fromStdString(input.name), Id<Process::Port>(i), &self);
308
309 if(auto it = previous_values.find(nm);
310 it != previous_values.end()
311 && it->second.get_type() == port->value().get_type())
312 port->setValue(it->second);
313
314 self.m_inlets.push_back(port);
315 self.controlAdded(port->id());
316 return port;
317 }
318 Process::Inlet* operator()(const image_input& v)
319 {
320 auto port = new Gfx::TextureInlet(
321 QString::fromStdString(input.name), Id<Process::Port>(i), &self);
322
323 self.m_inlets.push_back(port);
324 return port;
325 }
326 Process::Inlet* operator()(const cubemap_input& v)
327 {
328 auto port = new Gfx::TextureInlet(
329 QString::fromStdString(input.name), Id<Process::Port>(i), &self);
330
331 self.m_inlets.push_back(port);
332 return port;
333 }
334 Process::Inlet* operator()(const audio_input& v)
335 {
336 auto port = new Process::AudioInlet(
337 QString::fromStdString(input.name), Id<Process::Port>(i), &self);
338 self.m_inlets.push_back(port);
339 return port;
340 }
341 Process::Inlet* operator()(const audioFFT_input& v)
342 {
343 auto port = new Process::AudioInlet(
344 QString::fromStdString(input.name), Id<Process::Port>(i), &self);
345 self.m_inlets.push_back(port);
346 return port;
347 }
348 Process::Inlet* operator()(const audioHist_input& v)
349 {
350 auto port = new Process::AudioInlet(
351 QString::fromStdString(input.name), Id<Process::Port>(i), &self);
352 self.m_inlets.push_back(port);
353 return port;
354 }
355
356 // CSF-specific input handlers
357 Process::Inlet* operator()(const storage_input& v)
358 {
359 // Mirror the renderer (isf_input_port_vis in ISFNode.cpp): the access
360 // qualifier decides inlet vs outlet. Treating every storage_input as a
361 // read inlet would give write buffers a phantom TextureInlet, shifting
362 // every later port by one (positional routing) and never exposing the
363 // TextureOutlet the renderer produces.
364 if(v.access == "read_only")
365 {
366 // read inlet: an upstream Buffer-producing node (ScenePreprocessor's
367 // scene_* auxes, ExtractBuffer2 outputs, ...) has a target to land on.
368 // For aux-named storage_inputs the RawRaster renderer also auto-binds
369 // by name, so this inlet is optional but allows explicit wiring.
370 auto port = new Gfx::TextureInlet(
371 QString::fromStdString(input.name), Id<Process::Port>(i), &self);
372 self.m_inlets.push_back(port);
373 return port;
374 }
375
376 // write_only / read_write: the renderer pushes a Buffer output port for
377 // the produced SSBO so downstream nodes can connect to it.
378 auto outport = new Gfx::TextureOutlet(
379 QString::fromStdString(input.name), Id<Process::Port>(outlet_id++),
380 &self);
381 self.m_outlets.push_back(outport);
382
383 // Conditional sizing inlet: only buffers whose layout ends in a
384 // flexible-array member synthesize a "size" control -- the same
385 // condition as CSF/Process.cpp setupCSF, the renderer and the
386 // generated GLSL.
387 if(!v.layout.empty()
388 && v.layout.back().type.find("[]") != std::string::npos)
389 {
390 auto size_inl = new Process::IntSpinBox{
391 1, 536870911, 1024,
392 QString::fromStdString(input.name) + " size",
393 Id<Process::Port>(i), &self};
394 self.m_inlets.push_back(size_inl);
395 self.controlAdded(size_inl->id());
396 return size_inl;
397 }
398 return nullptr;
399 }
400 Process::Inlet* operator()(const uniform_input& v)
401 {
402 // uniform_input expects an upstream Buffer port (ScenePreprocessor's
403 // camera/env aux buffers, ExtractBuffer2 outputs, etc.). TextureInlet
404 // is score's Process-layer inlet for SSBO / texture / UBO data flow.
405 // Without this, the Process model has no inlet for the cable to land
406 // on and Score.inlet(proc, i) returns null.
407 auto port = new Gfx::TextureInlet(
408 QString::fromStdString(input.name), Id<Process::Port>(i), &self);
409 self.m_inlets.push_back(port);
410 return port;
411 }
412 Process::Inlet* operator()(const texture_input& v)
413 {
414 // The renderer (isf_input_port_vis) creates an Image input port for
415 // every texture_input, so the model must create the matching inlet:
416 // returning nullptr here would shift every subsequent port.
417 auto port = new Gfx::TextureInlet(
418 QString::fromStdString(input.name), Id<Process::Port>(i), &self);
419 self.m_inlets.push_back(port);
420 return port;
421 }
422 Process::Inlet* operator()(const csf_image_input& v)
423 {
424 // Mirror the renderer: read_only → input port (an upstream texture
425 // cable lands on it); write_only / read_write → output port for the
426 // produced storage image. Always creating an inlet would give write
427 // images a phantom inlet (port shift) and no outlet for downstream
428 // connection.
429 if(v.access == "read_only")
430 {
431 auto port = new Gfx::TextureInlet(
432 QString::fromStdString(input.name), Id<Process::Port>(i), &self);
433 self.m_inlets.push_back(port);
434 return port;
435 }
436 auto outport = new Gfx::TextureOutlet(
437 QString::fromStdString(input.name), Id<Process::Port>(outlet_id++),
438 &self);
439 self.m_outlets.push_back(outport);
440 return nullptr;
441 }
442 Process::Inlet* operator()(const geometry_input& v) { return nullptr; }
443 };
444
445 // Outlet ids for write-access storage / image inputs. Base 20000 keeps
446 // them clear of inlet ids (input index), the default outlet (id 1) and the
447 // MRT base (10000), and lets the MRT block below tell them apart.
448 static constexpr int storage_outlet_base = 20000;
449 int outlet_id = storage_outlet_base;
450
451 for(const isf::input& input : desc.inputs)
452 {
453 ossia::visit(input_vis{previous_values, input, i, self, outlet_id}, input.data);
454 i++;
455 }
456
457 // The renderer (isf_input_port_vis) pushes write-storage / write-image
458 // output ports first (in input order), then the color / MRT outputs. The
459 // model's outlets must follow the same order for positional routing. The
460 // default "Texture Out" outlet was created by the constructor *before* this
461 // loop, so it currently sits ahead of any storage outlets — pull the
462 // storage outlets (ids >= storage_outlet_base) to the front to match.
463 {
464 std::stable_partition(
465 self.m_outlets.begin(), self.m_outlets.end(),
466 [](Process::Outlet* o) { return o->id().val() >= storage_outlet_base; });
467 }
468
469 // MRT: recreate the color outlets from OUTPUTS declarations. Preserve the
470 // storage / image write outlets (ids >= storage_outlet_base); only the
471 // color / default outlets are replaced.
472 if(!desc.outputs.empty())
473 {
474 for(auto it = self.m_outlets.begin(); it != self.m_outlets.end();)
475 {
476 if((*it)->id().val() < storage_outlet_base)
477 {
478 delete *it;
479 it = self.m_outlets.erase(it);
480 }
481 else
482 {
483 ++it;
484 }
485 }
486
487 int outId = 10000; // High base to avoid ID collisions with inlets
488 for(const auto& out : desc.outputs)
489 {
490 self.m_outlets.push_back(new Gfx::TextureOutlet{
491 QString::fromStdString(out.name),
492 Id<Process::Port>(outId++), &self});
493 }
494 }
495 }
496};
497}
The DeviceList class.
Definition DeviceList.hpp:26
Definition Port.hpp:327
Definition Port.hpp:192
Definition Port.hpp:300
The id_base_t class.
Definition Identifier.hpp:59
Binds the rendering pipeline to ossia processes.
Definition AssetTable.cpp:7
Definition ISFProcess.hpp:24
Static metadata implementation.
Definition lib/score/tools/Metadata.hpp:36
Definition WidgetInlets.hpp:536
Definition WidgetInlets.hpp:467
Definition score-lib-process/Process/ProcessMetadata.hpp:36
Definition WidgetInlets.hpp:160
Definition WidgetInlets.hpp:560
Definition WidgetInlets.hpp:283
Definition WidgetInlets.hpp:326
Definition WidgetInlets.hpp:609
Definition WidgetInlets.hpp:633
Definition Address.hpp:108
The Address struct.
Definition Address.hpp:58