1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dcubemaptexture_p.h"
5#include "qquick3dscenerenderer_p.h"
6#include "qquick3dsceneenvironment_p.h"
7#include "qquick3dobject_p.h"
8#include "qquick3dnode_p.h"
9#include "qquick3dscenemanager_p.h"
10#include "qquick3dtexture_p.h"
11#include "qquick3dcamera_p.h"
12#include "qquick3dpickresult_p.h"
13#include "qquick3dmodel_p.h"
14#include "qquick3drenderstats_p.h"
15#include "qquick3ddebugsettings_p.h"
16#include "extensions/qquick3drenderextensions.h"
17#include <QtQuick3DUtils/private/qquick3dprofiler_p.h>
18
19#include <QtQuick3DRuntimeRender/private/qssgrendererutil_p.h>
20#include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
21
22#include <QtQuick/private/qquickwindow_p.h>
23#include <QtQuick/private/qsgdefaultrendercontext_p.h>
24#include <QtQuick/private/qsgtexture_p.h>
25#include <QtQuick/private/qsgplaintexture_p.h>
26#include <QtQuick/private/qsgrendernode_p.h>
27
28#include <QtQuick3DRuntimeRender/private/qssgrendereffect_p.h>
29#include <QtQuick3DRuntimeRender/private/qssgrhieffectsystem_p.h>
30#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
31#include <QtQuick3DRuntimeRender/private/qssgrhiquadrenderer_p.h>
32#include <QtQuick3DRuntimeRender/private/qssgrhicontext_p.h>
33#include <QtQuick3DRuntimeRender/private/qssgcputonemapper_p.h>
34#include <QtQuick3DUtils/private/qssgutils_p.h>
35#include <QtQuick3DUtils/private/qssgassert_p.h>
36
37
38#include <qtquick3d_tracepoints_p.h>
39
40#include <QtCore/QObject>
41#include <QtCore/qqueue.h>
42
43QT_BEGIN_NAMESPACE
44
45Q_TRACE_PREFIX(qtquick3d,
46 "QT_BEGIN_NAMESPACE" \
47 "class QQuick3DViewport;" \
48 "QT_END_NAMESPACE"
49)
50
51Q_TRACE_POINT(qtquick3d, QSSG_prepareFrame_entry, int width, int height);
52Q_TRACE_POINT(qtquick3d, QSSG_prepareFrame_exit);
53Q_TRACE_POINT(qtquick3d, QSSG_renderFrame_entry, int width, int height);
54Q_TRACE_POINT(qtquick3d, QSSG_renderFrame_exit);
55Q_TRACE_POINT(qtquick3d, QSSG_synchronize_entry, QQuick3DViewport *view3D, const QSize &size, float dpr);
56Q_TRACE_POINT(qtquick3d, QSSG_synchronize_exit);
57Q_TRACE_POINT(qtquick3d, QSSG_renderPass_entry, const QString &renderPass);
58Q_TRACE_POINT(qtquick3d, QSSG_renderPass_exit);
59
60static bool dumpRenderTimes = false;
61
62#if QT_CONFIG(qml_debug)
63
64static inline quint64 statDrawCallCount(const QSSGRhiContextStats &stats)
65{
66 quint64 count = 0;
67 const QSSGRhiContextStats::PerLayerInfo &info(stats.perLayerInfo[stats.layerKey]);
68 for (const auto &pass : info.renderPasses)
69 count += QSSGRhiContextStats::totalDrawCallCountForPass(pass);
70 count += QSSGRhiContextStats::totalDrawCallCountForPass(pass: info.externalRenderPass);
71 return count;
72}
73
74#define STAT_PAYLOAD(stats) \
75 (statDrawCallCount(stats) | (quint64(stats.perLayerInfo[stats.layerKey].renderPasses.size()) << 32))
76
77#endif
78
79template <typename In, typename Out>
80static void bfs(In *inExtension, QList<Out *> &outList)
81{
82 QSSG_ASSERT(inExtension, return);
83
84 QQueue<In *> queue { { inExtension } };
85 while (queue.size() > 0) {
86 if (auto cur = queue.dequeue()) {
87 if (auto *ext = static_cast<Out *>(QQuick3DObjectPrivate::get(cur)->spatialNode))
88 outList.push_back(ext);
89 for (auto &chld : cur->childItems())
90 queue.enqueue(qobject_cast<In *>(chld));
91 }
92 }
93}
94
95SGFramebufferObjectNode::SGFramebufferObjectNode()
96 : window(nullptr)
97 , renderer(nullptr)
98 , renderPending(true)
99 , invalidatePending(false)
100 , devicePixelRatio(1)
101{
102 qsgnode_set_description(node: this, QStringLiteral("fbonode"));
103 setFlag(QSGNode::UsePreprocess, true);
104}
105
106SGFramebufferObjectNode::~SGFramebufferObjectNode()
107{
108 delete renderer;
109 delete texture();
110}
111
112void SGFramebufferObjectNode::scheduleRender()
113{
114 renderPending = true;
115 markDirty(bits: DirtyMaterial);
116}
117
118QSGTexture *SGFramebufferObjectNode::texture() const
119{
120 return QSGSimpleTextureNode::texture();
121}
122
123void SGFramebufferObjectNode::preprocess()
124{
125 render();
126}
127
128// QQuickWindow::update() behaves differently depending on whether it's called from the GUI thread
129// or the render thread.
130// TODO: move this to QQuickWindow::fullUpdate(), if we can't change update()
131static void requestFullUpdate(QQuickWindow *window)
132{
133 if (QThread::currentThread() == QCoreApplication::instance()->thread())
134 window->update();
135 else
136 QCoreApplication::postEvent(receiver: window, event: new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
137}
138
139void SGFramebufferObjectNode::render()
140{
141 if (renderPending) {
142 if (renderer->renderStats())
143 renderer->renderStats()->startRender();
144
145 renderPending = false;
146
147 if (renderer->m_sgContext->rhiContext()->isValid()) {
148 QRhiTexture *rhiTexture = renderer->renderToRhiTexture(qw: window);
149 bool needsNewWrapper = false;
150 if (!texture() || (texture()->textureSize() != renderer->surfaceSize()
151 || texture()->rhiTexture() != rhiTexture))
152 {
153 needsNewWrapper = true;
154 }
155 if (needsNewWrapper) {
156 delete texture();
157 QSGPlainTexture *t = new QSGPlainTexture;
158 t->setOwnsTexture(false);
159 t->setHasAlphaChannel(true);
160 t->setTexture(rhiTexture);
161 t->setTextureSize(renderer->surfaceSize());
162 setTexture(t);
163 }
164 }
165
166 markDirty(bits: QSGNode::DirtyMaterial);
167 emit textureChanged();
168
169 if (renderer->renderStats())
170 renderer->renderStats()->endRender(dump: dumpRenderTimes);
171
172 if (renderer->requestedFramesCount > 0) {
173 scheduleRender();
174 requestFullUpdate(window);
175 renderer->requestedFramesCount--;
176 }
177 }
178}
179
180void SGFramebufferObjectNode::handleScreenChange()
181{
182 if (!qFuzzyCompare(p1: window->effectiveDevicePixelRatio(), p2: devicePixelRatio)) {
183 renderer->invalidateFramebufferObject();
184 quickFbo->update();
185 }
186}
187
188
189QQuick3DSceneRenderer::QQuick3DSceneRenderer(const std::shared_ptr<QSSGRenderContextInterface> &rci)
190 : m_sgContext(rci)
191{
192 dumpRenderTimes = (qEnvironmentVariableIntValue(varName: "QT_QUICK3D_DUMP_RENDERTIMES") > 0);
193}
194
195QQuick3DSceneRenderer::~QQuick3DSceneRenderer()
196{
197 const auto &rhiCtx = m_sgContext->rhiContext();
198 QSSGRhiContextStats::get(rhiCtx&: *rhiCtx).cleanupLayerInfo(layer: m_layer);
199 m_sgContext->bufferManager()->releaseResourcesForLayer(layer: m_layer);
200 delete m_layer;
201
202 delete m_texture;
203
204 releaseAaDependentRhiResources();
205 delete m_effectSystem;
206}
207
208void QQuick3DSceneRenderer::releaseAaDependentRhiResources()
209{
210 const auto &rhiCtx = m_sgContext->rhiContext();
211 if (!rhiCtx->isValid())
212 return;
213
214 delete m_textureRenderTarget;
215 m_textureRenderTarget = nullptr;
216
217 delete m_textureRenderPassDescriptor;
218 m_textureRenderPassDescriptor = nullptr;
219
220 delete m_depthStencilBuffer;
221 m_depthStencilBuffer = nullptr;
222
223 delete m_multiViewDepthStencilBuffer;
224 m_multiViewDepthStencilBuffer = nullptr;
225
226 delete m_msaaRenderBuffer;
227 m_msaaRenderBuffer = nullptr;
228
229 delete m_msaaMultiViewRenderBuffer;
230 m_msaaMultiViewRenderBuffer = nullptr;
231
232 delete m_ssaaTexture;
233 m_ssaaTexture = nullptr;
234
235 delete m_ssaaTextureToTextureRenderTarget;
236 m_ssaaTextureToTextureRenderTarget = nullptr;
237
238 delete m_ssaaTextureToTextureRenderPassDescriptor;
239 m_ssaaTextureToTextureRenderPassDescriptor = nullptr;
240
241 delete m_temporalAATexture;
242 m_temporalAATexture = nullptr;
243 delete m_temporalAARenderTarget;
244 m_temporalAARenderTarget = nullptr;
245 delete m_temporalAARenderPassDescriptor;
246 m_temporalAARenderPassDescriptor = nullptr;
247
248 delete m_prevTempAATexture;
249 m_prevTempAATexture = nullptr;
250}
251
252// Blend factors are in the form of (frame blend factor, accumulator blend factor)
253static const QVector2D s_ProgressiveAABlendFactors[QSSGLayerRenderData::MAX_AA_LEVELS] = {
254 QVector2D(0.500000f, 0.500000f), // 1x
255 QVector2D(0.333333f, 0.666667f), // 2x
256 QVector2D(0.250000f, 0.750000f), // 3x
257 QVector2D(0.200000f, 0.800000f), // 4x
258 QVector2D(0.166667f, 0.833333f), // 5x
259 QVector2D(0.142857f, 0.857143f), // 6x
260 QVector2D(0.125000f, 0.875000f), // 7x
261 QVector2D(0.111111f, 0.888889f), // 8x
262};
263
264static const QVector2D s_TemporalAABlendFactors = { 0.5f, 0.5f };
265
266QRhiTexture *QQuick3DSceneRenderer::renderToRhiTexture(QQuickWindow *qw)
267{
268 if (!m_layer)
269 return nullptr;
270
271 QRhiTexture *currentTexture = m_texture; // the result so far
272
273 if (qw) {
274 if (m_renderStats)
275 m_renderStats->startRenderPrepare();
276
277 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DPrepareFrame);
278
279 QSSGRhiContext *rhiCtx = m_sgContext->rhiContext().get();
280 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx);
281
282 rhiCtxD->setMainRenderPassDescriptor(m_textureRenderPassDescriptor);
283 rhiCtxD->setRenderTarget(m_textureRenderTarget);
284
285 QRhiCommandBuffer *cb = nullptr;
286 QRhiSwapChain *swapchain = qw->swapChain();
287 if (swapchain) {
288 cb = swapchain->currentFrameCommandBuffer();
289 rhiCtxD->setCommandBuffer(cb);
290 } else {
291 QSGRendererInterface *rif = qw->rendererInterface();
292 cb = static_cast<QRhiCommandBuffer *>(
293 rif->getResource(window: qw, resource: QSGRendererInterface::RhiRedirectCommandBuffer));
294 if (cb)
295 rhiCtxD->setCommandBuffer(cb);
296 else {
297 qWarning(msg: "Neither swapchain nor redirected command buffer are available.");
298 return currentTexture;
299 }
300 }
301
302 // Graphics pipeline objects depend on the MSAA sample count, so the
303 // renderer needs to know the value.
304 rhiCtxD->setMainPassSampleCount(m_msaaRenderBuffer ? m_msaaRenderBuffer->sampleCount() :
305 (m_msaaMultiViewRenderBuffer ? m_msaaMultiViewRenderBuffer->sampleCount() : 1));
306
307 // mainPassViewCount is left unchanged
308
309 int ssaaAdjustedWidth = m_surfaceSize.width();
310 int ssaaAdjustedHeight = m_surfaceSize.height();
311 if (m_layer->antialiasingMode == QSSGRenderLayer::AAMode::SSAA) {
312 ssaaAdjustedWidth *= m_ssaaMultiplier;
313 ssaaAdjustedHeight *= m_ssaaMultiplier;
314 }
315
316 Q_TRACE(QSSG_prepareFrame_entry, ssaaAdjustedWidth, ssaaAdjustedHeight);
317
318 float dpr = m_sgContext->renderer()->dpr();
319 const QRect vp = QRect(0, 0, ssaaAdjustedWidth, ssaaAdjustedHeight);
320 beginFrame();
321 rhiPrepare(viewport: vp, displayPixelRatio: dpr);
322
323 if (m_renderStats)
324 m_renderStats->endRenderPrepare();
325
326 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DPrepareFrame, quint64(ssaaAdjustedWidth) | quint64(ssaaAdjustedHeight) << 32, profilingId);
327
328 Q_TRACE(QSSG_prepareFrame_exit);
329
330 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderFrame);
331 Q_TRACE(QSSG_renderFrame_entry, ssaaAdjustedWidth, ssaaAdjustedHeight);
332
333 QColor clearColor = Qt::transparent;
334 if (m_backgroundMode == QSSGRenderLayer::Background::Color
335 || (m_backgroundMode == QSSGRenderLayer::Background::SkyBoxCubeMap && !m_layer->skyBoxCubeMap)
336 || (m_backgroundMode == QSSGRenderLayer::Background::SkyBox && !m_layer->lightProbe))
337 {
338 // Same logic as with the main render pass and skybox: tonemap
339 // based on tonemapMode (unless it is None), unless there are effects.
340 clearColor = m_layer->firstEffect ? m_linearBackgroundColor : m_tonemappedBackgroundColor;
341 }
342
343 // This is called from the node's preprocess() meaning Qt Quick has not
344 // actually began recording a renderpass. Do our own.
345 cb->beginPass(rt: m_textureRenderTarget, colorClearValue: clearColor, depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: nullptr, flags: rhiCtx->commonPassFlags());
346 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
347 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(m_textureRenderTarget));
348 rhiRender();
349 cb->endPass();
350 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
351 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, quint64(ssaaAdjustedWidth) | quint64(ssaaAdjustedHeight) << 32, QByteArrayLiteral("main"));
352
353 const bool temporalAA = m_layer->temporalAAIsActive;
354 const bool progressiveAA = m_layer->progressiveAAIsActive;
355 const bool superSamplingAA = m_layer->antialiasingMode == QSSGRenderLayer::AAMode::SSAA;
356 QRhi *rhi = rhiCtx->rhi();
357
358 currentTexture = superSamplingAA ? m_ssaaTexture : m_texture;
359
360 // Do effects before antialiasing
361 if (m_effectSystem && m_layer->firstEffect && !m_layer->renderedCameras.isEmpty()) {
362 const auto &renderer = m_sgContext->renderer();
363 QSSGLayerRenderData *theRenderData = renderer->getOrCreateLayerRenderData(layer&: *m_layer);
364 Q_ASSERT(theRenderData);
365 QRhiTexture *theDepthTexture = theRenderData->getRenderResult(id: QSSGFrameData::RenderResult::DepthTexture)->texture;
366 QVector2D cameraClipRange(m_layer->renderedCameras[0]->clipNear, m_layer->renderedCameras[0]->clipFar);
367
368 currentTexture = m_effectSystem->process(firstEffect: *m_layer->firstEffect,
369 inTexture: currentTexture,
370 inDepthTexture: theDepthTexture,
371 cameraClipRange);
372 }
373
374 // The only difference between temporal and progressive AA at this point is that tempAA always
375 // uses blend factors of 0.5 and copies currentTexture to m_prevTempAATexture, while progAA uses blend
376 // factors from a table and copies the blend result to m_prevTempAATexture
377
378 if ((progressiveAA || temporalAA) && m_prevTempAATexture) {
379 cb->debugMarkBegin(QByteArrayLiteral("Temporal AA"));
380 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
381 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Temporal AA"));
382 QRhiTexture *blendResult;
383 uint *aaIndex = progressiveAA ? &m_layer->progAAPassIndex : &m_layer->tempAAPassIndex; // TODO: can we use only one index?
384
385 if (*aaIndex > 0) {
386 if (temporalAA || *aaIndex < quint32(m_layer->antialiasingQuality)) {
387 const auto &renderer = m_sgContext->renderer();
388
389 // The fragment shader relies on per-target compilation and
390 // QSHADER_ macros of qsb, hence no need to communicate a flip
391 // flag from here.
392 const auto &shaderPipeline = m_sgContext->shaderCache()->getBuiltInRhiShaders().getRhiProgressiveAAShader();
393 QRhiResourceUpdateBatch *rub = nullptr;
394
395 QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData(key: { .cid: m_layer, .model: nullptr, .entry: nullptr, .entryIdx: 0 }));
396 QRhiBuffer *&ubuf = dcd.ubuf;
397 const int ubufSize = 2 * sizeof(float);
398 if (!ubuf) {
399 ubuf = rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: ubufSize);
400 ubuf->create();
401 }
402
403 rub = rhi->nextResourceUpdateBatch();
404 int idx = *aaIndex - 1;
405 const QVector2D *blendFactors = progressiveAA ? &s_ProgressiveAABlendFactors[idx] : &s_TemporalAABlendFactors;
406 rub->updateDynamicBuffer(buf: ubuf, offset: 0, size: 2 * sizeof(float), data: blendFactors);
407 renderer->rhiQuadRenderer()->prepareQuad(rhiCtx, maybeRub: rub);
408
409 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
410 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
411 QSSGRhiShaderResourceBindingList bindings;
412 bindings.addUniformBuffer(binding: 0, stage: QRhiShaderResourceBinding::FragmentStage, buf: ubuf);
413 bindings.addTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: currentTexture, sampler);
414 bindings.addTexture(binding: 2, stage: QRhiShaderResourceBinding::FragmentStage, tex: m_prevTempAATexture, sampler);
415
416 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
417
418 QSSGRhiGraphicsPipelineState ps;
419 const QSize textureSize = currentTexture->pixelSize();
420 ps.viewport = QRhiViewport(0, 0, float(textureSize.width()), float(textureSize.height()));
421 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, pipeline: shaderPipeline.get());
422
423 renderer->rhiQuadRenderer()->recordRenderQuadPass(rhiCtx, ps: &ps, srb, rt: m_temporalAARenderTarget, flags: QSSGRhiQuadRenderer::UvCoords);
424 blendResult = m_temporalAATexture;
425 } else {
426 blendResult = m_prevTempAATexture;
427 }
428 } else {
429 // For the first frame: no blend, only copy
430 blendResult = currentTexture;
431 }
432
433 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
434
435 if (temporalAA || (*aaIndex < quint32(m_layer->antialiasingQuality))) {
436 auto *rub = rhi->nextResourceUpdateBatch();
437 if (progressiveAA)
438 rub->copyTexture(dst: m_prevTempAATexture, src: blendResult);
439 else
440 rub->copyTexture(dst: m_prevTempAATexture, src: currentTexture);
441 cb->resourceUpdate(resourceUpdates: rub);
442 }
443
444 (*aaIndex)++;
445 cb->debugMarkEnd();
446 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("temporal_aa"));
447
448 currentTexture = blendResult;
449 }
450
451 if (m_layer->antialiasingMode == QSSGRenderLayer::AAMode::SSAA) {
452 // With supersampling antialiasing we at this point have the
453 // content rendered at a larger size into m_ssaaTexture. Now scale
454 // it down to the expected size into m_texture, using linear
455 // filtering. Unlike in the OpenGL world, there is no
456 // glBlitFramebuffer equivalent available, because APIs like D3D
457 // and Metal have no such operation (the generally supported
458 // texture copy operations are 1:1 copies, without support for
459 // scaling, which is what we would need here). So draw a quad.
460
461 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
462 const auto &renderer = m_sgContext->renderer();
463
464 cb->debugMarkBegin(QByteArrayLiteral("SSAA downsample"));
465 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
466
467 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("SSAA downsample"));
468
469 renderer->rhiQuadRenderer()->prepareQuad(rhiCtx, maybeRub: nullptr);
470
471 // Instead of passing in a flip flag we choose to rely on qsb's
472 // per-target compilation mode in the fragment shader. (it does UV
473 // flipping based on QSHADER_ macros) This is just better for
474 // performance and the shaders are very simple so introducing a
475 // uniform block and branching dynamically would be an overkill.
476 const auto &shaderPipeline = m_sgContext->shaderCache()->getBuiltInRhiShaders().getRhiSupersampleResolveShader(viewCount: rhiCtx->mainPassViewCount());
477
478 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
479 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat });
480 QSSGRhiShaderResourceBindingList bindings;
481 bindings.addTexture(binding: 0, stage: QRhiShaderResourceBinding::FragmentStage, tex: currentTexture, sampler);
482 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
483
484 QSSGRhiGraphicsPipelineState ps;
485 ps.viewport = QRhiViewport(0, 0, float(m_surfaceSize.width()), float(m_surfaceSize.height()));
486 ps.viewCount = rhiCtx->mainPassViewCount();
487 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, pipeline: shaderPipeline.get());
488
489 renderer->rhiQuadRenderer()->recordRenderQuadPass(rhiCtx, ps: &ps, srb, rt: m_ssaaTextureToTextureRenderTarget, flags: QSSGRhiQuadRenderer::UvCoords);
490 cb->debugMarkEnd();
491 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("ssaa_downsample"));
492
493 currentTexture = m_texture;
494 }
495
496 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DRenderFrame,
497 STAT_PAYLOAD(QSSGRhiContextStats::get(*rhiCtx)),
498 profilingId);
499 endFrame();
500
501 Q_TRACE(QSSG_renderFrame_exit);
502
503 }
504
505 return currentTexture;
506}
507
508void QQuick3DSceneRenderer::beginFrame()
509{
510 m_sgContext->renderer()->beginFrame(layer&: *m_layer);
511}
512
513void QQuick3DSceneRenderer::endFrame()
514{
515 m_sgContext->renderer()->endFrame(layer&: *m_layer);
516}
517
518void QQuick3DSceneRenderer::rhiPrepare(const QRect &viewport, qreal displayPixelRatio)
519{
520 if (!m_layer)
521 return;
522
523 const auto &renderer = m_sgContext->renderer();
524
525 renderer->setDpr(displayPixelRatio);
526
527 renderer->setViewport(viewport);
528
529 renderer->prepareLayerForRender(inLayer&: *m_layer);
530 // If sync was called the assumption is that the scene is dirty regardless of what
531 // the scene prep function says, we still should verify that we have a camera before
532 // we call render prep and render.
533 const bool renderReady = !m_layer->renderData->renderedCameras.isEmpty();
534 if (renderReady) {
535 renderer->rhiPrepare(inLayer&: *m_layer);
536 m_prepared = true;
537 }
538}
539
540void QQuick3DSceneRenderer::rhiRender()
541{
542 if (m_prepared) {
543 // There is no clearFirst flag - the rendering here does not record a
544 // beginPass() so it never clears on its own.
545
546 m_sgContext->renderer()->rhiRender(inLayer&: *m_layer);
547 }
548
549 m_prepared = false;
550}
551
552#if QT_CONFIG(quick_shadereffect)
553static QRhiTexture::Format toRhiTextureFormat(QQuickShaderEffectSource::Format format)
554{
555 switch (format) {
556 case QQuickShaderEffectSource::RGBA8:
557 return QRhiTexture::RGBA8;
558 case QQuickShaderEffectSource::RGBA16F:
559 return QRhiTexture::RGBA16F;
560 case QQuickShaderEffectSource::RGBA32F:
561 return QRhiTexture::RGBA32F;
562 default:
563 return QRhiTexture::RGBA8;
564 }
565}
566#endif
567
568static QVector3D tonemapRgb(const QVector3D &c, QQuick3DSceneEnvironment::QQuick3DEnvironmentTonemapModes tonemapMode)
569{
570 switch (tonemapMode) {
571 case QQuick3DSceneEnvironment::TonemapModeLinear:
572 return QSSGTonemapper::tonemapLinearToSrgb(c);
573 case QQuick3DSceneEnvironment::TonemapModeHejlDawson:
574 return QSSGTonemapper::tonemapHejlDawson(c);
575 case QQuick3DSceneEnvironment::TonemapModeAces:
576 return QSSGTonemapper::tonemapAces(c);
577 case QQuick3DSceneEnvironment::TonemapModeFilmic:
578 return QSSGTonemapper::tonemapFilmic(c);
579 default:
580 break;
581 }
582 return c;
583}
584
585void QQuick3DSceneRenderer::synchronize(QQuick3DViewport *view3D, const QSize &size, float dpr)
586{
587 Q_TRACE_SCOPE(QSSG_synchronize, view3D, size, dpr);
588
589 Q_ASSERT(view3D != nullptr); // This is not an option!
590 QSSGRhiContext *rhiCtx = m_sgContext->rhiContext().get();
591 Q_ASSERT(rhiCtx != nullptr);
592
593 bool newRenderStats = false;
594 if (!m_renderStats) {
595 m_renderStats = view3D->renderStats();
596 newRenderStats = true;
597 }
598
599 if (m_renderStats)
600 m_renderStats->startSync();
601
602 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DSynchronizeFrame);
603
604 m_sgContext->renderer()->setDpr(dpr);
605 bool layerSizeIsDirty = m_surfaceSize != size;
606 m_surfaceSize = size;
607
608 // Synchronize scene managers under this window
609 QSet<QSSGRenderGraphObject *> resourceLoaders;
610 QQuick3DWindowAttachment::SyncResult requestSharedUpdate = QQuick3DWindowAttachment::SyncResultFlag::None;
611 if (auto window = view3D->window()) {
612 if (!winAttacment || winAttacment->window() != window)
613 winAttacment = QQuick3DSceneManager::getOrSetWindowAttachment(window&: *window);
614
615 if (winAttacment && winAttacment->rci() != m_sgContext)
616 winAttacment->setRci(m_sgContext);
617
618 if (winAttacment)
619 requestSharedUpdate |= winAttacment->synchronize(resourceLoaders);
620 }
621
622 // Import scenes used in a multi-window application...
623 QQuick3DNode *importScene = view3D->importScene();
624 if (importScene) {
625 QQuick3DSceneManager *importSceneManager = QQuick3DObjectPrivate::get(item: importScene)->sceneManager;
626 // If the import scene is used with 3D views under a different window, then we'll
627 // need to trigger updates for those as well.
628 if (auto window = importSceneManager->window(); window && window != view3D->window()) {
629 if (auto winAttacment = importSceneManager->wattached) {
630 // Not the same window but backed by the same rhi?
631 auto rci = winAttacment->rci();
632 const bool inlineSync = (rci && rci->rhi() && (rci->rhi()->thread() == m_sgContext->rhi()->thread()));
633 if (inlineSync) {
634 // Given that we're on the same thread, we can do an immediate sync
635 // (rhi instances can differ, e.g., basic renderloop).
636 winAttacment->synchronize(resourceLoaders);
637 } else if (rci && !window->isExposed()) { // Forced sync of non-exposed windows
638 // Not exposed, so not rendering (playing with fire here)...
639 winAttacment->synchronize(resourceLoaders);
640 } else if (!rci || (requestSharedUpdate & QQuick3DWindowAttachment::SyncResultFlag::SharedResourcesDirty)) {
641 // If there's no RCI for the importscene we'll request an update, which should
642 // mean we only get here once. It also means the update to any secondary windows
643 // will be delayed. Note that calling this function on each sync would cause the
644 // different views to ping-pong for updated forever...
645 winAttacment->requestUpdate();
646 }
647 }
648 }
649 }
650
651 // Generate layer node
652 if (!m_layer)
653 m_layer = new QSSGRenderLayer();
654
655 if (newRenderStats)
656 m_renderStats->setRhiContext(ctx: rhiCtx, layer: m_layer);
657
658 // if the list is dirty we rebuild (assumption is that this won't happen frequently).
659 if ((requestSharedUpdate & QQuick3DWindowAttachment::SyncResultFlag::ExtensionsDiry) || view3D->extensionListDirty()) {
660 for (size_t i = 0; i != size_t(QSSGRenderLayer::RenderExtensionStage::Count); ++i)
661 m_layer->renderExtensions[i].clear();
662 // All items in the extension list are root items,
663 const auto &extensions = view3D->extensionList();
664 for (const auto &ext : extensions) {
665 const auto type = QQuick3DObjectPrivate::get(item: ext)->type;
666 if (QSSGRenderGraphObject::isExtension(type)) {
667 if (type == QSSGRenderGraphObject::Type::RenderExtension) {
668 if (auto *renderExt = qobject_cast<QQuick3DRenderExtension *>(object: ext)) {
669 if (QQuick3DObjectPrivate::get(item: renderExt)->spatialNode) {
670 const auto stage = static_cast<QSSGRenderExtension *>(QQuick3DObjectPrivate::get(item: renderExt)->spatialNode)->stage();
671 QSSG_ASSERT(size_t(stage) < std::size(m_layer->renderExtensions), continue);
672 auto &list = m_layer->renderExtensions[size_t(stage)];
673 bfs(inExtension: qobject_cast<QQuick3DRenderExtension *>(object: ext), outList&: list);
674 }
675 }
676 }
677 }
678 }
679
680 view3D->clearExtensionListDirty();
681 }
682
683 // Update the layer node properties
684 updateLayerNode(view3D, resourceLoaders: resourceLoaders.values());
685
686 bool postProcessingNeeded = m_layer->firstEffect;
687 bool postProcessingWasActive = m_effectSystem;
688 QSSGRenderTextureFormat::Format effectOutputFormatOverride = QSSGRenderTextureFormat::Unknown;
689 if (postProcessingNeeded) {
690 QSSGRenderEffect *lastEffect = m_layer->firstEffect;
691 while (lastEffect->m_nextEffect)
692 lastEffect = lastEffect->m_nextEffect;
693 effectOutputFormatOverride = QSSGRhiEffectSystem::overriddenOutputFormat(inEffect: lastEffect);
694 }
695 const auto layerTextureFormat = [effectOutputFormatOverride, view3D](QRhi *rhi, bool postProc) {
696 if (effectOutputFormatOverride != QSSGRenderTextureFormat::Unknown)
697 return QSSGBufferManager::toRhiFormat(format: effectOutputFormatOverride);
698
699 // Our standard choice for the postprocessing input/output textures'
700 // format is a floating point one. (unlike intermediate Buffers, which
701 // default to RGBA8 unless the format is explicitly specified)
702 // This is intentional since a float format allows passing in
703 // non-tonemapped content without colors being clamped when written out
704 // to the render target.
705 //
706 // When it comes to the output, this applies to that too due to
707 // QSSGRhiEffectSystem picking it up unless overridden (with a Buffer
708 // an empty 'name'). Here too a float format gives more flexibility:
709 // the effect may or may not do its own tonemapping and this approach
710 // is compatible with potential future on-screen HDR output support.
711
712 const QRhiTexture::Format preferredPostProcFormat = QRhiTexture::RGBA16F;
713 if (postProc && rhi->isTextureFormatSupported(format: preferredPostProcFormat))
714 return preferredPostProcFormat;
715
716#if QT_CONFIG(quick_shadereffect)
717 const QRhiTexture::Format preferredView3DFormat = toRhiTextureFormat(format: view3D->renderFormat());
718 if (rhi->isTextureFormatSupported(format: preferredView3DFormat))
719 return preferredView3DFormat;
720#endif
721
722 return QRhiTexture::RGBA8;
723 };
724 bool postProcessingStateDirty = postProcessingNeeded != postProcessingWasActive;
725
726 // Store from the layer properties the ones we need to handle ourselves (with the RHI code path)
727 m_backgroundMode = QSSGRenderLayer::Background(view3D->environment()->backgroundMode());
728
729 // This is stateful since we only want to recalculate the tonemapped color
730 // when the color changes, not in every frame.
731 QColor currentUserBackgroundColor = view3D->environment()->clearColor();
732 if (m_userBackgroundColor != currentUserBackgroundColor) {
733 m_userBackgroundColor = currentUserBackgroundColor;
734 m_linearBackgroundColor = QSSGUtils::color::sRGBToLinearColor(color: m_userBackgroundColor);
735 const QVector3D tc = tonemapRgb(c: QVector3D(m_linearBackgroundColor.redF(),
736 m_linearBackgroundColor.greenF(),
737 m_linearBackgroundColor.blueF()),
738 tonemapMode: view3D->environment()->tonemapMode());
739 m_tonemappedBackgroundColor = QColor::fromRgbF(r: tc.x(), g: tc.y(), b: tc.z(), a: m_linearBackgroundColor.alphaF());
740 }
741 m_layer->scissorRect = QRect(view3D->environment()->scissorRect().topLeft() * dpr,
742 view3D->environment()->scissorRect().size() * dpr);
743
744 // Set the root item for the scene to the layer
745 auto rootNode = static_cast<QSSGRenderNode*>(QQuick3DObjectPrivate::get(item: view3D->scene())->spatialNode);
746 if (rootNode != m_sceneRootNode) {
747 if (m_sceneRootNode)
748 removeNodeFromLayer(node: m_sceneRootNode);
749
750 if (rootNode)
751 addNodeToLayer(node: rootNode);
752
753 m_sceneRootNode = rootNode;
754 }
755
756 // Add the referenced scene root node to the layer as well if available
757 QSSGRenderNode *importRootNode = nullptr;
758 if (importScene)
759 importRootNode = static_cast<QSSGRenderNode*>(QQuick3DObjectPrivate::get(item: importScene)->spatialNode);
760
761 if (importRootNode != m_importRootNode) {
762 if (m_importRootNode)
763 m_layer->removeImportScene(rootNode&: *m_importRootNode);
764
765 if (importRootNode) {
766 // if importScene has the rendered viewport as ancestor, it probably means
767 // "importScene: MyScene { }" type of inclusion.
768 // In this case don't duplicate content by adding it again.
769 QObject *sceneParent = importScene->parent();
770 bool isEmbedded = false;
771 while (sceneParent) {
772 if (sceneParent == view3D) {
773 isEmbedded = true;
774 break;
775 }
776 sceneParent = sceneParent->parent();
777 }
778 if (!isEmbedded)
779 m_layer->setImportScene(*importRootNode);
780 }
781
782 m_importRootNode = importRootNode;
783 }
784
785 if (auto lightmapBaker = view3D->maybeLightmapBaker()) {
786 if (lightmapBaker->m_bakingRequested) {
787 m_layer->renderData->interactiveLightmapBakingRequested = true;
788
789 QQuick3DLightmapBaker::Callback qq3dCallback = lightmapBaker->m_callback;
790 QQuick3DLightmapBaker::BakingControl *qq3dBakingControl = lightmapBaker->m_bakingControl;
791 QSSGLightmapper::Callback callback =
792 [qq3dCallback, qq3dBakingControl](
793 QSSGLightmapper::BakingStatus qssgBakingStatus,
794 std::optional<QString> msg,
795 QSSGLightmapper::BakingControl *qssgBakingControl)
796 {
797 QQuick3DLightmapBaker::BakingStatus qq3dBakingStatus = QQuick3DLightmapBaker::BakingStatus::None;
798 switch (qssgBakingStatus)
799 {
800 case QSSGLightmapper::BakingStatus::None:
801 break;
802 case QSSGLightmapper::BakingStatus::Progress:
803 qq3dBakingStatus = QQuick3DLightmapBaker::BakingStatus::Progress;
804 break;
805 case QSSGLightmapper::BakingStatus::Warning:
806 qq3dBakingStatus = QQuick3DLightmapBaker::BakingStatus::Warning;
807 break;
808 case QSSGLightmapper::BakingStatus::Error:
809 qq3dBakingStatus = QQuick3DLightmapBaker::BakingStatus::Error;
810 break;
811 case QSSGLightmapper::BakingStatus::Cancelled:
812 qq3dBakingStatus = QQuick3DLightmapBaker::BakingStatus::Cancelled;
813 break;
814 case QSSGLightmapper::BakingStatus::Complete:
815 qq3dBakingStatus = QQuick3DLightmapBaker::BakingStatus::Complete;
816 break;
817 }
818
819 qq3dCallback(qq3dBakingStatus, msg, qq3dBakingControl);
820
821 if (qq3dBakingControl->isCancelled() && !qssgBakingControl->cancelled)
822 qssgBakingControl->cancelled = true;
823 };
824
825 m_layer->renderData->lightmapBakingOutputCallback = callback;
826 lightmapBaker->m_bakingRequested = false;
827 }
828 }
829
830 const bool progressiveAA = m_layer->antialiasingMode == QSSGRenderLayer::AAMode::ProgressiveAA;
831 const bool multiSamplingAA = m_layer->antialiasingMode == QSSGRenderLayer::AAMode::MSAA;
832 const bool temporalAA = m_layer->temporalAAEnabled && !multiSamplingAA;
833 const bool superSamplingAA = m_layer->antialiasingMode == QSSGRenderLayer::AAMode::SSAA;
834 const bool timeBasedAA = progressiveAA || temporalAA;
835 m_postProcessingStack = m_layer->firstEffect || timeBasedAA || superSamplingAA;
836 bool useFBO = view3D->renderMode() == QQuick3DViewport::RenderMode::Offscreen ||
837 ((view3D->renderMode() == QQuick3DViewport::RenderMode::Underlay || view3D->renderMode() == QQuick3DViewport::RenderMode::Overlay)
838 && m_postProcessingStack);
839 if (useFBO && rhiCtx->isValid()) {
840 QRhi *rhi = rhiCtx->rhi();
841 const QSize renderSize = superSamplingAA ? m_surfaceSize * m_ssaaMultiplier : m_surfaceSize;
842
843 if (m_texture) {
844 // the size changed, or the AA settings changed, or toggled between some effects - no effect
845 if (layerSizeIsDirty || postProcessingStateDirty) {
846 m_texture->setPixelSize(m_surfaceSize);
847 m_texture->setFormat(layerTextureFormat(rhi, postProcessingNeeded));
848 m_texture->create();
849
850 // If AA settings changed, then we drop and recreate all
851 // resources, otherwise use a lighter path if just the size
852 // changed.
853 if (!m_aaIsDirty) {
854 // A special case: when toggling effects and AA is on,
855 // use the heavier AA path because the renderbuffer for
856 // MSAA and texture for SSAA may need a different
857 // format now since m_texture's format could have
858 // changed between RBGA8 and RGBA16F (due to layerTextureFormat()).
859 if (postProcessingStateDirty && (m_layer->antialiasingMode != QSSGRenderLayer::AAMode::NoAA || temporalAA)) {
860 releaseAaDependentRhiResources();
861 } else {
862 if (m_ssaaTexture) {
863 m_ssaaTexture->setPixelSize(renderSize);
864 m_ssaaTexture->create();
865 }
866 if (m_depthStencilBuffer) {
867 m_depthStencilBuffer->setPixelSize(renderSize);
868 m_depthStencilBuffer->create();
869 }
870 if (m_multiViewDepthStencilBuffer) {
871 m_multiViewDepthStencilBuffer->setPixelSize(renderSize);
872 m_multiViewDepthStencilBuffer->create();
873 }
874 if (m_msaaRenderBuffer) {
875 m_msaaRenderBuffer->setPixelSize(renderSize);
876 m_msaaRenderBuffer->create();
877 }
878 if (m_msaaMultiViewRenderBuffer) {
879 m_msaaMultiViewRenderBuffer->setPixelSize(renderSize);
880 m_msaaMultiViewRenderBuffer->create();
881 }
882 // Toggling effects on and off will change the format
883 // (assuming effects default to a floating point
884 // format) and that needs on a different renderpass on
885 // Vulkan. Hence renewing m_textureRenderPassDescriptor as well.
886 if (postProcessingStateDirty) {
887 delete m_textureRenderPassDescriptor;
888 m_textureRenderPassDescriptor = m_textureRenderTarget->newCompatibleRenderPassDescriptor();
889 m_textureRenderTarget->setRenderPassDescriptor(m_textureRenderPassDescriptor);
890 }
891 m_textureRenderTarget->create();
892 if (m_ssaaTextureToTextureRenderTarget)
893 m_ssaaTextureToTextureRenderTarget->create();
894
895 if (m_temporalAATexture) {
896 m_temporalAATexture->setPixelSize(renderSize);
897 m_temporalAATexture->create();
898 }
899 if (m_prevTempAATexture) {
900 m_prevTempAATexture->setPixelSize(renderSize);
901 m_prevTempAATexture->create();
902 }
903 if (m_temporalAARenderTarget)
904 m_temporalAARenderTarget->create();
905 }
906 }
907 } else if (m_aaIsDirty && rhi->backend() == QRhi::Metal) { // ### to avoid garbage upon enabling MSAA with macOS 10.14 (why is this needed?)
908 m_texture->create();
909 }
910
911 if (m_aaIsDirty)
912 releaseAaDependentRhiResources();
913 }
914
915 const QRhiTexture::Flags textureFlags = QRhiTexture::RenderTarget
916 | QRhiTexture::UsedAsTransferSource; // transfer source is for progressive/temporal AA
917 const QRhiTexture::Format textureFormat = layerTextureFormat(rhi, postProcessingNeeded);
918
919 if (!m_texture) {
920 if (rhiCtx->mainPassViewCount() >= 2)
921 m_texture = rhi->newTextureArray(format: textureFormat, arraySize: rhiCtx->mainPassViewCount(), pixelSize: m_surfaceSize, sampleCount: 1, flags: textureFlags);
922 else
923 m_texture = rhi->newTexture(format: textureFormat, pixelSize: m_surfaceSize, sampleCount: 1, flags: textureFlags);
924 m_texture->create();
925 }
926
927 if (!m_ssaaTexture && superSamplingAA) {
928 if (rhiCtx->mainPassViewCount() >= 2)
929 m_ssaaTexture = rhi->newTextureArray(format: textureFormat, arraySize: rhiCtx->mainPassViewCount(), pixelSize: renderSize, sampleCount: 1, flags: textureFlags);
930 else
931 m_ssaaTexture = rhi->newTexture(format: textureFormat, pixelSize: renderSize, sampleCount: 1, flags: textureFlags);
932 m_ssaaTexture->create();
933 }
934
935 if (timeBasedAA && !m_temporalAATexture) {
936 m_temporalAATexture = rhi->newTexture(format: textureFormat, pixelSize: renderSize, sampleCount: 1, flags: textureFlags);
937 m_temporalAATexture->create();
938 m_prevTempAATexture = rhi->newTexture(format: textureFormat, pixelSize: renderSize, sampleCount: 1, flags: textureFlags);
939 m_prevTempAATexture->create();
940 }
941
942 // we need to re-render time-based AA not only when AA state changes, but also when resized
943 if (m_aaIsDirty || layerSizeIsDirty)
944 m_layer->tempAAPassIndex = m_layer->progAAPassIndex = 0;
945
946 if (m_aaIsDirty) {
947 m_samples = 1;
948 if (m_layer->antialiasingMode == QSSGRenderLayer::AAMode::MSAA) {
949 if (rhi->isFeatureSupported(feature: QRhi::MultisampleRenderBuffer)) {
950 m_samples = qMax(a: 1, b: int(m_layer->antialiasingQuality));
951 // The Quick3D API exposes high level values such as
952 // Medium, High, VeryHigh instead of direct sample
953 // count values. Therefore, be nice and find a sample
954 // count that's actually supported in case the one
955 // associated by default is not.
956 const QVector<int> supported = rhi->supportedSampleCounts(); // assumed to be sorted
957 if (!supported.contains(t: m_samples)) {
958 if (!supported.isEmpty()) {
959 auto it = std::lower_bound(first: supported.cbegin(), last: supported.cend(), val: m_samples);
960 m_samples = it == supported.cend() ? supported.last() : *it;
961 } else {
962 m_samples = 1;
963 }
964 }
965 } else {
966 static bool warned = false;
967 if (!warned) {
968 warned = true;
969 qWarning(msg: "Multisample renderbuffers are not supported, disabling MSAA for Offscreen View3D");
970 }
971 }
972 }
973 }
974
975 if (rhiCtx->mainPassViewCount() >= 2) {
976 if (!m_multiViewDepthStencilBuffer) {
977 m_multiViewDepthStencilBuffer = rhi->newTextureArray(format: QRhiTexture::D24S8, arraySize: rhiCtx->mainPassViewCount(), pixelSize: renderSize,
978 sampleCount: m_samples, flags: QRhiTexture::RenderTarget);
979 m_multiViewDepthStencilBuffer->create();
980 }
981 } else {
982 if (!m_depthStencilBuffer) {
983 m_depthStencilBuffer = rhi->newRenderBuffer(type: QRhiRenderBuffer::DepthStencil, pixelSize: renderSize, sampleCount: m_samples);
984 m_depthStencilBuffer->create();
985 }
986 }
987
988 if (!m_textureRenderTarget) {
989 QRhiTextureRenderTargetDescription rtDesc;
990 QRhiColorAttachment att;
991 if (m_samples > 1) {
992 if (rhiCtx->mainPassViewCount() >= 2) {
993 m_msaaMultiViewRenderBuffer = rhi->newTextureArray(format: textureFormat, arraySize: rhiCtx->mainPassViewCount(), pixelSize: renderSize, sampleCount: m_samples, flags: QRhiTexture::RenderTarget);
994 m_msaaMultiViewRenderBuffer->create();
995 att.setTexture(m_msaaMultiViewRenderBuffer);
996 } else {
997 // pass in the texture's format (which may be a floating point one!) as the preferred format hint
998 m_msaaRenderBuffer = rhi->newRenderBuffer(type: QRhiRenderBuffer::Color, pixelSize: renderSize, sampleCount: m_samples, flags: {}, backingFormatHint: m_texture->format());
999 m_msaaRenderBuffer->create();
1000 att.setRenderBuffer(m_msaaRenderBuffer);
1001 }
1002 att.setResolveTexture(m_texture);
1003 } else {
1004 if (m_layer->antialiasingMode == QSSGRenderLayer::AAMode::SSAA)
1005 att.setTexture(m_ssaaTexture);
1006 else
1007 att.setTexture(m_texture);
1008 }
1009 att.setMultiViewCount(rhiCtx->mainPassViewCount());
1010 rtDesc.setColorAttachments({ att });
1011 if (m_depthStencilBuffer)
1012 rtDesc.setDepthStencilBuffer(m_depthStencilBuffer);
1013 if (m_multiViewDepthStencilBuffer)
1014 rtDesc.setDepthTexture(m_multiViewDepthStencilBuffer);
1015
1016 m_textureRenderTarget = rhi->newTextureRenderTarget(desc: rtDesc);
1017 m_textureRenderTarget->setName(QByteArrayLiteral("View3D"));
1018 m_textureRenderPassDescriptor = m_textureRenderTarget->newCompatibleRenderPassDescriptor();
1019 m_textureRenderTarget->setRenderPassDescriptor(m_textureRenderPassDescriptor);
1020 m_textureRenderTarget->create();
1021 }
1022
1023 if (!m_ssaaTextureToTextureRenderTarget && m_layer->antialiasingMode == QSSGRenderLayer::AAMode::SSAA) {
1024 QRhiColorAttachment att(m_texture);
1025 att.setMultiViewCount(rhiCtx->mainPassViewCount());
1026 m_ssaaTextureToTextureRenderTarget = rhi->newTextureRenderTarget(desc: QRhiTextureRenderTargetDescription({ att }));
1027 m_ssaaTextureToTextureRenderTarget->setName(QByteArrayLiteral("SSAA texture"));
1028 m_ssaaTextureToTextureRenderPassDescriptor = m_ssaaTextureToTextureRenderTarget->newCompatibleRenderPassDescriptor();
1029 m_ssaaTextureToTextureRenderTarget->setRenderPassDescriptor(m_ssaaTextureToTextureRenderPassDescriptor);
1030 m_ssaaTextureToTextureRenderTarget->create();
1031 }
1032
1033 if (m_layer->firstEffect) {
1034 if (!m_effectSystem)
1035 m_effectSystem = new QSSGRhiEffectSystem(m_sgContext);
1036 m_effectSystem->setup(renderSize);
1037 } else if (m_effectSystem) {
1038 delete m_effectSystem;
1039 m_effectSystem = nullptr;
1040 }
1041
1042 if (timeBasedAA && !m_temporalAARenderTarget) {
1043 m_temporalAARenderTarget = rhi->newTextureRenderTarget(desc: { m_temporalAATexture });
1044 m_temporalAARenderTarget->setName(QByteArrayLiteral("Temporal AA texture"));
1045 m_temporalAARenderPassDescriptor = m_temporalAARenderTarget->newCompatibleRenderPassDescriptor();
1046 m_temporalAARenderTarget->setRenderPassDescriptor(m_temporalAARenderPassDescriptor);
1047 m_temporalAARenderTarget->create();
1048 }
1049
1050 m_textureNeedsFlip = rhi->isYUpInFramebuffer();
1051 m_aaIsDirty = false;
1052 }
1053
1054 if (m_renderStats)
1055 m_renderStats->endSync(dump: dumpRenderTimes);
1056
1057 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DSynchronizeFrame, quint64(m_surfaceSize.width()) | quint64(m_surfaceSize.height()) << 32, profilingId);
1058}
1059
1060void QQuick3DSceneRenderer::invalidateFramebufferObject()
1061{
1062 if (fboNode)
1063 fboNode->invalidatePending = true;
1064}
1065
1066void QQuick3DSceneRenderer::releaseCachedResources()
1067{
1068 if (m_layer && m_layer->renderData) {
1069 if (const auto &mgr = m_layer->renderData->getShadowMapManager())
1070 mgr->releaseCachedResources();
1071 if (const auto &mgr = m_layer->renderData->getReflectionMapManager())
1072 mgr->releaseCachedResources();
1073 }
1074}
1075
1076std::optional<QSSGRenderRay> QQuick3DSceneRenderer::getRayFromViewportPos(const QPointF &pos)
1077{
1078 if (!m_layer)
1079 return std::nullopt;
1080
1081 QMutexLocker locker(&m_layer->renderedCamerasMutex);
1082
1083 if (m_layer->renderedCameras.isEmpty())
1084 return std::nullopt;
1085
1086 const QVector2D viewportSize(m_surfaceSize.width(), m_surfaceSize.height());
1087 const QVector2D position(float(pos.x()), float(pos.y()));
1088 const QRectF viewportRect(QPointF{}, QSizeF(m_surfaceSize));
1089
1090 // First invert the y so we are dealing with numbers in a normal coordinate space.
1091 // Second, move into our layer's coordinate space
1092 QVector2D correctCoords(position.x(), viewportSize.y() - position.y());
1093 QVector2D theLocalMouse = QSSGUtils::rect::toRectRelative(r: viewportRect, absoluteCoordinates: correctCoords);
1094 if ((theLocalMouse.x() < 0.0f || theLocalMouse.x() >= viewportSize.x() || theLocalMouse.y() < 0.0f
1095 || theLocalMouse.y() >= viewportSize.y()))
1096 return std::nullopt;
1097
1098 return m_layer->renderedCameras[0]->unproject(inLayerRelativeMouseCoords: theLocalMouse, inViewport: viewportRect);
1099}
1100
1101QQuick3DSceneRenderer::PickResultList QQuick3DSceneRenderer::syncPick(const QSSGRenderRay &ray)
1102{
1103 if (!m_layer)
1104 return QQuick3DSceneRenderer::PickResultList();
1105
1106 return QSSGRendererPrivate::syncPick(ctx: *m_sgContext,
1107 layer: *m_layer,
1108 ray);
1109}
1110
1111QQuick3DSceneRenderer::PickResultList QQuick3DSceneRenderer::syncPickOne(const QSSGRenderRay &ray, QSSGRenderNode *node)
1112{
1113 if (!m_layer)
1114 return QQuick3DSceneRenderer::PickResultList();
1115
1116 return QSSGRendererPrivate::syncPick(ctx: *m_sgContext,
1117 layer: *m_layer,
1118 ray,
1119 target: node);
1120}
1121
1122QQuick3DSceneRenderer::PickResultList QQuick3DSceneRenderer::syncPickSubset(const QSSGRenderRay &ray,
1123 QVarLengthArray<QSSGRenderNode *> subset)
1124{
1125 if (!m_layer)
1126 return QQuick3DSceneRenderer::PickResultList();
1127
1128 return QSSGRendererPrivate::syncPickSubset(layer: *m_layer,
1129 bufferManager&: *m_sgContext->bufferManager(),
1130 ray,
1131 subset);
1132}
1133
1134QQuick3DSceneRenderer::PickResultList QQuick3DSceneRenderer::syncPickAll(const QSSGRenderRay &ray)
1135{
1136 if (!m_layer)
1137 return QQuick3DSceneRenderer::PickResultList();
1138
1139 return QSSGRendererPrivate::syncPickAll(ctx: *m_sgContext,
1140 layer: *m_layer,
1141 ray);
1142}
1143
1144void QQuick3DSceneRenderer::setGlobalPickingEnabled(bool isEnabled)
1145{
1146 QSSGRendererPrivate::setGlobalPickingEnabled(renderer&: *m_sgContext->renderer(), isEnabled);
1147}
1148
1149QQuick3DRenderStats *QQuick3DSceneRenderer::renderStats()
1150{
1151 return m_renderStats;
1152}
1153
1154void QQuick3DRenderLayerHelpers::updateLayerNodeHelper(const QQuick3DViewport &view3D, QSSGRenderLayer &layerNode, bool &aaIsDirty, bool &temporalIsDirty, float &ssaaMultiplier)
1155{
1156 QQuick3DSceneEnvironment *environment = view3D.environment();
1157
1158 QSSGRenderLayer::AAMode aaMode = QSSGRenderLayer::AAMode(environment->antialiasingMode());
1159 if (aaMode != layerNode.antialiasingMode) {
1160 layerNode.antialiasingMode = aaMode;
1161 layerNode.progAAPassIndex = 0;
1162 aaIsDirty = true;
1163 }
1164 QSSGRenderLayer::AAQuality aaQuality = QSSGRenderLayer::AAQuality(environment->antialiasingQuality());
1165 if (aaQuality != layerNode.antialiasingQuality) {
1166 layerNode.antialiasingQuality = aaQuality;
1167 ssaaMultiplier = (aaQuality == QSSGRenderLayer::AAQuality::Normal) ? 1.2f :
1168 (aaQuality == QSSGRenderLayer::AAQuality::High) ? 1.5f :
1169 2.0f;
1170 layerNode.ssaaMultiplier = ssaaMultiplier;
1171 aaIsDirty = true;
1172 }
1173
1174 bool temporalAAEnabled = environment->temporalAAEnabled();
1175 if (temporalAAEnabled != layerNode.temporalAAEnabled) {
1176 layerNode.temporalAAEnabled = environment->temporalAAEnabled();
1177 temporalIsDirty = true;
1178
1179 layerNode.tempAAPassIndex = 0;
1180 aaIsDirty = true;
1181 }
1182 layerNode.ssaaEnabled = environment->antialiasingMode()
1183 == QQuick3DSceneEnvironment::QQuick3DEnvironmentAAModeValues::SSAA;
1184
1185 layerNode.temporalAAStrength = environment->temporalAAStrength();
1186
1187 layerNode.specularAAEnabled = environment->specularAAEnabled();
1188
1189 layerNode.background = QSSGRenderLayer::Background(environment->backgroundMode());
1190 layerNode.clearColor = QVector3D(float(environment->clearColor().redF()),
1191 float(environment->clearColor().greenF()),
1192 float(environment->clearColor().blueF()));
1193
1194 layerNode.gridEnabled = environment->gridEnabled();
1195 layerNode.gridScale = environment->gridScale();
1196 layerNode.gridFlags = environment->gridFlags();
1197
1198 layerNode.aoStrength = environment->aoStrength();
1199 layerNode.aoDistance = environment->aoDistance();
1200 layerNode.aoSoftness = environment->aoSoftness();
1201 layerNode.aoEnabled = environment->aoEnabled();
1202 layerNode.aoBias = environment->aoBias();
1203 layerNode.aoSamplerate = environment->aoSampleRate();
1204 layerNode.aoDither = environment->aoDither();
1205
1206 // ### These images will not be registered anywhere
1207 if (environment->lightProbe())
1208 layerNode.lightProbe = environment->lightProbe()->getRenderImage();
1209 else
1210 layerNode.lightProbe = nullptr;
1211 if (view3D.environment()->skyBoxCubeMap())
1212 layerNode.skyBoxCubeMap = view3D.environment()->skyBoxCubeMap()->getRenderImage();
1213 else
1214 layerNode.skyBoxCubeMap = nullptr;
1215
1216 layerNode.lightProbeSettings.probeExposure = environment->probeExposure();
1217 // Remap the probeHorizon to the expected Range
1218 layerNode.lightProbeSettings.probeHorizon = qMin(a: environment->probeHorizon() - 1.0f, b: -0.001f);
1219 layerNode.setProbeOrientation(environment->probeOrientation());
1220
1221 layerNode.explicitCameras.clear();
1222 if (!view3D.m_multiViewCameras.isEmpty()) {
1223 for (QQuick3DCamera *camera : std::as_const(t: view3D.m_multiViewCameras))
1224 layerNode.explicitCameras.append(t: static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(item: camera)->spatialNode));
1225 } else if (view3D.camera()) {
1226 layerNode.explicitCameras.append(t: static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(item: view3D.camera())->spatialNode));
1227 }
1228
1229 layerNode.layerFlags.setFlag(flag: QSSGRenderLayer::LayerFlag::EnableDepthTest, on: environment->depthTestEnabled());
1230 layerNode.layerFlags.setFlag(flag: QSSGRenderLayer::LayerFlag::EnableDepthPrePass, on: environment->depthPrePassEnabled());
1231
1232 layerNode.tonemapMode = QQuick3DSceneRenderer::getTonemapMode(environment: *environment);
1233 layerNode.skyboxBlurAmount = environment->skyboxBlurAmount();
1234 if (auto debugSettings = view3D.environment()->debugSettings()) {
1235 layerNode.debugMode = QSSGRenderLayer::MaterialDebugMode(debugSettings->materialOverride());
1236 layerNode.wireframeMode = debugSettings->wireframeEnabled();
1237 layerNode.drawDirectionalLightShadowBoxes = debugSettings->drawDirectionalLightShadowBoxes();
1238 layerNode.drawShadowCastingBounds = debugSettings->drawShadowCastingBounds();
1239 layerNode.drawShadowReceivingBounds = debugSettings->drawShadowReceivingBounds();
1240 layerNode.drawCascades = debugSettings->drawCascades();
1241 layerNode.drawSceneCascadeIntersection = debugSettings->drawSceneCascadeIntersection();
1242 layerNode.disableShadowCameraUpdate = debugSettings->disableShadowCameraUpdate();
1243 } else {
1244 layerNode.debugMode = QSSGRenderLayer::MaterialDebugMode::None;
1245 layerNode.wireframeMode = false;
1246 }
1247
1248 if (environment->lightmapper()) {
1249 QQuick3DLightmapper *lightmapper = environment->lightmapper();
1250 layerNode.lmOptions.opacityThreshold = lightmapper->opacityThreshold();
1251 layerNode.lmOptions.bias = lightmapper->bias();
1252 layerNode.lmOptions.useAdaptiveBias = lightmapper->isAdaptiveBiasEnabled();
1253 layerNode.lmOptions.indirectLightEnabled = lightmapper->isIndirectLightEnabled();
1254 layerNode.lmOptions.indirectLightSamples = lightmapper->samples();
1255 layerNode.lmOptions.indirectLightWorkgroupSize = lightmapper->indirectLightWorkgroupSize();
1256 layerNode.lmOptions.indirectLightBounces = lightmapper->bounces();
1257 layerNode.lmOptions.indirectLightFactor = lightmapper->indirectLightFactor();
1258 } else {
1259 layerNode.lmOptions = {};
1260 }
1261
1262 if (environment->fog() && environment->fog()->isEnabled()) {
1263 layerNode.fog.enabled = true;
1264 const QQuick3DFog *fog = environment->fog();
1265 layerNode.fog.color = QSSGUtils::color::sRGBToLinear(color: fog->color()).toVector3D();
1266 layerNode.fog.density = fog->density();
1267 layerNode.fog.depthEnabled = fog->isDepthEnabled();
1268 layerNode.fog.depthBegin = fog->depthNear();
1269 layerNode.fog.depthEnd = fog->depthFar();
1270 layerNode.fog.depthCurve = fog->depthCurve();
1271 layerNode.fog.heightEnabled = fog->isHeightEnabled();
1272 layerNode.fog.heightMin = fog->leastIntenseY();
1273 layerNode.fog.heightMax = fog->mostIntenseY();
1274 layerNode.fog.heightCurve = fog->heightCurve();
1275 layerNode.fog.transmitEnabled = fog->isTransmitEnabled();
1276 layerNode.fog.transmitCurve = fog->transmitCurve();
1277 } else {
1278 layerNode.fog.enabled = false;
1279 }
1280}
1281
1282void QQuick3DSceneRenderer::updateLayerNode(QQuick3DViewport *view3D, const QList<QSSGRenderGraphObject *> &resourceLoaders)
1283{
1284 QSSGRenderLayer *layerNode = m_layer;
1285
1286 bool temporalIsDirty = false;
1287 QQuick3DRenderLayerHelpers::updateLayerNodeHelper(view3D: *view3D, layerNode&: *m_layer, aaIsDirty&: m_aaIsDirty, temporalIsDirty, ssaaMultiplier&: m_ssaaMultiplier);
1288
1289 int extraFramesToRender = 0;
1290
1291 if (layerNode->antialiasingMode == QSSGRenderLayer::AAMode::ProgressiveAA) {
1292 // with progressive AA, we need a number of extra frames after the last dirty one
1293 // if we always reset requestedFramesCount when dirty, we will get the extra frames eventually
1294 // +1 since we need a normal frame to start with, and we're not copying that from the screen
1295 extraFramesToRender = int(layerNode->antialiasingQuality) + 1;
1296 } else if (layerNode->temporalAAEnabled) {
1297 // When temporalAA is on and antialiasing mode changes,
1298 // layer needs to be re-rendered (at least) MAX_TEMPORAL_AA_LEVELS times
1299 // to generate temporal antialiasing.
1300 // Also, we need to do an extra render when animation stops
1301 extraFramesToRender = (m_aaIsDirty || temporalIsDirty) ? QSSGLayerRenderData::MAX_TEMPORAL_AA_LEVELS : 1;
1302 }
1303
1304 requestedFramesCount = extraFramesToRender;
1305 // Effects need to be rendered in reverse order as described in the file.
1306 layerNode->firstEffect = nullptr; // We reset the linked list
1307 const auto &effects = view3D->environment()->effectList();
1308 auto rit = effects.crbegin();
1309 const auto rend = effects.crend();
1310 for (; rit != rend; ++rit) {
1311 QQuick3DObjectPrivate *p = QQuick3DObjectPrivate::get(item: *rit);
1312 QSSGRenderEffect *effectNode = static_cast<QSSGRenderEffect *>(p->spatialNode);
1313 if (effectNode) {
1314 if (layerNode->hasEffect(inEffect: effectNode)) {
1315 qWarning() << "Duplicate effect found, skipping!";
1316 } else {
1317 effectNode->className = (*rit)->metaObject()->className(); //### persistent, but still icky to store a const char* returned from a function
1318 layerNode->addEffect(inEffect&: *effectNode);
1319 }
1320 }
1321 }
1322
1323 // Now that we have the effect list used for rendering, finalize the shader
1324 // code based on the layer (scene.env.) settings.
1325 for (QSSGRenderEffect *effectNode = layerNode->firstEffect; effectNode; effectNode = effectNode->m_nextEffect)
1326 effectNode->finalizeShaders(layer: *layerNode, renderContext: m_sgContext.get());
1327
1328 // ResourceLoaders
1329 layerNode->resourceLoaders.clear();
1330 layerNode->resourceLoaders = resourceLoaders;
1331}
1332
1333void QQuick3DSceneRenderer::removeNodeFromLayer(QSSGRenderNode *node)
1334{
1335 if (!m_layer)
1336 return;
1337
1338 m_layer->removeChild(inChild&: *node);
1339}
1340
1341void QQuick3DSceneRenderer::addNodeToLayer(QSSGRenderNode *node)
1342{
1343 if (!m_layer)
1344 return;
1345
1346 m_layer->addChild(inChild&: *node);
1347}
1348
1349QSGRenderNode::StateFlags QQuick3DSGRenderNode::changedStates() const
1350{
1351 return BlendState | StencilState | DepthState | ScissorState | ColorState | CullState | ViewportState | RenderTargetState;
1352}
1353
1354namespace {
1355inline QRect convertQtRectToGLViewport(const QRectF &rect, const QSize surfaceSize)
1356{
1357 const int x = int(rect.x());
1358 const int y = surfaceSize.height() - (int(rect.y()) + int(rect.height()));
1359 const int width = int(rect.width());
1360 const int height = int(rect.height());
1361 return QRect(x, y, width, height);
1362}
1363
1364inline void queryMainRenderPassDescriptorAndCommandBuffer(QQuickWindow *window, QSSGRhiContext *rhiCtx)
1365{
1366 if (rhiCtx->isValid()) {
1367 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx);
1368 // Query from the rif because that is available in the sync
1369 // phase (updatePaintNode) already. QSGDefaultRenderContext's
1370 // copies of the rp and cb are not there until the render
1371 // phase of the scenegraph.
1372 int sampleCount = 1;
1373 int viewCount = 1;
1374 QRhiSwapChain *swapchain = window->swapChain();
1375 if (swapchain) {
1376 rhiCtxD->setMainRenderPassDescriptor(swapchain->renderPassDescriptor());
1377 rhiCtxD->setCommandBuffer(swapchain->currentFrameCommandBuffer());
1378 rhiCtxD->setRenderTarget(swapchain->currentFrameRenderTarget());
1379 sampleCount = swapchain->sampleCount();
1380 } else {
1381 QSGRendererInterface *rif = window->rendererInterface();
1382 // no swapchain when using a QQuickRenderControl (redirecting to a texture etc.)
1383 QRhiCommandBuffer *cb = static_cast<QRhiCommandBuffer *>(
1384 rif->getResource(window, resource: QSGRendererInterface::RhiRedirectCommandBuffer));
1385 QRhiTextureRenderTarget *rt = static_cast<QRhiTextureRenderTarget *>(
1386 rif->getResource(window, resource: QSGRendererInterface::RhiRedirectRenderTarget));
1387 if (cb && rt) {
1388 rhiCtxD->setMainRenderPassDescriptor(rt->renderPassDescriptor());
1389 rhiCtxD->setCommandBuffer(cb);
1390 rhiCtxD->setRenderTarget(rt);
1391 const QRhiColorAttachment *color0 = rt->description().cbeginColorAttachments();
1392 if (color0 && color0->texture()) {
1393 sampleCount = color0->texture()->sampleCount();
1394 if (rt->resourceType() == QRhiResource::TextureRenderTarget) {
1395 const QRhiTextureRenderTargetDescription desc = static_cast<QRhiTextureRenderTarget *>(rt)->description();
1396 for (auto it = desc.cbeginColorAttachments(), end = desc.cendColorAttachments(); it != end; ++it) {
1397 if (it->multiViewCount() >= 2) {
1398 viewCount = it->multiViewCount();
1399 break;
1400 }
1401 }
1402 }
1403 }
1404 } else {
1405 qWarning(msg: "Neither swapchain nor redirected command buffer and render target are available.");
1406 }
1407 }
1408
1409 // MSAA is out of our control on this path: it is up to the
1410 // QQuickWindow and the scenegraph to set up the swapchain based on the
1411 // QSurfaceFormat's samples(). The only thing we need to do here is to
1412 // pass the sample count to the renderer because it is needed when
1413 // creating graphics pipelines.
1414 rhiCtxD->setMainPassSampleCount(sampleCount);
1415
1416 // The "direct renderer", i.e. the Underlay and Overlay modes are the
1417 // only ones that support multiview rendering. This becomes active when
1418 // the QQuickWindow is redirected into a texture array, typically with
1419 // an array size of 2 (2 views, for the left and right eye). Otherwise,
1420 // when targeting a window or redirected to a 2D texture, this is not
1421 // applicable and the view count is 1.
1422 rhiCtxD->setMainPassViewCount(viewCount);
1423 }
1424}
1425
1426// The alternative to queryMainRenderPassDescriptorAndCommandBuffer()
1427// specifically for the Inline render mode when there is a QSGRenderNode.
1428inline void queryInlineRenderPassDescriptorAndCommandBuffer(QSGRenderNode *node, QSSGRhiContext *rhiCtx)
1429{
1430 QSGRenderNodePrivate *d = QSGRenderNodePrivate::get(node);
1431 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiCtx);
1432 rhiCtxD->setMainRenderPassDescriptor(d->m_rt.rpDesc);
1433 rhiCtxD->setCommandBuffer(d->m_rt.cb);
1434 rhiCtxD->setRenderTarget(d->m_rt.rt);
1435 rhiCtxD->setMainPassSampleCount(d->m_rt.rt->sampleCount());
1436 rhiCtxD->setMainPassViewCount(1);
1437}
1438
1439} // namespace
1440
1441QQuick3DSGRenderNode::~QQuick3DSGRenderNode()
1442{
1443 delete renderer;
1444}
1445
1446void QQuick3DSGRenderNode::prepare()
1447{
1448 // this is outside the main renderpass
1449
1450 if (!renderer->m_sgContext->rhiContext()->isValid())
1451 return;
1452 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DPrepareFrame);
1453
1454 queryInlineRenderPassDescriptorAndCommandBuffer(node: this, rhiCtx: renderer->m_sgContext->rhiContext().get());
1455
1456 qreal dpr = window->effectiveDevicePixelRatio();
1457 const QSizeF itemSize = renderer->surfaceSize() / dpr;
1458 QRectF viewport = matrix()->mapRect(rect: QRectF(QPoint(0, 0), itemSize));
1459 viewport = QRectF(viewport.topLeft() * dpr, viewport.size() * dpr);
1460 const QRect vp = convertQtRectToGLViewport(rect: viewport, surfaceSize: window->size() * dpr);
1461
1462 Q_TRACE_SCOPE(QSSG_prepareFrame, vp.width(), vp.height());
1463
1464 renderer->beginFrame();
1465 renderer->rhiPrepare(viewport: vp, displayPixelRatio: dpr);
1466 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DPrepareFrame, quint64(vp.width()) | quint64(vp.height()) << 32, renderer->profilingId);
1467}
1468
1469void QQuick3DSGRenderNode::render(const QSGRenderNode::RenderState *state)
1470{
1471 Q_UNUSED(state);
1472
1473 const auto &rhiContext = renderer->m_sgContext->rhiContext();
1474
1475 if (rhiContext->isValid()) {
1476 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderFrame);
1477 Q_TRACE_SCOPE(QSSG_renderFrame, 0, 0);
1478
1479 queryInlineRenderPassDescriptorAndCommandBuffer(node: this, rhiCtx: renderer->m_sgContext->rhiContext().get());
1480
1481 renderer->rhiRender();
1482 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DRenderFrame,
1483 STAT_PAYLOAD(QSSGRhiContextStats::get(*rhiContext)), renderer->profilingId);
1484 renderer->endFrame();
1485 }
1486}
1487
1488void QQuick3DSGRenderNode::releaseResources()
1489{
1490}
1491
1492QSGRenderNode::RenderingFlags QQuick3DSGRenderNode::flags() const
1493{
1494 // don't want begin/endExternal() to be called by Quick
1495 return NoExternalRendering;
1496}
1497
1498QQuick3DSGDirectRenderer::QQuick3DSGDirectRenderer(QQuick3DSceneRenderer *renderer, QQuickWindow *window, QQuick3DSGDirectRenderer::QQuick3DSGDirectRendererMode mode)
1499 : m_renderer(renderer)
1500 , m_window(window)
1501{
1502 if (QSGRendererInterface::isApiRhiBased(api: window->rendererInterface()->graphicsApi())) {
1503 connect(sender: window, signal: &QQuickWindow::beforeRendering, context: this, slot: &QQuick3DSGDirectRenderer::prepare, type: Qt::DirectConnection);
1504 if (mode == Underlay)
1505 connect(sender: window, signal: &QQuickWindow::beforeRenderPassRecording, context: this, slot: &QQuick3DSGDirectRenderer::render, type: Qt::DirectConnection);
1506 else
1507 connect(sender: window, signal: &QQuickWindow::afterRenderPassRecording, context: this, slot: &QQuick3DSGDirectRenderer::render, type: Qt::DirectConnection);
1508 }
1509}
1510
1511QQuick3DSGDirectRenderer::~QQuick3DSGDirectRenderer()
1512{
1513 delete m_renderer;
1514}
1515
1516void QQuick3DSGDirectRenderer::setViewport(const QRectF &viewport)
1517{
1518 m_viewport = viewport;
1519}
1520
1521void QQuick3DSGDirectRenderer::setVisibility(bool visible)
1522{
1523 if (m_isVisible == visible)
1524 return;
1525 m_isVisible = visible;
1526 m_window->update();
1527}
1528
1529void QQuick3DSGDirectRenderer::requestRender()
1530{
1531 renderPending = true;
1532 requestFullUpdate(window: m_window);
1533}
1534
1535void QQuick3DSGDirectRenderer::preSynchronize()
1536{
1537 // This is called from the QQuick3DViewport's updatePaintNode(), before
1538 // QQuick3DSceneRenderer::synchronize(). It is essential to query things
1539 // such as the view count already here, so that synchronize() can rely on
1540 // mainPassViewCount() for instance. prepare() is too late as that is only
1541 // called on beforeRendering (so after the scenegraph sync phase).
1542 if (m_renderer->m_sgContext->rhiContext()->isValid())
1543 queryMainRenderPassDescriptorAndCommandBuffer(window: m_window, rhiCtx: m_renderer->m_sgContext->rhiContext().get());
1544}
1545
1546void QQuick3DSGDirectRenderer::prepare()
1547{
1548 if (!m_isVisible || !m_renderer)
1549 return;
1550
1551 if (m_renderer->m_sgContext->rhiContext()->isValid()) {
1552 // this is outside the main renderpass
1553 if (m_renderer->m_postProcessingStack) {
1554 if (renderPending) {
1555 renderPending = false;
1556 m_rhiTexture = m_renderer->renderToRhiTexture(qw: m_window);
1557 // Set up the main render target again, e.g. the postprocessing
1558 // stack could have clobbered some settings such as the sample count.
1559 queryMainRenderPassDescriptorAndCommandBuffer(window: m_window, rhiCtx: m_renderer->m_sgContext->rhiContext().get());
1560 const auto &quadRenderer = m_renderer->m_sgContext->renderer()->rhiQuadRenderer();
1561 quadRenderer->prepareQuad(rhiCtx: m_renderer->m_sgContext->rhiContext().get(), maybeRub: nullptr);
1562 if (m_renderer->requestedFramesCount > 0) {
1563 requestRender();
1564 m_renderer->requestedFramesCount--;
1565 }
1566 }
1567 }
1568 else
1569 {
1570 QQuick3DRenderStats *renderStats = m_renderer->renderStats();
1571 if (renderStats) {
1572 renderStats->startRender();
1573 renderStats->startRenderPrepare();
1574 }
1575
1576 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DPrepareFrame);
1577 queryMainRenderPassDescriptorAndCommandBuffer(window: m_window, rhiCtx: m_renderer->m_sgContext->rhiContext().get());
1578 const QRect vp = convertQtRectToGLViewport(rect: m_viewport, surfaceSize: m_window->size() * m_window->effectiveDevicePixelRatio());
1579
1580 Q_TRACE_SCOPE(QSSG_prepareFrame, vp.width(), vp.height());
1581 m_renderer->beginFrame();
1582 m_renderer->rhiPrepare(viewport: vp, displayPixelRatio: m_window->effectiveDevicePixelRatio());
1583 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DPrepareFrame, quint64(vp.width()) | quint64(vp.height()) << 32, m_renderer->profilingId);
1584
1585 if (renderStats)
1586 renderStats->endRenderPrepare();
1587 }
1588 }
1589}
1590
1591void QQuick3DSGDirectRenderer::render()
1592{
1593 if (!m_isVisible || !m_renderer)
1594 return;
1595
1596 const auto &rhiContext = m_renderer->m_sgContext->rhiContext();
1597
1598 if (rhiContext->isValid()) {
1599 // the command buffer is recording the main renderpass at this point
1600
1601 // No m_window->beginExternalCommands() must be done here. When the
1602 // renderer is using the same
1603 // QRhi/QRhiCommandBuffer/QRhiRenderPassDescriptor as the Qt Quick
1604 // scenegraph, there is no difference from the RHI's perspective. There are
1605 // no external (native) commands here.
1606
1607 // Requery the command buffer and co. since Offscreen mode View3Ds may
1608 // have altered these on the context.
1609 if (m_renderer->m_postProcessingStack) {
1610 if (m_rhiTexture) {
1611 queryMainRenderPassDescriptorAndCommandBuffer(window: m_window, rhiCtx: rhiContext.get());
1612 auto rhiCtx = m_renderer->m_sgContext->rhiContext().get();
1613 const auto &renderer = m_renderer->m_sgContext->renderer();
1614 QRhiCommandBuffer *cb = rhiContext->commandBuffer();
1615 cb->debugMarkBegin(QByteArrayLiteral("Post-processing result to main rt"));
1616
1617 // Instead of passing in a flip flag we choose to rely on qsb's
1618 // per-target compilation mode in the fragment shader. (it does UV
1619 // flipping based on QSHADER_ macros) This is just better for
1620 // performance and the shaders are very simple so introducing a
1621 // uniform block and branching dynamically would be an overkill.
1622 QRect vp = convertQtRectToGLViewport(rect: m_viewport, surfaceSize: m_window->size() * m_window->effectiveDevicePixelRatio());
1623
1624 const auto &shaderCache = m_renderer->m_sgContext->shaderCache();
1625 const auto &shaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiSimpleQuadShader(viewCount: rhiCtx->mainPassViewCount());
1626
1627 QRhiSampler *sampler = rhiCtx->sampler(samplerDescription: { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None,
1628 .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::ClampToEdge });
1629 QSSGRhiShaderResourceBindingList bindings;
1630 bindings.addTexture(binding: 0, stage: QRhiShaderResourceBinding::FragmentStage, tex: m_rhiTexture, sampler);
1631 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(q: rhiContext.get());
1632 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
1633
1634 QSSGRhiGraphicsPipelineState ps;
1635 ps.viewport = QRhiViewport(float(vp.x()), float(vp.y()), float(vp.width()), float(vp.height()));
1636 ps.samples = rhiCtx->mainPassSampleCount();
1637 ps.viewCount = rhiCtx->mainPassViewCount();
1638 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, pipeline: shaderPipeline.get());
1639 renderer->rhiQuadRenderer()->recordRenderQuad(rhiCtx, ps: &ps, srb, rpDesc: rhiCtx->mainRenderPassDescriptor(), flags: QSSGRhiQuadRenderer::UvCoords | QSSGRhiQuadRenderer::PremulBlend);
1640 cb->debugMarkEnd();
1641 }
1642 }
1643 else
1644 {
1645 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderFrame);
1646 Q_TRACE_SCOPE(QSSG_renderFrame, 0, 0);
1647
1648 queryMainRenderPassDescriptorAndCommandBuffer(window: m_window, rhiCtx: rhiContext.get());
1649
1650 m_renderer->rhiRender();
1651
1652 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DRenderFrame,
1653 STAT_PAYLOAD(QSSGRhiContextStats::get(*rhiContext)),
1654 m_renderer->profilingId);
1655 m_renderer->endFrame();
1656
1657 if (m_renderer->renderStats())
1658 m_renderer->renderStats()->endRender(dump: dumpRenderTimes);
1659 }
1660 }
1661}
1662
1663QT_END_NAMESPACE
1664

source code of qtquick3d/src/quick3d/qquick3dscenerenderer.cpp