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

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