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