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