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