Loading...
Searching...
No Matches
MultiWindowDevice.hpp
1#pragma once
2#include <Gfx/GfxParameter.hpp>
3#include <Gfx/Graph/MultiWindowNode.hpp>
4#include <Gfx/Graph/Window.hpp>
5#include <Gfx/Settings/Model.hpp>
6
7#include <ossia/network/base/device.hpp>
8#include <ossia/network/base/protocol.hpp>
9#include <ossia/network/generic/generic_node.hpp>
10
11#include <ossia-qt/invoke.hpp>
12
13#include <QGuiApplication>
14namespace Gfx
15{
16static score::gfx::MultiWindowNode* createMultiWindowNode(
17 const std::vector<OutputMapping>& mappings,
18 SwapchainFlag swapFlag = SwapchainFlag::NoFlag,
19 SwapchainFormat swapFormat = SwapchainFormat::SDR)
20{
21 const auto& gfx_settings = score::AppContext().settings<Gfx::Settings::Model>();
22
24 double rate = gfx_settings.getRate();
25 if(rate > 0)
26 conf = {.manualRenderingRate = 1000. / rate, .supportsVSync = false};
27 else
28 conf = {.manualRenderingRate = 1000. / 60., .supportsVSync = false};
29
30 auto node = new score::gfx::MultiWindowNode{conf, mappings};
31 node->setSwapchainFlag(swapFlag);
32 node->setSwapchainFormat(swapFormat);
33 return node;
34}
35
36class multiwindow_device : public ossia::net::device_base
37{
39 gfx_node_base m_root;
40 QObject m_qtContext;
41
42 ossia::net::parameter_base* fps_param{};
43 ossia::net::parameter_base* rendersize_param{};
44
45 struct PerWindowParams
46 {
47 ossia::net::parameter_base* scaled_cursor{};
48 ossia::net::parameter_base* abs_cursor{};
49 ossia::net::parameter_base* size_param{};
50 ossia::net::parameter_base* pos_param{};
51 ossia::net::parameter_base* source_pos{};
52 ossia::net::parameter_base* source_size{};
53 ossia::net::parameter_base* key_press_code{};
54 ossia::net::parameter_base* key_press_text{};
55 ossia::net::parameter_base* key_release_code{};
56 ossia::net::parameter_base* key_release_text{};
57
58 std::vector<QMetaObject::Connection> connections;
59 };
60 std::vector<PerWindowParams> m_perWindow;
61
62 void update_viewport(PerWindowParams& pw)
63 {
64 if(!rendersize_param || !pw.abs_cursor)
65 return;
66
67 auto v = rendersize_param->value();
68 if(auto val = v.target<ossia::vec2f>())
69 {
70 if((*val)[0] >= 1.f && (*val)[1] >= 1.f)
71 {
72 auto dom = pw.abs_cursor->get_domain();
73 ossia::set_max(dom, *val);
74 pw.abs_cursor->set_domain(std::move(dom));
75 }
76 else if(pw.size_param)
77 {
78 v = pw.size_param->value();
79 if(auto sz = v.target<ossia::vec2f>())
80 {
81 if((*sz)[0] >= 1.f && (*sz)[1] >= 1.f)
82 {
83 auto dom = pw.abs_cursor->get_domain();
84 ossia::set_max(dom, *sz);
85 pw.abs_cursor->set_domain(std::move(dom));
86 }
87 }
88 }
89 }
90 }
91
92public:
94 const std::vector<OutputMapping>& mappings,
95 std::unique_ptr<gfx_protocol_base> proto, std::string name,
96 SwapchainFlag swapFlag = SwapchainFlag::NoFlag,
97 SwapchainFormat swapFormat = SwapchainFormat::SDR)
98 : ossia::net::device_base{std::move(proto)}
99 , m_node{createMultiWindowNode(mappings, swapFlag, swapFormat)}
100 , m_root{*this, *static_cast<gfx_protocol_base*>(m_protocol.get()), m_node, name}
101 {
102 this->m_capabilities.change_tree = true;
103
104 // Connect window signals once windows are created by createOutput()
105 m_node->onWindowsCreated = [this] { connectWindowSignals(); };
106
107 // Global FPS output parameter
108 {
109 auto fps_node = std::make_unique<ossia::net::generic_node>("fps", *this, m_root);
110 fps_param = fps_node->create_parameter(ossia::val_type::FLOAT);
111 m_node->onFps = [this](float fps) { fps_param->push_value(fps); };
112 m_root.add_child(std::move(fps_node));
113 }
114
115 // Global rendersize parameter
116 {
117 auto rs_node
118 = std::make_unique<ossia::net::generic_node>("rendersize", *this, m_root);
119 ossia::net::set_description(*rs_node, "Set to [0, 0] to use the viewport's size");
120 rendersize_param = rs_node->create_parameter(ossia::val_type::VEC2F);
121 rendersize_param->push_value(ossia::vec2f{0.f, 0.f});
122 rendersize_param->add_callback([this](const ossia::value& v) {
123 if(auto val = v.target<ossia::vec2f>())
124 {
125 m_node->setRenderSize({(int)(*val)[0], (int)(*val)[1]});
126 for(auto& pw : m_perWindow)
127 update_viewport(pw);
128 }
129 });
130 m_root.add_child(std::move(rs_node));
131 }
132
133 // Per-window subtrees: /0, /1, /2, ...
134 m_perWindow.resize(mappings.size());
135
136 for(int i = 0; i < (int)mappings.size(); ++i)
137 {
138 auto& pw = m_perWindow[i];
139 const auto& mapping = mappings[i];
140 const auto idx_str = std::to_string(i);
141
142 auto win_node = std::make_unique<ossia::net::generic_node>(idx_str, *this, m_root);
143
144 // /i/cursor
145 {
146 auto cursor_node
147 = std::make_unique<ossia::net::generic_node>("cursor", *this, *win_node);
148 {
149 auto scale_node = std::make_unique<ossia::net::generic_node>(
150 "scaled", *this, *cursor_node);
151 pw.scaled_cursor = scale_node->create_parameter(ossia::val_type::VEC2F);
152 pw.scaled_cursor->set_domain(ossia::make_domain(0.f, 1.f));
153 pw.scaled_cursor->push_value(ossia::vec2f{0.f, 0.f});
154 cursor_node->add_child(std::move(scale_node));
155 }
156 {
157 auto abs_node = std::make_unique<ossia::net::generic_node>(
158 "absolute", *this, *cursor_node);
159 pw.abs_cursor = abs_node->create_parameter(ossia::val_type::VEC2F);
160 pw.abs_cursor->set_domain(
161 ossia::make_domain(
162 ossia::vec2f{0.f, 0.f}, ossia::vec2f{
163 (float)mapping.windowSize.width(),
164 (float)mapping.windowSize.height()}));
165 pw.abs_cursor->push_value(ossia::vec2f{0.f, 0.f});
166 cursor_node->add_child(std::move(abs_node));
167 }
168 win_node->add_child(std::move(cursor_node));
169 }
170
171 // /i/size
172 {
173 auto size_node
174 = std::make_unique<ossia::net::generic_node>("size", *this, *win_node);
175 pw.size_param = size_node->create_parameter(ossia::val_type::VEC2F);
176 pw.size_param->push_value(
177 ossia::vec2f{
178 (float)mapping.windowSize.width(), (float)mapping.windowSize.height()});
179 pw.size_param->add_callback([this, i](const ossia::value& v) {
180 if(auto val = v.target<ossia::vec2f>())
181 {
182 ossia::qt::run_async(&m_qtContext, [this, i, v = *val] {
183 const auto& outputs = m_node->windowOutputs();
184 if(i < (int)outputs.size())
185 {
186 if(auto& w = outputs[i].window)
187 w->resize(QSize{(int)v[0], (int)v[1]});
188 }
189 });
190 if(i < (int)m_perWindow.size())
191 update_viewport(m_perWindow[i]);
192 }
193 });
194 win_node->add_child(std::move(size_node));
195 }
196
197 // /i/position
198 {
199 auto pos_node
200 = std::make_unique<ossia::net::generic_node>("position", *this, *win_node);
201 pw.pos_param = pos_node->create_parameter(ossia::val_type::VEC2F);
202 pw.pos_param->push_value(
203 ossia::vec2f{
204 (float)mapping.windowPosition.x(), (float)mapping.windowPosition.y()});
205 pw.pos_param->add_callback([this, i](const ossia::value& v) {
206 if(auto val = v.target<ossia::vec2f>())
207 {
208 ossia::qt::run_async(&m_qtContext, [this, i, v = *val] {
209 const auto& outputs = m_node->windowOutputs();
210 if(i < (int)outputs.size())
211 {
212 if(auto& w = outputs[i].window)
213 w->setPosition(QPoint{(int)v[0], (int)v[1]});
214 }
215 });
216 }
217 });
218 win_node->add_child(std::move(pos_node));
219 }
220
221 // /i/fullscreen
222 {
223 auto fs_node
224 = std::make_unique<ossia::net::generic_node>("fullscreen", *this, *win_node);
225 auto fs_param = fs_node->create_parameter(ossia::val_type::BOOL);
226 fs_param->push_value(mapping.fullscreen);
227 fs_param->add_callback([this, i](const ossia::value& v) {
228 if(auto val = v.target<bool>())
229 {
230 ossia::qt::run_async(&m_qtContext, [this, i, v = *val] {
231 const auto& outputs = m_node->windowOutputs();
232 if(i < (int)outputs.size())
233 {
234 if(auto& w = outputs[i].window)
235 {
236 if(v)
237 w->showFullScreen();
238 else
239 w->showNormal();
240 }
241 }
242 });
243 }
244 });
245 win_node->add_child(std::move(fs_node));
246 }
247
248 // /i/screen (accepts an integer index or a screen name string)
249 {
250 auto screen_node
251 = std::make_unique<ossia::net::generic_node>("screen", *this, *win_node);
252 auto screen_param = screen_node->create_parameter(ossia::val_type::STRING);
253 screen_param->push_value(
254 mapping.screenIndex >= 0 ? std::to_string(mapping.screenIndex)
255 : std::string{});
256 screen_param->add_callback([this, i](const ossia::value& v) {
257 auto apply = [this, i](const std::string& scr) {
258 const auto& outputs = m_node->windowOutputs();
259 if(i >= (int)outputs.size())
260 return;
261 auto& w = outputs[i].window;
262 if(!w)
263 return;
264
265 const auto& cur_screens = qGuiApp->screens();
266
267 // Try parsing as integer index first
268 bool ok = false;
269 int idx = QString::fromStdString(scr).toInt(&ok);
270 if(ok)
271 {
272 if(ossia::valid_index(idx, cur_screens))
273 w->setScreen(cur_screens[idx]);
274 return;
275 }
276
277 // Otherwise match by screen name
278 for(auto s : cur_screens)
279 {
280 if(s->name().toStdString() == scr)
281 {
282 w->setScreen(s);
283 return;
284 }
285 }
286 };
287
288 if(auto val = v.target<std::string>())
289 {
290 ossia::qt::run_async(
291 &m_qtContext, [apply, scr = *val] { apply(scr); });
292 }
293 else if(auto val = v.target<int>())
294 {
295 ossia::qt::run_async(
296 &m_qtContext, [apply, scr = std::to_string(*val)] { apply(scr); });
297 }
298 });
299 win_node->add_child(std::move(screen_node));
300 }
301
302 // /i/source (UV mapping rectangle)
303 {
304 auto source_node
305 = std::make_unique<ossia::net::generic_node>("source", *this, *win_node);
306 {
307 auto pos_node = std::make_unique<ossia::net::generic_node>(
308 "position", *this, *source_node);
309 ossia::net::set_description(*pos_node, "UV position of source rect (0-1)");
310 pw.source_pos = pos_node->create_parameter(ossia::val_type::VEC2F);
311 pw.source_pos->set_domain(ossia::make_domain(0.f, 1.f));
312 pw.source_pos->push_value(
313 ossia::vec2f{
314 (float)mapping.sourceRect.x(), (float)mapping.sourceRect.y()});
315 pw.source_pos->add_callback([this, i](const ossia::value& v) {
316 if(auto val = v.target<ossia::vec2f>())
317 {
318 const auto& outputs = m_node->windowOutputs();
319 if(i < (int)outputs.size())
320 {
321 auto r = outputs[i].sourceRect;
322 r.moveTopLeft(QPointF{(*val)[0], (*val)[1]});
323 m_node->setSourceRect(i, r);
324 }
325 }
326 });
327 source_node->add_child(std::move(pos_node));
328 }
329 {
330 auto sz_node
331 = std::make_unique<ossia::net::generic_node>("size", *this, *source_node);
332 ossia::net::set_description(*sz_node, "UV size of source rect (0-1)");
333 pw.source_size = sz_node->create_parameter(ossia::val_type::VEC2F);
334 pw.source_size->set_domain(ossia::make_domain(0.f, 1.f));
335 pw.source_size->push_value(
336 ossia::vec2f{
337 (float)mapping.sourceRect.width(),
338 (float)mapping.sourceRect.height()});
339 pw.source_size->add_callback([this, i](const ossia::value& v) {
340 if(auto val = v.target<ossia::vec2f>())
341 {
342 const auto& outputs = m_node->windowOutputs();
343 if(i < (int)outputs.size())
344 {
345 auto r = outputs[i].sourceRect;
346 r.setSize(QSizeF{(*val)[0], (*val)[1]});
347 m_node->setSourceRect(i, r);
348 }
349 }
350 });
351 source_node->add_child(std::move(sz_node));
352 }
353 win_node->add_child(std::move(source_node));
354 }
355
356 // /i/blend (soft-edge blending per side)
357 {
358 auto blend_node
359 = std::make_unique<ossia::net::generic_node>("blend", *this, *win_node);
360
361 struct BlendSide
362 {
363 const char* name;
364 int side;
365 float initW;
366 float initG;
367 };
368 BlendSide sides[]
369 = {{"left", 0, mapping.blendLeft.width, mapping.blendLeft.gamma},
370 {"right", 1, mapping.blendRight.width, mapping.blendRight.gamma},
371 {"top", 2, mapping.blendTop.width, mapping.blendTop.gamma},
372 {"bottom", 3, mapping.blendBottom.width, mapping.blendBottom.gamma}};
373
374 for(auto& [sname, side, initW, initG] : sides)
375 {
376 auto side_node
377 = std::make_unique<ossia::net::generic_node>(sname, *this, *blend_node);
378 {
379 auto w_node
380 = std::make_unique<ossia::net::generic_node>("width", *this, *side_node);
381 ossia::net::set_description(*w_node, "Blend width in UV space (0-0.5)");
382 auto w_param = w_node->create_parameter(ossia::val_type::FLOAT);
383 w_param->set_domain(ossia::make_domain(0.f, 0.5f));
384 w_param->push_value(initW);
385 w_param->add_callback([this, i, side](const ossia::value& v) {
386 if(auto val = v.target<float>())
387 {
388 const auto& outputs = m_node->windowOutputs();
389 if(i < (int)outputs.size())
390 {
391 float gamma = 2.2f;
392 switch(side)
393 {
394 case 0:
395 gamma = outputs[i].blendLeft.gamma;
396 break;
397 case 1:
398 gamma = outputs[i].blendRight.gamma;
399 break;
400 case 2:
401 gamma = outputs[i].blendTop.gamma;
402 break;
403 case 3:
404 gamma = outputs[i].blendBottom.gamma;
405 break;
406 }
407 m_node->setEdgeBlend(i, side, *val, gamma);
408 }
409 }
410 });
411 side_node->add_child(std::move(w_node));
412 }
413 {
414 auto g_node
415 = std::make_unique<ossia::net::generic_node>("gamma", *this, *side_node);
416 ossia::net::set_description(*g_node, "Blend curve exponent (0.1-4.0)");
417 auto g_param = g_node->create_parameter(ossia::val_type::FLOAT);
418 g_param->set_domain(ossia::make_domain(0.1f, 4.f));
419 g_param->push_value(initG);
420 g_param->add_callback([this, i, side](const ossia::value& v) {
421 if(auto val = v.target<float>())
422 {
423 const auto& outputs = m_node->windowOutputs();
424 if(i < (int)outputs.size())
425 {
426 float width = 0.f;
427 switch(side)
428 {
429 case 0:
430 width = outputs[i].blendLeft.width;
431 break;
432 case 1:
433 width = outputs[i].blendRight.width;
434 break;
435 case 2:
436 width = outputs[i].blendTop.width;
437 break;
438 case 3:
439 width = outputs[i].blendBottom.width;
440 break;
441 }
442 m_node->setEdgeBlend(i, side, width, *val);
443 }
444 }
445 });
446 side_node->add_child(std::move(g_node));
447 }
448 blend_node->add_child(std::move(side_node));
449 }
450 win_node->add_child(std::move(blend_node));
451 }
452
453 // /i/key
454 {
455 auto key_node
456 = std::make_unique<ossia::net::generic_node>("key", *this, *win_node);
457
458 // /i/key/press
459 {
460 auto press_node
461 = std::make_unique<ossia::net::generic_node>("press", *this, *key_node);
462 {
463 auto code_node
464 = std::make_unique<ossia::net::generic_node>("code", *this, *press_node);
465 pw.key_press_code = code_node->create_parameter(ossia::val_type::INT);
466 press_node->add_child(std::move(code_node));
467 }
468 {
469 auto text_node
470 = std::make_unique<ossia::net::generic_node>("text", *this, *press_node);
471 pw.key_press_text = text_node->create_parameter(ossia::val_type::STRING);
472 press_node->add_child(std::move(text_node));
473 }
474 key_node->add_child(std::move(press_node));
475 }
476
477 // /i/key/release
478 {
479 auto release_node
480 = std::make_unique<ossia::net::generic_node>("release", *this, *key_node);
481 {
482 auto code_node = std::make_unique<ossia::net::generic_node>(
483 "code", *this, *release_node);
484 pw.key_release_code = code_node->create_parameter(ossia::val_type::INT);
485 release_node->add_child(std::move(code_node));
486 }
487 {
488 auto text_node = std::make_unique<ossia::net::generic_node>(
489 "text", *this, *release_node);
490 pw.key_release_text = text_node->create_parameter(ossia::val_type::STRING);
491 release_node->add_child(std::move(text_node));
492 }
493 key_node->add_child(std::move(release_node));
494 }
495
496 win_node->add_child(std::move(key_node));
497 }
498
499 m_root.add_child(std::move(win_node));
500 }
501 }
502
503 // Called after windows are created by createOutput() to connect Qt signals
504 void connectWindowSignals()
505 {
506 const auto& outputs = m_node->windowOutputs();
507 for(int i = 0; i < (int)outputs.size() && i < (int)m_perWindow.size(); ++i)
508 {
509 auto& pw = m_perWindow[i];
510 const auto& wo = outputs[i];
511 if(!wo.window)
512 continue;
513
514 auto* w = wo.window.get();
515
516 // Mouse cursor
517 pw.connections.push_back(
518 QObject::connect(
519 w, &score::gfx::Window::mouseMove, [&pw, w](QPointF screen, QPointF win) {
520 auto sz = w->size();
521 if(sz.width() > 0 && sz.height() > 0)
522 {
523 pw.scaled_cursor->push_value(
524 ossia::vec2f{float(win.x() / sz.width()), float(win.y() / sz.height())});
525 pw.abs_cursor->push_value(ossia::vec2f{float(win.x()), float(win.y())});
526 }
527 }));
528
529 // // Window position feedback
530 // pw.connections.push_back(
531 // QObject::connect(w, &QWindow::xChanged, [&pw, w](int x) {
532 // pw.pos_param->set_value(ossia::vec2f{float(x), float(w->y())});
533 // }));
534 // pw.connections.push_back(
535 // QObject::connect(w, &QWindow::yChanged, [&pw, w](int y) {
536 // pw.pos_param->set_value(ossia::vec2f{float(w->x()), float(y)});
537 // }));
538
539 // Keyboard
540 pw.connections.push_back(
541 QObject::connect(w, &score::gfx::Window::key, [&pw](int k, const QString& t) {
542 pw.key_press_code->push_value(k);
543 pw.key_press_text->push_value(t.toStdString());
544 }));
545 pw.connections.push_back(
546 QObject::connect(
547 w, &score::gfx::Window::keyRelease, [&pw](int k, const QString& t) {
548 pw.key_release_code->push_value(k);
549 pw.key_release_text->push_value(t.toStdString());
550 }));
551 }
552 }
553
554 ~multiwindow_device()
555 {
556 // Disconnect all window signals
557 for(auto& pw : m_perWindow)
558 for(auto& c : pw.connections)
559 QObject::disconnect(c);
560 m_perWindow.clear();
561
562 m_node->onWindowsCreated = [] { };
563 m_node->onFps = [](float) { };
564 m_protocol->stop();
565 m_root.clear_children();
566 m_protocol.reset();
567 }
568
569 const gfx_node_base& get_root_node() const override { return m_root; }
570 gfx_node_base& get_root_node() override { return m_root; }
571};
572
573}
Definition score-plugin-gfx/Gfx/Settings/Model.hpp:41
Definition GfxParameter.hpp:60
Definition GfxParameter.hpp:41
Definition MultiWindowDevice.hpp:37
Binds the rendering pipeline to ossia processes.
Definition CameraDevice.cpp:30
STL namespace.
T & settings() const
Access a specific Settings model instance.
Definition ApplicationContext.hpp:41
Definition MultiWindowNode.hpp:10
Definition OutputNode.hpp:61