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

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