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