Loading...
Searching...
No Matches
OffscreenDevice.hpp
1#pragma once
2
3#include <Gfx/GfxParameter.hpp>
4#include <Gfx/Graph/BackgroundNode.hpp>
5#include <Gfx/Window/PointerAddresses.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
13namespace Gfx
14{
15
16// Headless device used when SCORE_FORCE_OFFSCREEN_WINDOW selects this
17// window device by name. Wraps a BackgroundNode — which already drives
18// beginOffscreenFrame/endOffscreenFrame — without the ScenarioDocumentView
19// dependency of background_device. Exposes the parameters required by
20// offscreen tests (size, rendersize), the pointer addresses shaders bind to by
21// default (cursor/gl, cursor/clip, touch/points, touch/history; set from
22// scripts with WindowDevice::setPointer), and holds the shared_readback used
23// by WindowDevice::grabTo to write frames to disk.
24class offscreen_device : public ossia::net::device_base
25{
26 // unique_ptr ownership: BackgroundNode is not a QObject child of any
27 // parent in this class (it inherits NodeModel, not QObject), so nothing
28 // else would delete it -- and every offscreen device cycle would leak it
29 // along with the rhi resources its ~BackgroundNode → destroyOutput
30 // releases.
31 std::unique_ptr<score::gfx::BackgroundNode> m_node;
32 gfx_node_base m_root;
33 QObject m_qtContext;
34
35 ossia::net::parameter_base* size_param{};
36 ossia::net::parameter_base* rendersize_param{};
37 pointer_addresses m_pointers;
38
39public:
40 offscreen_device(std::unique_ptr<gfx_protocol_base> proto, std::string name)
41 : ossia::net::device_base{std::move(proto)}
42 , m_node{std::make_unique<score::gfx::BackgroundNode>()}
43 , m_root{*this, *static_cast<gfx_protocol_base*>(m_protocol.get()), m_node.get(), name}
44 {
45 this->m_capabilities.change_tree = true;
46 m_node->shared_readback = std::make_shared<QRhiReadbackResult>();
47
48 {
49 auto size_node = std::make_unique<ossia::net::generic_node>("size", *this, m_root);
50 size_param = size_node->create_parameter(ossia::val_type::VEC2F);
51 size_param->push_value(ossia::vec2f{1280.f, 720.f});
52 m_node->setSize(QSize{1280, 720});
53 size_param->add_callback([this](const ossia::value& v) {
54 if(auto val = v.target<ossia::vec2f>())
55 {
56 ossia::qt::run_async(&m_qtContext, [node = m_node.get(), v = *val] {
57 node->setSize({(int)v[0], (int)v[1]});
58 });
59 }
60 });
61 m_root.add_child(std::move(size_node));
62 }
63
64 {
65 auto size_node
66 = std::make_unique<ossia::net::generic_node>("rendersize", *this, m_root);
67 ossia::net::set_description(
68 *size_node, "Set to [0, 0] to use the viewport's size");
69 rendersize_param = size_node->create_parameter(ossia::val_type::VEC2F);
70 rendersize_param->push_value(ossia::vec2f{0.f, 0.f});
71 rendersize_param->add_callback([this](const ossia::value& v) {
72 if(auto val = v.target<ossia::vec2f>())
73 {
74 ossia::qt::run_async(&m_qtContext, [node = m_node.get(), v = *val] {
75 node->setRenderSize({(int)v[0], (int)v[1]});
76 });
77 }
78 });
79 m_root.add_child(std::move(size_node));
80 }
81
82 // No window events: the pointers move when their addresses are written.
83 {
84 auto node = std::make_unique<ossia::net::generic_node>("cursor", *this, m_root);
85 {
86 auto gl_node = std::make_unique<ossia::net::generic_node>("gl", *this, *node);
87 auto gl = gl_node->create_parameter(ossia::val_type::VEC2F);
88 gl->set_domain(ossia::make_domain(0.f, 1.f));
89 gl->push_value(ossia::vec2f{0.f, 0.f});
90 node->add_child(std::move(gl_node));
91 }
92 m_pointers.add_clip(*this, *node);
93 m_root.add_child(std::move(node));
94 }
95 m_pointers.add_touch(
96 *this, m_root, static_cast<gfx_protocol_base*>(m_protocol.get())->context);
97 }
98
99 ~offscreen_device()
100 {
101 // Remove our output from the render graph synchronously, while both the
102 // BackgroundNode and its QRhi are still alive. This output is owned by the
103 // device (not the graph), so on shutdown it is torn down here before
104 // Graph::~Graph runs — and the graph's async REMOVE_NODE queue is never
105 // drained again. Without this, the graph is left with a dangling output
106 // pointer + a RenderList over a freed QRhi (SIGSEGV in ~Graph teardown).
107 if(auto* proto = static_cast<gfx_protocol_base*>(m_protocol.get());
108 proto && proto->context && proto->context->ui)
109 proto->context->ui->destroyOutput(m_node.get());
110
111 m_protocol->stop();
112 m_root.clear_children();
113 m_protocol.reset();
114 // m_node destroyed by unique_ptr → ~BackgroundNode → destroyOutput
115 // (releases RT/RPD/depth tex/colour tex + the offscreen rhi).
116 }
117
118 score::gfx::BackgroundNode* node() const noexcept { return m_node.get(); }
119
120 const gfx_node_base& get_root_node() const override { return m_root; }
121 gfx_node_base& get_root_node() override { return m_root; }
122};
123
124}
Definition GfxParameter.hpp:73
Definition GfxParameter.hpp:54
Definition OffscreenDevice.hpp:25
Binds the rendering pipeline to ossia processes.
Definition AssetTable.cpp:7
Definition PointerAddresses.hpp:37
Definition BackgroundNode.hpp:16