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