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