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