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
1184std::optional<QSSGRenderPickResult> QQuick3DSceneRenderer::syncPickClosestPoint(const QVector3D &center, float radiusSquared, QSSGRenderNode *node)
1185{
1186 if (!m_layer)
1187 return std::nullopt;
1188
1189 return QSSGRendererPrivate::syncPickClosestPoint(*m_sgContext,
1190 *m_layer,
1191 center, radiusSquared,
1192 node);
1193}
1194
1196{
1197 if (!m_layer)
1198 return QQuick3DSceneRenderer::PickResultList();
1199
1200 return QSSGRendererPrivate::syncPick(*m_sgContext,
1201 *m_layer,
1202 ray);
1203}
1204
1205QQuick3DSceneRenderer::PickResultList QQuick3DSceneRenderer::syncPickOne(const QSSGRenderRay &ray, QSSGRenderNode *node)
1206{
1207 if (!m_layer)
1208 return QQuick3DSceneRenderer::PickResultList();
1209
1210 return QSSGRendererPrivate::syncPick(*m_sgContext,
1211 *m_layer,
1212 ray,
1213 node);
1214}
1215
1217 QVarLengthArray<QSSGRenderNode *> subset)
1218{
1219 if (!m_layer)
1220 return QQuick3DSceneRenderer::PickResultList();
1221
1222 return QSSGRendererPrivate::syncPickSubset(*m_layer,
1223 *m_sgContext->bufferManager(),
1224 ray,
1225 subset);
1226}
1227
1229{
1230 if (!m_layer)
1231 return QQuick3DSceneRenderer::PickResultList();
1232
1233 return QSSGRendererPrivate::syncPickAll(*m_sgContext,
1234 *m_layer,
1235 ray);
1236}
1237
1239{
1240 QSSGRendererPrivate::setGlobalPickingEnabled(*m_sgContext->renderer(), isEnabled);
1241}
1242
1244{
1245 return m_renderStats;
1246}
1247
1248void QQuick3DRenderLayerHelpers::updateLayerNodeHelper(const QQuick3DViewport &view3D,
1249 const std::shared_ptr<QSSGRenderContextInterface>& rci,
1250 QSSGRenderLayer &layerNode,
1251 bool &aaIsDirty,
1252 bool &temporalIsDirty)
1253{
1254 QList<QSSGRenderGraphObject *> resourceLoaders; // empty list
1255
1256 QQuick3DSceneRenderer dummyRenderer(rci);
1257
1258 // Update the layer node properties
1259 dummyRenderer.updateLayerNode(layerNode, view3D, resourceLoaders);
1260
1261 aaIsDirty = dummyRenderer.m_aaIsDirty;
1262 temporalIsDirty = dummyRenderer.m_temporalIsDirty;
1263}
1264
1265void QQuick3DSceneRenderer::updateLayerNode(QSSGRenderLayer &layerNode,
1266 const QQuick3DViewport &view3D,
1267 const QList<QSSGRenderGraphObject *> &resourceLoaders)
1268{
1269 QQuick3DSceneEnvironment *environment = view3D.environment();
1270 const auto &effects = environment->effectList();
1271
1272 QSSGRenderLayer::AAMode aaMode = QSSGRenderLayer::AAMode(environment->antialiasingMode());
1273 if (aaMode != layerNode.antialiasingMode) {
1274 layerNode.antialiasingMode = aaMode;
1275 layerNode.progAAPassIndex = 0;
1276 m_aaIsDirty = true;
1277 }
1278 QSSGRenderLayer::AAQuality aaQuality = QSSGRenderLayer::AAQuality(environment->antialiasingQuality());
1279 if (aaQuality != layerNode.antialiasingQuality) {
1280 layerNode.antialiasingQuality = aaQuality;
1281 layerNode.ssaaMultiplier = QSSGRenderLayer::ssaaMultiplierForQuality(aaQuality);
1282 m_aaIsDirty = true;
1283 }
1284
1285 // NOTE: Temporal AA is disabled when MSAA is enabled.
1286 const bool temporalAARequested = environment->temporalAAEnabled();
1287 const bool wasTaaEnabled = layerNode.isTemporalAAEnabled();
1288 layerNode.temporalAAMode = temporalAARequested ? QSSGRenderLayer::TAAMode::On
1289 : QSSGRenderLayer::TAAMode::Off;
1290
1291 // If the state changed we need to reset the temporal AA pass index etc.
1292 if (wasTaaEnabled != layerNode.isTemporalAAEnabled()) {
1293 layerNode.tempAAPassIndex = 0;
1294 m_aaIsDirty = true;
1295 m_temporalIsDirty = true;
1296 }
1297
1298 layerNode.temporalAAStrength = environment->temporalAAStrength();
1299
1300 layerNode.specularAAEnabled = environment->specularAAEnabled();
1301
1302 layerNode.background = QSSGRenderLayer::Background(environment->backgroundMode());
1303 layerNode.clearColor = QVector3D(float(environment->clearColor().redF()),
1304 float(environment->clearColor().greenF()),
1305 float(environment->clearColor().blueF()));
1306
1307 layerNode.gridEnabled = environment->gridEnabled();
1308 layerNode.gridScale = environment->gridScale();
1309 layerNode.gridFlags = environment->gridFlags();
1310
1311 layerNode.aoStrength = environment->aoStrength();
1312 layerNode.aoDistance = environment->aoDistance();
1313 layerNode.aoSoftness = environment->aoSoftness();
1314 layerNode.aoEnabled = environment->aoEnabled();
1315 layerNode.aoBias = environment->aoBias();
1316 layerNode.aoSamplerate = environment->aoSampleRate();
1317 layerNode.aoDither = environment->aoDither();
1318
1319 // ### These images will not be registered anywhere
1320 if (environment->lightProbe())
1321 layerNode.lightProbe = environment->lightProbe()->getRenderImage();
1322 else
1323 layerNode.lightProbe = nullptr;
1324 if (view3D.environment()->skyBoxCubeMap())
1325 layerNode.skyBoxCubeMap = view3D.environment()->skyBoxCubeMap()->getRenderImage();
1326 else
1327 layerNode.skyBoxCubeMap = nullptr;
1328
1329 layerNode.lightProbeSettings.probeExposure = environment->probeExposure();
1330 // Remap the probeHorizon to the expected Range
1331 layerNode.lightProbeSettings.probeHorizon = qMin(environment->probeHorizon() - 1.0f, -0.001f);
1332 layerNode.setProbeOrientation(environment->probeOrientation());
1333
1334 QQuick3DViewport::updateCameraForLayer(view3D, layerNode);
1335
1336 layerNode.layerFlags.setFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest, environment->depthTestEnabled());
1337 layerNode.layerFlags.setFlag(QSSGRenderLayer::LayerFlag::EnableDepthPrePass, environment->depthPrePassEnabled());
1338
1339 layerNode.tonemapMode = QQuick3DSceneRenderer::getTonemapMode(*environment);
1340 layerNode.skyboxBlurAmount = environment->skyboxBlurAmount();
1341 if (auto debugSettings = view3D.environment()->debugSettings()) {
1342 layerNode.debugMode = QSSGRenderLayer::MaterialDebugMode(debugSettings->materialOverride());
1343 layerNode.wireframeMode = debugSettings->wireframeEnabled();
1344 layerNode.drawDirectionalLightShadowBoxes = debugSettings->drawDirectionalLightShadowBoxes();
1345 layerNode.drawPointLightShadowBoxes = debugSettings->drawPointLightShadowBoxes();
1346 layerNode.drawShadowCastingBounds = debugSettings->drawShadowCastingBounds();
1347 layerNode.drawShadowReceivingBounds = debugSettings->drawShadowReceivingBounds();
1348 layerNode.drawCascades = debugSettings->drawCascades();
1349 layerNode.drawSceneCascadeIntersection = debugSettings->drawSceneCascadeIntersection();
1350 layerNode.disableShadowCameraUpdate = debugSettings->disableShadowCameraUpdate();
1351 layerNode.drawCulledObjects = debugSettings->drawCulledObjects();
1352 } else {
1353 layerNode.debugMode = QSSGRenderLayer::MaterialDebugMode::None;
1354 layerNode.wireframeMode = false;
1355 }
1356
1357 if (environment->fog() && environment->fog()->isEnabled()) {
1358 layerNode.fog.enabled = true;
1359 const QQuick3DFog *fog = environment->fog();
1360 layerNode.fog.color = QSSGUtils::color::sRGBToLinear(fog->color()).toVector3D();
1361 layerNode.fog.density = fog->density();
1362 layerNode.fog.depthEnabled = fog->isDepthEnabled();
1363 layerNode.fog.depthBegin = fog->depthNear();
1364 layerNode.fog.depthEnd = fog->depthFar();
1365 layerNode.fog.depthCurve = fog->depthCurve();
1366 layerNode.fog.heightEnabled = fog->isHeightEnabled();
1367 layerNode.fog.heightMin = fog->leastIntenseY();
1368 layerNode.fog.heightMax = fog->mostIntenseY();
1369 layerNode.fog.heightCurve = fog->heightCurve();
1370 layerNode.fog.transmitEnabled = fog->isTransmitEnabled();
1371 layerNode.fog.transmitCurve = fog->transmitCurve();
1372 } else {
1373 layerNode.fog.enabled = false;
1374 }
1375 const auto method = static_cast<QSSGRenderLayer::OITMethod>(environment->oitMethod());
1376 layerNode.oitMethodDirty = method != layerNode.oitMethod;
1377 layerNode.oitMethod = method;
1378
1379 // Effects need to be rendered in reverse order as described in the file.
1380 // NOTE: We only build up the list here, don't do anything that depends
1381 // on the collected layer state yet. See sync() for that.
1382 layerNode.firstEffect = nullptr; // We reset the linked list
1383 auto rit = effects.crbegin();
1384 const auto rend = effects.crend();
1385 for (; rit != rend; ++rit) {
1386 QQuick3DObjectPrivate *p = QQuick3DObjectPrivate::get(*rit);
1387 QSSGRenderEffect *effectNode = static_cast<QSSGRenderEffect *>(p->spatialNode);
1388 if (effectNode) {
1389 if (layerNode.hasEffect(effectNode)) {
1390 qWarning() << "Duplicate effect found, skipping!";
1391 } else {
1392 effectNode->className = (*rit)->metaObject()->className(); //### persistent, but still icky to store a const char* returned from a function
1393 layerNode.addEffect(*effectNode);
1394 }
1395 }
1396 }
1397
1398 const bool hasEffects = (layerNode.firstEffect != nullptr);
1399
1400 const auto renderMode = view3D.renderMode();
1401
1402 const bool progressiveAA = layerNode.isProgressiveAAEnabled();
1403 const bool temporalAA = layerNode.isTemporalAAEnabled();
1404 const bool superSamplingAA = layerNode.isSsaaEnabled();
1405 m_timeBasedAA = progressiveAA || temporalAA;
1406 m_postProcessingStack = hasEffects || m_timeBasedAA || superSamplingAA;
1407 m_useFBO = renderMode == QQuick3DViewport::RenderMode::Offscreen ||
1408 ((renderMode == QQuick3DViewport::RenderMode::Underlay || renderMode == QQuick3DViewport::RenderMode::Overlay)
1409 && m_postProcessingStack);
1410
1411 // Update the view count
1412
1413 // NOTE: If we're rendering to an FBO, the view count is more than 1, and the View3D is not an XR view instance,
1414 // we need to force the view count to 1 (The only time this should be the case is when embedding View3D(s)
1415 // in XR with multiview enabled).
1416 // Also, note that embedding View3D(s) in XR with multiview enabled only works if those View3D(s) are
1417 // being rendered through a FBO.
1418 if (m_useFBO && (layerNode.viewCount > 1) && !view3D.isXrViewInstance())
1419 layerNode.viewCount = 1;
1420
1421 // ResourceLoaders
1422 layerNode.resourceLoaders.clear();
1423 layerNode.resourceLoaders = resourceLoaders;
1424}
1425
1426void QQuick3DSceneRenderer::removeNodeFromLayer(QSSGRenderNode *node)
1427{
1428 if (!m_layer)
1429 return;
1430
1431 m_layer->removeChild(*node);
1432}
1433
1434void QQuick3DSceneRenderer::maybeSetupLightmapBaking(QQuick3DViewport *view3D)
1435{
1436 if (m_layer->renderData && m_layer->renderData->lightmapBaker)
1437 return;
1438
1439 // Check if we have interactive bake requested or if we are coming in here the second
1440 // time from cmd line request (needs to wait a frame before starting to bake).
1441 bool bakeRequested = false;
1442 bool denoiseRequested = false;
1443 bool fromCmd = false;
1444 QQuick3DLightmapBaker *lightmapBaker = view3D->maybeLightmapBaker();
1445 if (lightmapBaker && (lightmapBaker->m_bakingRequested || lightmapBaker->m_denoisingRequested)) {
1446 bakeRequested = std::exchange(lightmapBaker->m_bakingRequested, false);
1447 denoiseRequested = std::exchange(lightmapBaker->m_denoisingRequested, false);
1448 } else {
1449 bakeRequested = m_lightmapBakingFromCmdRequested;
1450 denoiseRequested = m_lightmapDenoisingFromCmdRequested;
1451 fromCmd = bakeRequested;
1452 }
1453
1454 // Start the bake (we should have a valid layer render data at this point).
1455 if (bakeRequested || denoiseRequested) {
1456 QSSGLightmapBaker::Context ctx;
1457 ctx.settings.bakeRequested = bakeRequested;
1458 ctx.settings.denoiseRequested = denoiseRequested;
1459 ctx.settings.quitWhenFinished = fromCmd;
1460
1461 // We want the frontend callback in the case that a QQuick3DLightmapBaker is present
1462 if (lightmapBaker) {
1463 QQuick3DLightmapBaker::Callback qq3dCallback = lightmapBaker->m_callback;
1464 QQuick3DLightmapBaker::BakingControl *qq3dBakingControl = lightmapBaker->m_bakingControl;
1465 QSSGLightmapper::Callback callback =
1466 [qq3dCallback,
1467 qq3dBakingControl](const QVariantMap &payload,
1468 QSSGLightmapper::BakingControl *qssgBakingControl) {
1469 qq3dCallback(payload, qq3dBakingControl);
1470
1471 if (qq3dBakingControl->isCancelled() && !qssgBakingControl->cancelled)
1472 qssgBakingControl->cancelled = true;
1473 };
1474 ctx.callbacks.lightmapBakingOutput = callback;
1475 }
1476
1477 // Both the QQuick3DLightmapBaker and cmd / env variant needs this
1478 ctx.callbacks.triggerNewFrame = [view3D](bool releaseResources) {
1479 if (releaseResources) {
1480 QMetaObject::invokeMethod(view3D->window(),
1481 &QQuickWindow::releaseResources,
1482 Qt::QueuedConnection);
1483 }
1484 QMetaObject::invokeMethod(view3D, &QQuick3DViewport::update, Qt::QueuedConnection);
1485 };
1486 ctx.callbacks.setCurrentlyBaking = [this](bool value) {
1487 m_sgContext->bufferManager()->setCurrentlyLightmapBaking(value);
1488 };
1489
1490 ctx.env.rhiCtx = m_sgContext->rhiContext().get();
1491 ctx.env.renderer = m_sgContext->renderer().get();
1492 ctx.env.lmOptions = lmOptions;
1493 m_layer->renderData->initializeLightmapBaking(ctx);
1494
1495 } else {
1496 // Check cmd line and env flags for request
1497 static bool flagsChecked = false;
1498 if (flagsChecked)
1499 return;
1500 flagsChecked = true;
1501
1502 auto isLightmapFlagSet = [](const QString &flag, const char *envVar) {
1503 return QCoreApplication::arguments().contains(flag)
1504 || qEnvironmentVariableIntValue(envVar);
1505 };
1506
1507 m_lightmapBakingFromCmdRequested = isLightmapFlagSet(QStringLiteral("--bake-lightmaps"), "QT_QUICK3D_BAKE_LIGHTMAPS");
1508 m_lightmapDenoisingFromCmdRequested = isLightmapFlagSet(QStringLiteral("--denoise-lightmaps"), "QT_QUICK3D_DENOISE_LIGHTMAPS");
1509
1510 if (m_lightmapBakingFromCmdRequested || m_lightmapDenoisingFromCmdRequested) {
1511 // Delay one frame so the render data is initialized
1512 QMetaObject::invokeMethod(view3D, &QQuick3DViewport::update, Qt::QueuedConnection);
1513 }
1514 }
1515}
1516
1517void QQuick3DSceneRenderer::addNodeToLayer(QSSGRenderNode *node)
1518{
1519 if (!m_layer)
1520 return;
1521
1522 m_layer->addChild(*node);
1523}
1524
1525QSGRenderNode::StateFlags QQuick3DSGRenderNode::changedStates() const
1526{
1527 return BlendState | StencilState | DepthState | ScissorState | ColorState | CullState | ViewportState | RenderTargetState;
1528}
1529
1530namespace {
1531inline QRect convertQtRectToGLViewport(const QRectF &rect, const QSize surfaceSize)
1532{
1533 const int x = int(rect.x());
1534 const int y = surfaceSize.height() - (int(rect.y()) + int(rect.height()));
1535 const int width = int(rect.width());
1536 const int height = int(rect.height());
1537 return QRect(x, y, width, height);
1538}
1539
1540inline void queryMainRenderPassDescriptorAndCommandBuffer(QQuickWindow *window, QSSGRhiContext *rhiCtx)
1541{
1542 if (rhiCtx->isValid()) {
1543 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
1544 // Query from the rif because that is available in the sync
1545 // phase (updatePaintNode) already. QSGDefaultRenderContext's
1546 // copies of the rp and cb are not there until the render
1547 // phase of the scenegraph.
1548 int sampleCount = 1;
1549 int viewCount = 1;
1550 QRhiSwapChain *swapchain = window->swapChain();
1551 if (swapchain) {
1552 rhiCtxD->setMainRenderPassDescriptor(swapchain->renderPassDescriptor());
1553 rhiCtxD->setCommandBuffer(swapchain->currentFrameCommandBuffer());
1554 rhiCtxD->setRenderTarget(swapchain->currentFrameRenderTarget());
1555 sampleCount = swapchain->sampleCount();
1556 } else {
1557 QSGRendererInterface *rif = window->rendererInterface();
1558 // no swapchain when using a QQuickRenderControl (redirecting to a texture etc.)
1559 QRhiCommandBuffer *cb = static_cast<QRhiCommandBuffer *>(
1560 rif->getResource(window, QSGRendererInterface::RhiRedirectCommandBuffer));
1561 QRhiTextureRenderTarget *rt = static_cast<QRhiTextureRenderTarget *>(
1562 rif->getResource(window, QSGRendererInterface::RhiRedirectRenderTarget));
1563 if (cb && rt) {
1564 rhiCtxD->setMainRenderPassDescriptor(rt->renderPassDescriptor());
1565 rhiCtxD->setCommandBuffer(cb);
1566 rhiCtxD->setRenderTarget(rt);
1567 const QRhiColorAttachment *color0 = rt->description().cbeginColorAttachments();
1568 if (color0 && color0->texture()) {
1569 sampleCount = color0->texture()->sampleCount();
1570 if (rt->resourceType() == QRhiResource::TextureRenderTarget) {
1571 const QRhiTextureRenderTargetDescription desc = static_cast<QRhiTextureRenderTarget *>(rt)->description();
1572 for (auto it = desc.cbeginColorAttachments(), end = desc.cendColorAttachments(); it != end; ++it) {
1573 if (it->multiViewCount() >= 2) {
1574 viewCount = it->multiViewCount();
1575 break;
1576 }
1577 }
1578 }
1579 }
1580 } else {
1581 qWarning("Neither swapchain nor redirected command buffer and render target are available.");
1582 }
1583 }
1584
1585 // MSAA is out of our control on this path: it is up to the
1586 // QQuickWindow and the scenegraph to set up the swapchain based on the
1587 // QSurfaceFormat's samples(). The only thing we need to do here is to
1588 // pass the sample count to the renderer because it is needed when
1589 // creating graphics pipelines.
1590 rhiCtxD->setMainPassSampleCount(sampleCount);
1591
1592 // The "direct renderer", i.e. the Underlay and Overlay modes are the
1593 // only ones that support multiview rendering. This becomes active when
1594 // the QQuickWindow is redirected into a texture array, typically with
1595 // an array size of 2 (2 views, for the left and right eye). Otherwise,
1596 // when targeting a window or redirected to a 2D texture, this is not
1597 // applicable and the view count is 1.
1598 rhiCtxD->setMainPassViewCount(viewCount);
1599 }
1600}
1601
1602// The alternative to queryMainRenderPassDescriptorAndCommandBuffer()
1603// specifically for the Inline render mode when there is a QSGRenderNode.
1604inline void queryInlineRenderPassDescriptorAndCommandBuffer(QSGRenderNode *node, QSSGRhiContext *rhiCtx)
1605{
1606 QSGRenderNodePrivate *d = QSGRenderNodePrivate::get(node);
1607 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
1608 rhiCtxD->setMainRenderPassDescriptor(d->m_rt.rpDesc);
1609 rhiCtxD->setCommandBuffer(d->m_rt.cb);
1610 rhiCtxD->setRenderTarget(d->m_rt.rt);
1611 rhiCtxD->setMainPassSampleCount(d->m_rt.rt->sampleCount());
1612 rhiCtxD->setMainPassViewCount(1);
1613}
1614
1615} // namespace
1616
1617QQuick3DSGRenderNode::~QQuick3DSGRenderNode()
1618{
1619 delete renderer;
1620}
1621
1622void QQuick3DSGRenderNode::prepare()
1623{
1624 // this is outside the main renderpass
1625
1626 if (!renderer->m_sgContext->rhiContext()->isValid())
1627 return;
1628 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DPrepareFrame);
1629
1630 queryInlineRenderPassDescriptorAndCommandBuffer(this, renderer->m_sgContext->rhiContext().get());
1631
1632 qreal dpr = window->effectiveDevicePixelRatio();
1633 const QSizeF itemSize = renderer->surfaceSize() / dpr;
1634 QRectF viewport = matrix()->mapRect(QRectF(QPoint(0, 0), itemSize));
1635 viewport = QRectF(viewport.topLeft() * dpr, viewport.size() * dpr);
1636 const QRect vp = convertQtRectToGLViewport(viewport, window->size() * dpr);
1637
1638 Q_TRACE_SCOPE(QSSG_prepareFrame, vp.width(), vp.height());
1639
1641 renderer->rhiPrepare(vp, dpr);
1642 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DPrepareFrame, quint64(vp.width()) | quint64(vp.height()) << 32, renderer->profilingId);
1643}
1644
1645void QQuick3DSGRenderNode::render(const QSGRenderNode::RenderState *state)
1646{
1647 Q_UNUSED(state);
1648
1649 const auto &rhiContext = renderer->m_sgContext->rhiContext();
1650
1651 if (rhiContext->isValid()) {
1652 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderFrame);
1653 Q_TRACE_SCOPE(QSSG_renderFrame, 0, 0);
1654
1655 queryInlineRenderPassDescriptorAndCommandBuffer(this, renderer->m_sgContext->rhiContext().get());
1656
1658 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DRenderFrame,
1659 STAT_PAYLOAD(QSSGRhiContextStats::get(*rhiContext)), renderer->profilingId);
1661 }
1662}
1663
1664void QQuick3DSGRenderNode::releaseResources()
1665{
1666}
1667
1668QSGRenderNode::RenderingFlags QQuick3DSGRenderNode::flags() const
1669{
1670 // don't want begin/endExternal() to be called by Quick
1671 return NoExternalRendering;
1672}
1673
1674QQuick3DSGDirectRenderer::QQuick3DSGDirectRenderer(QQuick3DSceneRenderer *renderer, QQuickWindow *window, QQuick3DSGDirectRenderer::QQuick3DSGDirectRendererMode mode)
1675 : m_renderer(renderer)
1676 , m_window(window)
1677{
1678 if (QSGRendererInterface::isApiRhiBased(window->rendererInterface()->graphicsApi())) {
1679 connect(window, &QQuickWindow::beforeRendering, this, &QQuick3DSGDirectRenderer::prepare, Qt::DirectConnection);
1680 if (mode == Underlay)
1681 connect(window, &QQuickWindow::beforeRenderPassRecording, this, &QQuick3DSGDirectRenderer::render, Qt::DirectConnection);
1682 else
1683 connect(window, &QQuickWindow::afterRenderPassRecording, this, &QQuick3DSGDirectRenderer::render, Qt::DirectConnection);
1684 }
1685}
1686
1688{
1689 delete m_renderer;
1690}
1691
1692void QQuick3DSGDirectRenderer::setViewport(const QRectF &viewport)
1693{
1694 m_viewport = viewport;
1695}
1696
1698{
1699 if (m_isVisible == visible)
1700 return;
1701 m_isVisible = visible;
1702 m_window->update();
1703}
1704
1706{
1707 renderPending = true;
1708 requestFullUpdate(m_window);
1709}
1710
1712{
1713 // This is called from the QQuick3DViewport's updatePaintNode(), before
1714 // QQuick3DSceneRenderer::synchronize(). It is essential to query things
1715 // such as the view count already here, so that synchronize() can rely on
1716 // mainPassViewCount() for instance. prepare() is too late as that is only
1717 // called on beforeRendering (so after the scenegraph sync phase).
1718 if (m_renderer->m_sgContext->rhiContext()->isValid())
1719 queryMainRenderPassDescriptorAndCommandBuffer(m_window, m_renderer->m_sgContext->rhiContext().get());
1720}
1721
1722void QQuick3DSGDirectRenderer::prepare()
1723{
1724 if (!m_isVisible || !m_renderer)
1725 return;
1726
1727 if (m_renderer->m_sgContext->rhiContext()->isValid()) {
1728 // this is outside the main renderpass
1729 if (m_renderer->m_postProcessingStack) {
1730 if (renderPending) {
1731 renderPending = false;
1732 m_rhiTexture = m_renderer->renderToRhiTexture(m_window);
1733 // Set up the main render target again, e.g. the postprocessing
1734 // stack could have clobbered some settings such as the sample count.
1735 queryMainRenderPassDescriptorAndCommandBuffer(m_window, m_renderer->m_sgContext->rhiContext().get());
1736 const auto &quadRenderer = m_renderer->m_sgContext->renderer()->rhiQuadRenderer();
1737 quadRenderer->prepareQuad(m_renderer->m_sgContext->rhiContext().get(), nullptr);
1738 if (m_renderer->m_requestedFramesCount > 0) {
1740 m_renderer->m_requestedFramesCount--;
1741 }
1742 }
1743 }
1744 else
1745 {
1746 QQuick3DRenderStats *renderStats = m_renderer->renderStats();
1747 if (renderStats) {
1748 renderStats->startRender();
1749 renderStats->startRenderPrepare();
1750 }
1751
1752 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DPrepareFrame);
1753 queryMainRenderPassDescriptorAndCommandBuffer(m_window, m_renderer->m_sgContext->rhiContext().get());
1754 const QRect vp = convertQtRectToGLViewport(m_viewport, m_window->size() * m_window->effectiveDevicePixelRatio());
1755
1756 Q_TRACE_SCOPE(QSSG_prepareFrame, vp.width(), vp.height());
1757 m_renderer->beginFrame();
1758 m_renderer->rhiPrepare(vp, m_window->effectiveDevicePixelRatio());
1759 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DPrepareFrame, quint64(vp.width()) | quint64(vp.height()) << 32, m_renderer->profilingId);
1760
1761 if (renderStats)
1762 renderStats->endRenderPrepare();
1763 }
1764 }
1765}
1766
1767void QQuick3DSGDirectRenderer::render()
1768{
1769 if (!m_isVisible || !m_renderer)
1770 return;
1771
1772 const auto &rhiContext = m_renderer->m_sgContext->rhiContext();
1773
1774 if (rhiContext->isValid()) {
1775 // the command buffer is recording the main renderpass at this point
1776
1777 // No m_window->beginExternalCommands() must be done here. When the
1778 // renderer is using the same
1779 // QRhi/QRhiCommandBuffer/QRhiRenderPassDescriptor as the Qt Quick
1780 // scenegraph, there is no difference from the RHI's perspective. There are
1781 // no external (native) commands here.
1782
1783 // Requery the command buffer and co. since Offscreen mode View3Ds may
1784 // have altered these on the context.
1785 if (m_renderer->m_postProcessingStack) {
1786 if (m_rhiTexture) {
1787 queryMainRenderPassDescriptorAndCommandBuffer(m_window, rhiContext.get());
1788 auto rhiCtx = m_renderer->m_sgContext->rhiContext().get();
1789 const auto &renderer = m_renderer->m_sgContext->renderer();
1790 QRhiCommandBuffer *cb = rhiContext->commandBuffer();
1791 cb->debugMarkBegin(QByteArrayLiteral("Post-processing result to main rt"));
1792
1793 // Instead of passing in a flip flag we choose to rely on qsb's
1794 // per-target compilation mode in the fragment shader. (it does UV
1795 // flipping based on QSHADER_ macros) This is just better for
1796 // performance and the shaders are very simple so introducing a
1797 // uniform block and branching dynamically would be an overkill.
1798 QRect vp = convertQtRectToGLViewport(m_viewport, m_window->size() * m_window->effectiveDevicePixelRatio());
1799
1800 const auto &shaderCache = m_renderer->m_sgContext->shaderCache();
1801 const auto &shaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiSimpleQuadShader(m_renderer->m_layer->viewCount);
1802
1803 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
1804 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge });
1805 QSSGRhiShaderResourceBindingList bindings;
1806 bindings.addTexture(0, QRhiShaderResourceBinding::FragmentStage, m_rhiTexture, sampler);
1807 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiContext.get());
1808 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
1809
1810 QSSGRhiGraphicsPipelineState ps;
1811 ps.viewport = QRhiViewport(float(vp.x()), float(vp.y()), float(vp.width()), float(vp.height()));
1812 ps.samples = rhiCtx->mainPassSampleCount();
1813 ps.viewCount = m_renderer->m_layer->viewCount;
1814 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, shaderPipeline.get());
1815 renderer->rhiQuadRenderer()->recordRenderQuad(rhiCtx, &ps, srb, rhiCtx->mainRenderPassDescriptor(), QSSGRhiQuadRenderer::UvCoords | QSSGRhiQuadRenderer::PremulBlend);
1816 cb->debugMarkEnd();
1817 }
1818 }
1819 else
1820 {
1821 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderFrame);
1822 Q_TRACE_SCOPE(QSSG_renderFrame, 0, 0);
1823
1824 queryMainRenderPassDescriptorAndCommandBuffer(m_window, rhiContext.get());
1825
1826 m_renderer->rhiRender();
1827
1828 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DRenderFrame,
1829 STAT_PAYLOAD(QSSGRhiContextStats::get(*rhiContext)),
1830 m_renderer->profilingId);
1831 m_renderer->endFrame();
1832
1833 if (m_renderer->renderStats())
1834 m_renderer->renderStats()->endRender(dumpRenderTimes());
1835 }
1836 }
1837}
1838
1839QT_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.
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)