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