Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qssgrenderpass.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
11#include "extensionapi/qssgrenderextensions.h"
14#include "graphobjects/qssgrenderskymaterial_p.h"
15
16#include "../utils/qssgassert_p.h"
17
18#include <QtQuick/private/qsgrenderer_p.h>
19#include <qtquick3d_tracepoints_p.h>
20
22
23static const char defaultFragOutputs[][12] = {
24 "fragOutput",
25 "fragOutput1",
26 "fragOutput2",
27 "fragOutput3",
28};
29
31{
32 QSSG_ASSERT(std::size(defaultFragOutputs) > index, return defaultFragOutputs[0]);
33 return defaultFragOutputs[index];
34}
35
36static inline QMatrix4x4 correctMVPForScissor(QRectF viewportRect, QRect scissorRect, bool isYUp) {
37 const auto &scissorCenter = scissorRect.center();
38 const auto &viewCenter = viewportRect.center();
39 const float scaleX = viewportRect.width() / float(scissorRect.width());
40 const float scaleY = viewportRect.height() / float(scissorRect.height());
41 const float dx = 2 * (viewCenter.x() - scissorCenter.x()) / scissorRect.width();
42 const float dyRect = isYUp ? (scissorCenter.y() - viewCenter.y())
43 : (viewCenter.y() - scissorCenter.y());
44 const float dy = 2 * dyRect / scissorRect.height();
45
46 return QMatrix4x4(scaleX, 0.0f, 0.0f, dx,
47 0.0f, scaleY, 0.0f, dy,
48 0.0f, 0.0f, 1.0f, 0.0f,
49 0.0f, 0.0f, 0.0f, 1.0f);
50}
51
56
57
58// MOTION VECTOR PASS
59
60void MotionVectorMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
61{
62 Q_UNUSED(renderer)
63 using namespace RenderHelpers;
64
65 QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
66 camera = data.renderedCameras[0];
67 QMatrix4x4 viewProjection(Qt::Uninitialized);
68 QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
69 cameraGlobalTransform = data.getGlobalTransform(*camera);
70 data.renderedCameras[0]->calculateViewProjectionMatrix(cameraGlobalTransform, viewProjection);
71
72 const auto &layerPrepResult = data.layerPrepResult;
73 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
74
75 const auto &renderedOpaques = data.getSortedOpaqueRenderableObjects(*camera);
76 const auto &renderedTransparent = data.getSortedTransparentRenderableObjects(*camera);
77
78 rhiMotionVectorTexture = data.getRenderResult(QSSGRenderResult::Key::MotionVectorTexture);
79 bool textureReady = rhiMotionVectorTexture &&
80 rhiPrepareMotionVectorTexture(rhiCtx.get(), layerPrepResult.textureDimensions(), rhiMotionVectorTexture);
81
82 if (!textureReady) {
83 rhiMotionVectorTexture = nullptr;
84 enabled = false;
85 return;
86 }
87
88 motionVectorMapManager = data.requestMotionVectorMapManager();
89 ps = data.getPipelineState();
90 ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled,
91 QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
92 ps.samples = 1;
93 ps.viewCount = data.layer.viewCount;
94
95 for (int i = 0; i < MaxBuckets; ++i)
96 motionVectorPassObjects[i].clear();
97 enabled = false;
98
99 for (const auto &handles : { &renderedOpaques, &renderedTransparent }) {
100 for (const auto &handle : *handles) {
101 if (handle.obj->type == QSSGRenderableObject::Type::DefaultMaterialMeshSubset ||
102 handle.obj->type == QSSGRenderableObject::Type::CustomMaterialMeshSubset) {
103 QSSGSubsetRenderable *renderable(static_cast<QSSGSubsetRenderable *>(handle.obj));
104 if (handle.obj->renderableFlags.isMotionVectorParticipant()) {
105 if (renderable->modelContext.model.instancing() && renderable->modelContext.model.instanceCount() == 0)
106 continue;
107 bool skin = renderable->modelContext.model.usesBoneTexture();
108 bool instance = renderable->modelContext.model.instanceCount() > 0;
109 bool morph = renderable->modelContext.model.morphTargets.size() > 0;
110 int bucketIndex = (int(skin) << 2) | (int(instance) << 1) | int(morph);
111 motionVectorPassObjects[bucketIndex].push_back(handle);
112
113 QSSGRhiGraphicsPipelineState tempPs = ps;
114
115 rhiPrepareMotionVectorRenderable(rhiCtx.get(),
116 this,
117 data,
118 viewProjection,
119 *handle.obj,
120 rhiMotionVectorTexture->rpDesc,
121 &tempPs,
122 *motionVectorMapManager);
123 enabled = true;
124 }
125 }
126 }
127 }
128}
129
130void MotionVectorMapPass::renderPass(QSSGRenderer &renderer)
131{
132 using namespace RenderHelpers;
133 if (enabled) {
134 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
135 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
136 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
137 cb->debugMarkBegin(QByteArrayLiteral("Quick3D motion vector map"));
138 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D motion vector map"));
139
140 if (Q_LIKELY(rhiMotionVectorTexture && rhiMotionVectorTexture->isValid())) {
141 cb->beginPass(rhiMotionVectorTexture->rt, QColor(0, 0, 0, 0), { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
142 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rhiMotionVectorTexture->rt));
143 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
144
145 rhiRenderMotionVector(rhiCtx.get(),
146 ps,
147 motionVectorPassObjects,
148 MaxBuckets);
149
150 cb->endPass();
151 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
152 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("motion_vector_map"));
153 }
154 cb->debugMarkEnd();
155 }
156}
157
159{
160 QSSG_CHECK(motionVectorMapManager);
161 motionVectorMapManager->endFrame();
162 camera = nullptr;
163 ps = {};
164 rhiMotionVectorTexture = nullptr;
165 for (int i = 0; i < MaxBuckets; ++i)
166 motionVectorPassObjects[i].clear();
167}
168
169// SHADOW PASS
170
171void ShadowMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
172{
173 Q_UNUSED(renderer)
174 using namespace RenderHelpers;
175
176 QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
177 camera = data.renderedCameras[0];
178
179 QSSG_ASSERT(shadowPassObjects.isEmpty(), shadowPassObjects.clear());
180
181 data.getShadowCastingObjects(*camera, shadowPassObjects, castingObjectsBox, receivingObjectsBox);
182
183 globalLights = data.globalLights;
184 enabled = !shadowPassObjects.isEmpty() || !globalLights.isEmpty();
185
186 if (enabled) {
187 shadowMapManager = data.requestShadowMapManager();
188
189 ps = data.getPipelineState();
190 ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled };
191 // Try reducing self-shadowing and artifacts.
192 ps.depthBias = 2;
193 ps.slopeScaledDepthBias = 1.5f;
194
195 if (!debugCamera) {
196 debugCamera = std::make_unique<QSSGRenderCamera>(QSSGRenderGraphObject::Type::OrthographicCamera);
197 }
198 }
199}
200
201void ShadowMapPass::renderPass(QSSGRenderer &renderer)
202{
203 using namespace RenderHelpers;
204
205 // INPUT: Sorted opaque and transparent + depth, global lights (scoped lights not supported) and camera.
206
207 // DEPENDECY: None
208
209 // OUTPUT: Texture (Shadowmap Texture Atlas)
210
211 // CONDITION: Lights (shadowPassObjects)
212
213 if (enabled) {
214 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
215 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
216 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
217 cb->debugMarkBegin(QByteArrayLiteral("Quick3D shadow map"));
218 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D shadow map"));
219 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
220
221 QSSG_CHECK(shadowMapManager);
222 rhiRenderShadowMap(rhiCtx.get(),
223 this,
224 ps,
225 *shadowMapManager,
226 *camera,
227 debugCamera.get(),
228 globalLights, // scoped lights are not relevant here
229 shadowPassObjects,
230 renderer,
231 castingObjectsBox,
232 receivingObjectsBox);
233
234 cb->debugMarkEnd();
235 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("shadow_map"));
236 }
237}
238
240{
241 enabled = false;
242 camera = nullptr;
243 castingObjectsBox = {};
244 receivingObjectsBox = {};
245 ps = {};
246 shadowPassObjects.clear();
247 globalLights.clear();
248}
249
250// REFLECTIONMAP PASS
251
253{
254 // Read the environment variable to check if the old behavior of including the screen texture objects
255 // in the reflection pass should be kept for compatibility reasons.
256 // Besides being expensive, we shouldn't be using the main screen texture in the probes at all.
257 m_includeSTO = qEnvironmentVariableIntValue("QT_QUICK3D_REFLECTION_PASS_INCLUDE_SCREEN_TEXTURE_OBJECTS") != 0;
258}
259
260void ReflectionMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
261{
262 Q_UNUSED(renderer);
263 Q_UNUSED(data);
264
265 QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
266 QSSGRenderCamera *camera = data.renderedCameras[0];
267
268 ps = data.getPipelineState();
269 ps.flags |= { QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled,
270 QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled,
271 QSSGRhiGraphicsPipelineState::Flag::BlendEnabled };
272
273 QSSG_ASSERT(data.reflectionProbesView.size() != 0, return);
274
275 reflectionProbes = { data.reflectionProbesView.begin(), data.reflectionProbesView.end() };
276 reflectionMapManager = data.requestReflectionMapManager();
277
278 const auto &sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(*camera);
279 const auto &sortedTransparentObjects = data.getSortedTransparentRenderableObjects(*camera);
280 QSSGRenderableObjectList emptyList{};
281 const auto &sortedScreenTextureObjects = m_includeSTO ? data.getSortedScreenTextureRenderableObjects(*camera) : emptyList;
282
283 QSSG_ASSERT(reflectionPassObjects.isEmpty(), reflectionPassObjects.clear());
284
285 // NOTE: We should consider keeping track of the reflection casting objects to avoid
286 // filtering this list on each prep.
287 for (const auto &handles : { &sortedOpaqueObjects, &sortedTransparentObjects, &sortedScreenTextureObjects }) {
288 for (const auto &handle : *handles) {
289 if (handle.obj->renderableFlags.testFlag(QSSGRenderableObjectFlag::CastsReflections))
290 reflectionPassObjects.push_back(handle);
291 }
292 }
293}
294
295void ReflectionMapPass::renderPass(QSSGRenderer &renderer)
296{
297 using namespace RenderHelpers;
298
299 // INPUT: Reflection probes, sorted opaque and transparent
300
301 // DEPENDECY: None
302
303 // OUTPUT: Cube maps (1 per probe)
304
305 // NOTE: Full pass with a sky box pass
306
307 // CONDITION: Probes and sorted opaque and transparent
308
309 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
310 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
311 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
312
313 const auto *layerData = QSSGLayerRenderData::getCurrent(renderer);
314 QSSG_ASSERT(layerData, return);
315
316 QSSG_CHECK(reflectionMapManager);
317 if (!reflectionPassObjects.isEmpty() || (reflectionProbes.size() != 0)) {
318 cb->debugMarkBegin(QByteArrayLiteral("Quick3D reflection map"));
319 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D reflection map"));
320 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
321 rhiRenderReflectionMap(*renderer.contextInterface(), this, *layerData, &ps, *reflectionMapManager, reflectionProbes, reflectionPassObjects, renderer);
322
323 cb->debugMarkEnd();
324 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("reflection_map"));
325 }
326}
327
329{
330 ps = {};
331 reflectionProbes.clear();
332 reflectionPassObjects.clear();
333}
334
335// SKY MATERIAL PASS
336
337void SkyMaterialPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
338{
339 Q_UNUSED(renderer);
340 Q_UNUSED(data);
341}
342
343void SkyMaterialPass::renderPass(QSSGRenderer &renderer)
344{
345 auto layerData = QSSGLayerRenderData::getCurrent(renderer);
346 QSSG_ASSERT(layerData, return);
347
348 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
349 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
350 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
351
352 cb->debugMarkBegin(QByteArrayLiteral("Quick3D Sky Material"));
353 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D Sky Material"));
354 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
355
356 layerData->resolveLayerIblTexture(this);
357
358 cb->debugMarkEnd();
359 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("sky_material"));
360}
361
363
364// ZPrePass
365void ZPrePassPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
366{
367 using namespace RenderHelpers;
368
369 // INPUT: Item2Ds + depth write + depth prepass
370
371 // DEPENDECY: none
372
373 // OUTPUT: Depth buffer attchment for current target
374
375 // NOTE: Could we make the depth pass more complete and just do a blit here?
376 //
377 // 1. If we have a depth map, just do a blit and then update with the rest
378 // 2. If we don't have a depth map (and/or SSAO) consider using a lower lod level.
379
380 // CONDITION: Input + globally enabled or ?
381
382 QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
383 QSSGRenderCamera *camera = data.renderedCameras[0];
384
385 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
386 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
387 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
388 ps = data.getPipelineState();
389
390 renderedDepthWriteObjects = data.getSortedRenderedDepthWriteObjects(*camera);
391 renderedOpaqueDepthPrepassObjects = data.getSortedrenderedOpaqueDepthPrepassObjects(*camera);
392
393 cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare Z prepass"));
394 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D prepare Z prepass"));
395 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
396 active = rhiPrepareDepthPass(rhiCtx.get(), this, ps, rhiCtx->mainRenderPassDescriptor(), data,
397 renderedDepthWriteObjects, renderedOpaqueDepthPrepassObjects,
398 rhiCtx->mainPassSampleCount(), data.layer.viewCount);
399 data.setZPrePassPrepResult(active);
400 cb->debugMarkEnd();
401 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("prepare_z_prepass"));
402}
403
404void ZPrePassPass::renderPass(QSSGRenderer &renderer)
405{
406 using namespace RenderHelpers;
407
408 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
409 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
410
411 bool needsSetViewport = true;
412 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
413
414 if (active) {
415 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
416 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render Z prepass"));
417 cb->debugMarkBegin(QByteArrayLiteral("Quick3D render Z prepass"));
418 rhiRenderDepthPass(rhiCtx.get(), ps, renderedDepthWriteObjects, renderedOpaqueDepthPrepassObjects, &needsSetViewport);
419 cb->debugMarkEnd();
420 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("render_z_prepass"));
421 }
422}
423
425{
426 renderedDepthWriteObjects.clear();
427 renderedOpaqueDepthPrepassObjects.clear();
428 ps = {};
429 active = false;
430}
431
432// SSAO PASS
433void SSAOMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
434{
435 using namespace RenderHelpers;
436
437 // Assumption for now is that all passes are keept alive and only reset once a frame is done.
438 // I.e., holding data like this should be safe (If that's no longer the case we need to do ref counting
439 // for shared data).
440
441 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
442 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
443
444 rhiAoTexture = data.getRenderResult(QSSGRenderResult::Key::AoTexture);
445 rhiDepthTexture = data.getRenderResult(QSSGRenderResult::Key::DepthTexture);
446 QSSG_ASSERT_X(!data.renderedCameras.isEmpty(), "Preparing AO pass failed, missing camera", return);
447 camera = data.renderedCameras[0];
448 QSSG_ASSERT_X((rhiDepthTexture && rhiDepthTexture->isValid()), "Preparing AO pass failed, missing equired texture(s)", return);
449
450 const auto &shaderCache = renderer.contextInterface()->shaderCache();
451 ssaoShaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiSsaoShader(data.layer.viewCount);
452 aoSettings = { data.layer.aoStrength, data.layer.aoDistance, data.layer.aoSoftness, data.layer.aoBias, data.layer.aoSamplerate, data.layer.aoDither };
453
454 ps = data.getPipelineState();
455 const auto &layerPrepResult = data.layerPrepResult;
456 const bool ready = rhiAoTexture && rhiPrepareAoTexture(rhiCtx.get(), layerPrepResult.textureDimensions(), rhiAoTexture, data.layer.viewCount);
457
458 if (Q_UNLIKELY(!ready))
459 rhiAoTexture = nullptr;
460}
461
462void SSAOMapPass::renderPass(QSSGRenderer &renderer)
463{
464 using namespace RenderHelpers;
465
466 // INPUT: Camera + depth map
467
468 // DEPENDECY: Depth map (zprepass)
469
470 // OUTPUT: AO Texture
471
472 // NOTE:
473
474 // CONDITION: SSAO enabled
475 QSSG_ASSERT(camera && rhiDepthTexture && rhiDepthTexture->isValid(), return);
476
477 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
478 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
479
480 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
481 cb->debugMarkBegin(QByteArrayLiteral("Quick3D SSAO map"));
482 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D SSAO map"));
483 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
484
485 if (Q_LIKELY(rhiAoTexture && rhiAoTexture->isValid())) {
486 rhiRenderAoTexture(rhiCtx.get(),
487 this,
488 renderer,
489 *ssaoShaderPipeline,
490 ps,
491 aoSettings,
492 *rhiAoTexture,
493 *rhiDepthTexture,
494 *camera);
495 }
496
497 cb->debugMarkEnd();
498 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("ssao_map"));
499}
500
502{
503 rhiDepthTexture = nullptr;
504 rhiAoTexture = nullptr;
505 camera = nullptr;
506 ps = {};
507 aoSettings = {};
508}
509
510// DEPTH TEXTURE PASS
511void DepthMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
512{
513 using namespace RenderHelpers;
514
515 QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
516 QSSGRenderCamera *camera = data.renderedCameras[0];
517
518 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
519 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
520 const auto &layerPrepResult = data.layerPrepResult;
521 bool ready = false;
522 ps = data.getPipelineState();
523
524 if (m_multisampling) {
525 ps.samples = rhiCtx->mainPassSampleCount();
526 rhiDepthTexture = data.getRenderResult(QSSGRenderResult::Key::DepthTextureMS);
527 } else {
528 ps.samples = 1;
529 rhiDepthTexture = data.getRenderResult(QSSGRenderResult::Key::DepthTexture);
530 }
531
532 if (Q_LIKELY(rhiDepthTexture && rhiPrepareDepthTexture(rhiCtx.get(), layerPrepResult.textureDimensions(), rhiDepthTexture, data.layer.viewCount, ps.samples))) {
533 sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(*camera);
534 sortedTransparentObjects = data.getSortedTransparentRenderableObjects(*camera);
535 // the depth texture is always non-MSAA, but is a 2D array with multiview
536 ready = rhiPrepareDepthPass(rhiCtx.get(), this, ps, rhiDepthTexture->rpDesc, data,
537 sortedOpaqueObjects, sortedTransparentObjects,
538 ps.samples, data.layer.viewCount);
539 }
540
541 if (Q_UNLIKELY(!ready))
542 rhiDepthTexture = nullptr;
543}
544
545void DepthMapPass::renderPass(QSSGRenderer &renderer)
546{
547 using namespace RenderHelpers;
548
549 // INPUT: sorted objects (opaque + transparent) (maybe...)
550
551 // DEPENDECY: If this is only used for the AO case, that dictates if this should be done or not.
552
553 // OUTPUT: Texture
554
555 // NOTE: Why are we prepping opaque + transparent object if we're not using them? And why are we staying compatible with 5.15?
556 // Only used for AO? Merge into the AO pass?
557
558 // NOTES:
559 //
560 // 1: If requested, use this and blit it in the z-pre pass.
561 // 2. Why are we handling the transparent objects in the render prep (only)?
562
563 // CONDITION:
564
565 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
566 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
567 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
568 cb->debugMarkBegin(QByteArrayLiteral("Quick3D depth texture"));
569
570 if (Q_LIKELY(rhiDepthTexture && rhiDepthTexture->isValid())) {
571 bool needsSetViewport = true;
572 cb->beginPass(rhiDepthTexture->rt, Qt::transparent, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
573 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rhiDepthTexture->rt));
574 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
575 // NB! We do not pass sortedTransparentObjects in the 4th
576 // argument to stay compatible with the 5.15 code base,
577 // which also does not include semi-transparent objects in
578 // the depth texture. In addition, capturing after the
579 // opaque pass, not including transparent objects, is part
580 // of the contract for screen reading custom materials,
581 // both for depth and color.
582 rhiRenderDepthPass(rhiCtx.get(), ps, sortedOpaqueObjects, {}, &needsSetViewport);
583 cb->endPass();
584 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
585 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("depth_texture"));
586 }
587
588 cb->debugMarkEnd();
589}
590
591void DepthMapPass::resetForFrame()
592{
593 rhiDepthTexture = nullptr;
594 sortedOpaqueObjects.clear();
595 sortedTransparentObjects.clear();
596 ps = {};
597}
598
599// NORMAL TEXTURE PASS
600
601void NormalPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
602{
603 using namespace RenderHelpers;
604
605 QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
606 QSSGRenderCamera *camera = data.renderedCameras[0];
607
608 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
609 QRhi *rhi = rhiCtx->rhi();
610 QSSG_ASSERT(rhi->isRecordingFrame(), return);
611 const auto &layerPrepResult = data.layerPrepResult;
612
613 // the normal texture is not multiview-dependent and it is always a 2D texture
614 // (so not an array with multiview either)
615
616 // the normal texture is always non-MSAA
617
618 ps = data.getPipelineState();
619 ps.samples = 1;
620 ps.viewCount = 1;
621
622 sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(*camera);
623
624 // transparent objects are not included in the normal texture pass
625
626 QSSGShaderFeatures shaderFeatures = data.getShaderFeatures();
627 shaderFeatures.set(QSSGShaderFeatures::Feature::NormalPass, true);
628
629 normalTexture = data.getRenderResult(QSSGRenderResult::Key::NormalTexture);
630
631 const QSize size = layerPrepResult.textureDimensions();
632 bool needsBuild = false;
633
634 if (!normalTexture->texture) {
635 QRhiTexture::Format format = QRhiTexture::RGBA16F;
636 if (!rhi->isTextureFormatSupported(format)) {
637 qWarning("No float formats, not great");
638 format = QRhiTexture::RGBA8;
639 }
640 normalTexture->texture = rhiCtx->rhi()->newTexture(format, size, 1, QRhiTexture::RenderTarget);
641 needsBuild = true;
642 normalTexture->texture->setName(QByteArrayLiteral("Normal texture"));
643 } else if (normalTexture->texture->pixelSize() != size) {
644 normalTexture->texture->setPixelSize(size);
645 needsBuild = true;
646 }
647
648 if (!normalTexture->depthStencil) {
649 normalTexture->depthStencil = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, size);
650 needsBuild = true;
651 } else if (normalTexture->depthStencil->pixelSize() != size) {
652 normalTexture->depthStencil->setPixelSize(size);
653 needsBuild = true;
654 }
655
656 if (needsBuild) {
657 if (!normalTexture->texture->create()) {
658 qWarning("Failed to build normal texture (size %dx%d, format %d)",
659 size.width(), size.height(), int(normalTexture->texture->format()));
660 normalTexture->reset();
661 return;
662 }
663
664 if (!normalTexture->depthStencil->create()) {
665 qWarning("Failed to build depth-stencil buffer for normal texture (size %dx%d)",
666 size.width(), size.height());
667 normalTexture->reset();
668 return;
669 }
670
671 normalTexture->resetRenderTarget();
672
673 QRhiTextureRenderTargetDescription rtDesc;
674 QRhiColorAttachment colorAttachment(normalTexture->texture);
675 rtDesc.setColorAttachments({ colorAttachment });
676 rtDesc.setDepthStencilBuffer(normalTexture->depthStencil);
677
678 normalTexture->rt = rhi->newTextureRenderTarget(rtDesc);
679 normalTexture->rt->setName(QByteArrayLiteral("Normal texture RT"));
680 normalTexture->rpDesc = normalTexture->rt->newCompatibleRenderPassDescriptor();
681 normalTexture->rt->setRenderPassDescriptor(normalTexture->rpDesc);
682 if (!normalTexture->rt->create()) {
683 qWarning("Failed to build render target for normal texture");
684 normalTexture->reset();
685 return;
686 }
687 }
688
689 rhiPrepareNormalPass(rhiCtx.get(), this, ps, normalTexture->rpDesc, data, sortedOpaqueObjects);
690}
691
692void NormalPass::renderPass(QSSGRenderer &renderer)
693{
694 using namespace RenderHelpers;
695
696 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
697 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
698 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
699 cb->debugMarkBegin(QByteArrayLiteral("Quick3D normal texture"));
700
701 if (Q_LIKELY(normalTexture && normalTexture->isValid())) {
702 bool needsSetViewport = true;
703 cb->beginPass(normalTexture->rt, Qt::transparent, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
704 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(normalTexture->rt));
705 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
706
707 rhiRenderNormalPass(rhiCtx.get(), ps, sortedOpaqueObjects, &needsSetViewport);
708
709 cb->endPass();
710 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
711 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("normal_texture"));
712 }
713
714 cb->debugMarkEnd();
715}
716
717void NormalPass::resetForFrame()
718{
719 normalTexture = nullptr;
720 depthBuffer = nullptr;
721 sortedOpaqueObjects.clear();
722 ps = {};
723}
724
725// SCREEN TEXTURE PASS
726
727void ScreenMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
728{
729 using namespace RenderHelpers;
730
731 QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
732 QSSGRenderCamera *camera = data.renderedCameras[0];
733
734 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
735 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
736 rhiScreenTexture = data.getRenderResult(QSSGRenderResult::Key::ScreenTexture);
737 auto &layer = data.layer;
738 const auto &layerPrepResult = data.layerPrepResult;
739 wantsMips = layerPrepResult.getFlags().requiresMipmapsForScreenTexture();
740 sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(*camera);
741 ps = data.getPipelineState();
742 ps.samples = 1; // screen texture is always non-MSAA
743 ps.viewCount = data.layer.viewCount; // but is a 2D texture array when multiview
744 if (ps.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled))
745 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, true);
746
747 if (layer.background == QSSGRenderLayer::Background::Color)
748 clearColor = QColor::fromRgbF(layer.clearColor.x(), layer.clearColor.y(), layer.clearColor.z());
749
750 if (rhiCtx->rhi()->isFeatureSupported(QRhi::TexelFetch)) {
751 if (layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap && layer.skyBoxCubeMap) {
752 if (!skyboxCubeMapPass)
753 skyboxCubeMapPass = SkyboxCubeMapPass();
754
755 skyboxCubeMapPass->skipTonemapping = true;
756 skyboxCubeMapPass->renderPrep(renderer, data);
757
758 // The pass expects to output to the main render target, but we have
759 // our own texture here, possibly with a differing sample count, so
760 // override the relevant settings once renderPrep() is done.
761 skyboxCubeMapPass->ps.samples = ps.samples;
762
763 skyboxPass = std::nullopt;
764 } else if ((layer.background == QSSGRenderLayer::Background::SkyBox && layer.lightProbe)
765 || (layer.background == QSSGRenderLayer::Background::SkyMaterial && layer.skyMaterial)) {
766 if (!skyboxPass)
767 skyboxPass = SkyboxPass();
768
769 skyboxPass->skipTonemapping = true;
770 skyboxPass->renderPrep(renderer, data);
771
772 skyboxPass->ps.samples = ps.samples;
773
774 skyboxCubeMapPass = std::nullopt;
775 }
776 }
777
778 bool ready = false;
779 if (Q_LIKELY(rhiScreenTexture && rhiPrepareScreenTexture(rhiCtx.get(), layerPrepResult.textureDimensions(), wantsMips, rhiScreenTexture, layer.viewCount))) {
780 ready = true;
781 if (skyboxCubeMapPass)
782 skyboxCubeMapPass->rpDesc = rhiScreenTexture->rpDesc;
783 if (skyboxPass)
784 skyboxPass->rpDesc = rhiScreenTexture->rpDesc;
785 // NB: not compatible with disabling LayerEnableDepthTest
786 // because there are effectively no "opaque" objects then.
787 // Disable Tonemapping for all materials in the screen pass texture
788 shaderFeatures = data.getShaderFeatures();
789 shaderFeatures.disableTonemapping();
790 const auto &sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(*camera);
791 for (const auto &handle : sortedOpaqueObjects) {
792 // Reflection cube maps are not available at this point, make sure they are turned off.
793 bool recRef = handle.obj->renderableFlags.receivesReflections();
794 handle.obj->renderableFlags.setReceivesReflections(false);
795 rhiPrepareRenderableForScreenMapPass(rhiCtx.get(), this, data, *handle.obj, rhiScreenTexture->rpDesc, &ps, shaderFeatures, 1, data.layer.viewCount);
796 handle.obj->renderableFlags.setReceivesReflections(recRef);
797 }
798 }
799
800 if (Q_UNLIKELY(!ready))
801 rhiScreenTexture = nullptr;
802}
803
804void ScreenMapPass::renderPass(QSSGRenderer &renderer)
805{
806 using namespace RenderHelpers;
807
808 // INPUT: Sorted opaque objects + depth objects
809
810 // DEPENDECY: Depth pass (if enabled)
811
812 // OUTPUT: Texture (screen texture).
813
814 // NOTE: Used for refrection and effects (?)
815
816 // CONDITION:
817
818 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
819 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
820 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
821
822 cb->debugMarkBegin(QByteArrayLiteral("Quick3D screen texture"));
823
824 if (Q_LIKELY(rhiScreenTexture && rhiScreenTexture->isValid())) {
825 cb->beginPass(rhiScreenTexture->rt, clearColor, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
826 QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rhiScreenTexture->rt));
827 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
828
829 bool needsSetViewport = true;
830 for (const auto &handle : std::as_const(sortedOpaqueObjects))
831 rhiRenderRenderable(rhiCtx.get(), ps, *handle.obj, &needsSetViewport);
832
833 if (skyboxCubeMapPass)
834 skyboxCubeMapPass->renderPass(renderer);
835 else if (skyboxPass)
836 skyboxPass->renderPass(renderer);
837
838 QRhiResourceUpdateBatch *rub = nullptr;
839 if (wantsMips) {
840 rub = rhiCtx->rhi()->nextResourceUpdateBatch();
841 rub->generateMips(rhiScreenTexture->texture);
842 }
843 cb->endPass(rub);
844 QSSGRHICTX_STAT(rhiCtx, endRenderPass());
845 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("screen_texture"));
846 }
847
848 cb->debugMarkEnd();
849}
850
852{
853 rhiScreenTexture = nullptr;
854 if (skyboxPass)
855 skyboxPass->resetForFrame();
856 if (skyboxCubeMapPass)
857 skyboxCubeMapPass->resetForFrame();
858 ps = {};
859 wantsMips = false;
860 clearColor = Qt::transparent;
861 shaderFeatures = {};
862 sortedOpaqueObjects.clear();
863}
864
865void ScreenReflectionPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
866{
867 QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
868 QSSGRenderCamera *camera = data.renderedCameras[0];
869
870 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
871 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
872 rhiScreenTexture = data.getRenderResult(QSSGRenderResult::Key::ScreenTexture);
873 QSSG_ASSERT_X(rhiScreenTexture && rhiScreenTexture->isValid(), "Invalid screen texture!", return);
874
875 const auto &layer = data.layer;
876 const auto shaderFeatures = data.getShaderFeatures();
877 const bool layerEnableDepthTest = layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest);
878
879 QRhiRenderPassDescriptor *mainRpDesc = rhiCtx->mainRenderPassDescriptor();
880 const int samples = rhiCtx->mainPassSampleCount();
881 const int viewCount = data.layer.viewCount;
882
883 // NOTE: We're piggybacking on the screen map pass for now, but we could do better.
884 ps = data.getPipelineState();
885 const bool depthTestEnabled = (data.screenMapPass.ps.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled));
886 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, depthTestEnabled);
887 const bool depthWriteEnabled = (data.screenMapPass.ps.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled));
888 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, depthWriteEnabled);
889 sortedScreenTextureObjects = data.getSortedScreenTextureRenderableObjects(*camera);
890 for (const auto &handle : std::as_const(sortedScreenTextureObjects)) {
891 QSSGRenderableObject *theObject = handle.obj;
892 const auto depthWriteMode = theObject->depthWriteMode;
893 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, theObject->renderableFlags.hasTransparency());
894 const bool curDepthWriteEnabled = !(depthWriteMode == QSSGDepthDrawMode::Never || depthWriteMode == QSSGDepthDrawMode::OpaquePrePass
895 || data.isZPrePassActive() || !layerEnableDepthTest);
896 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, curDepthWriteEnabled);
897 RenderHelpers::rhiPrepareRenderable(rhiCtx.get(), this, data, *theObject, mainRpDesc, &ps, shaderFeatures, samples, viewCount);
898 }
899}
900
901void ScreenReflectionPass::renderPass(QSSGRenderer &renderer)
902{
903 if (QSSG_GUARD(rhiScreenTexture && rhiScreenTexture->isValid())) {
904 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
905 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
906 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
907
908 // 3. Screen texture depended objects
909 cb->debugMarkBegin(QByteArrayLiteral("Quick3D render screen texture dependent"));
910 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
911 Q_TRACE(QSSG_renderPass_entry, QStringLiteral("Quick3D render screen texture dependent"));
912 bool needsSetViewport = true;
913 for (const auto &handle : std::as_const(sortedScreenTextureObjects)) {
914 QSSGRenderableObject *theObject = handle.obj;
915 RenderHelpers::rhiRenderRenderable(rhiCtx.get(), ps, *theObject, &needsSetViewport);
916 }
917 cb->debugMarkEnd();
918 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("screen_texture_dependent"));
919 Q_TRACE(QSSG_renderPass_exit);
920 }
921}
922
924{
925 sortedScreenTextureObjects.clear();
926 rhiScreenTexture = nullptr;
927 ps = {};
928}
929
932 QSSGPassKey passKey,
933 QSSGRhiGraphicsPipelineState &ps,
934 QSSGShaderFeatures shaderFeatures,
935 QRhiRenderPassDescriptor *rpDesc,
936 const QSSGRenderableObjectList &sortedOpaqueObjects)
937{
938 const auto &rhiCtx = ctx.rhiContext();
939 QSSG_ASSERT(rpDesc && rhiCtx->rhi()->isRecordingFrame(), return);
940
941 const auto &layer = data.layer;
942 const bool layerEnableDepthTest = layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest);
943
944 for (const auto &handle : std::as_const(sortedOpaqueObjects)) {
945 QSSGRenderableObject *theObject = handle.obj;
946 const auto depthWriteMode = theObject->depthWriteMode;
947 const bool curDepthWriteEnabled = !(depthWriteMode == QSSGDepthDrawMode::Never ||
948 depthWriteMode == QSSGDepthDrawMode::OpaquePrePass ||
949 data.isZPrePassActive() || !layerEnableDepthTest);
950 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, curDepthWriteEnabled);
951 RenderHelpers::rhiPrepareRenderable(rhiCtx.get(), passKey, data, *theObject, rpDesc, &ps, shaderFeatures, ps.samples, ps.viewCount);
952 }
953}
954
956 const QSSGRhiGraphicsPipelineState &ps,
957 const QSSGRenderableObjectList &sortedOpaqueObjects)
958{
959 const auto &rhiCtx = ctx.rhiContext();
960 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
961 bool needsSetViewport = true;
962 for (const auto &handle : std::as_const(sortedOpaqueObjects)) {
963 QSSGRenderableObject *theObject = handle.obj;
964 RenderHelpers::rhiRenderRenderable(rhiCtx.get(), ps, *theObject, &needsSetViewport);
965 }
966}
967
968void OpaquePass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
969{
970 auto *ctx = renderer.contextInterface();
971 const auto &rhiCtx = ctx->rhiContext();
972 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
973 QSSG_ASSERT(!data.renderedCameras.isEmpty() && data.renderedCameraData.has_value() , return);
974 QSSGRenderCamera *camera = data.renderedCameras[0];
975
976 ps = data.getPipelineState();
977 ps.samples = rhiCtx->mainPassSampleCount();
978 ps.viewCount = data.layer.viewCount;
979 ps.depthFunc = QRhiGraphicsPipeline::LessOrEqual;
980 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, false);
981
982 // opaque objects (or, this list is empty when LayerEnableDepthTest is disabled)
983 sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(*camera);
984 shaderFeatures = data.getShaderFeatures();
985
986 QRhiRenderPassDescriptor *mainRpDesc = rhiCtx->mainRenderPassDescriptor();
987 prep(*ctx, data, this, ps, shaderFeatures, mainRpDesc, sortedOpaqueObjects);
988}
989
990void OpaquePass::renderPass(QSSGRenderer &renderer)
991{
992 auto *ctx = renderer.contextInterface();
993 const auto &rhiCtx = ctx->rhiContext();
994 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
995 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
996
997 cb->debugMarkBegin(QByteArrayLiteral("Quick3D render opaque"));
998 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
999 Q_TRACE(QSSG_renderPass_entry, QStringLiteral("Quick3D render opaque"));
1000 render(*ctx, ps, sortedOpaqueObjects);
1001 cb->debugMarkEnd();
1002 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("opaque_pass"));
1003 Q_TRACE(QSSG_renderPass_exit);
1004}
1005
1007{
1008 sortedOpaqueObjects.clear();
1009 ps = {};
1010 shaderFeatures = {};
1011}
1012
1014 QSSGLayerRenderData &data,
1015 QSSGPassKey passKey,
1016 QSSGRhiGraphicsPipelineState &ps,
1017 QSSGShaderFeatures shaderFeatures,
1018 QRhiRenderPassDescriptor *rpDesc,
1019 const QSSGRenderableObjectList &sortedTransparentObjects,
1020 bool oit)
1021{
1022 const auto &rhiCtx = ctx.rhiContext();
1023 QSSG_ASSERT(rpDesc && rhiCtx->rhi()->isRecordingFrame(), return);
1024
1025 const bool zPrePassActive = data.isZPrePassActive();
1026 for (const auto &handle : std::as_const(sortedTransparentObjects)) {
1027 QSSGRenderableObject *theObject = handle.obj;
1028 const auto depthWriteMode = theObject->depthWriteMode;
1029 const bool curDepthWriteEnabled = (depthWriteMode == QSSGDepthDrawMode::Always && !zPrePassActive);
1030 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, curDepthWriteEnabled);
1031 if (!(theObject->renderableFlags.isCompletelyTransparent())) {
1032 RenderHelpers::rhiPrepareRenderable(rhiCtx.get(), passKey, data, *theObject, rpDesc, &ps, shaderFeatures,
1033 ps.samples, ps.viewCount, nullptr, nullptr, QSSGRenderTextureCubeFaceNone, nullptr, oit);
1034 }
1035 }
1036}
1037
1039 const QSSGRhiGraphicsPipelineState &ps,
1040 const QSSGRenderableObjectList &sortedTransparentObjects)
1041{
1042 const auto &rhiCtx = ctx.rhiContext();
1043 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
1044 // If scissorRect is set, Item2Ds will be drawn by a workaround of modifying
1045 // viewport, not using actual 3D scissor test.
1046 // It means non-opaque objects may be affected by this viewport setting.
1047 bool needsSetViewport = true;
1048 for (const auto &handle : std::as_const(sortedTransparentObjects)) {
1049 QSSGRenderableObject *theObject = handle.obj;
1050 if (!theObject->renderableFlags.isCompletelyTransparent())
1051 RenderHelpers::rhiRenderRenderable(rhiCtx.get(), ps, *theObject, &needsSetViewport);
1052 }
1053}
1054
1055void TransparentPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
1056{
1057 auto *ctx = renderer.contextInterface();
1058 const auto &rhiCtx = ctx->rhiContext();
1059
1060 QSSG_ASSERT(!data.renderedCameras.isEmpty() && data.renderedCameraData.has_value() , return);
1061 QSSGRenderCamera *camera = data.renderedCameras[0];
1062
1063 QRhiRenderPassDescriptor *mainRpDesc = rhiCtx->mainRenderPassDescriptor();
1064
1065 ps = data.getPipelineState();
1066 ps.samples = rhiCtx->mainPassSampleCount();
1067 ps.viewCount = data.layer.viewCount;
1068
1069 // transparent objects (or, without LayerEnableDepthTest, all objects)
1070 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
1071 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, false);
1072
1073 shaderFeatures = data.getShaderFeatures();
1074 sortedTransparentObjects = data.getSortedTransparentRenderableObjects(*camera);
1075
1076 prep(*ctx, data, this, ps, shaderFeatures, mainRpDesc, sortedTransparentObjects);
1077}
1078
1079void TransparentPass::renderPass(QSSGRenderer &renderer)
1080{
1081 auto *ctx = renderer.contextInterface();
1082 const auto &rhiCtx = ctx->rhiContext();
1083 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
1084 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1085
1086 cb->debugMarkBegin(QByteArrayLiteral("Quick3D render alpha"));
1087 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1088 Q_TRACE(QSSG_renderPass_entry, QStringLiteral("Quick3D render alpha"));
1089 render(*ctx, ps, sortedTransparentObjects);
1090 cb->debugMarkEnd();
1091 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("transparent_pass"));
1092 Q_TRACE(QSSG_renderPass_exit);
1093}
1094
1096{
1097 sortedTransparentObjects.clear();
1098 ps = {};
1099 shaderFeatures = {};
1100}
1101
1102static void skyMaterialTonemapFeatures(QSSGRenderLayer::TonemapMode mode, QSSGShaderFeatures &features, quint32 &key)
1103{
1104 switch (mode) {
1105 case QSSGRenderLayer::TonemapMode::Linear:
1106 features.set(QSSGShaderFeatures::Feature::LinearTonemapping, true);
1107 key = 1;
1108 break;
1109 case QSSGRenderLayer::TonemapMode::Aces:
1110 features.set(QSSGShaderFeatures::Feature::AcesTonemapping, true);
1111 key = 2;
1112 break;
1113 case QSSGRenderLayer::TonemapMode::HejlDawson:
1114 features.set(QSSGShaderFeatures::Feature::HejlDawsonTonemapping, true);
1115 key = 3;
1116 break;
1117 case QSSGRenderLayer::TonemapMode::Filmic:
1118 features.set(QSSGShaderFeatures::Feature::FilmicTonemapping, true);
1119 key = 4;
1120 break;
1121 case QSSGRenderLayer::TonemapMode::None:
1122 case QSSGRenderLayer::TonemapMode::Custom:
1123 key = 0; // passthrough (Custom tonemapping in the sky is not yet supported)
1124 break;
1125 }
1126}
1127
1128void SkyboxPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
1129{
1130 if (!skipPrep) {
1131 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
1132 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
1133 QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
1134 QSSG_ASSERT(data.renderedCameras.count() == data.layer.viewCount, return);
1135 layer = &data.layer;
1136 QSSG_ASSERT(layer, return);
1137
1138 rpDesc = rhiCtx->mainRenderPassDescriptor();
1139 ps = data.getPipelineState();
1140 ps.samples = rhiCtx->mainPassSampleCount();
1141 ps.viewCount = data.layer.viewCount;
1142 ps.polygonMode = QRhiGraphicsPipeline::Fill;
1143
1144 // When there are effects, then it is up to the last pass of the
1145 // last effect to perform tonemapping, neither the skybox nor the
1146 // main render pass should alter the colors then.
1147 skipTonemapping = layer->firstEffect != nullptr;
1148
1149 RenderHelpers::rhiPrepareSkyBox(*renderer.contextInterface(), this, *layer, data.renderedCameras, renderer);
1150 skipPrep = true;
1151 }
1152}
1153
1154void SkyboxPass::renderPass(QSSGRenderer &renderer)
1155{
1156 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
1157 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
1158 QSSG_ASSERT(layer, return);
1159
1160 QRhiShaderResourceBindings *srb = layer->skyBoxSrb;
1161 if (!srb)
1162 return;
1163
1164 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1165 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render skybox"));
1166
1167 // Note: We get the shader here, as the screen map pass might modify the state of
1168 // the tonemap mode.
1169
1170 QSSGRenderLayer::TonemapMode tonemapMode = skipTonemapping && (layer->tonemapMode != QSSGRenderLayer::TonemapMode::Custom) ? QSSGRenderLayer::TonemapMode::None : layer->tonemapMode;
1171 const auto &shaderCache = renderer.contextInterface()->shaderCache();
1172 auto shaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiSkyBoxShader(tonemapMode, layer->skyBoxIsRgbe8, layer->viewCount);
1173 QSSG_CHECK(shaderPipeline);
1174 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, shaderPipeline.get());
1175 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, srb, rpDesc, { QSSGRhiQuadRenderer::DepthTest | QSSGRhiQuadRenderer::RenderBehind });
1176 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("skybox_map"));
1177}
1178
1180{
1181 ps = {};
1182 layer = nullptr;
1183 skipPrep = false;
1184}
1185
1186// SKY MATERIAL BACKGROUND PASS (screen-space)
1187
1189{
1190 const auto &ctx = *renderer.contextInterface();
1191 const auto &rhiCtx = ctx.rhiContext();
1192 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
1193 QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
1194 QSSG_ASSERT(data.renderedCameras.count() == data.layer.viewCount, return);
1195 layer = &data.layer;
1196 QSSG_ASSERT(layer && layer->skyMaterial, return);
1197
1198 ready = false;
1199
1200 rpDesc = rhiCtx->mainRenderPassDescriptor();
1201 ps = data.getPipelineState();
1202 ps.samples = rhiCtx->mainPassSampleCount();
1203 ps.viewCount = data.layer.viewCount;
1204 ps.polygonMode = QRhiGraphicsPipeline::Fill;
1205 // When there are effects, the last effect pass performs tonemapping, so the sky must
1206 // output linear here (TonemapMode::None below).
1207 skipTonemapping = layer->firstEffect != nullptr;
1208
1209 QRhi *rhi = rhiCtx->rhi();
1210 QSSGRenderSkyMaterial &skyMaterial = *layer->skyMaterial;
1211 const int viewCount = data.layer.viewCount;
1212
1213 const QSSGRenderLayer::TonemapMode tonemapMode =
1214 skipTonemapping && (layer->tonemapMode != QSSGRenderLayer::TonemapMode::Custom)
1215 ? QSSGRenderLayer::TonemapMode::None
1216 : layer->tonemapMode;
1217 QSSGShaderFeatures tonemapFeatures;
1218 quint32 tonemapKey = 0;
1219 skyMaterialTonemapFeatures(tonemapMode, tonemapFeatures, tonemapKey);
1220
1221 pipeline = skyMaterial.ensureBackgroundPipeline(ctx, tonemapFeatures, tonemapKey, viewCount);
1222 if (!pipeline) // shader/textures not ready yet — skip this frame (clear color shows)
1223 return;
1224
1225 // Reconstruct the view direction inputs exactly like skybox.vert, per view:
1226 // eye = orientation * (cameraRotation * (inverseProjection * ndcPos))
1227 // viewRotation folds orientation * cameraRotation into one translation-free matrix.
1228 // orientation (the probe orientation) is shared across views.
1229 const QMatrix3x3 o = layer->lightProbeSettings.probeOrientation;
1230 const QMatrix4x4 orientation(o(0, 0), o(0, 1), o(0, 2), 0.0f,
1231 o(1, 0), o(1, 1), o(1, 2), 0.0f,
1232 o(2, 0), o(2, 1), o(2, 2), 0.0f,
1233 0.0f, 0.0f, 0.0f, 1.0f);
1234 QVarLengthArray<QMatrix4x4, 2> inverseProjections;
1235 QVarLengthArray<QMatrix4x4, 2> viewRotations;
1236 for (int v = 0; v < viewCount; ++v) {
1237 const QSSGRenderCamera &camera = *data.renderedCameras[v];
1238 inverseProjections.append(camera.projection.inverted());
1239 QMatrix4x4 cameraRotation = data.getGlobalTransform(camera);
1240 cameraRotation.setColumn(3, QVector4D(0.0f, 0.0f, 0.0f, 1.0f)); // drop translation
1241 viewRotations.append(orientation * cameraRotation);
1242 }
1243 const float adjustY = rhi->isYUpInNDC() ? 1.0f : -1.0f;
1244 const float exposure = layer->lightProbeSettings.probeExposure;
1245
1246 skyMaterial.updateBackgroundUniforms(ctx, this, inverseProjections, viewRotations, adjustY, exposure);
1247
1248 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
1249 backgroundSrb = rhiCtxD->srb(skyMaterial.backgroundBindings);
1250
1251 switch (skyMaterial.skyboxMode) {
1252 case QSSGRenderSkyMaterial::SkyboxMode::ScreenSpaceHalf:
1253 scaleDivisor = 2;
1254 break;
1255 case QSSGRenderSkyMaterial::SkyboxMode::ScreenSpaceQuarter:
1256 scaleDivisor = 4;
1257 break;
1258 case QSSGRenderSkyMaterial::SkyboxMode::ScreenSpaceFull:
1259 case QSSGRenderSkyMaterial::SkyboxMode::Cubemap:
1260 scaleDivisor = 1;
1261 break;
1262 }
1263
1264 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx.get(), nullptr);
1265
1266 // Reduced-scale: render the (tonemapped) sky into a smaller offscreen target now, in
1267 // the prepare phase, so renderPass only has to upscale-blit it into the main pass.
1268 if (scaleDivisor > 1) {
1269 const std::array<float, 4> vp = ps.viewport.viewport();
1270 const QSize fullSize(int(vp.at(2)), int(vp.at(3)));
1271 const QSize targetSize = (fullSize / qreal(scaleDivisor)).expandedTo(QSize(1, 1));
1272 const bool multiView = viewCount >= 2;
1273 const int wantArraySize = multiView ? viewCount : 0;
1274
1275 offscreenTexture = data.getRenderResult(QSSGRenderResult::Key::SkyMaterialBackgroundTexture);
1276 QSSGRhiRenderableTexture *tex = offscreenTexture;
1277 if (!tex->texture || tex->texture->pixelSize() != targetSize || tex->texture->arraySize() != wantArraySize) {
1278 tex->reset();
1279 tex->texture = multiView ? rhi->newTextureArray(QRhiTexture::RGBA16F, viewCount, targetSize, 1, QRhiTexture::RenderTarget)
1280 : rhi->newTexture(QRhiTexture::RGBA16F, targetSize, 1, QRhiTexture::RenderTarget);
1281 if (!tex->texture->create()) {
1282 qWarning("Failed to build SkyMaterial screen-space background texture");
1283 tex->reset();
1284 offscreenTexture = nullptr;
1285 return;
1286 }
1287 QRhiColorAttachment att(tex->texture);
1288 att.setMultiViewCount(viewCount);
1289 QRhiTextureRenderTargetDescription desc;
1290 desc.setColorAttachments({ att });
1291 tex->rt = rhi->newTextureRenderTarget(desc);
1292 tex->rt->setName(QByteArrayLiteral("SkyMaterial screen-space background"));
1293 tex->rpDesc = tex->rt->newCompatibleRenderPassDescriptor();
1294 tex->rt->setRenderPassDescriptor(tex->rpDesc);
1295 if (!tex->rt->create()) {
1296 qWarning("Failed to build SkyMaterial screen-space background render target");
1297 tex->reset();
1298 offscreenTexture = nullptr;
1299 return;
1300 }
1301 }
1302
1303 QSSGRhiGraphicsPipelineState offscreenPs;
1304 offscreenPs.viewport = QRhiViewport(0, 0, float(targetSize.width()), float(targetSize.height()));
1305 offscreenPs.samples = 1;
1306 offscreenPs.viewCount = viewCount;
1307 offscreenPs.polygonMode = QRhiGraphicsPipeline::Fill;
1308 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(offscreenPs, pipeline.get());
1309 renderer.rhiQuadRenderer()->recordRenderQuadPass(rhiCtx.get(), &offscreenPs, backgroundSrb,
1310 offscreenTexture->rt, QSSGRhiQuadRenderer::RenderBehind);
1311 }
1312
1313 ready = true;
1314}
1315
1316void SkyMaterialBackgroundPass::renderPass(QSSGRenderer &renderer)
1317{
1318 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
1319 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
1320 QSSG_ASSERT(layer, return);
1321
1322 if (!ready) // sky shader not ready this frame
1323 return;
1324
1325 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1326 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render sky material background"));
1327
1328 if (scaleDivisor > 1) {
1329 QSSG_ASSERT(offscreenTexture && offscreenTexture->isValid(), return);
1330 const auto &shaderCache = renderer.contextInterface()->shaderCache();
1331 auto upscaleShader = shaderCache->getBuiltInRhiShaders().getRhiSimpleQuadShader(layer->viewCount,
1332 QSSGRenderLayer::TonemapMode::None);
1333 QSSG_CHECK(upscaleShader);
1334
1335 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
1336 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
1337 QSSGRhiShaderResourceBindingList bindings;
1338 bindings.addTexture(0, QRhiShaderResourceBinding::FragmentStage, offscreenTexture->texture, sampler);
1339 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
1340 QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
1341
1342 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, upscaleShader.get());
1343 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, srb, rpDesc,
1344 { QSSGRhiQuadRenderer::UvCoords | QSSGRhiQuadRenderer::DepthTest | QSSGRhiQuadRenderer::RenderBehind });
1345 } else {
1346 QSSG_ASSERT(pipeline && backgroundSrb, return);
1347 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, pipeline.get());
1348 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, backgroundSrb, rpDesc,
1349 { QSSGRhiQuadRenderer::DepthTest | QSSGRhiQuadRenderer::RenderBehind });
1350 }
1351 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("sky_material_background"));
1352}
1353
1355{
1356 ps = {};
1357 layer = nullptr;
1358 ready = false;
1359 scaleDivisor = 1;
1360 pipeline = nullptr;
1361 backgroundSrb = nullptr;
1362 offscreenTexture = nullptr;
1363}
1364
1365void SkyboxCubeMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
1366{
1367 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
1368 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
1369 QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
1370 QSSG_ASSERT(data.renderedCameras.count() == data.layer.viewCount, return);
1371 layer = &data.layer;
1372 QSSG_ASSERT(layer, return);
1373
1374 rpDesc = rhiCtx->mainRenderPassDescriptor();
1375 ps = data.getPipelineState();
1376 ps.samples = rhiCtx->mainPassSampleCount();
1377 ps.viewCount = data.layer.viewCount;
1378 ps.polygonMode = QRhiGraphicsPipeline::Fill;
1379
1380 QSSGRenderLayer::TonemapMode tonemapMode = skipTonemapping && (layer->tonemapMode != QSSGRenderLayer::TonemapMode::Custom) ? QSSGRenderLayer::TonemapMode::None : layer->tonemapMode;
1381
1382 const auto &shaderCache = renderer.contextInterface()->shaderCache();
1383 skyBoxCubeShader = shaderCache->getBuiltInRhiShaders().getRhiSkyBoxCubeShader(tonemapMode, !data.layer.skyBoxIsSrgb, data.layer.viewCount);
1384
1385 RenderHelpers::rhiPrepareSkyBox(*renderer.contextInterface(), this, *layer, data.renderedCameras, renderer, uint(tonemapMode));
1386}
1387
1388void SkyboxCubeMapPass::renderPass(QSSGRenderer &renderer)
1389{
1390 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
1391 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
1392 QSSG_ASSERT(layer && skyBoxCubeShader, return);
1393
1394 QRhiShaderResourceBindings *srb = layer->skyBoxSrb;
1395 QSSG_ASSERT(srb, return);
1396
1397 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1398 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render cubemap skybox"));
1399
1400 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, skyBoxCubeShader.get());
1401 renderer.rhiCubeRenderer()->recordRenderCube(rhiCtx.get(), &ps, srb, rpDesc, { QSSGRhiQuadRenderer::DepthTest | QSSGRhiQuadRenderer::RenderBehind });
1402 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("skybox_cube"));
1403}
1404
1406{
1407 ps = {};
1408 layer = nullptr;
1409}
1410
1411void Item2DPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
1412{
1413 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
1414 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
1415 const auto &layer = data.layer;
1416
1417 const auto &item2Ds = data.getRenderableItem2Ds();
1418 prepdItem2DRenderers.reserve(size_t(item2Ds.size()));
1419 // NOTE: This marks the start of the 2D sub-scene rendering as it might result in
1420 // a nested 3D scene to be rendered and if we don't save the state here, we can
1421 // end up with a mismatched state in the QtQuick3D renderer.
1422 // See the end of this function for the corresponding end call (endSubLayerRender()).
1423 renderer.beginSubLayerRender(data);
1424 for (const auto *item2D: std::as_const(item2Ds)) {
1425 // Find data for item
1426 auto item2DData = data.getItem2DRenderer(*item2D);
1427 const auto &mvps = data.getItem2DMvps(*item2D);
1428 QSGRenderer *renderer2d = item2DData;
1429
1430 QSSG_ASSERT(renderer2d, continue);
1431
1432 // NOTE: We shouldn't get into this state...
1433 if (renderer2d && renderer2d->currentRhi() != rhiCtx->rhi()) {
1434 static bool contextWarningShown = false;
1435 if (!contextWarningShown) {
1436 qWarning () << "Scene with embedded 2D content can only be rendered in one window.";
1437 contextWarningShown = true;
1438 }
1439 continue;
1440 }
1441
1442 // Set the projection matrix
1443
1444 auto layerPrepResult = data.layerPrepResult;
1445
1446 QRhiRenderTarget *renderTarget = rhiCtx->renderTarget();
1447 auto *rpd = renderTarget->renderPassDescriptor();
1448 renderer2d->setDevicePixelRatio(renderTarget->devicePixelRatio());
1449 const QRect deviceRect(QPoint(0, 0), renderTarget->pixelSize());
1450 const int viewCount = data.layer.viewCount;
1451 if (layer.scissorRect.isValid()) {
1452 QRect effScissor = layer.scissorRect & layerPrepResult.getViewport().toRect();
1453 QMatrix4x4 correctionMat = correctMVPForScissor(layerPrepResult.getViewport(),
1454 effScissor,
1455 rhiCtx->rhi()->isYUpInNDC());
1456 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex) {
1457 const QMatrix4x4 projectionMatrix = correctionMat * mvps[viewIndex];
1458 renderer2d->setProjectionMatrix(projectionMatrix, viewIndex);
1459 }
1460 renderer2d->setViewportRect(effScissor);
1461 } else {
1462 for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
1463 renderer2d->setProjectionMatrix(mvps[viewIndex], viewIndex);
1464 renderer2d->setViewportRect(RenderHelpers::correctViewportCoordinates(layerPrepResult.getViewport(), deviceRect));
1465 }
1466 renderer2d->setDeviceRect(deviceRect);
1467 QSGRenderTarget sgRt(renderTarget, rpd, rhiCtx->commandBuffer());
1468 sgRt.multiViewCount = data.layer.viewCount;
1469 renderer2d->setRenderTarget(sgRt);
1470 renderer2d->prepareSceneInline();
1471 prepdItem2DRenderers.push_back(renderer2d);
1472 }
1473 renderer.endSubLayerRender(data);
1474}
1475
1476void Item2DPass::renderPass(QSSGRenderer &renderer)
1477{
1478 QSSG_ASSERT(prepdItem2DRenderers.size() > 0, return);
1479
1480 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
1481 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
1482 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1483
1484 cb->debugMarkBegin(QByteArrayLiteral("Quick3D render 2D sub-scene"));
1485 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1486 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render 2D sub-scene"));
1487 QSSGLayerRenderData *data = QSSGLayerRenderData::getCurrent(renderer);
1488 renderer.beginSubLayerRender(*data);
1489 for (QSGRenderer *renderer2d : std::as_const(prepdItem2DRenderers))
1490 renderer2d->renderSceneInline();
1491 renderer.endSubLayerRender(*data);
1492 cb->debugMarkEnd();
1493 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("2D_sub_scene"));
1494}
1495
1497{
1498 prepdItem2DRenderers.clear();
1499}
1500
1501void InfiniteGridPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
1502{
1503 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
1504 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
1505 QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
1506 QSSG_ASSERT(data.renderedCameras.count() == data.layer.viewCount, return);
1507 layer = &data.layer;
1508 QSSG_ASSERT(layer, return);
1509
1510 const auto &shaderCache = renderer.contextInterface()->shaderCache();
1511 gridShader = shaderCache->getBuiltInRhiShaders().getRhiGridShader(data.layer.viewCount);
1512
1513 ps = data.getPipelineState();
1514 ps.samples = rhiCtx->mainPassSampleCount();
1515 ps.viewCount = data.layer.viewCount;
1516 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
1517 ps.polygonMode = QRhiGraphicsPipeline::Fill;
1518
1519 RenderHelpers::rhiPrepareGrid(rhiCtx.get(), this, *layer, data.renderedCameras, renderer);
1520}
1521
1522void InfiniteGridPass::renderPass(QSSGRenderer &renderer)
1523{
1524 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
1525 QSSG_ASSERT(gridShader && rhiCtx->rhi()->isRecordingFrame(), return);
1526 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1527
1528 cb->debugMarkBegin(QByteArrayLiteral("Quick3D render grid"));
1529 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1530 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render grid"));
1531 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, gridShader.get());
1532 QRhiShaderResourceBindings *srb = layer->gridSrb;
1533 QRhiRenderPassDescriptor *rpDesc = rhiCtx->mainRenderPassDescriptor();
1534 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, srb, rpDesc, { QSSGRhiQuadRenderer::DepthTest });
1535 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("render_grid"));
1536}
1537
1539{
1540 ps = {};
1541 layer = nullptr;
1542}
1543
1544void DebugDrawPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
1545{
1546 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
1547 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
1548 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
1549 QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
1550 QSSG_ASSERT(data.renderedCameras.count() == data.layer.viewCount, return);
1551
1552 const auto &shaderCache = renderer.contextInterface()->shaderCache();
1553 debugObjectShader = shaderCache->getBuiltInRhiShaders().getRhiDebugObjectShader(data.layer.viewCount);
1554 ps = data.getPipelineState();
1555 ps.samples = rhiCtx->mainPassSampleCount();
1556 ps.viewCount = data.layer.viewCount;
1557
1558 // debug objects
1559 const auto &debugDraw = renderer.contextInterface()->debugDrawSystem();
1560 if (debugDraw && debugDraw->hasContent()) {
1561 QRhi *rhi = rhiCtx->rhi();
1562 QRhiResourceUpdateBatch *rub = rhi->nextResourceUpdateBatch();
1563 debugDraw->prepareGeometry(rhiCtx.get(), rub);
1564 QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData({ this, nullptr, nullptr, nullptr, 0 });
1565 if (!dcd.ubuf) {
1566 dcd.ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 * data.renderedCameras.count());
1567 dcd.ubuf->create();
1568 }
1569 char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
1570 QMatrix4x4 viewProjection(Qt::Uninitialized);
1571 QMatrix4x4 cameraGlobalTransform(Qt::Uninitialized);
1572 for (qsizetype viewIdx = 0; viewIdx < data.renderedCameras.count(); ++viewIdx) {
1573 cameraGlobalTransform = data.getGlobalTransform(*data.renderedCameras[viewIdx]);
1574 data.renderedCameras[viewIdx]->calculateViewProjectionMatrix(cameraGlobalTransform, viewProjection);
1575 viewProjection = rhi->clipSpaceCorrMatrix() * viewProjection;
1576 memcpy(ubufData, viewProjection.constData() + viewIdx * 64, 64);
1577 }
1578 dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
1579
1580 QSSGRhiShaderResourceBindingList bindings;
1581 bindings.addUniformBuffer(0, QRhiShaderResourceBinding::VertexStage, dcd.ubuf);
1582 dcd.srb = rhiCtxD->srb(bindings);
1583
1584 rhiCtx->commandBuffer()->resourceUpdate(rub);
1585 }
1586}
1587
1588void DebugDrawPass::renderPass(QSSGRenderer &renderer)
1589{
1590 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
1591 QSSG_ASSERT(debugObjectShader && rhiCtx->rhi()->isRecordingFrame(), return);
1592 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1593 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
1594
1595 const auto &debugDraw = renderer.contextInterface()->debugDrawSystem();
1596 if (debugDraw && debugDraw->hasContent()) {
1597 cb->debugMarkBegin(QByteArrayLiteral("Quick 3D debug objects"));
1598 Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick 3D debug objects"));
1599 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1600 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, debugObjectShader.get());
1601 QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData({ this, nullptr, nullptr, nullptr, 0 });
1602 QRhiShaderResourceBindings *srb = dcd.srb;
1603 QRhiRenderPassDescriptor *rpDesc = rhiCtx->mainRenderPassDescriptor();
1604 debugDraw->recordRenderDebugObjects(rhiCtx.get(), &ps, srb, rpDesc);
1605 cb->debugMarkEnd();
1606 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("debug_objects"));
1607 }
1608}
1609
1611{
1612 ps = {};
1613}
1614
1615void UserExtensionPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
1616{
1617 Q_UNUSED(renderer);
1618 auto &frameData = data.getFrameData();
1619 for (const auto &p : std::as_const(extensions)) {
1620 p->prepareRender(frameData);
1621 if (p->mode() == QSSGRenderExtension::RenderMode::Standalone)
1622 p->render(frameData);
1623 }
1624}
1625
1626void UserExtensionPass::renderPass(QSSGRenderer &renderer)
1627{
1628 auto *data = QSSGLayerRenderData::getCurrent(renderer);
1629 QSSG_ASSERT(data, return);
1630 auto &frameData = data->getFrameData();
1631 for (const auto &p : std::as_const(extensions)) {
1632 if (p->mode() == QSSGRenderExtension::RenderMode::Main)
1633 p->render(frameData);
1634 }
1635}
1636
1638{
1639 for (const auto &p : std::as_const(extensions))
1640 p->resetForFrame();
1641
1642 // TODO: We should track if we need to update this list.
1643 extensions.clear();
1644}
1645
1646static quint32 nextMultipleOf(quint32 value, quint32 multiple)
1647{
1648 return multiple * ((value / multiple) + 1);
1649}
1650
1651static quint32 ensureFreeNodes(quint32 value, quint32 multiple)
1652{
1653 quint32 multipleOf = nextMultipleOf(value, multiple);
1654 if (multipleOf - value < multiple)
1655 multipleOf += multiple;
1656 return multipleOf;
1657}
1658
1660{
1661 quint32 reported = reportedNodeCount;
1662 quint32 current = currentNodeCount;
1663 quint32 fullCount = dim.width() * dim.height();
1664
1665 // resize if reported is greater than current or if there are less than 50% of screen size nodes free
1666 if (reported > current)
1667 return true;
1668 quint32 freeCount = current - reported;
1669 if (freeCount < fullCount / 2)
1670 return true;
1671 // resize if we have more than 100% screen size free nodes
1672 if (freeCount > fullCount)
1673 return true;
1674 return false;
1675}
1676
1677void OITRenderPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
1678{
1679 auto *ctx = renderer.contextInterface();
1680 const auto &rhiCtx = ctx->rhiContext();
1681 auto *rhi = rhiCtx->rhi();
1682
1683 QSSG_ASSERT(!data.renderedCameras.isEmpty() && data.renderedCameraData.has_value() , return);
1684 QSSGRenderCamera *camera = data.renderedCameras[0];
1685
1686 ps = data.getPipelineState();
1687 ps.samples = rhiCtx->mainPassSampleCount();
1688 ps.viewCount = rhiCtx->mainPassViewCount();
1689
1690 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
1691 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, false);
1692
1693 shaderFeatures = data.getShaderFeatures();
1694 sortedTransparentObjects = data.getSortedTransparentRenderableObjects(*camera);
1695
1696 if (method == QSSGRenderLayer::OITMethod::WeightedBlended) {
1697 ps.colorAttachmentCount = 2;
1698
1699 rhiAccumTexture = data.getRenderResult(QSSGRenderResult::Key::AccumTexture);
1700 rhiRevealageTexture = data.getRenderResult(QSSGRenderResult::Key::RevealageTexture);
1701 if (ps.samples > 1)
1702 rhiDepthTexture = data.getRenderResult(QSSGRenderResult::Key::DepthTextureMS);
1703 else
1704 rhiDepthTexture = data.getRenderResult(QSSGRenderResult::Key::DepthTexture);
1705 if (!rhiDepthTexture->isValid())
1706 return;
1707 auto &oitrt = data.getOitRenderContext();
1708 if (!oitrt.oitRenderTarget || oitrt.oitRenderTarget->pixelSize() != data.layerPrepResult.textureDimensions()
1709 || rhiDepthTexture->texture != oitrt.oitRenderTarget->description().depthTexture()
1710 || ps.samples != oitrt.oitRenderTarget->sampleCount()) {
1711 if (oitrt.oitRenderTarget) {
1712 delete oitrt.oitRenderTarget;
1713 delete oitrt.renderPassDescriptor;
1714 delete rhiAccumTexture->texture;
1715 delete rhiRevealageTexture->texture;
1716 oitrt.oitRenderTarget = nullptr;
1717 oitrt.renderPassDescriptor = nullptr;
1718 rhiAccumTexture->texture = nullptr;
1719 rhiRevealageTexture->texture = nullptr;
1720 }
1721 const QRhiTexture::Flags textureFlags = QRhiTexture::RenderTarget;
1722 if (ps.viewCount >= 2) {
1723 rhiAccumTexture->texture = rhi->newTextureArray(QRhiTexture::RGBA16F, ps.viewCount, data.layerPrepResult.textureDimensions(), ps.samples, textureFlags);
1724 rhiRevealageTexture->texture = rhi->newTextureArray(QRhiTexture::R16F, ps.viewCount, data.layerPrepResult.textureDimensions(), ps.samples, textureFlags);
1725 } else {
1726 rhiAccumTexture->texture = rhi->newTexture(QRhiTexture::RGBA16F, data.layerPrepResult.textureDimensions(), ps.samples, textureFlags);
1727 rhiRevealageTexture->texture = rhi->newTexture(QRhiTexture::R16F, data.layerPrepResult.textureDimensions(), ps.samples, textureFlags);
1728 }
1729 rhiAccumTexture->texture->create();
1730 rhiRevealageTexture->texture->create();
1731
1732 QRhiTextureRenderTargetDescription desc;
1733 desc.setColorAttachments({{rhiAccumTexture->texture}, {rhiRevealageTexture->texture}});
1734 desc.setDepthTexture(rhiDepthTexture->texture);
1735
1736 if (oitrt.oitRenderTarget == nullptr) {
1737 oitrt.oitRenderTarget = rhi->newTextureRenderTarget(desc, QRhiTextureRenderTarget::PreserveDepthStencilContents);
1738 oitrt.renderPassDescriptor = oitrt.oitRenderTarget->newCompatibleRenderPassDescriptor();
1739 oitrt.oitRenderTarget->setRenderPassDescriptor(oitrt.renderPassDescriptor);
1740 oitrt.oitRenderTarget->create();
1741
1742 renderTarget = oitrt.oitRenderTarget;
1743 }
1744 }
1745 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
1746 const auto &shaderCache = renderer.contextInterface()->shaderCache();
1747 clearPipeline = shaderCache->getBuiltInRhiShaders().getRhiClearMRTShader();
1748
1749 QSSGRhiShaderResourceBindingList bindings;
1750 QVector4D clearData[2];
1751 clearData[0] = QVector4D(0.0, 0.0, 0.0, 0.0);
1752 clearData[1] = QVector4D(1.0, 1.0, 1.0, 1.0);
1753
1754 QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData({ this, clearPipeline.get(), nullptr, nullptr, 0 }));
1755 QRhiBuffer *&ubuf = dcd.ubuf;
1756 const int ubufSize = sizeof(clearData);
1757 if (!ubuf) {
1758 ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufSize);
1759 ubuf->create();
1760 }
1761
1762 QRhiResourceUpdateBatch *rub = rhi->nextResourceUpdateBatch();
1763 rub->updateDynamicBuffer(ubuf, 0, ubufSize, &clearData);
1764 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx.get(), rub);
1765
1766 bindings.addUniformBuffer(0, QRhiShaderResourceBinding::FragmentStage, ubuf);
1767
1768 clearSrb = rhiCtxD->srb(bindings);
1769
1770 ps.targetBlend[0].srcAlpha = QRhiGraphicsPipeline::One;
1771 ps.targetBlend[0].srcColor = QRhiGraphicsPipeline::One;
1772 ps.targetBlend[0].dstAlpha = QRhiGraphicsPipeline::One;
1773 ps.targetBlend[0].dstColor = QRhiGraphicsPipeline::One;
1774 ps.targetBlend[1].srcAlpha = QRhiGraphicsPipeline::Zero;
1775 ps.targetBlend[1].srcColor = QRhiGraphicsPipeline::Zero;
1776 ps.targetBlend[1].dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
1777 ps.targetBlend[1].dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
1778
1779 TransparentPass::prep(*ctx, data, this, ps, shaderFeatures, oitrt.renderPassDescriptor, sortedTransparentObjects, true);
1780 } else if (method == QSSGRenderLayer::OITMethod::LinkedList) {
1781 oitRenderContext = &data.getOitRenderContext();
1782 if (oitRenderContext->rub) {
1783 rhiCtx->commandBuffer()->resourceUpdate(oitRenderContext->rub);
1784 oitRenderContext->rub = nullptr;
1785 }
1786
1787 // same as transparent pass
1788 // transparent objects (or, without LayerEnableDepthTest, all objects)
1789 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
1790 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, false);
1791 ps.targetBlend[0].srcAlpha = QRhiGraphicsPipeline::One;
1792 ps.targetBlend[0].srcColor = QRhiGraphicsPipeline::SrcAlpha;
1793 ps.targetBlend[0].dstAlpha = QRhiGraphicsPipeline::OneMinusSrcAlpha;
1794 ps.targetBlend[0].dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
1795
1796 shaderFeatures = data.getShaderFeatures();
1797 sortedTransparentObjects = data.getSortedTransparentRenderableObjects(*camera);
1798
1799#ifdef QSSG_OIT_USE_BUFFERS
1800 auto &oitCtx = data.getOitRenderContext();
1801 rhiABuffer = oitCtx.aBuffer;
1802 rhiAuxBuffer = oitCtx.auxBuffer;
1803 rhiCounterBuffer = oitCtx.counterBuffer;
1804#else
1805 rhiABufferImage = data.getRenderResult(QSSGRenderResult::Key::ABufferImage);
1806 rhiAuxiliaryImage = data.getRenderResult(QSSGRenderResult::Key::AuxiliaryImage);
1807 rhiCounterImage = data.getRenderResult(QSSGRenderResult::Key::CounterImage);
1808#endif
1809 QSize dim = data.layerPrepResult.textureDimensions();
1810 dim.setWidth(dim.width() * ps.samples);
1811 dim.setHeight(dim.height() * ps.viewCount);
1812#ifdef QSSG_OIT_USE_BUFFERS
1813 if (!rhiAuxBuffer || rhiAuxBuffer->size() != (dim.width() * dim.height() * 4u) || currentNodeCount == 0 || linkedListRequiresResize(dim))
1814#else
1815 if (!rhiAuxiliaryImage->texture || rhiAuxiliaryImage->texture->pixelSize() != dim || currentNodeCount == 0 || linkedListRequiresResize(dim))
1816#endif
1817 {
1818 quint32 extraNodeCount = 0;
1819#ifdef QSSG_OIT_USE_BUFFERS
1820 if (rhiAuxBuffer && rhiAuxBuffer->size() != (dim.width() * dim.height() * 4u)) {
1821 if (rhiAuxBuffer->size() < (dim.width() * dim.height() * 4u))
1822 extraNodeCount = ensureFreeNodes((dim.width() * dim.height() * 4u) - rhiAuxBuffer->size(), 32u * 1024u);
1823 rhiAuxBuffer->destroy();
1824 rhiAuxBuffer = nullptr;
1825 }
1826#else
1827 if (rhiABufferImage->texture) {
1828 const auto s = rhiAuxiliaryImage->texture->pixelSize();
1829 if (s.width() * s.height() < dim.width() * dim.height())
1830 extraNodeCount = ensureFreeNodes(dim.width() * dim.height() - s.width() * s.height(), 32u * 1024u);
1831 rhiABufferImage->texture->destroy();
1832 rhiAuxiliaryImage->texture->destroy();
1833 }
1834#endif
1835
1836 if (reportedNodeCount) {
1837 // ensure there are at least 50% of the screen size nodes available
1838 currentNodeCount = ensureFreeNodes(reportedNodeCount + extraNodeCount, dim.width() * dim.height() / 2);
1839 } else {
1840 quint32 size = RenderHelpers::rhiCalculateABufferSize(data.layerPrepResult.textureDimensions(), 4 * ps.samples * ps.viewCount);
1841 currentNodeCount = ensureFreeNodes(size * size, 32u * 1024u);
1842 }
1843 data.layer.oitNodeCount = currentNodeCount;
1844
1845#ifdef QSSG_OIT_USE_BUFFERS
1846 if (rhiABuffer && currentNodeCount * 16 != rhiABuffer->size()) {
1847 rhiABuffer->destroy();
1848 rhiABuffer = nullptr;
1849 }
1850 if (!rhiABuffer) {
1851 rhiABuffer = rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, currentNodeCount * 16);
1852 rhiABuffer->create();
1853 }
1854 if (!rhiAuxBuffer) {
1855 rhiAuxBuffer = rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, dim.width() * dim.height() * 4);
1856 rhiAuxBuffer->create();
1857 }
1858 if (!rhiCounterBuffer) {
1859 rhiCounterBuffer = rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 4);
1860 rhiCounterBuffer->create();
1861 oitCtx.counterBuffer = rhiCounterBuffer;
1862 }
1863 oitCtx.aBuffer = rhiABuffer;
1864 oitCtx.auxBuffer = rhiAuxBuffer;
1865#else
1866 quint32 sizeWithLayers = RenderHelpers::rhiCalculateABufferSize(currentNodeCount);
1867 const QRhiTexture::Flags textureFlags = QRhiTexture::UsedWithLoadStore;
1868 rhiABufferImage->texture = rhi->newTexture(QRhiTexture::RGBA32UI, QSize(sizeWithLayers, sizeWithLayers), 1, textureFlags);
1869 rhiABufferImage->texture->create();
1870 rhiAuxiliaryImage->texture = rhi->newTexture(QRhiTexture::R32UI, dim, 1, textureFlags);
1871 rhiAuxiliaryImage->texture->create();
1872 if (!rhiCounterImage->texture) {
1873 rhiCounterImage->texture = rhi->newTexture(QRhiTexture::R32UI, QSize(1, 1), 1, textureFlags | QRhiTexture::UsedAsTransferSource);
1874 rhiCounterImage->texture->create();
1875
1876 auto &oitrt = data.getOitRenderContext();
1877 readbackImage = rhi->newTexture(QRhiTexture::R32UI, QSize(1, 1), 1, QRhiTexture::UsedAsTransferSource);
1878 readbackImage->create();
1879 oitrt.copyTexture = readbackImage;
1880 }
1881#endif
1882 }
1883 QRhiResourceUpdateBatch *rub = rhiCtx->rhi()->nextResourceUpdateBatch();
1884
1885 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
1886 const auto &shaderCache = renderer.contextInterface()->shaderCache();
1887#ifdef QSSG_OIT_USE_BUFFERS
1888 clearPipeline = shaderCache->getBuiltInRhiShaders().getRhiClearBufferShader();
1889#else
1890 clearPipeline = shaderCache->getBuiltInRhiShaders().getRhiClearImageShader();
1891#endif
1892 QSSGRhiShaderResourceBindingList bindings;
1893 quint32 clearImageData[8] = {0};
1894
1895 QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData({ this, clearPipeline.get(), nullptr, nullptr, 0 }));
1896 QRhiBuffer *&ubuf = dcd.ubuf;
1897 const int ubufSize = sizeof(clearImageData);
1898 if (!ubuf) {
1899 ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufSize);
1900 ubuf->create();
1901 }
1902
1903 clearImageData[4] = data.layerPrepResult.textureDimensions().width();
1904 clearImageData[5] = data.layerPrepResult.textureDimensions().height();
1905 clearImageData[6] = ps.samples;
1906 clearImageData[7] = ps.viewCount;
1907
1908 rub->updateDynamicBuffer(ubuf, 0, ubufSize, clearImageData);
1909 quint32 zero = 0;
1910#ifdef QSSG_OIT_USE_BUFFERS
1911 rub->uploadStaticBuffer(rhiCounterBuffer, 0, 4, &zero);
1912#else
1913 rub->uploadTexture(rhiCounterImage->texture, {{0, 0, {&zero, 4}}});
1914#endif
1915
1916 bindings.addUniformBuffer(0, QRhiShaderResourceBinding::FragmentStage, ubuf);
1917#ifdef QSSG_OIT_USE_BUFFERS
1918 bindings.addStorageBufferStore(1, QRhiShaderResourceBinding::FragmentStage, rhiAuxBuffer);
1919#else
1920 bindings.addImageStore(1, QRhiShaderResourceBinding::FragmentStage, rhiAuxiliaryImage->texture, 0);
1921#endif
1922
1923 clearSrb = rhiCtxD->srb(bindings);
1924
1925 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx.get(), rub);
1926
1927 TransparentPass::prep(*ctx, data, this, ps, shaderFeatures, rhiCtx->mainRenderPassDescriptor(), sortedTransparentObjects, true);
1928 }
1929}
1930
1931void OITRenderPass::renderPass(QSSGRenderer &renderer)
1932{
1933 auto *ctx = renderer.contextInterface();
1934 const auto &rhiCtx = ctx->rhiContext();
1935 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
1936 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
1937
1938 if (method == QSSGRenderLayer::OITMethod::WeightedBlended) {
1939 if (Q_LIKELY(renderTarget)) {
1940 cb->beginPass(renderTarget, Qt::black, {});
1941
1942 QRhiShaderResourceBindings *srb = clearSrb;
1943 QSSG_ASSERT(srb, return);
1944 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, false);
1945 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, clearPipeline.get());
1946 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, srb, renderTarget->renderPassDescriptor(), {});
1947 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
1948
1949 cb->debugMarkBegin(QByteArrayLiteral("Quick3D render order-independent alpha"));
1950 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1951 Q_TRACE(QSSG_renderPass_entry, QStringLiteral("Quick3D render order-independent alpha"));
1952 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, true);
1953 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, false);
1954 TransparentPass::render(*ctx, ps, sortedTransparentObjects);
1955 cb->debugMarkEnd();
1956 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("transparent_order_independent_pass"));
1957 Q_TRACE(QSSG_renderPass_exit);
1958
1959 cb->endPass();
1960 }
1961 } else if (method == QSSGRenderLayer::OITMethod::LinkedList) {
1962 cb->debugMarkBegin(QByteArrayLiteral("Quick3D render alpha"));
1963 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
1964 Q_TRACE(QSSG_renderPass_entry, QStringLiteral("Quick3D render alpha"));
1965
1966 QRhiShaderResourceBindings *srb = clearSrb;
1967 QSSG_ASSERT(srb, return);
1968 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
1969 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, clearPipeline.get());
1970 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, srb, rhiCtx->mainRenderPassDescriptor(), {});
1971
1972 TransparentPass::render(*ctx, ps, sortedTransparentObjects);
1973
1974 if (!oitRenderContext->pendingResult) {
1975 QRhiResourceUpdateBatch *rub = rhiCtx->rhi()->nextResourceUpdateBatch();
1976 QRhiReadbackResult *result = nullptr;
1977 if (oitRenderContext->completedResults.size()) {
1978 result = oitRenderContext->completedResults.takeLast();
1979 result->pixelSize = {};
1980 result->data = {};
1981 result->format = QRhiTexture::UnknownFormat;
1982 } else {
1983 result = new QRhiReadbackResult();
1984 }
1985 auto *oitrt = oitRenderContext;
1986 const auto completedFunc = [this, oitrt, result](){
1987 if (result) {
1988 const quint32 *d = reinterpret_cast<const quint32 *>(result->data.constData());
1989 quint32 nodeCount = *d;
1990 if (nodeCount)
1991 this->reportedNodeCount = ensureFreeNodes(nodeCount, 32u * 1024u);
1992 oitrt->completedResults.append(result);
1993 QSSG_ASSERT(oitrt->pendingResult == result, return;);
1994 oitrt->pendingResult = nullptr;
1995 }
1996 };
1997 result->completed = completedFunc;
1998#ifdef QSSG_OIT_USE_BUFFERS
1999 rub->readBackBuffer(rhiCounterBuffer, 0, 4, result);
2000#else
2001 rub->copyTexture(readbackImage, rhiCounterImage->texture);
2002 QRhiReadbackDescription rbdesc;
2003 rbdesc.setTexture(readbackImage);
2004 rub->readBackTexture(rbdesc, result);
2005#endif
2006 oitRenderContext->rub = rub;
2007 oitRenderContext->pendingResult = result;
2008 }
2009 cb->debugMarkEnd();
2010 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("transparent_order_independent_pass"));
2011 Q_TRACE(QSSG_renderPass_exit);
2012 }
2013}
2014
2016{
2017 if (method == QSSGRenderLayer::OITMethod::WeightedBlended)
2018 return Type::Standalone;
2019 return Type::Main;
2020}
2021
2022
2024{
2025 sortedTransparentObjects.clear();
2026 ps = {};
2027 shaderFeatures = {};
2028 rhiAccumTexture = nullptr;
2029 rhiRevealageTexture = nullptr;
2030 rhiDepthTexture = nullptr;
2031 rhiCounterImage = nullptr;
2032 oitRenderContext = nullptr;
2033}
2034
2035void OITCompositePass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
2036{
2037 using namespace RenderHelpers;
2038
2039 QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
2040
2041 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
2042 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
2043 const auto &shaderCache = renderer.contextInterface()->shaderCache();
2044
2045 ps = data.getPipelineState();
2046 ps.samples = rhiCtx->mainPassSampleCount();
2047 ps.viewCount = rhiCtx->mainPassViewCount();
2048
2049 if (method == QSSGRenderLayer::OITMethod::WeightedBlended) {
2050 rhiAccumTexture = data.getRenderResult(QSSGRenderResult::Key::AccumTexture);
2051 rhiRevealageTexture = data.getRenderResult(QSSGRenderResult::Key::RevealageTexture);
2052 compositeShaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiOitCompositeShader(method, ps.samples > 1 ? true : false);
2053 } else if (method == QSSGRenderLayer::OITMethod::LinkedList) {
2054#ifdef QSSG_OIT_USE_BUFFERS
2055 rhiABuffer = data.getOitRenderContext().aBuffer;
2056 rhiAuxBuffer = data.getOitRenderContext().auxBuffer;
2057 compositeShaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiOitCompositeShader(method, ps.samples > 1 ? true : false, true);
2058#else
2059 rhiABufferImage = data.getRenderResult(QSSGRenderResult::Key::ABufferImage);
2060 rhiAuxiliaryImage = data.getRenderResult(QSSGRenderResult::Key::AuxiliaryImage);
2061 compositeShaderPipeline = shaderCache->getBuiltInRhiShaders().getRhiOitCompositeShader(method, ps.samples > 1 ? true : false);
2062#endif
2063
2064 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
2065 QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData({ this, nullptr, nullptr, nullptr, 0 }));
2066 QRhiBuffer *&ubuf = dcd.ubuf;
2067 const int ubufSize = 6 * sizeof(quint32);
2068 if (!ubuf) {
2069 ubuf = rhiCtx->rhi()->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, ubufSize);
2070 ubuf->create();
2071 }
2072#ifdef QSSG_OIT_USE_BUFFERS
2073 const quint32 values[6] = { 0,
2074 quint32(data.layer.oitNodeCount),
2075 quint32(ps.samples),
2076 0,
2077 quint32(data.layerPrepResult.textureDimensions().width()),
2078 quint32(data.layerPrepResult.textureDimensions().height())
2079 };
2080#else
2081 const quint32 sizeWithLayers = rhiABufferImage->texture->pixelSize().width();
2082 const quint32 values[6] = { sizeWithLayers,
2083 sizeWithLayers * sizeWithLayers,
2084 quint32(ps.samples),
2085 0,
2086 quint32(data.layerPrepResult.textureDimensions().width()),
2087 quint32(data.layerPrepResult.textureDimensions().height())
2088 };
2089#endif
2090 QRhiResourceUpdateBatch *rub = rhiCtx->rhi()->nextResourceUpdateBatch();
2091 rub->updateDynamicBuffer(ubuf, 0, ubufSize, values);
2092 renderer.rhiQuadRenderer()->prepareQuad(rhiCtx.get(), rub);
2093 }
2094}
2095
2096void OITCompositePass::renderPass(QSSGRenderer &renderer)
2097{
2098 using namespace RenderHelpers;
2099
2100 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
2101 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
2102 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2103
2104 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
2105
2106 if (!rhiAccumTexture->texture || !rhiRevealageTexture->texture)
2107 return;
2108
2109 if (method == QSSGRenderLayer::OITMethod::WeightedBlended) {
2110 QSSGRhiShaderResourceBindingList bindings;
2111
2112 QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest,
2113 QRhiSampler::Nearest,
2114 QRhiSampler::None,
2115 QRhiSampler::ClampToEdge,
2116 QRhiSampler::ClampToEdge,
2117 QRhiSampler::ClampToEdge });
2118 bindings.addTexture(1, QRhiShaderResourceBinding::FragmentStage, rhiAccumTexture->texture, sampler);
2119 bindings.addTexture(2, QRhiShaderResourceBinding::FragmentStage, rhiRevealageTexture->texture, sampler);
2120
2121 compositeSrb = rhiCtxD->srb(bindings);
2122
2123 QRhiShaderResourceBindings *srb = compositeSrb;
2124 QSSG_ASSERT(srb, return);
2125
2126 cb->debugMarkBegin(QByteArrayLiteral("Quick3D revealage"));
2127 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, compositeShaderPipeline.get());
2128 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
2129 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, srb, rhiCtx->mainRenderPassDescriptor(),
2130 { QSSGRhiQuadRenderer::UvCoords | QSSGRhiQuadRenderer::DepthTest | QSSGRhiQuadRenderer::PremulBlend});
2131 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("revealage"));
2132 cb->debugMarkEnd();
2133 } else if (method == QSSGRenderLayer::OITMethod::LinkedList) {
2134 QSSGRhiShaderResourceBindingList bindings;
2135
2136 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
2137 QSSGRhiDrawCallData &dcd(rhiCtxD->drawCallData({ this, nullptr, nullptr, nullptr, 0 }));
2138 QRhiBuffer *&ubuf = dcd.ubuf;
2139
2140 bindings.addUniformBuffer(0, QRhiShaderResourceBinding::FragmentStage, ubuf);
2141#ifdef QSSG_OIT_USE_BUFFERS
2142 bindings.addStorageBufferLoad(1, QRhiShaderResourceBinding::FragmentStage, rhiABuffer);
2143 bindings.addStorageBufferLoad(2, QRhiShaderResourceBinding::FragmentStage, rhiAuxBuffer);
2144#else
2145 bindings.addImageLoad(1, QRhiShaderResourceBinding::FragmentStage, rhiABufferImage->texture, 0);
2146 bindings.addImageLoad(2, QRhiShaderResourceBinding::FragmentStage, rhiAuxiliaryImage->texture, 0);
2147#endif
2148
2149 compositeSrb = rhiCtxD->srb(bindings);
2150
2151 QRhiShaderResourceBindings *srb = compositeSrb;
2152 QSSG_ASSERT(srb, return);
2153
2154 cb->debugMarkBegin(QByteArrayLiteral("Quick3D oit-composite"));
2155 QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, compositeShaderPipeline.get());
2156 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
2157 renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx.get(), &ps, srb, rhiCtx->mainRenderPassDescriptor(),
2158 { QSSGRhiQuadRenderer::UvCoords | QSSGRhiQuadRenderer::DepthTest | QSSGRhiQuadRenderer::PremulBlend});
2159 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("oit-composite"));
2160 }
2161}
2162
2164{
2165 ps = {};
2166 shaderFeatures = {};
2167 rhiAccumTexture = nullptr;
2168 rhiRevealageTexture = nullptr;
2169}
2170
2171Q_STATIC_LOGGING_CATEGORY(lcUserRenderPass, "qt.quick3d.rhi.userrenderpass")
2172
2173void UserRenderPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data)
2174{
2175 QSSG_ASSERT(!data.renderedCameras.isEmpty(), return);
2176 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
2177 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
2178
2179 for (auto *passNode : std::as_const(userPasses)) {
2180 QSSG_ASSERT(passNode != nullptr, continue);
2181 prepareTopLevelPass(renderer, data, passNode);
2182 }
2183}
2184
2185void UserRenderPass::renderPass(QSSGRenderer &renderer)
2186{
2187
2188 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
2189 QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return);
2190 QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
2191
2192 for (UserPassData &passData : userPassData) {
2193
2194 const auto &ps = passData.ps;
2195 const auto &renderables = passData.renderables;
2196 const auto &clearColor = passData.clearColor;
2197 const auto &depthStencilClearValue = passData.depthStencilClearValue;
2198 const auto &renderableTexture = passData.renderableTexture;
2199
2200 cb->debugMarkBegin(QByteArrayLiteral("Quick3D UserRenderPass"));
2201
2202 if (Q_LIKELY(renderableTexture && renderableTexture->isValid())) {
2203 cb->beginPass(renderableTexture->getRenderTarget().get(), clearColor, depthStencilClearValue, nullptr, rhiCtx->commonPassFlags());
2204 if (passData.skyboxCubeMapPass) {
2205 passData.skyboxCubeMapPass->renderPass(renderer);
2206 } else if (passData.skyboxPass) {
2207 passData.skyboxPass->renderPass(renderer);
2208 } else if (passData.item2DPass) {
2209 passData.item2DPass->renderPass(renderer);
2210 } else {
2211 // Regular User Passes
2212 bool needsSetViewport = true;
2213 if (passData.index >= 0) {
2214 for (const auto &handle : std::as_const(renderables))
2215 RenderHelpers::rhiRenderRenderable(rhiCtx.get(), ps, *handle.obj, &needsSetViewport, QSSGRenderTextureCubeFaceNone, qsizetype(passData.index));
2216 }
2217 }
2218 QRhiResourceUpdateBatch *rub = nullptr;
2219
2220 // Sub Passes
2221 for (auto &subPassData : passData.subPassData) {
2222 const auto &subPs = subPassData.ps;
2223 const auto &subRenderables = subPassData.renderables;
2224
2225 if (subPassData.skyboxCubeMapPass) {
2226 subPassData.skyboxCubeMapPass->renderPass(renderer);
2227 } else if (subPassData.skyboxPass) {
2228 subPassData.skyboxPass->renderPass(renderer);
2229 } else if (subPassData.item2DPass) {
2230 subPassData.item2DPass->renderPass(renderer);
2231 } else {
2232 bool needsSetViewport = true;
2233 if (subPassData.index >= 0) {
2234 for (const auto &handle : std::as_const(subRenderables))
2235 RenderHelpers::rhiRenderRenderable(rhiCtx.get(), subPs, *handle.obj, &needsSetViewport, QSSGRenderTextureCubeFaceNone, qsizetype(subPassData.index));
2236 }
2237 }
2238 }
2239
2240 cb->endPass(rub);
2241 }
2242
2243 cb->debugMarkEnd();
2244 }
2245}
2246
2248{
2249 qCDebug(lcUserRenderPass) << "resetForFrame in UserRenderPass";
2250 userPasses.clear();
2251
2252 userPassData.clear();
2253}
2254
2255void UserRenderPass::preparePassImpl(QSSGRenderer &renderer,
2256 QSSGLayerRenderData &data,
2257 QSSGRenderUserPass *passNode,
2258 std::vector<UserPassData> &outData,
2259 QSSGRhiRenderableTextureV2Ptr renderableTexture)
2260{
2261 // NOTE: Only top-level passes should create their own renderable texture by passing in a null renderable texture.
2262 // Ideally we this should be more explict...
2263 const bool isTopLevelPass = (renderableTexture == nullptr);
2264
2265 QSSGRenderCamera *camera = data.renderedCameras[0];
2266 const auto &rhiCtx = renderer.contextInterface()->rhiContext();
2267
2268 const QSize targetSize = data.layerPrepResult.textureDimensions();
2269
2270 static const auto needsRebuild = [](QRhiTexture *texture, const QSize &size, QRhiTexture::Format format) {
2271 return !texture || texture->pixelSize() != size || texture->format() != format;
2272 };
2273
2274 qCDebug(lcUserRenderPass, "renderPrep in UserRenderPass");
2275
2276 const QSSGResourceId currentPassId = QSSGRenderGraphObjectUtils::getResourceId(*passNode);
2277 if (visitedPasses.find(currentPassId) != visitedPasses.end()) {
2278 qWarning("UserRenderPass: Circular dependency detected in SubRenderPass chain. Ignoring pass.");
2279 return;
2280 }
2281
2282 // Check max depth
2283 if (visitedPasses.size() >= MAX_SUBPASS_DEPTH) {
2284 qWarning("UserRenderPass: Maximum SubRenderPass nesting depth (%zu) exceeded. Ignoring pass.", MAX_SUBPASS_DEPTH);
2285 return;
2286 }
2287
2288 visitedPasses.insert(currentPassId);
2289
2290 UserPassData currentPassData;
2291 currentPassData.clearColor = passNode->clearColor;
2292 currentPassData.depthStencilClearValue = passNode->depthStencilClearValue;
2293 if (isTopLevelPass)
2294 renderableTexture = currentPassData.renderableTexture = data.requestUserRenderPassManager()->getOrCreateRenderableTexture(*passNode);
2295 else
2296 currentPassData.renderableTexture = renderableTexture;
2297
2298 QSSGRenderableObjectList &renderables = currentPassData.renderables;
2299 QSSGRhiGraphicsPipelineState &ps = currentPassData.ps;
2300 const auto &renderTarget = currentPassData.renderableTexture;
2301
2302 // Initial pipeline state from layer data
2303 ps = data.getPipelineState();
2304
2305 bool renderablesFiltered = false;
2306
2307 bool needsDepthStencilRenderBuffer = false;
2308 QSSGAllocateTexturePtr depthTextureAllocCommand;
2309
2310 QVarLengthArray<QSSGColorAttachment *, 16> colorAttachments;
2311 QVarLengthArray<QSSGResourceId, 4> subPassIds;
2312
2313 // Process commands in passNode
2314 for (const QSSGCommand *theCommand : std::as_const(passNode->commands)) {
2315 QSSG_ASSERT(theCommand != nullptr, continue);
2316
2317 qCDebug(lcUserRenderPass) << "Exec. command: >" << theCommand->typeAsString() << "--" << theCommand->debugString();
2318
2319 switch (theCommand->m_type) {
2320 case CommandType::ColorAttachment:
2321 {
2322 const QSSGColorAttachment *colorAttachCmd = static_cast<const QSSGColorAttachment *>(theCommand);
2323 colorAttachments.push_back(const_cast<QSSGColorAttachment *>(colorAttachCmd));
2324 }
2325 break;
2326 case CommandType::DepthTextureAttachment:
2327 {
2328 QSSG_ASSERT(depthTextureAllocCommand == nullptr, break);
2329 const QSSGDepthTextureAttachment *depthAttachCmd = static_cast<const QSSGDepthTextureAttachment *>(theCommand);
2330 needsDepthStencilRenderBuffer = false;
2331 depthTextureAllocCommand = depthAttachCmd->m_textureCmd;
2332 }
2333 break;
2334 case CommandType::AddShaderDefine:
2335 {
2336 const auto *defineCmd = static_cast<const QSSGAddShaderDefine *>(theCommand);
2337 const auto &defineName = defineCmd->m_name;
2338 if (defineName.size() > 0) {
2339 QByteArray value = QByteArray::number(defineCmd->m_value);
2340 currentPassData.shaderDefines.push_back({ defineName, value });
2341 }
2342 }
2343 break;
2344 case CommandType::RenderablesFilter:
2345 {
2346 auto filterCommand = static_cast<const QSSGRenderablesFilterCommand *>(theCommand);
2347
2348 // Use the filter to select which renderables to include
2349 // renderableTypes can be: None (0x0), Opaque (0x1), Transparent (0x2), or both
2350 enum RenderableType : quint8 {
2351 None = 0x0,
2352 Opaque = 0x1,
2353 Transparent = 0x2,
2354 };
2355
2356 if (filterCommand->renderableTypes & RenderableType::Opaque) // Opaque
2357 renderables = data.getSortedOpaqueRenderableObjects(*camera, 0, filterCommand->layerMask);
2358 if (filterCommand->renderableTypes & RenderableType::Transparent) // Transparent
2359 renderables += data.getSortedTransparentRenderableObjects(*camera, 0, filterCommand->layerMask);
2360
2361 // NOTE: If renderableTypes is None (0x0), no renderables are added
2362 // NOTE: If no filter is run, all opaque objects are rendered.
2363 renderablesFiltered = true;
2364 }
2365 break;
2366
2367 case CommandType::PipelineStateOverride:
2368 {
2369 auto pipelineCommand = static_cast<const QSSGPipelineStateOverrideCommand *>(theCommand);
2370 if (pipelineCommand->m_depthTestEnabled)
2371 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, *pipelineCommand->m_depthTestEnabled);
2372 if (pipelineCommand->m_depthWriteEnabled)
2373 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, *pipelineCommand->m_depthWriteEnabled);
2374 if (pipelineCommand->m_blendEnabled)
2375 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, *pipelineCommand->m_blendEnabled);
2376 if (pipelineCommand->m_usesStencilReference)
2377 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::UsesStencilRef, *pipelineCommand->m_usesStencilReference);
2378 if (pipelineCommand->m_usesScissor)
2379 ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::UsesScissor, *pipelineCommand->m_usesScissor);
2380 if (pipelineCommand->m_depthFunction)
2381 ps.depthFunc = *pipelineCommand->m_depthFunction;
2382 if (pipelineCommand->m_cullMode) {
2383 ps.cullMode = *pipelineCommand->m_cullMode;
2384 ps.userSetCullMode = true;
2385 }
2386 if (pipelineCommand->m_polygonMode)
2387 ps.polygonMode = *pipelineCommand->m_polygonMode;
2388 if (pipelineCommand->m_stencilOpFrontState)
2389 ps.stencilOpFrontState = *pipelineCommand->m_stencilOpFrontState;
2390 if (pipelineCommand->m_stencilWriteMask)
2391 ps.stencilWriteMask = *pipelineCommand->m_stencilWriteMask;
2392 if (pipelineCommand->m_stencilReference)
2393 ps.stencilRef = *pipelineCommand->m_stencilReference;
2394 if (pipelineCommand->m_viewport)
2395 ps.viewport = *pipelineCommand->m_viewport;
2396 if (pipelineCommand->m_scissor)
2397 ps.scissor = *pipelineCommand->m_scissor;
2398 if (pipelineCommand->m_targetBlend0)
2399 ps.targetBlend[0] = *pipelineCommand->m_targetBlend0;
2400 if (pipelineCommand->m_targetBlend1)
2401 ps.targetBlend[1] = *pipelineCommand->m_targetBlend1;
2402 if (pipelineCommand->m_targetBlend2)
2403 ps.targetBlend[2] = *pipelineCommand->m_targetBlend2;
2404 if (pipelineCommand->m_targetBlend3)
2405 ps.targetBlend[3] = *pipelineCommand->m_targetBlend3;
2406 if (pipelineCommand->m_targetBlend4)
2407 ps.targetBlend[4] = *pipelineCommand->m_targetBlend4;
2408 if (pipelineCommand->m_targetBlend5)
2409 ps.targetBlend[5] = *pipelineCommand->m_targetBlend5;
2410 if (pipelineCommand->m_targetBlend6)
2411 ps.targetBlend[6] = *pipelineCommand->m_targetBlend6;
2412 if (pipelineCommand->m_targetBlend7)
2413 ps.targetBlend[7] = *pipelineCommand->m_targetBlend7;
2414 break;
2415 }
2416 case CommandType::DepthStencilAttachment:
2417 //auto depthStencilCommand = static_cast<const QSSGDepthStencilAttachment *>(theCommand);
2418 QSSG_ASSERT(depthTextureAllocCommand == nullptr, break);
2419 needsDepthStencilRenderBuffer = true;
2420 break;
2421 case CommandType::SubRenderPass:
2422 {
2423 auto subPassCommand = static_cast<const QSSGSubRenderPass *>(theCommand);
2424 if (subPassCommand && subPassCommand->m_userPassId != QSSGResourceId::Invalid)
2425 subPassIds.append(subPassCommand->m_userPassId);
2426 break;
2427 }
2428 default:
2429 qWarning() << "Effect command" << theCommand->typeAsString() << "not implemented";
2430 break;
2431 }
2432 }
2433
2434 if (colorAttachments.size() > 4) {
2435 colorAttachments.resize(4);
2436 qWarning() << "UserRenderPass supports up to 4 color attachments only.";
2437 }
2438
2439 // m_passNode contains state for this UserRenderPass
2440
2441 if (isTopLevelPass) {
2442 // 1) Setup the render target
2443 bool needsBuild = !renderTarget->isValid();
2444
2445 const qsizetype oldAttachmentCount = renderTarget->colorAttachmentCount();
2446 // A pass without ColorAttachment commands gets one implicit default
2447 // attachment, so compare against the effective count.
2448 const qsizetype wantedAttachmentCount = qMax<qsizetype>(colorAttachments.size(), 1);
2449 // If the number of attachments has changed, we need to rebuild.
2450 if (oldAttachmentCount != wantedAttachmentCount)
2451 needsBuild = true;
2452
2453 // Even if the render target is valid we need to check if the textures are still compatible.
2454 if (!needsBuild) {
2455 // Render target flags (e.g. PreserveColorContents) change the Metal load/store actions
2456 // baked into the QRhiRenderPassDescriptor, so a flag change requires a full rebuild.
2457 if (renderTarget->getRenderTarget() && renderTarget->getRenderTarget()->flags() != passNode->renderTargetFlags)
2458 needsBuild = true;
2459 }
2460
2461 if (!needsBuild) {
2462 // Color attachments (oldAttachmentCount == wantedAttachmentCount here;
2463 // an attachment without a command is the implicit default).
2464 for (int i = 0; i != oldAttachmentCount; ++i) {
2465 QRhiTexture::Format expectedFormat = QRhiTexture::RGBA8;
2466 if (i < colorAttachments.size()) {
2467 const auto &colorAttachment = colorAttachments.at(i);
2468 QSSG_ASSERT(colorAttachment != nullptr, continue);
2469 expectedFormat = QSSGBufferManager::toRhiFormat(colorAttachment->format());
2470 }
2471 const auto &texture = renderTarget->getColorTexture(i);
2472 needsBuild = needsBuild || needsRebuild(&(*texture->texture()), targetSize, expectedFormat);
2473 }
2474
2475 // depthStencilBuffer
2476 if (needsDepthStencilRenderBuffer != (renderTarget->getDepthStencil() != nullptr))
2477 needsBuild = true;
2478
2479 // depthTexture
2480 if (depthTextureAllocCommand) {
2481 const auto format = QSSGBufferManager::toRhiFormat(depthTextureAllocCommand->format());
2482 const auto &depthTextureWrapper = renderTarget->getDepthTexture();
2483 needsBuild = depthTextureWrapper == nullptr;
2484 needsBuild = needsBuild || needsRebuild(&(*depthTextureWrapper->texture()), targetSize, format);
2485 } else {
2486 if (renderTarget->getDepthTexture() != nullptr)
2487 needsBuild = true;
2488 }
2489
2490 // Flags are baked into the render pass descriptor.
2491 if (!needsBuild) {
2492 const auto &rt = renderTarget->getRenderTarget();
2493 if (rt && rt->flags() != passNode->renderTargetFlags)
2494 needsBuild = true;
2495 }
2496 }
2497
2498 if (needsBuild) {
2499 renderTarget->reset();
2500
2501 QRhiTextureRenderTargetDescription rtDesc;
2502
2503 // If no attachments are specified, create one (the implicit default).
2504 const qsizetype colorAttachmentCount = wantedAttachmentCount;
2505
2506 bool createSucceeded = true;
2507 bool colorAllocatorsNeedsUpdate = false;
2508
2509 {
2510 // Used to set the color attachments in rtDesc below (don't let the raw ptrs leave this scope).
2511 QVarLengthArray<QRhiTexture *, 4> textures;
2512 for (qsizetype i = 0; i < colorAttachmentCount && createSucceeded ; ++i) {
2513 if (i >= colorAttachments.size()) {
2514 // No ColorAttachment command; create the implicit default attachment.
2515 auto *tex = rhiCtx->rhi()->newTexture(QRhiTexture::RGBA8, targetSize, ps.samples, QRhiTexture::RenderTarget);
2516 tex->setName(QByteArrayLiteral("Default color attachment"));
2517 createSucceeded = createSucceeded && tex->create();
2518 textures.push_back(tex);
2519 continue;
2520 }
2521 const auto &colorAttCmd = colorAttachments.at(i);
2522 const auto &name = colorAttCmd->m_name;
2523 const auto format = QSSGBufferManager::toRhiFormat(colorAttCmd->format());
2524 const auto &allocateTexCmd = colorAttCmd->m_textureCmd;
2525 if (allocateTexCmd->texture() && !needsRebuild(&(*allocateTexCmd->texture()->texture()), targetSize, format)) {
2526 // NOTE: The QRhiTexture is tracked by the QSSGUserRenderPassManager, even if we create a new
2527 // shared pointer wrapper here, it will ask the manager before destroying it.
2528 textures.push_back(allocateTexCmd->texture()->texture().get());
2529 } else {
2530 auto *tex = rhiCtx->rhi()->newTexture(format, targetSize, ps.samples, QRhiTexture::RenderTarget);
2531 tex->setName(name);
2532 createSucceeded = createSucceeded && tex->create();
2533 textures.push_back(tex);
2534 // We created a new texture, so we need to update the allocator.
2535 // NOTE: Since the render target type will take ownership of the texture, we need to
2536 // request the textures once the render target description is set!
2537 colorAllocatorsNeedsUpdate = true;
2538 }
2539 }
2540
2541 rtDesc.setColorAttachments(textures.cbegin(), textures.cend());
2542 }
2543
2544 if (needsDepthStencilRenderBuffer) {
2545 auto renderBuffer = rhiCtx->rhi()->newRenderBuffer(QRhiRenderBuffer::DepthStencil, targetSize, ps.samples);
2546 if (renderBuffer->create())
2547 rtDesc.setDepthStencilBuffer(renderBuffer);
2548 } else if (depthTextureAllocCommand) {
2549 const auto format = QSSGBufferManager::toRhiFormat(depthTextureAllocCommand->format());
2550 if (depthTextureAllocCommand->texture() && !needsRebuild(&(*depthTextureAllocCommand->texture()->texture()), targetSize, format)) {
2551 rtDesc.setDepthTexture(depthTextureAllocCommand->texture()->texture().get());
2552 } else {
2553 // Create new depth texture
2554 QRhiTexture *depthTex = rhiCtx->rhi()->newTexture(format, targetSize, ps.samples, QRhiTexture::RenderTarget);
2555 if (depthTex->create())
2556 rtDesc.setDepthTexture(depthTex);
2557 }
2558 }
2559
2560 if (createSucceeded) {
2561 // Set description takes ownership of rtDesc and the textures inside it (Color + Depth).
2562 renderTarget->setDescription(rhiCtx->rhi(), std::move(rtDesc), passNode->renderTargetFlags);
2563
2564 // Now we can update the allocators for the color attachments that were created here.
2565 // The implicit default attachment has no command, so only command-backed
2566 // attachments are updated.
2567 if (colorAllocatorsNeedsUpdate) {
2568 for (qsizetype i = 0; i < colorAttachments.size(); ++i) {
2569 const auto &colorAttCmd = colorAttachments.at(i);
2570 const auto &allocateTexCmd = colorAttCmd->m_textureCmd;
2571 allocateTexCmd->setTexture(renderTarget->getColorTexture(i));
2572 }
2573 }
2574
2575 if (depthTextureAllocCommand)
2576 depthTextureAllocCommand->setTexture(renderTarget->getDepthTexture());
2577
2578 } else {
2579 renderTarget->resetRenderTarget();
2580 qWarning() << "Failed to create textures for UserRenderPass";
2581 }
2582 }
2583
2584 }
2585
2586 Q_ASSERT(renderTarget->isValid());
2587
2588 ps.colorAttachmentCount = int(renderTarget->colorAttachmentCount());
2589
2590 // Subpasses
2591 for (const auto &subPassId : std::as_const(subPassIds)) {
2592 QSSGRenderUserPass *userPassNode = QSSGRenderGraphObjectUtils::getResource<QSSGRenderUserPass>(subPassId);
2593 QSSG_ASSERT(userPassNode && userPassNode->type == QSSGRenderGraphObject::Type::RenderPass, continue);
2594 prepareSubPass(renderer, data, userPassNode, currentPassData.subPassData, currentPassData.renderableTexture);
2595 }
2596
2597 if (passNode->passMode == QSSGRenderUserPass::PassModes::UserPass) {
2598 // If no filter is specified, render all opaque objects
2599 if (!renderablesFiltered && renderables.isEmpty())
2600 renderables = data.getSortedOpaqueRenderableObjects(*camera);
2601
2602 if (passNode->materialMode == QSSGRenderUserPass::MaterialModes::AugmentMaterial) {
2603
2604 QSSGUserShaderAugmentation shaderAugmentation = passNode->shaderAugmentation;
2605
2606 QSSG_ASSERT(shaderAugmentation.outputs.size() == 0, shaderAugmentation.outputs.clear());
2607 shaderAugmentation.defines = std::move(currentPassData.shaderDefines);
2608
2609 for (int i = 0, end = colorAttachments.size(); i < end; ++i) {
2610 const auto &colorAttCmd = colorAttachments.at(i);
2611 const auto &name = colorAttCmd->m_name;
2612 if (name.size() > 0)
2613 shaderAugmentation.outputs.push_back(name);
2614 else
2615 shaderAugmentation.outputs.push_back(getDefaultOutputName(size_t(i)));
2616 }
2617
2618 QSSGShaderFeatures shaderFeatures = data.getShaderFeatures();
2619 shaderFeatures.disableTonemapping();
2620
2621 currentPassData.index = RenderHelpers::rhiPrepareAugmentedUserPass(&(*rhiCtx), this, ps, renderTarget->getRenderPassDescriptor().get(), shaderAugmentation, data, renderables, shaderFeatures);
2622 } else if (passNode->materialMode == QSSGRenderUserPass::MaterialModes::OverrideMaterial) {
2623 // Every renderable will use the override material
2624 QSSGShaderFeatures shaderFeatures = data.getShaderFeatures();
2625 shaderFeatures.disableTonemapping();
2626
2627 currentPassData.index = RenderHelpers::rhiPrepareOverrideMaterialUserPass(&(*rhiCtx), this, ps, renderTarget->getRenderPassDescriptor().get(), passNode->overrideMaterial, data, renderables, shaderFeatures);
2628
2629 } else {
2630 // Use original material of the renderables
2631 QSSGShaderFeatures shaderFeatures = data.getShaderFeatures();
2632 shaderFeatures.disableTonemapping();
2633 currentPassData.index = RenderHelpers::rhiPrepareOriginalMaterialUserPass(&(*rhiCtx), this, ps, renderTarget->getRenderPassDescriptor().get(), data, renderables, shaderFeatures);
2634 }
2635 outData.push_back(currentPassData);
2636 } else {
2637 // Wrapped Built-in Passes
2638 if (passNode->passMode == QSSGRenderUserPass::PassModes::SkyboxPass) {
2639 if (rhiCtx->rhi()->isFeatureSupported(QRhi::TexelFetch)) {
2640 if (data.layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap && data.layer.skyBoxCubeMap) {
2641 if (!currentPassData.skyboxCubeMapPass)
2642 currentPassData.skyboxCubeMapPass = SkyboxCubeMapPass();
2643
2644 currentPassData.skyboxCubeMapPass->skipTonemapping = true;
2645 currentPassData.skyboxCubeMapPass->renderPrep(renderer, data);
2646 currentPassData.skyboxCubeMapPass->ps.samples = ps.samples;
2647 currentPassData.skyboxCubeMapPass->rpDesc = renderTarget->getRenderPassDescriptor().get();
2648
2649 currentPassData.skyboxPass = std::nullopt;
2650 } else if ((data.layer.background == QSSGRenderLayer::Background::SkyBox && data.layer.lightProbe)
2651 || (data.layer.background == QSSGRenderLayer::Background::SkyMaterial && data.layer.skyMaterial)) {
2652 if (!currentPassData.skyboxPass)
2653 currentPassData.skyboxPass = SkyboxPass();
2654
2655 currentPassData.skyboxPass->skipTonemapping = true;
2656 currentPassData.skyboxPass->renderPrep(renderer, data);
2657 currentPassData.skyboxPass->ps.samples = ps.samples;
2658 currentPassData.skyboxPass->rpDesc = renderTarget->getRenderPassDescriptor().get();
2659
2660 currentPassData.skyboxCubeMapPass = std::nullopt;
2661 }
2662 outData.push_back(currentPassData);
2663 }
2664 } else if (passNode->passMode == QSSGRenderUserPass::PassModes::Item2DPass) {
2665 if (!currentPassData.item2DPass)
2666 currentPassData.item2DPass = Item2DPass();
2667 const bool hasItem2Ds = (data.item2DsView.size() > 0);
2668 if (hasItem2Ds) {
2669 //backup render target
2670 QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(renderer.contextInterface()->rhiContext().get());
2671 QRhiRenderTarget *prevRenderTarget = rhiCtx->renderTarget();
2672 rhiCtxD->setRenderTarget(renderTarget->getRenderTarget().get());
2673 currentPassData.item2DPass->renderPrep(renderer, data);
2674 //restore render target
2675 rhiCtxD->setRenderTarget(prevRenderTarget);
2676 outData.push_back(currentPassData);
2677 }
2678 }
2679 }
2680}
2681
2682QT_END_NAMESPACE
void resetForFrame() final
void resetForFrame() final
void resetForFrame() final
static constexpr int MaxBuckets
QSSGRenderCamera * camera
void resetForFrame() final
void resetForFrame() final
QRhiTexture * readbackImage
bool linkedListRequiresResize(QSize dim)
Type passType() const final
QSSGOITRenderContext * oitRenderContext
void resetForFrame() final
virtual ~QSSGRenderPass()
friend class QSSGRenderContextInterface
void resetForFrame() final
void resetForFrame() final
const QSSGRenderCamera * camera
void resetForFrame() final
QSSGRenderCamera * camera
void resetForFrame() final
void resetForFrame() final
void resetForFrame() final
void resetForFrame() final
void resetForFrame() final
void resetForFrame() final
void resetForFrame() final
void resetForFrame() final
Combined button and popup list for selecting options.
void rhiPrepareGrid(QSSGRhiContext *rhiCtx, QSSGPassKey passKey, QSSGRenderLayer &layer, QSSGRenderCameraList &cameras, QSSGRenderer &renderer)
void rhiPrepareSkyBox(const QSSGRenderContextInterface &context, QSSGPassKey passKey, QSSGRenderLayer &layer, QSSGRenderCameraList &cameras, QSSGRenderer &renderer, uint tonemapMode=0)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)
#define QSSG_ASSERT_X(cond, msg, action)
#define QSSG_CHECK(cond)
#define QSSG_ASSERT(cond, action)
#define QSSG_GUARD(cond)
static void skyMaterialTonemapFeatures(QSSGRenderLayer::TonemapMode mode, QSSGShaderFeatures &features, quint32 &key)
static QMatrix4x4 correctMVPForScissor(QRectF viewportRect, QRect scissorRect, bool isYUp)
static quint32 ensureFreeNodes(quint32 value, quint32 multiple)
static QT_BEGIN_NAMESPACE const char defaultFragOutputs[][12]
static QByteArrayView getDefaultOutputName(size_t index)
static quint32 nextMultipleOf(quint32 value, quint32 multiple)