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