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