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