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