1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dcubemaptexture_p.h"
5#include "qquick3dscenerenderer_p.h"
6#include "qquick3dsceneenvironment_p.h"
7#include "qquick3dobject_p.h"
8#include "qquick3dnode_p.h"
9#include "qquick3dscenemanager_p.h"
10#include "qquick3dtexture_p.h"
11#include "qquick3dcamera_p.h"
12#include "qquick3dpickresult_p.h"
13#include "qquick3dmodel_p.h"
14#include "qquick3drenderstats_p.h"
15#include "qquick3ddebugsettings_p.h"
16#include "extensions/qquick3drenderextensions.h"
17#include <QtQuick3DUtils/private/qquick3dprofiler_p.h>
18
19#include <QtQuick3DRuntimeRender/private/qssgrendererutil_p.h>
20#include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
21
22#include <QtQuick/private/qquickwindow_p.h>
23#include <QtQuick/private/qsgdefaultrendercontext_p.h>
24#include <QtQuick/private/qsgtexture_p.h>
25#include <QtQuick/private/qsgplaintexture_p.h>
26#include <QtQuick/private/qsgrendernode_p.h>
27
28#include <QtQuick3DRuntimeRender/private/qssgrendereffect_p.h>
29#include <QtQuick3DRuntimeRender/private/qssgrhieffectsystem_p.h>
30#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
31#include <QtQuick3DRuntimeRender/private/qssgrhiquadrenderer_p.h>
32#include <QtQuick3DRuntimeRender/private/qssgrhicontext_p.h>
33#include <QtQuick3DRuntimeRender/private/qssgcputonemapper_p.h>
34#include <QtQuick3DUtils/private/qssgutils_p.h>
35#include <QtQuick3DUtils/private/qssgassert_p.h>
36
37
38#include <qtquick3d_tracepoints_p.h>
39
40#include <QtCore/QObject>
41#include <QtCore/qqueue.h>
42
43QT_BEGIN_NAMESPACE
44
45Q_TRACE_PREFIX(qtquick3d,
46 "QT_BEGIN_NAMESPACE" \
47 "class QQuick3DViewport;" \
48 "QT_END_NAMESPACE"
49)
50
51Q_TRACE_POINT(qtquick3d, QSSG_prepareFrame_entry, int width, int height);
52Q_TRACE_POINT(qtquick3d, QSSG_prepareFrame_exit);
53Q_TRACE_POINT(qtquick3d, QSSG_renderFrame_entry, int width, int height);
54Q_TRACE_POINT(qtquick3d, QSSG_renderFrame_exit);
55Q_TRACE_POINT(qtquick3d, QSSG_synchronize_entry, QQuick3DViewport *view3D, const QSize &size, float dpr);
56Q_TRACE_POINT(qtquick3d, QSSG_synchronize_exit);
57Q_TRACE_POINT(qtquick3d, QSSG_renderPass_entry, const QString &renderPass);
58Q_TRACE_POINT(qtquick3d, QSSG_renderPass_exit);
59
60static bool dumpRenderTimes()
61{
62 static bool val = (qEnvironmentVariableIntValue(varName: "QT_QUICK3D_DUMP_RENDERTIMES") > 0);
63 return val;
64}
65
66#if QT_CONFIG(qml_debug)
67
68static inline quint64 statDrawCallCount(const QSSGRhiContextStats &stats)
69{
70 quint64 count = 0;
71 const QSSGRhiContextStats::PerLayerInfo &info(stats.perLayerInfo[stats.layerKey]);
72 for (const auto &pass : info.renderPasses)
73 count += QSSGRhiContextStats::totalDrawCallCountForPass(pass);
74 count += QSSGRhiContextStats::totalDrawCallCountForPass(pass: info.externalRenderPass);
75 return count;
76}
77
78#define STAT_PAYLOAD(stats) \
79 (statDrawCallCount(stats) | (quint64(stats.perLayerInfo[stats.layerKey].renderPasses.size()) << 32))
80
81#endif
82
83template <typename In, typename Out>
84static void bfs(In *inExtension, QList<Out *> &outList)
85{
86 QSSG_ASSERT(inExtension, return);
87
88 QQueue<In *> queue { { inExtension } };
89 while (queue.size() > 0) {
90 if (auto cur = queue.dequeue()) {
91 if (auto *ext = static_cast<Out *>(QQuick3DObjectPrivate::get(cur)->spatialNode))
92 outList.push_back(ext);
93 for (auto &chld : cur->childItems())
94 queue.enqueue(qobject_cast<In *>(chld));
95 }
96 }
97}
98
99SGFramebufferObjectNode::SGFramebufferObjectNode()
100 : window(nullptr)
101 , renderer(nullptr)
102 , renderPending(true)
103 , invalidatePending(false)
104 , devicePixelRatio(1)
105{
106 qsgnode_set_description(node: this, QStringLiteral("fbonode"));
107 setFlag(QSGNode::UsePreprocess, true);
108}
109
110SGFramebufferObjectNode::~SGFramebufferObjectNode()
111{
112 delete renderer;
113 delete texture();
114}
115
116void SGFramebufferObjectNode::scheduleRender()
117{
118 renderPending = true;
119 markDirty(bits: DirtyMaterial);
120}
121
122QSGTexture *SGFramebufferObjectNode::texture() const
123{
124 return QSGSimpleTextureNode::texture();
125}
126
127void SGFramebufferObjectNode::preprocess()
128{
129 render();
130}
131
132// QQuickWindow::update() behaves differently depending on whether it's called from the GUI thread
133// or the render thread.
134// TODO: move this to QQuickWindow::fullUpdate(), if we can't change update()
135static void requestFullUpdate(QQuickWindow *window)
136{
137 if (QThread::currentThread() == QCoreApplication::instance()->thread())
138 window->update();
139 else
140 QCoreApplication::postEvent(receiver: window, event: new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
141}
142
143void SGFramebufferObjectNode::render()
144{
145 if (renderPending) {
146 if (renderer->renderStats())
147 renderer->renderStats()->startRender();
148
149 renderPending = false;
150
151 if (renderer->m_sgContext->rhiContext()->isValid()) {
152 QRhiTexture *rhiTexture = renderer->renderToRhiTexture(qw: window);
153 bool needsNewWrapper = false;
154 if (!texture() || (texture()->textureSize() != renderer->surfaceSize()
155 || texture()->rhiTexture() != rhiTexture))
156 {
157 needsNewWrapper = true;
158 }
159 if (needsNewWrapper) {
160 delete texture();
161 QSGPlainTexture *t = new QSGPlainTexture;
162 t->setOwnsTexture(false);
163 t->setHasAlphaChannel(true);
164 t->setTexture(rhiTexture);
165 t->setTextureSize(renderer->surfaceSize());
166 setTexture(t);
167 }
168 }
169
170 markDirty(bits: QSGNode::DirtyMaterial);
171 emit textureChanged();
172
173 if (renderer->renderStats())
174 renderer->renderStats()->endRender(dump: dumpRenderTimes());
175
176 if (renderer->m_requestedFramesCount > 0) {
177 scheduleRender();
178 requestFullUpdate(window);
179 renderer->m_requestedFramesCount--;
180 }
181 }
182}
183
184void SGFramebufferObjectNode::handleScreenChange()
185{
186 if (!qFuzzyCompare(p1: window->effectiveDevicePixelRatio(), p2: devicePixelRatio)) {
187 renderer->invalidateFramebufferObject();
188 quickFbo->update();
189 }
190}
191
192
193QQuick3DSceneRenderer::QQuick3DSceneRenderer(const std::shared_ptr<QSSGRenderContextInterface> &rci)
194 : m_sgContext(rci)
195{
196}
197
198QQuick3DSceneRenderer::~QQuick3DSceneRenderer()
199{
200 const auto &rhiCtx = m_sgContext->rhiContext();
201 QSSGRhiContextStats::get(rhiCtx&: *rhiCtx).cleanupLayerInfo(layer: m_layer);
202 m_sgContext->bufferManager()->releaseResourcesForLayer(layer: m_layer);
203 delete m_layer;
204
205 delete m_texture;
206
207 releaseAaDependentRhiResources();
208 delete m_effectSystem;
209}
210
211void QQuick3DSceneRenderer::releaseAaDependentRhiResources()
212{
213 const auto &rhiCtx = m_sgContext->rhiContext();
214 if (!rhiCtx->isValid())
215 return;
216
217 delete m_textureRenderTarget;
218 m_textureRenderTarget = nullptr;
219
220 delete m_textureRenderPassDescriptor;
221 m_textureRenderPassDescriptor = nullptr;
222
223 delete m_depthStencilBuffer;
224 m_depthStencilBuffer = nullptr;
225
226 delete m_multiViewDepthStencilBuffer;
227 m_multiViewDepthStencilBuffer = nullptr;
228
229 delete m_msaaRenderBuffer;
230 m_msaaRenderBuffer = nullptr;
231
232 delete m_msaaMultiViewRenderBuffer;
233 m_msaaMultiViewRenderBuffer = nullptr;
234
235 delete m_ssaaTexture;
236 m_ssaaTexture = nullptr;
237
238 delete m_ssaaTextureToTextureRenderTarget;
239 m_ssaaTextureToTextureRenderTarget = nullptr;
240
241 delete m_ssaaTextureToTextureRenderPassDescriptor;
242 m_ssaaTextureToTextureRenderPassDescriptor = nullptr;
243
244 delete m_temporalAATexture;
245 m_temporalAATexture = nullptr;
246 delete m_temporalAARenderTarget;
247 m_temporalAARenderTarget = nullptr;
248 delete m_temporalAARenderPassDescriptor;
249 m_temporalAARenderPassDescriptor = nullptr;
250
251 delete m_prevTempAATexture;
252 m_prevTempAATexture = nullptr;
253}
254
255// Blend factors are in the form of (frame blend factor, accumulator blend factor)
256static const QVector2D s_ProgressiveAABlendFactors[QSSGLayerRenderData::MAX_AA_LEVELS] = {
257 QVector2D(0.500000f, 0.500000f), // 1x
258 QVector2D(0.333333f, 0.666667f), // 2x
259 QVector2D(0.250000f, 0.750000f), // 3x
260 QVector2D(0.200000f, 0.800000f), // 4x
261 QVector2D(0.166667f, 0.833333f), // 5x
262 QVector2D(0.142857f, 0.857143f), // 6x
263 QVector2D(0.125000f, 0.875000f), // 7x
264 QVector2D(0.111111f, 0.888889f), // 8x
265};
266
267static const QVector2D s_TemporalAABlendFactors = { 0.5f, 0.5f };
268
269QRhiTexture *QQuick3DSceneRenderer::renderToRhiTexture(QQuickWindow *qw)
270{
271 if (!m_layer)
272 return nullptr;
273
274 QRhiTexture *currentTexture = m_texture; // the result so far
275
276 if (qw) {
277 if (m_renderStats)
278 m_renderStats->startRenderPrepare();
279
280 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DPrepareFrame);
281
282 QSSGRhiContext *rhiCtx = m_sgContext->rhiContext().get();
283 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx);
284
285 rhiCtxD->setMainRenderPassDescriptor(m_textureRenderPassDescriptor);
286 rhiCtxD->setRenderTarget(m_textureRenderTarget);
287
288 QRhiCommandBuffer *cb = nullptr;
289 QRhiSwapChain *swapchain = qw->swapChain();
290 if (swapchain) {
291 cb = swapchain->currentFrameCommandBuffer();
292 rhiCtxD->setCommandBuffer(cb);
293 } else {
294 QSGRendererInterface *rif = qw->rendererInterface();
295 cb = static_cast<QRhiCommandBuffer *>(
296 rif->getResource(window: qw, resource: QSGRendererInterface::RhiRedirectCommandBuffer));
297 if (cb)
298 rhiCtxD->setCommandBuffer(cb);
299 else {
300 qWarning(msg: "Neither swapchain nor redirected command buffer are available.");
301 return currentTexture;
302 }
303 }
304
305 // Graphics pipeline objects depend on the MSAA sample count, so the
306 // renderer needs to know the value.
307 rhiCtxD->setMainPassSampleCount(m_msaaRenderBuffer ? m_msaaRenderBuffer->sampleCount() :
308 (m_msaaMultiViewRenderBuffer ? m_msaaMultiViewRenderBuffer->sampleCount() : 1));
309
310 // mainPassViewCount is left unchanged
311
312 int ssaaAdjustedWidth = m_surfaceSize.width();
313 int ssaaAdjustedHeight = m_surfaceSize.height();
314 if (m_layer->antialiasingMode == QSSGRenderLayer::AAMode::SSAA) {
315 ssaaAdjustedWidth *= m_layer->ssaaMultiplier;
316 ssaaAdjustedHeight *= m_layer->ssaaMultiplier;
317 }
318
319 Q_TRACE(QSSG_prepareFrame_entry, ssaaAdjustedWidth, ssaaAdjustedHeight);
320
321 float dpr = m_sgContext->renderer()->dpr();
322 const QRect vp = QRect(0, 0, ssaaAdjustedWidth, ssaaAdjustedHeight);
323 beginFrame();
324 rhiPrepare(viewport: vp, displayPixelRatio: dpr);
325
326 if (m_renderStats)
327 m_renderStats->endRenderPrepare();
328
329 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DPrepareFrame, quint64(ssaaAdjustedWidth) | quint64(ssaaAdjustedHeight) << 32, profilingId);
330
331 Q_TRACE(QSSG_prepareFrame_exit);
332
333 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderFrame);
334 Q_TRACE(QSSG_renderFrame_entry, ssaaAdjustedWidth, ssaaAdjustedHeight);
335
336 QColor clearColor = Qt::transparent;
337 if (m_backgroundMode == QSSGRenderLayer::Background::Color
338 || (m_backgroundMode == QSSGRenderLayer::Background::SkyBoxCubeMap && !m_layer->skyBoxCubeMap)
339 || (m_backgroundMode == QSSGRenderLayer::Background::SkyBox && !m_layer->lightProbe))
340 {
341 // Same logic as with the main render pass and skybox: tonemap
342 // based on tonemapMode (unless it is None), unless there are effects.
343 clearColor = m_layer->firstEffect ? m_linearBackgroundColor : m_tonemappedBackgroundColor;
344 }
345
346 // This is called from the node's preprocess() meaning Qt Quick has not
347 // actually began recording a renderpass. Do our own.
348 cb->beginPass(rt: m_textureRenderTarget, colorClearValue: clearColor, depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: nullptr, flags: rhiCtx->commonPassFlags());
349 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
350 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(m_textureRenderTarget));
351 rhiRender();
352 cb->endPass();
353 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
354 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, quint64(ssaaAdjustedWidth) | quint64(ssaaAdjustedHeight) << 32, QByteArrayLiteral("main"));
355
356 const bool temporalAA = m_layer->temporalAAIsActive;
357 const bool progressiveAA = m_layer->progressiveAAIsActive;
358 const bool superSamplingAA = m_layer->antialiasingMode == QSSGRenderLayer::AAMode::SSAA;
359 QRhi *rhi = rhiCtx->rhi();
360
361 currentTexture = superSamplingAA ? m_ssaaTexture : m_texture;
362
363 // Do effects before antialiasing
364 if (m_effectSystem && m_layer->firstEffect && !m_layer->renderedCameras.isEmpty()) {
365 const auto &renderer = m_sgContext->renderer();
366 QSSGLayerRenderData *theRenderData = renderer->getOrCreateLayerRenderData(layer&: *m_layer);
367 Q_ASSERT(theRenderData);
368 QRhiTexture *theDepthTexture = theRenderData->getRenderResult(id: QSSGFrameData::RenderResult::DepthTexture)->texture;
369 QVector2D cameraClipRange(m_layer->renderedCameras[0]->clipNear, m_layer->renderedCameras[0]->clipFar);
370
371 currentTexture = m_effectSystem->process(layer: *m_layer,
372 inTexture: currentTexture,
373 inDepthTexture: theDepthTexture,
374 cameraClipRange);
375 }
376
377 // The only difference between temporal and progressive AA at this point is that tempAA always
378 // uses blend factors of 0.5 and copies currentTexture to m_prevTempAATexture, while progAA uses blend
379 // factors from a table and copies the blend result to m_prevTempAATexture
380
381 if ((progressiveAA || temporalAA) && m_prevTempAATexture) {
382 cb->debugMarkBegin(QByteArrayLiteral("Temporal AA"));
383 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
384 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Temporal AA"));
385 QRhiTexture *blendResult;
386 uint *aaIndex = progressiveAA ? &m_layer->progAAPassIndex : &m_layer->tempAAPassIndex; // TODO: can we use only one index?
387
388 if (*aaIndex > 0) {
389 if (temporalAA || *aaIndex < quint32(m_layer->antialiasingQuality)) {
390 const auto &renderer = m_sgContext->renderer();
391
392 // The fragment shader relies on per-target compilation and
393 // QSHADER_ macros of qsb, hence no need to communicate a flip
394 // flag from here.
395 const auto &shaderPipeline = m_sgContext->shaderCache()->getBuiltInRhiShaders().getRhiProgressiveAAShader();
396 QRhiResourceUpdateBatch *rub = nullptr;
397
398 QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData(key: { .cid: m_layer, .model: nullptr, .entry: nullptr, .entryIdx: 0 }));
399 QRhiBuffer *&ubuf = dcd.ubuf;
400 const int ubufSize = 2 * sizeof(float);
401 if (!ubuf) {
402 ubuf = rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: ubufSize);
403 ubuf->create();
404 }
405
406 rub = rhi->nextResourceUpdateBatch();
407 int idx = *aaIndex - 1;
408 const QVector2D *blendFactors = progressiveAA ? &s_ProgressiveAABlendFactors[idx] : &s_TemporalAABlendFactors;
409 rub->updateDynamicBuffer(buf: ubuf, offset: 0, size: 2 * sizeof(float), data: blendFactors);
410 renderer->rhiQuadRenderer()->prepareQuad(rhiCtx, maybeRub: rub);
411
412 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
413 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
414 QSSGRhiShaderResourceBindingList bindings;
415 bindings.addUniformBuffer(binding: 0, stage: QRhiShaderResourceBinding::FragmentStage, buf: ubuf);
416 bindings.addTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: currentTexture, sampler);
417 bindings.addTexture(binding: 2, stage: QRhiShaderResourceBinding::FragmentStage, tex: m_prevTempAATexture, sampler);
418
419 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
420
421 QSSGRhiGraphicsPipelineState ps;
422 const QSize textureSize = currentTexture->pixelSize();
423 ps.viewport = QRhiViewport(0, 0, float(textureSize.width()), float(textureSize.height()));
424 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, pipeline: shaderPipeline.get());
425
426 renderer->rhiQuadRenderer()->recordRenderQuadPass(rhiCtx, ps: &ps, srb, rt: m_temporalAARenderTarget, flags: QSSGRhiQuadRenderer::UvCoords);
427 blendResult = m_temporalAATexture;
428 } else {
429 blendResult = m_prevTempAATexture;
430 }
431 } else {
432 // For the first frame: no blend, only copy
433 blendResult = currentTexture;
434 }
435
436 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
437
438 if (temporalAA || (*aaIndex < quint32(m_layer->antialiasingQuality))) {
439 auto *rub = rhi->nextResourceUpdateBatch();
440 if (progressiveAA)
441 rub->copyTexture(dst: m_prevTempAATexture, src: blendResult);
442 else
443 rub->copyTexture(dst: m_prevTempAATexture, src: currentTexture);
444 cb->resourceUpdate(resourceUpdates: rub);
445 }
446
447 (*aaIndex)++;
448 cb->debugMarkEnd();
449 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("temporal_aa"));
450
451 currentTexture = blendResult;
452 }
453
454 if (m_layer->antialiasingMode == QSSGRenderLayer::AAMode::SSAA) {
455 // With supersampling antialiasing we at this point have the
456 // content rendered at a larger size into m_ssaaTexture. Now scale
457 // it down to the expected size into m_texture, using linear
458 // filtering. Unlike in the OpenGL world, there is no
459 // glBlitFramebuffer equivalent available, because APIs like D3D
460 // and Metal have no such operation (the generally supported
461 // texture copy operations are 1:1 copies, without support for
462 // scaling, which is what we would need here). So draw a quad.
463
464 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
465 const auto &renderer = m_sgContext->renderer();
466
467 cb->debugMarkBegin(QByteArrayLiteral("SSAA downsample"));
468 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
469
470 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("SSAA downsample"));
471
472 renderer->rhiQuadRenderer()->prepareQuad(rhiCtx, maybeRub: nullptr);
473
474 // Instead of passing in a flip flag we choose to rely on qsb's
475 // per-target compilation mode in the fragment shader. (it does UV
476 // flipping based on QSHADER_ macros) This is just better for
477 // performance and the shaders are very simple so introducing a
478 // uniform block and branching dynamically would be an overkill.
479 const auto &shaderPipeline = m_sgContext->shaderCache()->getBuiltInRhiShaders().getRhiSupersampleResolveShader(viewCount: m_layer->viewCount);
480
481 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
482 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
483 QSSGRhiShaderResourceBindingList bindings;
484 bindings.addTexture(binding: 0, stage: QRhiShaderResourceBinding::FragmentStage, tex: currentTexture, sampler);
485 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
486
487 QSSGRhiGraphicsPipelineState ps;
488 ps.viewport = QRhiViewport(0, 0, float(m_surfaceSize.width()), float(m_surfaceSize.height()));
489 ps.viewCount = m_layer->viewCount;
490 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, pipeline: shaderPipeline.get());
491
492 renderer->rhiQuadRenderer()->recordRenderQuadPass(rhiCtx, ps: &ps, srb, rt: m_ssaaTextureToTextureRenderTarget, flags: QSSGRhiQuadRenderer::UvCoords);
493 cb->debugMarkEnd();
494 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("ssaa_downsample"));
495
496 currentTexture = m_texture;
497 }
498
499 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DRenderFrame,
500 STAT_PAYLOAD(QSSGRhiContextStats::get(*rhiCtx)),
501 profilingId);
502 endFrame();
503
504 Q_TRACE(QSSG_renderFrame_exit);
505
506 }
507
508 return currentTexture;
509}
510
511void QQuick3DSceneRenderer::beginFrame()
512{
513 m_sgContext->renderer()->beginFrame(layer&: *m_layer);
514}
515
516void QQuick3DSceneRenderer::endFrame()
517{
518 m_sgContext->renderer()->endFrame(layer&: *m_layer);
519}
520
521void QQuick3DSceneRenderer::rhiPrepare(const QRect &viewport, qreal displayPixelRatio)
522{
523 if (!m_layer)
524 return;
525
526 const auto &renderer = m_sgContext->renderer();
527
528 renderer->setDpr(displayPixelRatio);
529
530 renderer->setViewport(viewport);
531
532 renderer->prepareLayerForRender(inLayer&: *m_layer);
533 // If sync was called the assumption is that the scene is dirty regardless of what
534 // the scene prep function says, we still should verify that we have a camera before
535 // we call render prep and render.
536 const bool renderReady = !m_layer->renderData->renderedCameras.isEmpty();
537 if (renderReady) {
538 renderer->rhiPrepare(inLayer&: *m_layer);
539 m_prepared = true;
540 }
541}
542
543void QQuick3DSceneRenderer::rhiRender()
544{
545 if (m_prepared) {
546 // There is no clearFirst flag - the rendering here does not record a
547 // beginPass() so it never clears on its own.
548
549 m_sgContext->renderer()->rhiRender(inLayer&: *m_layer);
550 }
551
552 m_prepared = false;
553}
554
555#if QT_CONFIG(quick_shadereffect)
556static QRhiTexture::Format toRhiTextureFormat(QQuickShaderEffectSource::Format format)
557{
558 switch (format) {
559 case QQuickShaderEffectSource::RGBA8:
560 return QRhiTexture::RGBA8;
561 case QQuickShaderEffectSource::RGBA16F:
562 return QRhiTexture::RGBA16F;
563 case QQuickShaderEffectSource::RGBA32F:
564 return QRhiTexture::RGBA32F;
565 default:
566 return QRhiTexture::RGBA8;
567 }
568}
569#endif
570
571static QVector3D tonemapRgb(const QVector3D &c, QQuick3DSceneEnvironment::QQuick3DEnvironmentTonemapModes tonemapMode)
572{
573 switch (tonemapMode) {
574 case QQuick3DSceneEnvironment::TonemapModeLinear:
575 return QSSGTonemapper::tonemapLinearToSrgb(c);
576 case QQuick3DSceneEnvironment::TonemapModeHejlDawson:
577 return QSSGTonemapper::tonemapHejlDawson(c);
578 case QQuick3DSceneEnvironment::TonemapModeAces:
579 return QSSGTonemapper::tonemapAces(c);
580 case QQuick3DSceneEnvironment::TonemapModeFilmic:
581 return QSSGTonemapper::tonemapFilmic(c);
582 default:
583 break;
584 }
585 return c;
586}
587
588void QQuick3DSceneRenderer::synchronize(QQuick3DViewport *view3D, const QSize &size, float dpr)
589{
590 Q_TRACE_SCOPE(QSSG_synchronize, view3D, size, dpr);
591
592 Q_ASSERT(view3D != nullptr); // This is not an option!
593 QSSGRhiContext *rhiCtx = m_sgContext->rhiContext().get();
594 Q_ASSERT(rhiCtx != nullptr);
595
596 bool newRenderStats = false;
597 if (!m_renderStats) {
598 m_renderStats = view3D->renderStats();
599 newRenderStats = true;
600 }
601
602 if (m_renderStats)
603 m_renderStats->startSync();
604
605 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DSynchronizeFrame);
606
607 m_sgContext->renderer()->setDpr(dpr);
608 bool layerSizeIsDirty = m_surfaceSize != size;
609 m_surfaceSize = size;
610
611 // Synchronize scene managers under this window
612 QSet<QSSGRenderGraphObject *> resourceLoaders;
613 QQuick3DWindowAttachment::SyncResult requestSharedUpdate = QQuick3DWindowAttachment::SyncResultFlag::None;
614 if (auto window = view3D->window()) {
615 if (!winAttacment || winAttacment->window() != window)
616 winAttacment = QQuick3DSceneManager::getOrSetWindowAttachment(window&: *window);
617
618 if (winAttacment && winAttacment->rci() != m_sgContext)
619 winAttacment->setRci(m_sgContext);
620
621 if (winAttacment)
622 requestSharedUpdate |= winAttacment->synchronize(resourceLoaders);
623 }
624
625 // Import scenes used in a multi-window application...
626 QQuick3DNode *importScene = view3D->importScene();
627 if (importScene) {
628 QQuick3DSceneManager *importSceneManager = QQuick3DObjectPrivate::get(item: importScene)->sceneManager;
629 // If the import scene is used with 3D views under a different window, then we'll
630 // need to trigger updates for those as well.
631 if (auto window = importSceneManager->window(); window && window != view3D->window()) {
632 if (auto winAttacment = importSceneManager->wattached) {
633 // Not the same window but backed by the same rhi?
634 auto rci = winAttacment->rci();
635 const bool inlineSync = (rci && rci->rhi() && (rci->rhi()->thread() == m_sgContext->rhi()->thread()));
636 if (inlineSync) {
637 // Given that we're on the same thread, we can do an immediate sync
638 // (rhi instances can differ, e.g., basic renderloop).
639 winAttacment->synchronize(resourceLoaders);
640 } else if (rci && !window->isExposed()) { // Forced sync of non-exposed windows
641 // Not exposed, so not rendering (playing with fire here)...
642 winAttacment->synchronize(resourceLoaders);
643 } else if (!rci || (requestSharedUpdate & QQuick3DWindowAttachment::SyncResultFlag::SharedResourcesDirty)) {
644 // If there's no RCI for the importscene we'll request an update, which should
645 // mean we only get here once. It also means the update to any secondary windows
646 // will be delayed. Note that calling this function on each sync would cause the
647 // different views to ping-pong for updated forever...
648 winAttacment->requestUpdate();
649 }
650 }
651 }
652 }
653
654 // Generate layer node
655 if (!m_layer)
656 m_layer = new QSSGRenderLayer();
657
658 // Update the layer node properties
659 // Store the view count in the layer. If there are multiple, or nested views, sync is called multiple times and the view count
660 // can change (see: updateLayerNode()), so we need to store the value on the layer to make sure we don't end up with a mismatch
661 // between between the view count of the views rendering directly to the screen (XrView instance) and the view count of the offscreen
662 // rendered View3Ds.
663 // See also: preSynchronize(), queryMainRenderPassDescriptorAndCommandBuffer() and queryInlineRenderPassDescriptorAndCommandBuffer()
664 // (At this point the mainPassViewCount for this view should be set to the correct value)
665 m_layer->viewCount = rhiCtx->mainPassViewCount();
666 updateLayerNode(layerNode&: *m_layer, view3D: *view3D, resourceLoaders: resourceLoaders.values());
667
668 // Request extra frames for antialiasing (ProgressiveAA/TemporalAA)
669
670 m_requestedFramesCount = 0;
671 if (m_layer->isProgressiveAAEnabled()) {
672 // with progressive AA, we need a number of extra frames after the last dirty one
673 // if we always reset requestedFramesCount when dirty, we will get the extra frames eventually
674 // +1 since we need a normal frame to start with, and we're not copying that from the screen
675 m_requestedFramesCount = int(m_layer->antialiasingQuality) + 1;
676 } else if (m_layer->isTemporalAAEnabled()) {
677 // When temporalAA is on and antialiasing mode changes,
678 // layer needs to be re-rendered (at least) MAX_TEMPORAL_AA_LEVELS times
679 // to generate temporal antialiasing.
680 // Also, we need to do an extra render when animation stops
681 m_requestedFramesCount = (m_aaIsDirty || m_temporalIsDirty) ? QSSGLayerRenderData::MAX_TEMPORAL_AA_LEVELS : 1;
682 }
683
684 // Now that we have the effect list used for rendering, finalize the shader
685 // code based on the layer (scene.env.) settings.
686 for (QSSGRenderEffect *effectNode = m_layer->firstEffect; effectNode; effectNode = effectNode->m_nextEffect)
687 effectNode->finalizeShaders(layer: *m_layer, renderContext: m_sgContext.get());
688
689 if (newRenderStats)
690 m_renderStats->setRhiContext(ctx: rhiCtx, layer: m_layer);
691
692 // if the list is dirty we rebuild (assumption is that this won't happen frequently).
693 if ((requestSharedUpdate & QQuick3DWindowAttachment::SyncResultFlag::ExtensionsDiry) || view3D->extensionListDirty()) {
694 for (size_t i = 0; i != size_t(QSSGRenderLayer::RenderExtensionStage::Count); ++i)
695 m_layer->renderExtensions[i].clear();
696 // All items in the extension list are root items,
697 const auto &extensions = view3D->extensionList();
698 for (const auto &ext : extensions) {
699 const auto type = QQuick3DObjectPrivate::get(item: ext)->type;
700 if (QSSGRenderGraphObject::isExtension(type)) {
701 if (type == QSSGRenderGraphObject::Type::RenderExtension) {
702 if (auto *renderExt = qobject_cast<QQuick3DRenderExtension *>(object: ext)) {
703 if (QQuick3DObjectPrivate::get(item: renderExt)->spatialNode) {
704 const auto stage = static_cast<QSSGRenderExtension *>(QQuick3DObjectPrivate::get(item: renderExt)->spatialNode)->stage();
705 QSSG_ASSERT(size_t(stage) < std::size(m_layer->renderExtensions), continue);
706 auto &list = m_layer->renderExtensions[size_t(stage)];
707 bfs(inExtension: qobject_cast<QQuick3DRenderExtension *>(object: ext), outList&: list);
708 }
709 }
710 }
711 }
712 }
713
714 view3D->clearExtensionListDirty();
715 }
716
717 bool postProcessingNeeded = m_layer->firstEffect;
718 bool postProcessingWasActive = m_effectSystem;
719 QSSGRenderTextureFormat::Format effectOutputFormatOverride = QSSGRenderTextureFormat::Unknown;
720 if (postProcessingNeeded) {
721 QSSGRenderEffect *lastEffect = m_layer->firstEffect;
722 while (lastEffect->m_nextEffect)
723 lastEffect = lastEffect->m_nextEffect;
724 effectOutputFormatOverride = QSSGRhiEffectSystem::overriddenOutputFormat(inEffect: lastEffect);
725 }
726 const auto layerTextureFormat = [effectOutputFormatOverride, view3D](QRhi *rhi, bool postProc) {
727 if (effectOutputFormatOverride != QSSGRenderTextureFormat::Unknown)
728 return QSSGBufferManager::toRhiFormat(format: effectOutputFormatOverride);
729
730 // Our standard choice for the postprocessing input/output textures'
731 // format is a floating point one. (unlike intermediate Buffers, which
732 // default to RGBA8 unless the format is explicitly specified)
733 // This is intentional since a float format allows passing in
734 // non-tonemapped content without colors being clamped when written out
735 // to the render target.
736 //
737 // When it comes to the output, this applies to that too due to
738 // QSSGRhiEffectSystem picking it up unless overridden (with a Buffer
739 // an empty 'name'). Here too a float format gives more flexibility:
740 // the effect may or may not do its own tonemapping and this approach
741 // is compatible with potential future on-screen HDR output support.
742
743 const QRhiTexture::Format preferredPostProcFormat = QRhiTexture::RGBA16F;
744 if (postProc && rhi->isTextureFormatSupported(format: preferredPostProcFormat))
745 return preferredPostProcFormat;
746
747#if QT_CONFIG(quick_shadereffect)
748 const QRhiTexture::Format preferredView3DFormat = toRhiTextureFormat(format: view3D->renderFormat());
749 if (rhi->isTextureFormatSupported(format: preferredView3DFormat))
750 return preferredView3DFormat;
751#endif
752
753 return QRhiTexture::RGBA8;
754 };
755 bool postProcessingStateDirty = postProcessingNeeded != postProcessingWasActive;
756
757 // Store from the layer properties the ones we need to handle ourselves (with the RHI code path)
758 m_backgroundMode = QSSGRenderLayer::Background(view3D->environment()->backgroundMode());
759
760 // This is stateful since we only want to recalculate the tonemapped color
761 // when the color changes, not in every frame.
762 QColor currentUserBackgroundColor = view3D->environment()->clearColor();
763 if (m_userBackgroundColor != currentUserBackgroundColor) {
764 m_userBackgroundColor = currentUserBackgroundColor;
765 m_linearBackgroundColor = QSSGUtils::color::sRGBToLinearColor(color: m_userBackgroundColor);
766 const QVector3D tc = tonemapRgb(c: QVector3D(m_linearBackgroundColor.redF(),
767 m_linearBackgroundColor.greenF(),
768 m_linearBackgroundColor.blueF()),
769 tonemapMode: view3D->environment()->tonemapMode());
770 m_tonemappedBackgroundColor = QColor::fromRgbF(r: tc.x(), g: tc.y(), b: tc.z(), a: m_linearBackgroundColor.alphaF());
771 }
772 m_layer->scissorRect = QRect(view3D->environment()->scissorRect().topLeft() * dpr,
773 view3D->environment()->scissorRect().size() * dpr);
774
775 // Set the root item for the scene to the layer
776 auto rootNode = static_cast<QSSGRenderNode*>(QQuick3DObjectPrivate::get(item: view3D->scene())->spatialNode);
777 if (rootNode != m_sceneRootNode) {
778 if (m_sceneRootNode)
779 removeNodeFromLayer(node: m_sceneRootNode);
780
781 if (rootNode)
782 addNodeToLayer(node: rootNode);
783
784 m_sceneRootNode = rootNode;
785 }
786
787 // Add the referenced scene root node to the layer as well if available
788 QSSGRenderNode *importRootNode = nullptr;
789 if (importScene)
790 importRootNode = static_cast<QSSGRenderNode*>(QQuick3DObjectPrivate::get(item: importScene)->spatialNode);
791
792 if (importRootNode != m_importRootNode) {
793 if (m_importRootNode)
794 m_layer->removeImportScene(rootNode&: *m_importRootNode);
795
796 if (importRootNode) {
797 // if importScene has the rendered viewport as ancestor, it probably means
798 // "importScene: MyScene { }" type of inclusion.
799 // In this case don't duplicate content by adding it again.
800 QObject *sceneParent = importScene->parent();
801 bool isEmbedded = false;
802 while (sceneParent) {
803 if (sceneParent == view3D) {
804 isEmbedded = true;
805 break;
806 }
807 sceneParent = sceneParent->parent();
808 }
809 if (!isEmbedded)
810 m_layer->setImportScene(*importRootNode);
811 }
812
813 m_importRootNode = importRootNode;
814 }
815
816 if (auto lightmapBaker = view3D->maybeLightmapBaker()) {
817 if (lightmapBaker->m_bakingRequested) {
818 m_layer->renderData->interactiveLightmapBakingRequested = true;
819
820 QQuick3DLightmapBaker::Callback qq3dCallback = lightmapBaker->m_callback;
821 QQuick3DLightmapBaker::BakingControl *qq3dBakingControl = lightmapBaker->m_bakingControl;
822 QSSGLightmapper::Callback callback =
823 [qq3dCallback, qq3dBakingControl](
824 QSSGLightmapper::BakingStatus qssgBakingStatus,
825 std::optional<QString> msg,
826 QSSGLightmapper::BakingControl *qssgBakingControl)
827 {
828 QQuick3DLightmapBaker::BakingStatus qq3dBakingStatus = QQuick3DLightmapBaker::BakingStatus::None;
829 switch (qssgBakingStatus)
830 {
831 case QSSGLightmapper::BakingStatus::None:
832 break;
833 case QSSGLightmapper::BakingStatus::Progress:
834 qq3dBakingStatus = QQuick3DLightmapBaker::BakingStatus::Progress;
835 break;
836 case QSSGLightmapper::BakingStatus::Warning:
837 qq3dBakingStatus = QQuick3DLightmapBaker::BakingStatus::Warning;
838 break;
839 case QSSGLightmapper::BakingStatus::Error:
840 qq3dBakingStatus = QQuick3DLightmapBaker::BakingStatus::Error;
841 break;
842 case QSSGLightmapper::BakingStatus::Cancelled:
843 qq3dBakingStatus = QQuick3DLightmapBaker::BakingStatus::Cancelled;
844 break;
845 case QSSGLightmapper::BakingStatus::Complete:
846 qq3dBakingStatus = QQuick3DLightmapBaker::BakingStatus::Complete;
847 break;
848 }
849
850 qq3dCallback(qq3dBakingStatus, msg, qq3dBakingControl);
851
852 if (qq3dBakingControl->isCancelled() && !qssgBakingControl->cancelled)
853 qssgBakingControl->cancelled = true;
854 };
855
856 m_layer->renderData->lightmapBakingOutputCallback = callback;
857 lightmapBaker->m_bakingRequested = false;
858 }
859 }
860
861 if (m_useFBO && rhiCtx->isValid()) {
862 QRhi *rhi = rhiCtx->rhi();
863 const QSize renderSize = m_layer->isSsaaEnabled() ? m_surfaceSize * m_layer->ssaaMultiplier : m_surfaceSize;
864
865 if (m_texture) {
866 // the size changed, or the AA settings changed, or toggled between some effects - no effect
867 if (layerSizeIsDirty || postProcessingStateDirty) {
868 m_texture->setPixelSize(m_surfaceSize);
869 m_texture->setFormat(layerTextureFormat(rhi, postProcessingNeeded));
870 m_texture->create();
871
872 // If AA settings changed, then we drop and recreate all
873 // resources, otherwise use a lighter path if just the size
874 // changed.
875 if (!m_aaIsDirty) {
876 // A special case: when toggling effects and AA is on,
877 // use the heavier AA path because the renderbuffer for
878 // MSAA and texture for SSAA may need a different
879 // format now since m_texture's format could have
880 // changed between RBGA8 and RGBA16F (due to layerTextureFormat()).
881 if (postProcessingStateDirty && (m_layer->antialiasingMode != QSSGRenderLayer::AAMode::NoAA || m_layer->isTemporalAAEnabled())) {
882 releaseAaDependentRhiResources();
883 } else {
884 if (m_ssaaTexture) {
885 m_ssaaTexture->setPixelSize(renderSize);
886 m_ssaaTexture->create();
887 }
888 if (m_depthStencilBuffer) {
889 m_depthStencilBuffer->setPixelSize(renderSize);
890 m_depthStencilBuffer->create();
891 }
892 if (m_multiViewDepthStencilBuffer) {
893 m_multiViewDepthStencilBuffer->setPixelSize(renderSize);
894 m_multiViewDepthStencilBuffer->create();
895 }
896 if (m_msaaRenderBuffer) {
897 m_msaaRenderBuffer->setPixelSize(renderSize);
898 m_msaaRenderBuffer->create();
899 }
900 if (m_msaaMultiViewRenderBuffer) {
901 m_msaaMultiViewRenderBuffer->setPixelSize(renderSize);
902 m_msaaMultiViewRenderBuffer->create();
903 }
904 // Toggling effects on and off will change the format
905 // (assuming effects default to a floating point
906 // format) and that needs on a different renderpass on
907 // Vulkan. Hence renewing m_textureRenderPassDescriptor as well.
908 if (postProcessingStateDirty) {
909 delete m_textureRenderPassDescriptor;
910 m_textureRenderPassDescriptor = m_textureRenderTarget->newCompatibleRenderPassDescriptor();
911 m_textureRenderTarget->setRenderPassDescriptor(m_textureRenderPassDescriptor);
912 }
913 m_textureRenderTarget->create();
914 if (m_ssaaTextureToTextureRenderTarget)
915 m_ssaaTextureToTextureRenderTarget->create();
916
917 if (m_temporalAATexture) {
918 m_temporalAATexture->setPixelSize(renderSize);
919 m_temporalAATexture->create();
920 }
921 if (m_prevTempAATexture) {
922 m_prevTempAATexture->setPixelSize(renderSize);
923 m_prevTempAATexture->create();
924 }
925 if (m_temporalAARenderTarget)
926 m_temporalAARenderTarget->create();
927 }
928 }
929 } else if (m_aaIsDirty && rhi->backend() == QRhi::Metal) { // ### to avoid garbage upon enabling MSAA with macOS 10.14 (why is this needed?)
930 m_texture->create();
931 }
932
933 if (m_aaIsDirty)
934 releaseAaDependentRhiResources();
935 }
936
937 const QRhiTexture::Flags textureFlags = QRhiTexture::RenderTarget
938 | QRhiTexture::UsedAsTransferSource; // transfer source is for progressive/temporal AA
939 const QRhiTexture::Format textureFormat = layerTextureFormat(rhi, postProcessingNeeded);
940
941 if (!m_texture) {
942 if (m_layer->viewCount >= 2)
943 m_texture = rhi->newTextureArray(format: textureFormat, arraySize: m_layer->viewCount, pixelSize: m_surfaceSize, sampleCount: 1, flags: textureFlags);
944 else
945 m_texture = rhi->newTexture(format: textureFormat, pixelSize: m_surfaceSize, sampleCount: 1, flags: textureFlags);
946 m_texture->create();
947 }
948
949 if (!m_ssaaTexture && m_layer->isSsaaEnabled()) {
950 if (m_layer->viewCount >= 2)
951 m_ssaaTexture = rhi->newTextureArray(format: textureFormat, arraySize: m_layer->viewCount, pixelSize: renderSize, sampleCount: 1, flags: textureFlags);
952 else
953 m_ssaaTexture = rhi->newTexture(format: textureFormat, pixelSize: renderSize, sampleCount: 1, flags: textureFlags);
954 m_ssaaTexture->create();
955 }
956
957 if (m_timeBasedAA && !m_temporalAATexture) {
958 m_temporalAATexture = rhi->newTexture(format: textureFormat, pixelSize: renderSize, sampleCount: 1, flags: textureFlags);
959 m_temporalAATexture->create();
960 m_prevTempAATexture = rhi->newTexture(format: textureFormat, pixelSize: renderSize, sampleCount: 1, flags: textureFlags);
961 m_prevTempAATexture->create();
962 }
963
964 // we need to re-render time-based AA not only when AA state changes, but also when resized
965 if (m_aaIsDirty || layerSizeIsDirty)
966 m_layer->tempAAPassIndex = m_layer->progAAPassIndex = 0;
967
968 if (m_aaIsDirty) {
969 m_samples = 1;
970 if (m_layer->antialiasingMode == QSSGRenderLayer::AAMode::MSAA) {
971 if (rhi->isFeatureSupported(feature: QRhi::MultisampleRenderBuffer)) {
972 m_samples = qMax(a: 1, b: int(m_layer->antialiasingQuality));
973 // The Quick3D API exposes high level values such as
974 // Medium, High, VeryHigh instead of direct sample
975 // count values. Therefore, be nice and find a sample
976 // count that's actually supported in case the one
977 // associated by default is not.
978 const QVector<int> supported = rhi->supportedSampleCounts(); // assumed to be sorted
979 if (!supported.contains(t: m_samples)) {
980 if (!supported.isEmpty()) {
981 auto it = std::lower_bound(first: supported.cbegin(), last: supported.cend(), val: m_samples);
982 m_samples = it == supported.cend() ? supported.last() : *it;
983 } else {
984 m_samples = 1;
985 }
986 }
987 } else {
988 static bool warned = false;
989 if (!warned) {
990 warned = true;
991 qWarning(msg: "Multisample renderbuffers are not supported, disabling MSAA for Offscreen View3D");
992 }
993 }
994 }
995 }
996
997 if (m_layer->viewCount >= 2) {
998 if (!m_multiViewDepthStencilBuffer) {
999 m_multiViewDepthStencilBuffer = rhi->newTextureArray(format: QRhiTexture::D24S8, arraySize: m_layer->viewCount, pixelSize: renderSize,
1000 sampleCount: m_samples, flags: QRhiTexture::RenderTarget);
1001 m_multiViewDepthStencilBuffer->create();
1002 }
1003 } else {
1004 if (!m_depthStencilBuffer) {
1005 m_depthStencilBuffer = rhi->newRenderBuffer(type: QRhiRenderBuffer::DepthStencil, pixelSize: renderSize, sampleCount: m_samples);
1006 m_depthStencilBuffer->create();
1007 }
1008 }
1009
1010 if (!m_textureRenderTarget) {
1011 QRhiTextureRenderTargetDescription rtDesc;
1012 QRhiColorAttachment att;
1013 if (m_samples > 1) {
1014 if (m_layer->viewCount >= 2) {
1015 m_msaaMultiViewRenderBuffer = rhi->newTextureArray(format: textureFormat, arraySize: m_layer->viewCount, pixelSize: renderSize, sampleCount: m_samples, flags: QRhiTexture::RenderTarget);
1016 m_msaaMultiViewRenderBuffer->create();
1017 att.setTexture(m_msaaMultiViewRenderBuffer);
1018 } else {
1019 // pass in the texture's format (which may be a floating point one!) as the preferred format hint
1020 m_msaaRenderBuffer = rhi->newRenderBuffer(type: QRhiRenderBuffer::Color, pixelSize: renderSize, sampleCount: m_samples, flags: {}, backingFormatHint: m_texture->format());
1021 m_msaaRenderBuffer->create();
1022 att.setRenderBuffer(m_msaaRenderBuffer);
1023 }
1024 att.setResolveTexture(m_texture);
1025 } else {
1026 if (m_layer->antialiasingMode == QSSGRenderLayer::AAMode::SSAA)
1027 att.setTexture(m_ssaaTexture);
1028 else
1029 att.setTexture(m_texture);
1030 }
1031 att.setMultiViewCount(m_layer->viewCount);
1032 rtDesc.setColorAttachments({ att });
1033 if (m_depthStencilBuffer)
1034 rtDesc.setDepthStencilBuffer(m_depthStencilBuffer);
1035 if (m_multiViewDepthStencilBuffer)
1036 rtDesc.setDepthTexture(m_multiViewDepthStencilBuffer);
1037
1038 m_textureRenderTarget = rhi->newTextureRenderTarget(desc: rtDesc);
1039 m_textureRenderTarget->setName(QByteArrayLiteral("View3D"));
1040 m_textureRenderPassDescriptor = m_textureRenderTarget->newCompatibleRenderPassDescriptor();
1041 m_textureRenderTarget->setRenderPassDescriptor(m_textureRenderPassDescriptor);
1042 m_textureRenderTarget->create();
1043 }
1044
1045 if (!m_ssaaTextureToTextureRenderTarget && m_layer->antialiasingMode == QSSGRenderLayer::AAMode::SSAA) {
1046 QRhiColorAttachment att(m_texture);
1047 att.setMultiViewCount(m_layer->viewCount);
1048 m_ssaaTextureToTextureRenderTarget = rhi->newTextureRenderTarget(desc: QRhiTextureRenderTargetDescription({ att }));
1049 m_ssaaTextureToTextureRenderTarget->setName(QByteArrayLiteral("SSAA texture"));
1050 m_ssaaTextureToTextureRenderPassDescriptor = m_ssaaTextureToTextureRenderTarget->newCompatibleRenderPassDescriptor();
1051 m_ssaaTextureToTextureRenderTarget->setRenderPassDescriptor(m_ssaaTextureToTextureRenderPassDescriptor);
1052 m_ssaaTextureToTextureRenderTarget->create();
1053 }
1054
1055 if (m_layer->firstEffect) {
1056 if (!m_effectSystem)
1057 m_effectSystem = new QSSGRhiEffectSystem(m_sgContext);
1058 m_effectSystem->setup(renderSize);
1059 } else if (m_effectSystem) {
1060 delete m_effectSystem;
1061 m_effectSystem = nullptr;
1062 }
1063
1064 if (m_timeBasedAA && !m_temporalAARenderTarget) {
1065 m_temporalAARenderTarget = rhi->newTextureRenderTarget(desc: { m_temporalAATexture });
1066 m_temporalAARenderTarget->setName(QByteArrayLiteral("Temporal AA texture"));
1067 m_temporalAARenderPassDescriptor = m_temporalAARenderTarget->newCompatibleRenderPassDescriptor();
1068 m_temporalAARenderTarget->setRenderPassDescriptor(m_temporalAARenderPassDescriptor);
1069 m_temporalAARenderTarget->create();
1070 }
1071
1072 m_textureNeedsFlip = rhi->isYUpInFramebuffer();
1073 m_aaIsDirty = false;
1074 }
1075
1076 if (m_renderStats)
1077 m_renderStats->endSync(dump: dumpRenderTimes());
1078
1079 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DSynchronizeFrame, quint64(m_surfaceSize.width()) | quint64(m_surfaceSize.height()) << 32, profilingId);
1080}
1081
1082void QQuick3DSceneRenderer::invalidateFramebufferObject()
1083{
1084 if (fboNode)
1085 fboNode->invalidatePending = true;
1086}
1087
1088void QQuick3DSceneRenderer::releaseCachedResources()
1089{
1090 if (m_layer && m_layer->renderData) {
1091 if (const auto &mgr = m_layer->renderData->getShadowMapManager())
1092 mgr->releaseCachedResources();
1093 if (const auto &mgr = m_layer->renderData->getReflectionMapManager())
1094 mgr->releaseCachedResources();
1095 }
1096}
1097
1098std::optional<QSSGRenderRay> QQuick3DSceneRenderer::getRayFromViewportPos(const QPointF &pos)
1099{
1100 if (!m_layer)
1101 return std::nullopt;
1102
1103 QMutexLocker locker(&m_layer->renderedCamerasMutex);
1104
1105 if (m_layer->renderedCameras.isEmpty())
1106 return std::nullopt;
1107
1108 const QVector2D viewportSize(m_surfaceSize.width(), m_surfaceSize.height());
1109 const QVector2D position(float(pos.x()), float(pos.y()));
1110 const QRectF viewportRect(QPointF{}, QSizeF(m_surfaceSize));
1111
1112 // First invert the y so we are dealing with numbers in a normal coordinate space.
1113 // Second, move into our layer's coordinate space
1114 QVector2D correctCoords(position.x(), viewportSize.y() - position.y());
1115 QVector2D theLocalMouse = QSSGUtils::rect::toRectRelative(r: viewportRect, absoluteCoordinates: correctCoords);
1116 if ((theLocalMouse.x() < 0.0f || theLocalMouse.x() >= viewportSize.x() || theLocalMouse.y() < 0.0f
1117 || theLocalMouse.y() >= viewportSize.y()))
1118 return std::nullopt;
1119
1120 return m_layer->renderedCameras[0]->unproject(inLayerRelativeMouseCoords: theLocalMouse, inViewport: viewportRect);
1121}
1122
1123QQuick3DSceneRenderer::PickResultList QQuick3DSceneRenderer::syncPick(const QSSGRenderRay &ray)
1124{
1125 if (!m_layer)
1126 return QQuick3DSceneRenderer::PickResultList();
1127
1128 return QSSGRendererPrivate::syncPick(ctx: *m_sgContext,
1129 layer: *m_layer,
1130 ray);
1131}
1132
1133QQuick3DSceneRenderer::PickResultList QQuick3DSceneRenderer::syncPickOne(const QSSGRenderRay &ray, QSSGRenderNode *node)
1134{
1135 if (!m_layer)
1136 return QQuick3DSceneRenderer::PickResultList();
1137
1138 return QSSGRendererPrivate::syncPick(ctx: *m_sgContext,
1139 layer: *m_layer,
1140 ray,
1141 target: node);
1142}
1143
1144QQuick3DSceneRenderer::PickResultList QQuick3DSceneRenderer::syncPickSubset(const QSSGRenderRay &ray,
1145 QVarLengthArray<QSSGRenderNode *> subset)
1146{
1147 if (!m_layer)
1148 return QQuick3DSceneRenderer::PickResultList();
1149
1150 return QSSGRendererPrivate::syncPickSubset(layer: *m_layer,
1151 bufferManager&: *m_sgContext->bufferManager(),
1152 ray,
1153 subset);
1154}
1155
1156QQuick3DSceneRenderer::PickResultList QQuick3DSceneRenderer::syncPickAll(const QSSGRenderRay &ray)
1157{
1158 if (!m_layer)
1159 return QQuick3DSceneRenderer::PickResultList();
1160
1161 return QSSGRendererPrivate::syncPickAll(ctx: *m_sgContext,
1162 layer: *m_layer,
1163 ray);
1164}
1165
1166void QQuick3DSceneRenderer::setGlobalPickingEnabled(bool isEnabled)
1167{
1168 QSSGRendererPrivate::setGlobalPickingEnabled(renderer&: *m_sgContext->renderer(), isEnabled);
1169}
1170
1171QQuick3DRenderStats *QQuick3DSceneRenderer::renderStats()
1172{
1173 return m_renderStats;
1174}
1175
1176void QQuick3DRenderLayerHelpers::updateLayerNodeHelper(const QQuick3DViewport &view3D,
1177 const std::shared_ptr<QSSGRenderContextInterface>& rci,
1178 QSSGRenderLayer &layerNode,
1179 bool &aaIsDirty,
1180 bool &temporalIsDirty)
1181{
1182 QList<QSSGRenderGraphObject *> resourceLoaders; // empty list
1183
1184 QQuick3DSceneRenderer dummyRenderer(rci);
1185
1186 // Update the layer node properties
1187 dummyRenderer.updateLayerNode(layerNode, view3D, resourceLoaders);
1188
1189 aaIsDirty = dummyRenderer.m_aaIsDirty;
1190 temporalIsDirty = dummyRenderer.m_temporalIsDirty;
1191}
1192
1193void QQuick3DSceneRenderer::updateLayerNode(QSSGRenderLayer &layerNode,
1194 const QQuick3DViewport &view3D,
1195 const QList<QSSGRenderGraphObject *> &resourceLoaders)
1196{
1197 QQuick3DSceneEnvironment *environment = view3D.environment();
1198 const auto &effects = environment->effectList();
1199
1200 QSSGRenderLayer::AAMode aaMode = QSSGRenderLayer::AAMode(environment->antialiasingMode());
1201 if (aaMode != layerNode.antialiasingMode) {
1202 layerNode.antialiasingMode = aaMode;
1203 layerNode.progAAPassIndex = 0;
1204 m_aaIsDirty = true;
1205 }
1206 QSSGRenderLayer::AAQuality aaQuality = QSSGRenderLayer::AAQuality(environment->antialiasingQuality());
1207 if (aaQuality != layerNode.antialiasingQuality) {
1208 layerNode.antialiasingQuality = aaQuality;
1209 layerNode.ssaaMultiplier = QSSGRenderLayer::ssaaMultiplierForQuality(quality: aaQuality);
1210 m_aaIsDirty = true;
1211 }
1212
1213 // NOTE: Temporal AA is disabled when MSAA is enabled.
1214 const bool temporalAARequested = environment->temporalAAEnabled();
1215 const bool wasTaaEnabled = layerNode.isTemporalAAEnabled();
1216 layerNode.temporalAAMode = temporalAARequested ? QSSGRenderLayer::TAAMode::On
1217 : QSSGRenderLayer::TAAMode::Off;
1218
1219 // If the state changed we need to reset the temporal AA pass index etc.
1220 if (wasTaaEnabled != layerNode.isTemporalAAEnabled()) {
1221 layerNode.tempAAPassIndex = 0;
1222 m_aaIsDirty = true;
1223 m_temporalIsDirty = true;
1224 }
1225
1226 layerNode.temporalAAStrength = environment->temporalAAStrength();
1227
1228 layerNode.specularAAEnabled = environment->specularAAEnabled();
1229
1230 layerNode.background = QSSGRenderLayer::Background(environment->backgroundMode());
1231 layerNode.clearColor = QVector3D(float(environment->clearColor().redF()),
1232 float(environment->clearColor().greenF()),
1233 float(environment->clearColor().blueF()));
1234
1235 layerNode.gridEnabled = environment->gridEnabled();
1236 layerNode.gridScale = environment->gridScale();
1237 layerNode.gridFlags = environment->gridFlags();
1238
1239 layerNode.aoStrength = environment->aoStrength();
1240 layerNode.aoDistance = environment->aoDistance();
1241 layerNode.aoSoftness = environment->aoSoftness();
1242 layerNode.aoEnabled = environment->aoEnabled();
1243 layerNode.aoBias = environment->aoBias();
1244 layerNode.aoSamplerate = environment->aoSampleRate();
1245 layerNode.aoDither = environment->aoDither();
1246
1247 // ### These images will not be registered anywhere
1248 if (environment->lightProbe())
1249 layerNode.lightProbe = environment->lightProbe()->getRenderImage();
1250 else
1251 layerNode.lightProbe = nullptr;
1252 if (view3D.environment()->skyBoxCubeMap())
1253 layerNode.skyBoxCubeMap = view3D.environment()->skyBoxCubeMap()->getRenderImage();
1254 else
1255 layerNode.skyBoxCubeMap = nullptr;
1256
1257 layerNode.lightProbeSettings.probeExposure = environment->probeExposure();
1258 // Remap the probeHorizon to the expected Range
1259 layerNode.lightProbeSettings.probeHorizon = qMin(a: environment->probeHorizon() - 1.0f, b: -0.001f);
1260 layerNode.setProbeOrientation(environment->probeOrientation());
1261
1262 QQuick3DViewport::updateCameraForLayer(view3D, layerNode);
1263
1264 layerNode.layerFlags.setFlag(flag: QSSGRenderLayer::LayerFlag::EnableDepthTest, on: environment->depthTestEnabled());
1265 layerNode.layerFlags.setFlag(flag: QSSGRenderLayer::LayerFlag::EnableDepthPrePass, on: environment->depthPrePassEnabled());
1266
1267 layerNode.tonemapMode = QQuick3DSceneRenderer::getTonemapMode(environment: *environment);
1268 layerNode.skyboxBlurAmount = environment->skyboxBlurAmount();
1269 if (auto debugSettings = view3D.environment()->debugSettings()) {
1270 layerNode.debugMode = QSSGRenderLayer::MaterialDebugMode(debugSettings->materialOverride());
1271 layerNode.wireframeMode = debugSettings->wireframeEnabled();
1272 layerNode.drawDirectionalLightShadowBoxes = debugSettings->drawDirectionalLightShadowBoxes();
1273 layerNode.drawShadowCastingBounds = debugSettings->drawShadowCastingBounds();
1274 layerNode.drawShadowReceivingBounds = debugSettings->drawShadowReceivingBounds();
1275 layerNode.drawCascades = debugSettings->drawCascades();
1276 layerNode.drawSceneCascadeIntersection = debugSettings->drawSceneCascadeIntersection();
1277 layerNode.disableShadowCameraUpdate = debugSettings->disableShadowCameraUpdate();
1278 } else {
1279 layerNode.debugMode = QSSGRenderLayer::MaterialDebugMode::None;
1280 layerNode.wireframeMode = false;
1281 }
1282
1283 if (environment->lightmapper()) {
1284 QQuick3DLightmapper *lightmapper = environment->lightmapper();
1285 layerNode.lmOptions.opacityThreshold = lightmapper->opacityThreshold();
1286 layerNode.lmOptions.bias = lightmapper->bias();
1287 layerNode.lmOptions.useAdaptiveBias = lightmapper->isAdaptiveBiasEnabled();
1288 layerNode.lmOptions.indirectLightEnabled = lightmapper->isIndirectLightEnabled();
1289 layerNode.lmOptions.indirectLightSamples = lightmapper->samples();
1290 layerNode.lmOptions.indirectLightWorkgroupSize = lightmapper->indirectLightWorkgroupSize();
1291 layerNode.lmOptions.indirectLightBounces = lightmapper->bounces();
1292 layerNode.lmOptions.indirectLightFactor = lightmapper->indirectLightFactor();
1293 } else {
1294 layerNode.lmOptions = {};
1295 }
1296
1297 if (environment->fog() && environment->fog()->isEnabled()) {
1298 layerNode.fog.enabled = true;
1299 const QQuick3DFog *fog = environment->fog();
1300 layerNode.fog.color = QSSGUtils::color::sRGBToLinear(color: fog->color()).toVector3D();
1301 layerNode.fog.density = fog->density();
1302 layerNode.fog.depthEnabled = fog->isDepthEnabled();
1303 layerNode.fog.depthBegin = fog->depthNear();
1304 layerNode.fog.depthEnd = fog->depthFar();
1305 layerNode.fog.depthCurve = fog->depthCurve();
1306 layerNode.fog.heightEnabled = fog->isHeightEnabled();
1307 layerNode.fog.heightMin = fog->leastIntenseY();
1308 layerNode.fog.heightMax = fog->mostIntenseY();
1309 layerNode.fog.heightCurve = fog->heightCurve();
1310 layerNode.fog.transmitEnabled = fog->isTransmitEnabled();
1311 layerNode.fog.transmitCurve = fog->transmitCurve();
1312 } else {
1313 layerNode.fog.enabled = false;
1314 }
1315
1316 // Effects need to be rendered in reverse order as described in the file.
1317 // NOTE: We only build up the list here, don't do anything that depends
1318 // on the collected layer state yet. See sync() for that.
1319 layerNode.firstEffect = nullptr; // We reset the linked list
1320 auto rit = effects.crbegin();
1321 const auto rend = effects.crend();
1322 for (; rit != rend; ++rit) {
1323 QQuick3DObjectPrivate *p = QQuick3DObjectPrivate::get(item: *rit);
1324 QSSGRenderEffect *effectNode = static_cast<QSSGRenderEffect *>(p->spatialNode);
1325 if (effectNode) {
1326 if (layerNode.hasEffect(inEffect: effectNode)) {
1327 qWarning() << "Duplicate effect found, skipping!";
1328 } else {
1329 effectNode->className = (*rit)->metaObject()->className(); //### persistent, but still icky to store a const char* returned from a function
1330 layerNode.addEffect(inEffect&: *effectNode);
1331 }
1332 }
1333 }
1334
1335 const bool hasEffects = (layerNode.firstEffect != nullptr);
1336
1337 const auto renderMode = view3D.renderMode();
1338
1339 const bool progressiveAA = layerNode.isProgressiveAAEnabled();
1340 const bool temporalAA = layerNode.isTemporalAAEnabled();
1341 const bool superSamplingAA = layerNode.isSsaaEnabled();
1342 m_timeBasedAA = progressiveAA || temporalAA;
1343 m_postProcessingStack = hasEffects || m_timeBasedAA || superSamplingAA;
1344 m_useFBO = renderMode == QQuick3DViewport::RenderMode::Offscreen ||
1345 ((renderMode == QQuick3DViewport::RenderMode::Underlay || renderMode == QQuick3DViewport::RenderMode::Overlay)
1346 && m_postProcessingStack);
1347
1348 // Update the view count
1349
1350 // NOTE: If we're rendering to an FBO, the view count is more than 1, and the View3D is not an XR view instance,
1351 // we need to force the view count to 1 (The only time this should be the case is when embedding View3D(s)
1352 // in XR with multiview enabled).
1353 // Also, note that embedding View3D(s) in XR with multiview enabled only works if those View3D(s) are
1354 // being rendered through a FBO.
1355 if (m_useFBO && (layerNode.viewCount > 1) && !view3D.isXrViewInstance())
1356 layerNode.viewCount = 1;
1357
1358 // ResourceLoaders
1359 layerNode.resourceLoaders.clear();
1360 layerNode.resourceLoaders = resourceLoaders;
1361}
1362
1363void QQuick3DSceneRenderer::removeNodeFromLayer(QSSGRenderNode *node)
1364{
1365 if (!m_layer)
1366 return;
1367
1368 m_layer->removeChild(inChild&: *node);
1369}
1370
1371void QQuick3DSceneRenderer::addNodeToLayer(QSSGRenderNode *node)
1372{
1373 if (!m_layer)
1374 return;
1375
1376 m_layer->addChild(inChild&: *node);
1377}
1378
1379QSGRenderNode::StateFlags QQuick3DSGRenderNode::changedStates() const
1380{
1381 return BlendState | StencilState | DepthState | ScissorState | ColorState | CullState | ViewportState | RenderTargetState;
1382}
1383
1384namespace {
1385inline QRect convertQtRectToGLViewport(const QRectF &rect, const QSize surfaceSize)
1386{
1387 const int x = int(rect.x());
1388 const int y = surfaceSize.height() - (int(rect.y()) + int(rect.height()));
1389 const int width = int(rect.width());
1390 const int height = int(rect.height());
1391 return QRect(x, y, width, height);
1392}
1393
1394inline void queryMainRenderPassDescriptorAndCommandBuffer(QQuickWindow *window, QSSGRhiContext *rhiCtx)
1395{
1396 if (rhiCtx->isValid()) {
1397 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx);
1398 // Query from the rif because that is available in the sync
1399 // phase (updatePaintNode) already. QSGDefaultRenderContext's
1400 // copies of the rp and cb are not there until the render
1401 // phase of the scenegraph.
1402 int sampleCount = 1;
1403 int viewCount = 1;
1404 QRhiSwapChain *swapchain = window->swapChain();
1405 if (swapchain) {
1406 rhiCtxD->setMainRenderPassDescriptor(swapchain->renderPassDescriptor());
1407 rhiCtxD->setCommandBuffer(swapchain->currentFrameCommandBuffer());
1408 rhiCtxD->setRenderTarget(swapchain->currentFrameRenderTarget());
1409 sampleCount = swapchain->sampleCount();
1410 } else {
1411 QSGRendererInterface *rif = window->rendererInterface();
1412 // no swapchain when using a QQuickRenderControl (redirecting to a texture etc.)
1413 QRhiCommandBuffer *cb = static_cast<QRhiCommandBuffer *>(
1414 rif->getResource(window, resource: QSGRendererInterface::RhiRedirectCommandBuffer));
1415 QRhiTextureRenderTarget *rt = static_cast<QRhiTextureRenderTarget *>(
1416 rif->getResource(window, resource: QSGRendererInterface::RhiRedirectRenderTarget));
1417 if (cb && rt) {
1418 rhiCtxD->setMainRenderPassDescriptor(rt->renderPassDescriptor());
1419 rhiCtxD->setCommandBuffer(cb);
1420 rhiCtxD->setRenderTarget(rt);
1421 const QRhiColorAttachment *color0 = rt->description().cbeginColorAttachments();
1422 if (color0 && color0->texture()) {
1423 sampleCount = color0->texture()->sampleCount();
1424 if (rt->resourceType() == QRhiResource::TextureRenderTarget) {
1425 const QRhiTextureRenderTargetDescription desc = static_cast<QRhiTextureRenderTarget *>(rt)->description();
1426 for (auto it = desc.cbeginColorAttachments(), end = desc.cendColorAttachments(); it != end; ++it) {
1427 if (it->multiViewCount() >= 2) {
1428 viewCount = it->multiViewCount();
1429 break;
1430 }
1431 }
1432 }
1433 }
1434 } else {
1435 qWarning(msg: "Neither swapchain nor redirected command buffer and render target are available.");
1436 }
1437 }
1438
1439 // MSAA is out of our control on this path: it is up to the
1440 // QQuickWindow and the scenegraph to set up the swapchain based on the
1441 // QSurfaceFormat's samples(). The only thing we need to do here is to
1442 // pass the sample count to the renderer because it is needed when
1443 // creating graphics pipelines.
1444 rhiCtxD->setMainPassSampleCount(sampleCount);
1445
1446 // The "direct renderer", i.e. the Underlay and Overlay modes are the
1447 // only ones that support multiview rendering. This becomes active when
1448 // the QQuickWindow is redirected into a texture array, typically with
1449 // an array size of 2 (2 views, for the left and right eye). Otherwise,
1450 // when targeting a window or redirected to a 2D texture, this is not
1451 // applicable and the view count is 1.
1452 rhiCtxD->setMainPassViewCount(viewCount);
1453 }
1454}
1455
1456// The alternative to queryMainRenderPassDescriptorAndCommandBuffer()
1457// specifically for the Inline render mode when there is a QSGRenderNode.
1458inline void queryInlineRenderPassDescriptorAndCommandBuffer(QSGRenderNode *node, QSSGRhiContext *rhiCtx)
1459{
1460 QSGRenderNodePrivate *d = QSGRenderNodePrivate::get(node);
1461 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx);
1462 rhiCtxD->setMainRenderPassDescriptor(d->m_rt.rpDesc);
1463 rhiCtxD->setCommandBuffer(d->m_rt.cb);
1464 rhiCtxD->setRenderTarget(d->m_rt.rt);
1465 rhiCtxD->setMainPassSampleCount(d->m_rt.rt->sampleCount());
1466 rhiCtxD->setMainPassViewCount(1);
1467}
1468
1469} // namespace
1470
1471QQuick3DSGRenderNode::~QQuick3DSGRenderNode()
1472{
1473 delete renderer;
1474}
1475
1476void QQuick3DSGRenderNode::prepare()
1477{
1478 // this is outside the main renderpass
1479
1480 if (!renderer->m_sgContext->rhiContext()->isValid())
1481 return;
1482 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DPrepareFrame);
1483
1484 queryInlineRenderPassDescriptorAndCommandBuffer(node: this, rhiCtx: renderer->m_sgContext->rhiContext().get());
1485
1486 qreal dpr = window->effectiveDevicePixelRatio();
1487 const QSizeF itemSize = renderer->surfaceSize() / dpr;
1488 QRectF viewport = matrix()->mapRect(rect: QRectF(QPoint(0, 0), itemSize));
1489 viewport = QRectF(viewport.topLeft() * dpr, viewport.size() * dpr);
1490 const QRect vp = convertQtRectToGLViewport(rect: viewport, surfaceSize: window->size() * dpr);
1491
1492 Q_TRACE_SCOPE(QSSG_prepareFrame, vp.width(), vp.height());
1493
1494 renderer->beginFrame();
1495 renderer->rhiPrepare(viewport: vp, displayPixelRatio: dpr);
1496 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DPrepareFrame, quint64(vp.width()) | quint64(vp.height()) << 32, renderer->profilingId);
1497}
1498
1499void QQuick3DSGRenderNode::render(const QSGRenderNode::RenderState *state)
1500{
1501 Q_UNUSED(state);
1502
1503 const auto &rhiContext = renderer->m_sgContext->rhiContext();
1504
1505 if (rhiContext->isValid()) {
1506 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderFrame);
1507 Q_TRACE_SCOPE(QSSG_renderFrame, 0, 0);
1508
1509 queryInlineRenderPassDescriptorAndCommandBuffer(node: this, rhiCtx: renderer->m_sgContext->rhiContext().get());
1510
1511 renderer->rhiRender();
1512 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DRenderFrame,
1513 STAT_PAYLOAD(QSSGRhiContextStats::get(*rhiContext)), renderer->profilingId);
1514 renderer->endFrame();
1515 }
1516}
1517
1518void QQuick3DSGRenderNode::releaseResources()
1519{
1520}
1521
1522QSGRenderNode::RenderingFlags QQuick3DSGRenderNode::flags() const
1523{
1524 // don't want begin/endExternal() to be called by Quick
1525 return NoExternalRendering;
1526}
1527
1528QQuick3DSGDirectRenderer::QQuick3DSGDirectRenderer(QQuick3DSceneRenderer *renderer, QQuickWindow *window, QQuick3DSGDirectRenderer::QQuick3DSGDirectRendererMode mode)
1529 : m_renderer(renderer)
1530 , m_window(window)
1531{
1532 if (QSGRendererInterface::isApiRhiBased(api: window->rendererInterface()->graphicsApi())) {
1533 connect(sender: window, signal: &QQuickWindow::beforeRendering, context: this, slot: &QQuick3DSGDirectRenderer::prepare, type: Qt::DirectConnection);
1534 if (mode == Underlay)
1535 connect(sender: window, signal: &QQuickWindow::beforeRenderPassRecording, context: this, slot: &QQuick3DSGDirectRenderer::render, type: Qt::DirectConnection);
1536 else
1537 connect(sender: window, signal: &QQuickWindow::afterRenderPassRecording, context: this, slot: &QQuick3DSGDirectRenderer::render, type: Qt::DirectConnection);
1538 }
1539}
1540
1541QQuick3DSGDirectRenderer::~QQuick3DSGDirectRenderer()
1542{
1543 delete m_renderer;
1544}
1545
1546void QQuick3DSGDirectRenderer::setViewport(const QRectF &viewport)
1547{
1548 m_viewport = viewport;
1549}
1550
1551void QQuick3DSGDirectRenderer::setVisibility(bool visible)
1552{
1553 if (m_isVisible == visible)
1554 return;
1555 m_isVisible = visible;
1556 m_window->update();
1557}
1558
1559void QQuick3DSGDirectRenderer::requestRender()
1560{
1561 renderPending = true;
1562 requestFullUpdate(window: m_window);
1563}
1564
1565void QQuick3DSGDirectRenderer::preSynchronize()
1566{
1567 // This is called from the QQuick3DViewport's updatePaintNode(), before
1568 // QQuick3DSceneRenderer::synchronize(). It is essential to query things
1569 // such as the view count already here, so that synchronize() can rely on
1570 // mainPassViewCount() for instance. prepare() is too late as that is only
1571 // called on beforeRendering (so after the scenegraph sync phase).
1572 if (m_renderer->m_sgContext->rhiContext()->isValid())
1573 queryMainRenderPassDescriptorAndCommandBuffer(window: m_window, rhiCtx: m_renderer->m_sgContext->rhiContext().get());
1574}
1575
1576void QQuick3DSGDirectRenderer::prepare()
1577{
1578 if (!m_isVisible || !m_renderer)
1579 return;
1580
1581 if (m_renderer->m_sgContext->rhiContext()->isValid()) {
1582 // this is outside the main renderpass
1583 if (m_renderer->m_postProcessingStack) {
1584 if (renderPending) {
1585 renderPending = false;
1586 m_rhiTexture = m_renderer->renderToRhiTexture(qw: m_window);
1587 // Set up the main render target again, e.g. the postprocessing
1588 // stack could have clobbered some settings such as the sample count.
1589 queryMainRenderPassDescriptorAndCommandBuffer(window: m_window, rhiCtx: m_renderer->m_sgContext->rhiContext().get());
1590 const auto &quadRenderer = m_renderer->m_sgContext->renderer()->rhiQuadRenderer();
1591 quadRenderer->prepareQuad(rhiCtx: m_renderer->m_sgContext->rhiContext().get(), maybeRub: nullptr);
1592 if (m_renderer->m_requestedFramesCount > 0) {
1593 requestRender();
1594 m_renderer->m_requestedFramesCount--;
1595 }
1596 }
1597 }
1598 else
1599 {
1600 QQuick3DRenderStats *renderStats = m_renderer->renderStats();
1601 if (renderStats) {
1602 renderStats->startRender();
1603 renderStats->startRenderPrepare();
1604 }
1605
1606 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DPrepareFrame);
1607 queryMainRenderPassDescriptorAndCommandBuffer(window: m_window, rhiCtx: m_renderer->m_sgContext->rhiContext().get());
1608 const QRect vp = convertQtRectToGLViewport(rect: m_viewport, surfaceSize: m_window->size() * m_window->effectiveDevicePixelRatio());
1609
1610 Q_TRACE_SCOPE(QSSG_prepareFrame, vp.width(), vp.height());
1611 m_renderer->beginFrame();
1612 m_renderer->rhiPrepare(viewport: vp, displayPixelRatio: m_window->effectiveDevicePixelRatio());
1613 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DPrepareFrame, quint64(vp.width()) | quint64(vp.height()) << 32, m_renderer->profilingId);
1614
1615 if (renderStats)
1616 renderStats->endRenderPrepare();
1617 }
1618 }
1619}
1620
1621void QQuick3DSGDirectRenderer::render()
1622{
1623 if (!m_isVisible || !m_renderer)
1624 return;
1625
1626 const auto &rhiContext = m_renderer->m_sgContext->rhiContext();
1627
1628 if (rhiContext->isValid()) {
1629 // the command buffer is recording the main renderpass at this point
1630
1631 // No m_window->beginExternalCommands() must be done here. When the
1632 // renderer is using the same
1633 // QRhi/QRhiCommandBuffer/QRhiRenderPassDescriptor as the Qt Quick
1634 // scenegraph, there is no difference from the RHI's perspective. There are
1635 // no external (native) commands here.
1636
1637 // Requery the command buffer and co. since Offscreen mode View3Ds may
1638 // have altered these on the context.
1639 if (m_renderer->m_postProcessingStack) {
1640 if (m_rhiTexture) {
1641 queryMainRenderPassDescriptorAndCommandBuffer(window: m_window, rhiCtx: rhiContext.get());
1642 auto rhiCtx = m_renderer->m_sgContext->rhiContext().get();
1643 const auto &renderer = m_renderer->m_sgContext->renderer();
1644 QRhiCommandBuffer *cb = rhiContext->commandBuffer();
1645 cb->debugMarkBegin(QByteArrayLiteral("Post-processing result to main rt"));
1646
1647 // Instead of passing in a flip flag we choose to rely on qsb's
1648 // per-target compilation mode in the fragment shader. (it does UV
1649 // flipping based on QSHADER_ macros) This is just better for
1650 // performance and the shaders are very simple so introducing a
1651 // uniform block and branching dynamically would be an overkill.
1652 QRect vp = convertQtRectToGLViewport(rect: m_viewport, surfaceSize: m_window->size() * m_window->effectiveDevicePixelRatio());
1653
1654 const auto &shaderCache = m_renderer->m_sgContext->shaderCache();
1655 const auto &shaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiSimpleQuadShader(viewCount: m_renderer->m_layer->viewCount);
1656
1657 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
1658 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::ClampToEdge });
1659 QSSGRhiShaderResourceBindingList bindings;
1660 bindings.addTexture(binding: 0, stage: QRhiShaderResourceBinding::FragmentStage, tex: m_rhiTexture, sampler);
1661 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiContext.get());
1662 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
1663
1664 QSSGRhiGraphicsPipelineState ps;
1665 ps.viewport = QRhiViewport(float(vp.x()), float(vp.y()), float(vp.width()), float(vp.height()));
1666 ps.samples = rhiCtx->mainPassSampleCount();
1667 ps.viewCount = m_renderer->m_layer->viewCount;
1668 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, pipeline: shaderPipeline.get());
1669 renderer->rhiQuadRenderer()->recordRenderQuad(rhiCtx, ps: &ps, srb, rpDesc: rhiCtx->mainRenderPassDescriptor(), flags: QSSGRhiQuadRenderer::UvCoords | QSSGRhiQuadRenderer::PremulBlend);
1670 cb->debugMarkEnd();
1671 }
1672 }
1673 else
1674 {
1675 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderFrame);
1676 Q_TRACE_SCOPE(QSSG_renderFrame, 0, 0);
1677
1678 queryMainRenderPassDescriptorAndCommandBuffer(window: m_window, rhiCtx: rhiContext.get());
1679
1680 m_renderer->rhiRender();
1681
1682 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DRenderFrame,
1683 STAT_PAYLOAD(QSSGRhiContextStats::get(*rhiContext)),
1684 m_renderer->profilingId);
1685 m_renderer->endFrame();
1686
1687 if (m_renderer->renderStats())
1688 m_renderer->renderStats()->endRender(dump: dumpRenderTimes());
1689 }
1690 }
1691}
1692
1693QT_END_NAMESPACE
1694

source code of qtquick3d/src/quick3d/qquick3dscenerenderer.cpp