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