Loading...
Searching...
No Matches
BackgroundNode.hpp
1#pragma once
2
3#include <Gfx/GfxParameter.hpp>
4#include <Gfx/Graph/RenderList.hpp>
5#include <Gfx/Graph/Window.hpp>
6#include <Gfx/InvertYRenderer.hpp>
7#include <Gfx/Settings/Model.hpp>
8#include <Gfx/Window/WindowSettings.hpp>
9
10#include <score/application/GUIApplicationContext.hpp>
11#include <score/tools/Debug.hpp>
12
13namespace score::gfx
14{
16{
17 explicit BackgroundNode(SharedDeviceMode deviceMode = SharedDeviceMode::Owned)
18 : m_deviceMode{deviceMode}
19 {
20 input.push_back(new Port{this, {}, Types::Image, {}});
21 m_conf = {
22 .manualRenderingRate = 1000. / Gfx::Settings::renderRateForCurrentApplication()};
23 }
24
25 virtual ~BackgroundNode() { destroyOutput(); }
26
36 void setSingleSample(bool b) noexcept { m_singleSample = b; }
37
38 void startRendering() override { }
39 void render() override
40 {
41 if(m_deviceLost)
42 return;
43 auto renderer = m_renderer.lock();
44 if(renderer && m_renderState)
45 {
46 if(renderer->renderers.size() > 1)
47 {
48 auto& rhi = *m_renderState->rhi;
50 if(!frame)
51 {
52 checkDeviceLost(frame, rhi, "BackgroundNode");
53 return;
54 }
55
56 renderer->render(frame.commands());
57 frame.end();
58 checkDeviceLost(frame, rhi, "BackgroundNode");
59 }
60 else
61 {
62 // Nothing upstream: the render list holds only this output. Clearing
63 // leaves pixelSize at QSize(-1,-1), which WindowDevice::grabTo reports
64 // as "nothing rendered ... no process is connected to this device's
65 // input" -- a false blank when the graph is in fact connected, hence
66 // the trace.
67 static const bool trace = qEnvironmentVariableIsSet("SCORE_GFX_TRACE");
68 if(trace)
69 fprintf(
70 stderr, "GFX-BACKGROUND readback cleared: renderers=%zu\n",
71 renderer->renderers.size());
72 shared_readback->data.clear();
73 shared_readback->pixelSize = {};
74 }
75 }
76 }
77 void onRendererChange() override { }
78 bool canRender() const override { return m_renderState && m_renderTarget; }
79 void stopRendering() override { }
80
81 void setRenderer(std::shared_ptr<RenderList> r) override { m_renderer = r; }
82 RenderList* renderer() const override { return m_renderer.lock().get(); }
83
84 void createOutput(score::gfx::OutputConfiguration conf) override
85 {
86 m_onResize = conf.onResize;
87 m_onReleaseRenderList = conf.onReleaseRenderList;
88 // Cache the requested graphics API so setSwapchainFormat can rebuild
89 // through createOutput when the format changes (live HDR↔SDR toggle):
90 // setting m_swapchainFormat alone leaves the QRhiTexture in its original
91 // format.
92 m_lastGraphicsApi = conf.graphicsApi;
93 resetDeviceLost();
94
95 QSize newSz = m_renderSize;
96 if(newSz.width() <= 0 || newSz.height() <= 0)
97 newSz = m_size;
98 if(newSz.width() <= 0 || newSz.height() <= 0)
99 newSz = QSize{1024, 1024};
100
101 m_renderState = score::gfx::createRenderState(
102 conf.graphicsApi, newSz, nullptr, m_deviceMode);
103 if(!m_renderState || !m_renderState->rhi)
104 {
105 qWarning() << "BackgroundNode: failed to create QRhi";
106 m_renderState.reset();
107 return;
108 }
109 m_renderState->outputSize = m_renderState->renderSize;
110 if(m_singleSample)
111 m_renderState->samples = 1;
112 m_renderState->renderFormat
113 = (m_swapchainFormat != Gfx::SwapchainFormat::SDR)
114 ? QRhiTexture::RGBA32F
115 : QRhiTexture::RGBA8;
116
117 auto rhi = m_renderState->rhi;
118 m_texture = rhi->newTexture(
119 m_renderState->renderFormat, m_renderState->renderSize, 1,
120 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
121 m_texture->create();
122
123 // Reverse-Z project rule: the depth attachment is D32F (float). Fixed-point
124 // D24 combined with reverse-Z gives strictly worse precision than
125 // standard-Z.
126 m_renderTarget
127 = createOutputRenderTarget(*m_renderState, *m_texture, OutputDepth::Texture);
128
129 conf.onReady();
130 }
131
132 void destroyOutput() override
133 {
134 if(m_renderState)
135 {
136 // Drain the GPU before tearing resources down. Same rationale as
137 // ScreenNode::destroyOutput: when setSwapchainFormat invokes
138 // destroyOutput synchronously, an unfinished cbWrapper from a prior
139 // offscreen frame can still be referenced by ScenePreprocessor's
140 // per-frame copyBuffer. Recording into that CB after we've
141 // freed the rhi triggers VUID-vkCmdCopyBuffer-commandBuffer-
142 // recording and a device loss.
143 if(m_renderState->rhi)
144 {
145 SCORE_ASSERT(!m_renderState->rhi->isRecordingFrame());
146 m_renderState->rhi->finish();
147 }
148
149 // The registry survives render list teardown, so its QRhi resources
150 // must be released here, before RenderState::destroy() tears down the
151 // QRhi: destroyOwned() `delete`s the wrappers while the device is alive.
153
154 releaseOutputRenderTarget(*m_renderState, m_renderTarget);
155
156 delete m_texture;
157 m_texture = nullptr;
158
159 m_renderState->destroy();
160 m_renderState.reset();
161 }
162 }
163 void updateGraphicsAPI(GraphicsApi api) override
164 {
165 if(!m_renderState)
166 return;
167 if(m_renderState->api != api)
168 destroyOutput();
169 }
170
171 void setSwapchainFormat(Gfx::SwapchainFormat format)
172 {
173 if(m_swapchainFormat == format)
174 return;
175 m_swapchainFormat = format;
176
177 // Live format change while rendering: m_texture was allocated by
178 // createOutput with the prior format, and setFormat alone does not
179 // re-allocate its GPU memory backing. Route through destroyOutput +
180 // createOutput so the render target, RPD, depth and colour textures all
181 // come back in the matching format. With no output yet (m_renderState
182 // null) createOutput picks the format up from m_swapchainFormat.
183 if(m_renderState)
184 {
186 conf.graphicsApi = m_lastGraphicsApi;
187 conf.onResize = m_onResize;
188 conf.onReleaseRenderList = m_onReleaseRenderList;
189 // The Graph-owned RenderList holds QRhiResources belonging to the QRhi
190 // destroyOutput() is about to `delete`; release it first.
192 destroyOutput();
193 createOutput(std::move(conf));
194 if(m_onResize)
195 m_onResize();
196 }
197 }
198
199 void setSize(QSize newSz)
200 {
201 if(m_size != newSz)
202 {
203 m_size = newSz;
204 resize();
205 }
206 }
207 QSize size() const noexcept { return m_size; }
208 void setRenderSize(QSize newSz)
209 {
210 if(m_renderSize != newSz)
211 {
212 m_renderSize = newSz;
213 resize();
214 }
215 }
216
217 void resize()
218 {
219 QSize newSz = m_renderSize;
220 if(newSz.width() <= 0 || newSz.height() <= 0)
221 newSz = m_size;
222 if(newSz.width() <= 0 || newSz.height() <= 0)
223 newSz = QSize{1024, 1024};
224
225 if(m_renderState)
226 {
227 m_renderState->renderSize = newSz;
228 m_renderState->outputSize = newSz;
229
230 auto rhi = m_renderState->rhi;
231
232 // Drain the GPU before destroying the render target and m_texture, as
233 // destroyOutput does: the current frame's offscreen CB, or a queued one,
234 // may still reference these resources, and the setPixelSize + create
235 // below does not drain internally. Without this, validation fires on
236 // the next vkCmd* recording (-recording / -commandBuffer-recording /
237 // -in-use) and can lose the device.
238 rhi->finish();
239
240 if(m_renderTarget.renderTarget)
241 m_renderTarget.renderTarget->destroy();
242 releaseOutputRenderTarget(*m_renderState, m_renderTarget, true);
243 m_texture->destroy();
244 m_texture->setPixelSize(newSz);
245 m_texture->create();
246
247 m_renderTarget
248 = createOutputRenderTarget(*m_renderState, *m_texture, OutputDepth::Texture);
249 }
250
251 if(m_onResize)
252 m_onResize();
253 }
254
255 std::shared_ptr<RenderState> renderState() const override { return m_renderState; }
256
258 {
259 if(!m_renderState)
260 return {};
261 return m_renderTarget;
262 }
263
265 {
266 return new Gfx::InvertYRenderer{
267 *this, currentRenderTarget(),
268 const_cast<QRhiReadbackResult&>(*shared_readback)};
269 }
270
271 OutputNode::Configuration configuration() const noexcept override { return m_conf; }
272
273 std::shared_ptr<QRhiReadbackResult> shared_readback;
274
275private:
276 Configuration m_conf;
277 SharedDeviceMode m_deviceMode{SharedDeviceMode::Owned};
278 bool m_singleSample{};
279
280 std::weak_ptr<score::gfx::RenderList> m_renderer{};
281 QRhiTexture* m_texture{};
282 score::gfx::TextureRenderTarget m_renderTarget;
283 std::shared_ptr<score::gfx::RenderState> m_renderState{};
284
285 std::function<void()> m_onResize;
286 QSize m_size{1024, 1024};
287 QSize m_renderSize{};
288 Gfx::SwapchainFormat m_swapchainFormat{};
289 // Cached graphics API from the last createOutput so setSwapchainFormat
290 // can route a live format change through destroyOutput + createOutput
291 // without having to re-derive it from the host.
292 GraphicsApi m_lastGraphicsApi{};
293};
294}
Definition InvertYRenderer.hpp:10
std::vector< Port * > input
Input ports of that node.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:144
Base class for sink nodes (QWindow, spout, syphon, NDI output, ...)
Definition OutputNode.hpp:56
void releaseOwnedRenderList()
Invoke OutputConfiguration::onReleaseRenderList, if any.
Definition OutputNode.cpp:35
bool checkDeviceLost(const OffscreenFrame &frame, QRhi &rhi, const char *output) noexcept
Calls handleDeviceLost when the frame or the QRhi report a lost device. Returns deviceLost().
Definition OutputNode.cpp:178
void releaseRegistry()
Tear down the registry's QRhi resources directly. Idempotent.
Definition OutputNode.cpp:221
Definition OutputNode.hpp:38
List of nodes to be rendered to an output.
Definition RenderList.hpp:36
void render(QRhiCommandBuffer &commands, bool force=false) noexcept
Render every node in order.
Definition RenderList.cpp:2294
std::vector< score::gfx::NodeRenderer * > renderers
Renderers - one per node.
Definition RenderList.hpp:321
Graphics rendering pipeline for ossia score.
Definition Filter/PreviewWidget.hpp:11
TextureRenderTarget createOutputRenderTarget(RenderState &state, QRhiTexture &texture, OutputDepth depth, int samples)
The render target an offscreen output's input is drawn into.
Definition OutputNode.cpp:338
@ Texture
D32F texture, the format reverse-Z wants.
GraphicsApi
Available graphics APIs to use.
Definition RenderState.hpp:25
SharedDeviceMode
Who owns the imported Vulkan device behind a RenderState.
Definition RenderState.hpp:193
void releaseOutputRenderTarget(RenderState &state, TextureRenderTarget &target, bool deferred) noexcept
Frees what createOutputRenderTarget allocated, but not the texture.
Definition OutputNode.cpp:374
Definition BackgroundNode.hpp:16
void setSingleSample(bool b) noexcept
Render at one sample whatever the anti-aliasing setting.
Definition BackgroundNode.hpp:36
score::gfx::OutputNodeRenderer * createRenderer(RenderList &r) const noexcept override
Create a renderer in a given context for this node.
Definition BackgroundNode.hpp:264
score::gfx::TextureRenderTarget currentRenderTarget() const noexcept override
The output's current backing render target + render-pass descriptor, as the upstream nodes should ren...
Definition BackgroundNode.hpp:257
RAII bracket for QRhi::beginOffscreenFrame / endOffscreenFrame.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:54
Definition OutputNode.hpp:17
std::function< void()> onReleaseRenderList
Ask the owning Graph to release + deregister the RenderList that was built against this output's QRhi...
Definition OutputNode.hpp:34
Definition OutputNode.hpp:94
Port of a score::gfx::Node.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:215
Useful abstraction for storing all the data related to a render target.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:307