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