1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qssgrenderpass_p.h" |
5 | #include "qssgrhiquadrenderer_p.h" |
6 | #include "qssglayerrenderdata_p.h" |
7 | #include "qssgrendercontextcore_p.h" |
8 | #include "qssgdebugdrawsystem_p.h" |
9 | #include "extensionapi/qssgrenderextensions_p.h" |
10 | |
11 | #include "../utils/qssgassert_p.h" |
12 | |
13 | #include <QtQuick/private/qsgrenderer_p.h> |
14 | #include <qtquick3d_tracepoints_p.h> |
15 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | static inline QMatrix4x4 correctMVPForScissor(QRectF viewportRect, QRect scissorRect, bool isYUp) { |
19 | const auto &scissorCenter = scissorRect.center(); |
20 | const auto &viewCenter = viewportRect.center(); |
21 | const float scaleX = viewportRect.width() / float(scissorRect.width()); |
22 | const float scaleY = viewportRect.height() / float(scissorRect.height()); |
23 | const float dx = 2 * (viewCenter.x() - scissorCenter.x()) / scissorRect.width(); |
24 | const float dyRect = isYUp ? (scissorCenter.y() - viewCenter.y()) |
25 | : (viewCenter.y() - scissorCenter.y()); |
26 | const float dy = 2 * dyRect / scissorRect.height(); |
27 | |
28 | return QMatrix4x4(scaleX, 0.0f, 0.0f, dx, |
29 | 0.0f, scaleY, 0.0f, dy, |
30 | 0.0f, 0.0f, 1.0f, 0.0f, |
31 | 0.0f, 0.0f, 0.0f, 1.0f); |
32 | } |
33 | // SHADOW PASS |
34 | |
35 | void ShadowMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data) |
36 | { |
37 | Q_UNUSED(renderer) |
38 | using namespace RenderHelpers; |
39 | |
40 | camera = data.camera; |
41 | QSSG_ASSERT(camera, return); |
42 | |
43 | const auto &renderedDepthWriteObjects = data.getSortedRenderedDepthWriteObjects(); |
44 | const auto &renderedOpaqueDepthPrepassObjects = data.getSortedrenderedOpaqueDepthPrepassObjects(); |
45 | |
46 | QSSG_ASSERT(shadowPassObjects.isEmpty(), shadowPassObjects.clear()); |
47 | |
48 | for (const auto &handles : { &renderedDepthWriteObjects, &renderedOpaqueDepthPrepassObjects }) { |
49 | for (const auto &handle : *handles) { |
50 | if (handle.obj->renderableFlags.castsShadows()) |
51 | shadowPassObjects.push_back(t: handle); |
52 | } |
53 | } |
54 | |
55 | globalLights = data.globalLights; |
56 | |
57 | enabled = !shadowPassObjects.isEmpty() || !globalLights.isEmpty(); |
58 | |
59 | if (enabled) { |
60 | shadowMapManager = data.requestShadowMapManager(); |
61 | |
62 | ps = data.getPipelineState(); |
63 | ps.depthTestEnable = true; |
64 | ps.depthWriteEnable = true; |
65 | // Try reducing self-shadowing and artifacts. |
66 | ps.depthBias = 2; |
67 | ps.slopeScaledDepthBias = 1.5f; |
68 | |
69 | const auto &sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(); |
70 | const auto &sortedTransparentObjects = data.getSortedTransparentRenderableObjects(); |
71 | const auto [casting, receiving] = calculateSortedObjectBounds(sortedOpaqueObjects, |
72 | sortedTransparentObjects); |
73 | castingObjectsBox = casting; |
74 | receivingObjectsBox = receiving; |
75 | } |
76 | } |
77 | |
78 | void ShadowMapPass::renderPass(QSSGRenderer &renderer) |
79 | { |
80 | using namespace RenderHelpers; |
81 | |
82 | // INPUT: Sorted opaque and transparent + depth, global lights (scoped lights not supported) and camera. |
83 | |
84 | // DEPENDECY: None |
85 | |
86 | // OUTPUT: Textures or cube maps |
87 | |
88 | // CONDITION: Lights (shadowPassObjects) |
89 | |
90 | if (enabled) { |
91 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
92 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
93 | QRhiCommandBuffer *cb = rhiCtx->commandBuffer(); |
94 | cb->debugMarkBegin(QByteArrayLiteral("Quick3D shadow map" )); |
95 | Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D shadow map" )); |
96 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
97 | |
98 | QSSG_CHECK(shadowMapManager); |
99 | rhiRenderShadowMap(rhiCtx: rhiCtx.get(), |
100 | passKey: this, |
101 | ps, |
102 | shadowMapManager&: *shadowMapManager, |
103 | camera: *camera, |
104 | globalLights, // scoped lights are not relevant here |
105 | sortedOpaqueObjects: shadowPassObjects, |
106 | renderer, |
107 | castingObjectsBox, |
108 | receivingObjectsBox); |
109 | |
110 | cb->debugMarkEnd(); |
111 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("shadow_map" )); |
112 | } |
113 | } |
114 | |
115 | void ShadowMapPass::release() |
116 | { |
117 | enabled = false; |
118 | camera = nullptr; |
119 | castingObjectsBox = {}; |
120 | receivingObjectsBox = {}; |
121 | ps = {}; |
122 | shadowPassObjects.clear(); |
123 | globalLights.clear(); |
124 | } |
125 | |
126 | // REFLECTIONMAP PASS |
127 | |
128 | void ReflectionMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data) |
129 | { |
130 | Q_UNUSED(renderer); |
131 | Q_UNUSED(data); |
132 | |
133 | ps = data.getPipelineState(); |
134 | ps.depthTestEnable = true; |
135 | ps.depthWriteEnable = true; |
136 | ps.blendEnable = true; |
137 | |
138 | reflectionProbes = data.reflectionProbes; |
139 | reflectionMapManager = data.requestReflectionMapManager(); |
140 | |
141 | const auto &sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(); |
142 | const auto &sortedTransparentObjects = data.getSortedTransparentRenderableObjects(); |
143 | |
144 | QSSG_ASSERT(reflectionPassObjects.isEmpty(), reflectionPassObjects.clear()); |
145 | |
146 | // NOTE: We should consider keeping track of the reflection casting objects to avoid |
147 | // filtering this list on each prep. |
148 | for (const auto &handles : { &sortedOpaqueObjects, &sortedTransparentObjects }) { |
149 | for (const auto &handle : *handles) { |
150 | if (handle.obj->renderableFlags.testFlag(flag: QSSGRenderableObjectFlag::CastsReflections)) |
151 | reflectionPassObjects.push_back(t: handle); |
152 | } |
153 | } |
154 | } |
155 | |
156 | void ReflectionMapPass::renderPass(QSSGRenderer &renderer) |
157 | { |
158 | using namespace RenderHelpers; |
159 | |
160 | // INPUT: Reflection probes, sorted opaque and transparent |
161 | |
162 | // DEPENDECY: None |
163 | |
164 | // OUTPUT: Cube maps (1 per probe) |
165 | |
166 | // NOTE: Full pass with a sky box pass |
167 | |
168 | // CONDITION: Probes and sorted opaque and transparent |
169 | |
170 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
171 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
172 | QRhiCommandBuffer *cb = rhiCtx->commandBuffer(); |
173 | |
174 | // TODO: Workaroud as we using the layer data in rhiRenderReflectionMap() consider |
175 | // if we can extract the data we need for rendering in the prep step... |
176 | QSSG_ASSERT(renderer.getLayerGlobalRenderProperties().layer.renderData, return); |
177 | const auto &data = *renderer.getLayerGlobalRenderProperties().layer.renderData; |
178 | |
179 | QSSG_CHECK(reflectionMapManager); |
180 | if (!reflectionPassObjects.isEmpty() || !reflectionProbes.isEmpty()) { |
181 | cb->debugMarkBegin(QByteArrayLiteral("Quick3D reflection map" )); |
182 | Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D reflection map" )); |
183 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
184 | rhiRenderReflectionMap(rhiCtx: rhiCtx.get(), |
185 | passKey: this, |
186 | inData: data, |
187 | ps: &ps, |
188 | reflectionMapManager&: *reflectionMapManager, |
189 | reflectionProbes, |
190 | reflectionPassObjects, |
191 | renderer); |
192 | |
193 | cb->debugMarkEnd(); |
194 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("reflection_map" )); |
195 | } |
196 | } |
197 | |
198 | void ReflectionMapPass::release() |
199 | { |
200 | ps = {}; |
201 | reflectionProbes.clear(); |
202 | reflectionPassObjects.clear(); |
203 | } |
204 | |
205 | // ZPrePass |
206 | void ZPrePassPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data) |
207 | { |
208 | using namespace RenderHelpers; |
209 | |
210 | // INPUT: Item2Ds + depth write + depth prepass |
211 | |
212 | // DEPENDECY: none |
213 | |
214 | // OUTPUT: Depth buffer attchment for current target |
215 | |
216 | // NOTE: Could we make the depth pass more complete and just do a blit here? |
217 | |
218 | // CONDITION: Input + globally enabled or ? |
219 | |
220 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
221 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
222 | QRhiCommandBuffer *cb = rhiCtx->commandBuffer(); |
223 | ps = data.getPipelineState(); |
224 | |
225 | renderedDepthWriteObjects = data.getSortedRenderedDepthWriteObjects(); |
226 | renderedOpaqueDepthPrepassObjects = data.getSortedrenderedOpaqueDepthPrepassObjects(); |
227 | |
228 | cb->debugMarkBegin(QByteArrayLiteral("Quick3D prepare Z prepass" )); |
229 | Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D prepare Z prepass" )); |
230 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
231 | active = rhiPrepareDepthPass(rhiCtx: rhiCtx.get(), passKey: this, basePipelineState: ps, rpDesc: rhiCtx->mainRenderPassDescriptor(), inData&: data, |
232 | sortedOpaqueObjects: renderedDepthWriteObjects, sortedTransparentObjects: renderedOpaqueDepthPrepassObjects, |
233 | samples: rhiCtx->mainPassSampleCount()); |
234 | data.setZPrePassPrepResult(active); |
235 | cb->debugMarkEnd(); |
236 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("prepare_z_prepass" )); |
237 | } |
238 | |
239 | void ZPrePassPass::renderPass(QSSGRenderer &renderer) |
240 | { |
241 | using namespace RenderHelpers; |
242 | |
243 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
244 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
245 | |
246 | bool needsSetViewport = true; |
247 | QRhiCommandBuffer *cb = rhiCtx->commandBuffer(); |
248 | |
249 | if (active) { |
250 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
251 | Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render Z prepass" )); |
252 | cb->debugMarkBegin(QByteArrayLiteral("Quick3D render Z prepass" )); |
253 | rhiRenderDepthPass(rhiCtx: rhiCtx.get(), ps, sortedOpaqueObjects: renderedDepthWriteObjects, sortedTransparentObjects: renderedOpaqueDepthPrepassObjects, needsSetViewport: &needsSetViewport); |
254 | cb->debugMarkEnd(); |
255 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("render_z_prepass" )); |
256 | } |
257 | } |
258 | |
259 | void ZPrePassPass::release() |
260 | { |
261 | renderedDepthWriteObjects.clear(); |
262 | renderedOpaqueDepthPrepassObjects.clear(); |
263 | ps = {}; |
264 | active = false; |
265 | } |
266 | |
267 | // SSAO PASS |
268 | void SSAOMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data) |
269 | { |
270 | using namespace RenderHelpers; |
271 | |
272 | // Assumption for now is that all passes are keept alive and only reset once a frame is done. |
273 | // I.e., holding data like this should be safe (If that's no longer the case we need to do ref counting |
274 | // for shared data). |
275 | |
276 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
277 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
278 | |
279 | rhiAoTexture = data.getRenderResult(id: QSSGFrameData::RenderResult::AoTexture); |
280 | rhiDepthTexture = data.getRenderResult(id: QSSGFrameData::RenderResult::DepthTexture); |
281 | camera = data.camera; |
282 | QSSG_ASSERT_X((camera && rhiDepthTexture && rhiDepthTexture->isValid()), "Preparing AO pass failed, missing camera or required texture(s)" , return); |
283 | |
284 | ssaoShaderPipeline = data.renderer->getRhiSsaoShader(); |
285 | ambientOcclusion = { .aoStrength: data.layer.aoStrength, .aoDistance: data.layer.aoDistance, .aoSoftness: data.layer.aoSoftness, .aoBias: data.layer.aoBias, .aoSamplerate: data.layer.aoSamplerate, .aoDither: data.layer.aoDither }; |
286 | |
287 | ps = data.getPipelineState(); |
288 | const auto &layerPrepResult = data.layerPrepResult; |
289 | const bool ready = rhiAoTexture && rhiPrepareAoTexture(rhiCtx: rhiCtx.get(), size: layerPrepResult->textureDimensions(), renderableTex: rhiAoTexture); |
290 | |
291 | if (Q_UNLIKELY(!ready)) |
292 | rhiAoTexture = nullptr; |
293 | } |
294 | |
295 | void SSAOMapPass::renderPass(QSSGRenderer &renderer) |
296 | { |
297 | using namespace RenderHelpers; |
298 | |
299 | // INPUT: Camera + depth map |
300 | |
301 | // DEPENDECY: Depth map (zprepass) |
302 | |
303 | // OUTPUT: AO Texture |
304 | |
305 | // NOTE: |
306 | |
307 | // CONDITION: SSAO enabled |
308 | QSSG_ASSERT(camera && rhiDepthTexture && rhiDepthTexture->isValid(), return); |
309 | |
310 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
311 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
312 | |
313 | QRhiCommandBuffer *cb = rhiCtx->commandBuffer(); |
314 | cb->debugMarkBegin(QByteArrayLiteral("Quick3D SSAO map" )); |
315 | Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D SSAO map" )); |
316 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
317 | |
318 | if (Q_LIKELY(rhiAoTexture && rhiAoTexture->isValid())) { |
319 | rhiRenderAoTexture(rhiCtx: rhiCtx.get(), |
320 | passKey: this, |
321 | renderer, |
322 | shaderPipeline&: *ssaoShaderPipeline, |
323 | ps, |
324 | ao: ambientOcclusion, |
325 | rhiAoTexture: *rhiAoTexture, |
326 | rhiDepthTexture: *rhiDepthTexture, |
327 | camera: *camera); |
328 | } |
329 | |
330 | cb->debugMarkEnd(); |
331 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("ssao_map" )); |
332 | } |
333 | |
334 | void SSAOMapPass::release() |
335 | { |
336 | rhiDepthTexture = nullptr; |
337 | rhiAoTexture = nullptr; |
338 | camera = nullptr; |
339 | ps = {}; |
340 | ao = AmbientOcclusion(); |
341 | } |
342 | |
343 | // DEPTH TEXTURE PASS |
344 | void DepthMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data) |
345 | { |
346 | using namespace RenderHelpers; |
347 | |
348 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
349 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
350 | const auto &layerPrepResult = data.layerPrepResult; |
351 | bool ready = false; |
352 | ps = data.getPipelineState(); |
353 | rhiDepthTexture = data.getRenderResult(id: QSSGFrameData::RenderResult::DepthTexture); |
354 | if (Q_LIKELY(rhiDepthTexture && rhiPrepareDepthTexture(rhiCtx.get(), layerPrepResult->textureDimensions(), rhiDepthTexture))) { |
355 | sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(); |
356 | sortedTransparentObjects = data.getSortedTransparentRenderableObjects(); |
357 | ready = rhiPrepareDepthPass(rhiCtx: rhiCtx.get(), passKey: this, basePipelineState: ps, rpDesc: rhiDepthTexture->rpDesc, inData&: data, |
358 | sortedOpaqueObjects, sortedTransparentObjects, |
359 | samples: 1); |
360 | } |
361 | |
362 | if (Q_UNLIKELY(!ready)) |
363 | rhiDepthTexture = nullptr; |
364 | } |
365 | |
366 | void DepthMapPass::renderPass(QSSGRenderer &renderer) |
367 | { |
368 | using namespace RenderHelpers; |
369 | |
370 | // INPUT: sorted objects (opaque + transparent) (maybe...) |
371 | |
372 | // DEPENDECY: If this is only used for the AO case, that dictates if this should be done or not. |
373 | |
374 | // OUTPUT: Texture |
375 | |
376 | // NOTE: Why are we prepping opaque + transparent object if we're not using them? And why are we staying compatible with 5.15? |
377 | // Only used for AO? Merge into the AO pass? |
378 | |
379 | // CONDITION: |
380 | |
381 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
382 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
383 | QRhiCommandBuffer *cb = rhiCtx->commandBuffer(); |
384 | cb->debugMarkBegin(QByteArrayLiteral("Quick3D depth texture" )); |
385 | |
386 | if (Q_LIKELY(rhiDepthTexture && rhiDepthTexture->isValid())) { |
387 | bool needsSetViewport = true; |
388 | cb->beginPass(rt: rhiDepthTexture->rt, colorClearValue: Qt::transparent, depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: nullptr, flags: QSSGRhiContext::commonPassFlags()); |
389 | QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rhiDepthTexture->rt)); |
390 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
391 | // NB! We do not pass sortedTransparentObjects in the 4th |
392 | // argument to stay compatible with the 5.15 code base, |
393 | // which also does not include semi-transparent objects in |
394 | // the depth texture. In addition, capturing after the |
395 | // opaque pass, not including transparent objects, is part |
396 | // of the contract for screen reading custom materials, |
397 | // both for depth and color. |
398 | rhiRenderDepthPass(rhiCtx: rhiCtx.get(), ps, sortedOpaqueObjects, sortedTransparentObjects: {}, needsSetViewport: &needsSetViewport); |
399 | cb->endPass(); |
400 | QSSGRHICTX_STAT(rhiCtx, endRenderPass()); |
401 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("depth_texture" )); |
402 | } |
403 | |
404 | cb->debugMarkEnd(); |
405 | } |
406 | |
407 | void DepthMapPass::release() |
408 | { |
409 | rhiDepthTexture = nullptr; |
410 | sortedOpaqueObjects.clear(); |
411 | sortedTransparentObjects.clear(); |
412 | ps = {}; |
413 | } |
414 | |
415 | // SCREEN TEXTURE PASS |
416 | |
417 | void ScreenMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data) |
418 | { |
419 | using namespace RenderHelpers; |
420 | |
421 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
422 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
423 | rhiScreenTexture = data.getRenderResult(id: QSSGFrameData::RenderResult::ScreenTexture); |
424 | auto &layer = data.layer; |
425 | const auto &layerPrepResult = data.layerPrepResult; |
426 | wantsMips = layerPrepResult->flags.requiresMipmapsForScreenTexture(); |
427 | sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(); |
428 | ps = data.getPipelineState(); |
429 | |
430 | if (layer.background == QSSGRenderLayer::Background::Color) |
431 | clearColor = QColor::fromRgbF(r: layer.clearColor.x(), g: layer.clearColor.y(), b: layer.clearColor.z()); |
432 | |
433 | if (rhiCtx->rhi()->isFeatureSupported(feature: QRhi::TexelFetch)) { |
434 | if (layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap && layer.skyBoxCubeMap) { |
435 | skyboxPass = &data.skyboxCubeMapPass; |
436 | data.skyboxCubeMapPass.renderPrep(renderer, data); |
437 | } else if (layer.background == QSSGRenderLayer::Background::SkyBox && layer.lightProbe) { |
438 | skyboxPass = &data.skyboxPass; |
439 | const bool tonemappingEnabled = data.skyboxPass.skipTonemapping; |
440 | data.skyboxPass.skipTonemapping = true; |
441 | data.skyboxPass.renderPrep(renderer, data); |
442 | data.skyboxPass.skipTonemapping = tonemappingEnabled; |
443 | } |
444 | } |
445 | |
446 | bool ready = false; |
447 | if (Q_LIKELY(rhiScreenTexture && rhiPrepareScreenTexture(rhiCtx.get(), layerPrepResult->textureDimensions(), wantsMips, rhiScreenTexture))) { |
448 | ready = true; |
449 | // NB: not compatible with disabling LayerEnableDepthTest |
450 | // because there are effectively no "opaque" objects then. |
451 | // Disable Tonemapping for all materials in the screen pass texture |
452 | shaderFeatures = data.getShaderFeatures(); |
453 | shaderFeatures.disableTonemapping(); |
454 | const auto &sortedOpaqueObjects = data.getSortedOpaqueRenderableObjects(); |
455 | for (const auto &handle : sortedOpaqueObjects) |
456 | rhiPrepareRenderable(rhiCtx: rhiCtx.get(), passKey: this, inData: data, inObject&: *handle.obj, renderPassDescriptor: rhiScreenTexture->rpDesc, ps: &ps, featureSet: shaderFeatures, samples: 1); |
457 | } |
458 | |
459 | if (Q_UNLIKELY(!ready)) |
460 | rhiScreenTexture = nullptr; |
461 | } |
462 | |
463 | void ScreenMapPass::renderPass(QSSGRenderer &renderer) |
464 | { |
465 | using namespace RenderHelpers; |
466 | |
467 | // INPUT: Sorted opaque objects + depth objects |
468 | |
469 | // DEPENDECY: Depth pass (if enabled) |
470 | |
471 | // OUTPUT: Texture (screen texture). |
472 | |
473 | // NOTE: Used for refrection and effects (?) |
474 | |
475 | // CONDITION: |
476 | |
477 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
478 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
479 | QRhiCommandBuffer *cb = rhiCtx->commandBuffer(); |
480 | |
481 | cb->debugMarkBegin(QByteArrayLiteral("Quick3D screen texture" )); |
482 | |
483 | if (Q_LIKELY(rhiScreenTexture && rhiScreenTexture->isValid())) { |
484 | cb->beginPass(rt: rhiScreenTexture->rt, colorClearValue: clearColor, depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: nullptr, flags: QSSGRhiContext::commonPassFlags()); |
485 | QSSGRHICTX_STAT(rhiCtx, beginRenderPass(rhiScreenTexture->rt)); |
486 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
487 | |
488 | bool needsSetViewport = true; |
489 | for (const auto &handle : std::as_const(t&: sortedOpaqueObjects)) |
490 | rhiRenderRenderable(rhiCtx: rhiCtx.get(), state: ps, object&: *handle.obj, needsSetViewport: &needsSetViewport); |
491 | |
492 | if (skyboxPass) |
493 | skyboxPass->renderPass(renderer); |
494 | |
495 | QRhiResourceUpdateBatch *rub = nullptr; |
496 | if (wantsMips) { |
497 | rub = rhiCtx->rhi()->nextResourceUpdateBatch(); |
498 | rub->generateMips(tex: rhiScreenTexture->texture); |
499 | } |
500 | cb->endPass(resourceUpdates: rub); |
501 | QSSGRHICTX_STAT(rhiCtx, endRenderPass()); |
502 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("screen_texture" )); |
503 | } |
504 | |
505 | cb->debugMarkEnd(); |
506 | } |
507 | |
508 | void ScreenMapPass::release() |
509 | { |
510 | rhiScreenTexture = nullptr; |
511 | if (skyboxPass) { |
512 | // NOTE: The screen map pass is prepped and rendered before we render the skybox to the main render target, |
513 | // i.e., we are not interfering with the skybox pass' state. Just make sure sure to leave the skybox pass |
514 | // in a good state now that we're done with it. |
515 | skyboxPass->release(); |
516 | skyboxPass = nullptr; |
517 | } |
518 | ps = {}; |
519 | wantsMips = false; |
520 | clearColor = Qt::transparent; |
521 | shaderFeatures = {}; |
522 | sortedOpaqueObjects.clear(); |
523 | } |
524 | |
525 | void ScreenReflectionPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data) |
526 | { |
527 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
528 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
529 | rhiScreenTexture = data.getRenderResult(id: QSSGFrameData::RenderResult::ScreenTexture); |
530 | QSSG_ASSERT_X(rhiScreenTexture && rhiScreenTexture->isValid(), "Invalid screen texture!" , return); |
531 | |
532 | const auto &layer = data.layer; |
533 | const auto shaderFeatures = data.getShaderFeatures(); |
534 | const bool layerEnableDepthTest = layer.layerFlags.testFlag(flag: QSSGRenderLayer::LayerFlag::EnableDepthTest); |
535 | |
536 | QRhiRenderPassDescriptor *mainRpDesc = rhiCtx->mainRenderPassDescriptor(); |
537 | const int samples = rhiCtx->mainPassSampleCount(); |
538 | |
539 | // NOTE: We're piggybacking on the screen map pass for now, but we could do better. |
540 | ps = data.getPipelineState(); |
541 | ps.depthTestEnable = data.screenMapPass.ps.depthTestEnable; |
542 | ps.depthWriteEnable = data.screenMapPass.ps.depthWriteEnable; |
543 | sortedScreenTextureObjects = data.getSortedScreenTextureRenderableObjects(); |
544 | for (const auto &handle : std::as_const(t&: sortedScreenTextureObjects)) { |
545 | QSSGRenderableObject *theObject = handle.obj; |
546 | const auto depthWriteMode = theObject->depthWriteMode; |
547 | ps.blendEnable = theObject->renderableFlags.hasTransparency(); |
548 | ps.depthWriteEnable = !(depthWriteMode == QSSGDepthDrawMode::Never || depthWriteMode == QSSGDepthDrawMode::OpaquePrePass |
549 | || data.isZPrePassActive() || !layerEnableDepthTest); |
550 | RenderHelpers::rhiPrepareRenderable(rhiCtx: rhiCtx.get(), passKey: this, inData: data, inObject&: *theObject, renderPassDescriptor: mainRpDesc, ps: &ps, featureSet: shaderFeatures, samples); |
551 | } |
552 | } |
553 | |
554 | void ScreenReflectionPass::renderPass(QSSGRenderer &renderer) |
555 | { |
556 | if (QSSG_GUARD(rhiScreenTexture && rhiScreenTexture->isValid())) { |
557 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
558 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
559 | QRhiCommandBuffer *cb = rhiCtx->commandBuffer(); |
560 | |
561 | // 3. Screen texture depended objects |
562 | cb->debugMarkBegin(QByteArrayLiteral("Quick3D render screen texture dependent" )); |
563 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
564 | Q_TRACE(QSSG_renderPass_entry, QStringLiteral("Quick3D render screen texture dependent" )); |
565 | bool needsSetViewport = true; |
566 | for (const auto &handle : std::as_const(t&: sortedScreenTextureObjects)) { |
567 | QSSGRenderableObject *theObject = handle.obj; |
568 | RenderHelpers::rhiRenderRenderable(rhiCtx: rhiCtx.get(), state: ps, object&: *theObject, needsSetViewport: &needsSetViewport); |
569 | } |
570 | cb->debugMarkEnd(); |
571 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("screen_texture_dependent" )); |
572 | Q_TRACE(QSSG_renderPass_exit); |
573 | } |
574 | } |
575 | |
576 | void ScreenReflectionPass::release() |
577 | { |
578 | sortedScreenTextureObjects.clear(); |
579 | rhiScreenTexture = nullptr; |
580 | ps = {}; |
581 | } |
582 | |
583 | void OpaquePass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data) |
584 | { |
585 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
586 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
587 | auto camera = data.camera; |
588 | QSSG_ASSERT(camera, return); |
589 | |
590 | ps = data.getPipelineState(); |
591 | |
592 | { // Frustum culling |
593 | const auto &clippingFrustum = data.clippingFrustum; |
594 | const auto &opaqueObjects = data.getSortedOpaqueRenderableObjects(); |
595 | if (clippingFrustum.has_value()) |
596 | QSSGLayerRenderData::frustumCulling(clipFrustum: clippingFrustum.value(), renderables: opaqueObjects, visibleRenderables&: sortedOpaqueObjects); |
597 | else |
598 | sortedOpaqueObjects = opaqueObjects; |
599 | } |
600 | |
601 | shaderFeatures = data.getShaderFeatures(); |
602 | |
603 | const auto &layer = data.layer; |
604 | const bool layerEnableDepthTest = layer.layerFlags.testFlag(flag: QSSGRenderLayer::LayerFlag::EnableDepthTest); |
605 | |
606 | ps.depthFunc = QRhiGraphicsPipeline::LessOrEqual; |
607 | ps.blendEnable = false; |
608 | |
609 | QRhiRenderPassDescriptor *mainRpDesc = rhiCtx->mainRenderPassDescriptor(); |
610 | const int samples = rhiCtx->mainPassSampleCount(); |
611 | |
612 | // opaque objects (or, this list is empty when LayerEnableDepthTest is disabled) |
613 | for (const auto &handle : std::as_const(t&: sortedOpaqueObjects)) { |
614 | QSSGRenderableObject *theObject = handle.obj; |
615 | const auto depthWriteMode = theObject->depthWriteMode; |
616 | ps.depthWriteEnable = !(depthWriteMode == QSSGDepthDrawMode::Never || |
617 | depthWriteMode == QSSGDepthDrawMode::OpaquePrePass || |
618 | data.isZPrePassActive() || !layerEnableDepthTest); |
619 | RenderHelpers::rhiPrepareRenderable(rhiCtx: rhiCtx.get(), passKey: this, inData: data, inObject&: *theObject, renderPassDescriptor: mainRpDesc, ps: &ps, featureSet: shaderFeatures, samples); |
620 | } |
621 | } |
622 | |
623 | void OpaquePass::renderPass(QSSGRenderer &renderer) |
624 | { |
625 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
626 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
627 | QRhiCommandBuffer *cb = rhiCtx->commandBuffer(); |
628 | |
629 | bool needsSetViewport = true; |
630 | |
631 | cb->debugMarkBegin(QByteArrayLiteral("Quick3D render opaque" )); |
632 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
633 | Q_TRACE(QSSG_renderPass_entry, QStringLiteral("Quick3D render opaque" )); |
634 | for (const auto &handle : std::as_const(t&: sortedOpaqueObjects)) { |
635 | QSSGRenderableObject *theObject = handle.obj; |
636 | RenderHelpers::rhiRenderRenderable(rhiCtx: rhiCtx.get(), state: ps, object&: *theObject, needsSetViewport: &needsSetViewport); |
637 | } |
638 | cb->debugMarkEnd(); |
639 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("opaque_pass" )); |
640 | Q_TRACE(QSSG_renderPass_exit); |
641 | } |
642 | |
643 | void OpaquePass::release() |
644 | { |
645 | sortedOpaqueObjects.clear(); |
646 | ps = {}; |
647 | shaderFeatures = {}; |
648 | } |
649 | |
650 | void TransparentPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data) |
651 | { |
652 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
653 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
654 | auto camera = data.camera; |
655 | QSSG_ASSERT(camera, return); |
656 | |
657 | QRhiRenderPassDescriptor *mainRpDesc = rhiCtx->mainRenderPassDescriptor(); |
658 | const int samples = rhiCtx->mainPassSampleCount(); |
659 | |
660 | ps = data.getPipelineState(); |
661 | |
662 | // transparent objects (or, without LayerEnableDepthTest, all objects) |
663 | ps.blendEnable = true; |
664 | ps.depthWriteEnable = false; |
665 | |
666 | shaderFeatures = data.getShaderFeatures(); |
667 | |
668 | { // Frustum culling |
669 | const auto &clippingFrustum = data.clippingFrustum; |
670 | const auto &transparentObject = data.getSortedTransparentRenderableObjects(); |
671 | if (clippingFrustum.has_value()) |
672 | QSSGLayerRenderData::frustumCulling(clipFrustum: clippingFrustum.value(), renderables: transparentObject, visibleRenderables&: sortedTransparentObjects); |
673 | else |
674 | sortedTransparentObjects = transparentObject; |
675 | } |
676 | |
677 | const bool zPrePassActive = data.isZPrePassActive(); |
678 | for (const auto &handle : std::as_const(t&: sortedTransparentObjects)) { |
679 | QSSGRenderableObject *theObject = handle.obj; |
680 | const auto depthWriteMode = theObject->depthWriteMode; |
681 | ps.depthWriteEnable = (depthWriteMode == QSSGDepthDrawMode::Always && !zPrePassActive); |
682 | if (!(theObject->renderableFlags.isCompletelyTransparent())) |
683 | RenderHelpers::rhiPrepareRenderable(rhiCtx: rhiCtx.get(), passKey: this, inData: data, inObject&: *theObject, renderPassDescriptor: mainRpDesc, ps: &ps, featureSet: shaderFeatures, samples); |
684 | } |
685 | } |
686 | |
687 | void TransparentPass::renderPass(QSSGRenderer &renderer) |
688 | { |
689 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
690 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
691 | QRhiCommandBuffer *cb = rhiCtx->commandBuffer(); |
692 | |
693 | cb->debugMarkBegin(QByteArrayLiteral("Quick3D render alpha" )); |
694 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
695 | Q_TRACE(QSSG_renderPass_entry, QStringLiteral("Quick3D render alpha" )); |
696 | // If scissorRect is set, Item2Ds will be drawn by a workaround of modifying |
697 | // viewport, not using actual 3D scissor test. |
698 | // It means non-opaque objects may be affected by this viewport setting. |
699 | bool needsSetViewport = true; |
700 | for (const auto &handle : std::as_const(t&: sortedTransparentObjects)) { |
701 | QSSGRenderableObject *theObject = handle.obj; |
702 | if (!theObject->renderableFlags.isCompletelyTransparent()) |
703 | RenderHelpers::rhiRenderRenderable(rhiCtx: rhiCtx.get(), state: ps, object&: *theObject, needsSetViewport: &needsSetViewport); |
704 | } |
705 | cb->debugMarkEnd(); |
706 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("transparent_pass" )); |
707 | Q_TRACE(QSSG_renderPass_exit); |
708 | } |
709 | |
710 | void TransparentPass::release() |
711 | { |
712 | sortedTransparentObjects.clear(); |
713 | ps = {}; |
714 | shaderFeatures = {}; |
715 | } |
716 | |
717 | void SkyboxPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data) |
718 | { |
719 | if (!skipPrep) { |
720 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
721 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
722 | auto camera = data.camera; |
723 | QSSG_ASSERT(camera, return); |
724 | layer = &data.layer; |
725 | QSSG_ASSERT(layer, return); |
726 | |
727 | ps = data.getPipelineState(); |
728 | ps.polygonMode = QRhiGraphicsPipeline::Fill; |
729 | |
730 | // When there are effects, then it is up to the last pass of the |
731 | // last effect to perform tonemapping, neither the skybox nor the |
732 | // main render pass should alter the colors then. |
733 | skipTonemapping = layer->firstEffect != nullptr; |
734 | |
735 | RenderHelpers::rhiPrepareSkyBox(rhiCtx: rhiCtx.get(), passKey: this, layer&: *layer, inCamera&: *camera, renderer); |
736 | skipPrep = true; |
737 | } |
738 | } |
739 | |
740 | void SkyboxPass::renderPass(QSSGRenderer &renderer) |
741 | { |
742 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
743 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
744 | QSSG_ASSERT(layer, return); |
745 | |
746 | QRhiShaderResourceBindings *srb = layer->skyBoxSrb; |
747 | QSSG_ASSERT(srb, return); |
748 | |
749 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
750 | Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render skybox" )); |
751 | QSSGRenderLayer::TonemapMode tonemapMode = skipTonemapping ? QSSGRenderLayer::TonemapMode::None : layer->tonemapMode; |
752 | |
753 | auto shaderPipeline = renderer.getRhiSkyBoxShader(tonemapMode, isRGBE: layer->skyBoxIsRgbe8); |
754 | QSSG_CHECK(shaderPipeline); |
755 | ps.shaderPipeline = shaderPipeline.get(); |
756 | QRhiRenderPassDescriptor *rpDesc = rhiCtx->mainRenderPassDescriptor(); |
757 | renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx: rhiCtx.get(), ps: &ps, srb, rpDesc, flags: { QSSGRhiQuadRenderer::DepthTest | QSSGRhiQuadRenderer::RenderBehind }); |
758 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("skybox_map" )); |
759 | } |
760 | |
761 | void SkyboxPass::release() |
762 | { |
763 | ps = {}; |
764 | layer = nullptr; |
765 | skipPrep = false; |
766 | } |
767 | |
768 | void SkyboxCubeMapPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data) |
769 | { |
770 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
771 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
772 | auto camera = data.camera; |
773 | QSSG_ASSERT(camera, return); |
774 | layer = &data.layer; |
775 | QSSG_ASSERT(layer, return); |
776 | |
777 | ps = data.getPipelineState(); |
778 | ps.polygonMode = QRhiGraphicsPipeline::Fill; |
779 | |
780 | RenderHelpers::rhiPrepareSkyBox(rhiCtx: rhiCtx.get(), passKey: this, layer&: *layer, inCamera&: *camera, renderer); |
781 | } |
782 | |
783 | void SkyboxCubeMapPass::renderPass(QSSGRenderer &renderer) |
784 | { |
785 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
786 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
787 | QSSG_ASSERT(layer, return); |
788 | |
789 | QRhiShaderResourceBindings *srb = layer->skyBoxSrb; |
790 | QSSG_ASSERT(srb, return); |
791 | |
792 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
793 | Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render skybox" )); |
794 | auto shaderPipeline = renderer.getRhiSkyBoxCubeShader(); |
795 | QSSG_CHECK(shaderPipeline); |
796 | ps.shaderPipeline = shaderPipeline.get(); |
797 | QRhiRenderPassDescriptor *rpDesc = rhiCtx->mainRenderPassDescriptor(); |
798 | renderer.rhiCubeRenderer()->recordRenderCube(rhiCtx: rhiCtx.get(), ps: &ps, srb, rpDesc, flags: { QSSGRhiQuadRenderer::DepthTest | QSSGRhiQuadRenderer::RenderBehind }); |
799 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("skybox_cube" )); |
800 | } |
801 | |
802 | void SkyboxCubeMapPass::release() |
803 | { |
804 | ps = {}; |
805 | layer = nullptr; |
806 | } |
807 | |
808 | void Item2DPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data) |
809 | { |
810 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
811 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
812 | const auto &layer = data.layer; |
813 | |
814 | ps = data.getPipelineState(); |
815 | |
816 | // objects rendered by Qt Quick 2D |
817 | ps.blendEnable = false; |
818 | |
819 | item2Ds = data.getRenderableItem2Ds(); |
820 | for (const auto &item2D: std::as_const(t&: item2Ds)) { |
821 | // Set the projection matrix |
822 | if (!item2D->m_renderer) |
823 | continue; |
824 | if (item2D->m_renderer && item2D->m_renderer->currentRhi() != renderer.contextInterface()->rhi()) { |
825 | static bool contextWarningShown = false; |
826 | if (!contextWarningShown) { |
827 | qWarning () << "Scene with embedded 2D content can only be rendered in one window." ; |
828 | contextWarningShown = true; |
829 | } |
830 | continue; |
831 | } |
832 | |
833 | auto layerPrepResult = data.layerPrepResult; |
834 | |
835 | const auto &renderTarget = rhiCtx->renderTarget(); |
836 | item2D->m_renderer->setDevicePixelRatio(renderTarget->devicePixelRatio()); |
837 | const QRect deviceRect(QPoint(0, 0), renderTarget->pixelSize()); |
838 | if (layer.scissorRect.isValid()) { |
839 | QRect effScissor = layer.scissorRect & layerPrepResult->viewport.toRect(); |
840 | QMatrix4x4 correctionMat = correctMVPForScissor(viewportRect: layerPrepResult->viewport, |
841 | scissorRect: effScissor, |
842 | isYUp: rhiCtx->rhi()->isYUpInNDC()); |
843 | item2D->m_renderer->setProjectionMatrix(correctionMat * item2D->MVP); |
844 | item2D->m_renderer->setViewportRect(effScissor); |
845 | } else { |
846 | item2D->m_renderer->setProjectionMatrix(item2D->MVP); |
847 | item2D->m_renderer->setViewportRect(RenderHelpers::correctViewportCoordinates(layerViewport: layerPrepResult->viewport, deviceRect)); |
848 | } |
849 | item2D->m_renderer->setDeviceRect(deviceRect); |
850 | QRhiRenderPassDescriptor *oldRp = nullptr; |
851 | if (item2D->m_rp) { |
852 | // Changing render target, and so incompatible renderpass |
853 | // descriptors should be uncommon, but possible. |
854 | if (!item2D->m_rp->isCompatible(other: rhiCtx->mainRenderPassDescriptor())) |
855 | std::swap(a&: item2D->m_rp, b&: oldRp); |
856 | } |
857 | if (!item2D->m_rp) { |
858 | // Do not pass our object to the Qt Quick scenegraph. It may |
859 | // hold on to it, leading to lifetime and ownership issues. |
860 | // Rather, create a dedicated, compatible object. |
861 | item2D->m_rp = rhiCtx->mainRenderPassDescriptor()->newCompatibleRenderPassDescriptor(); |
862 | QSSG_CHECK(item2D->m_rp); |
863 | } |
864 | item2D->m_renderer->setRenderTarget({ renderTarget, item2D->m_rp, rhiCtx->commandBuffer() }); |
865 | delete oldRp; |
866 | item2D->m_renderer->prepareSceneInline(); |
867 | } |
868 | } |
869 | |
870 | void Item2DPass::renderPass(QSSGRenderer &renderer) |
871 | { |
872 | QSSG_ASSERT(!item2Ds.isEmpty(), return); |
873 | |
874 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
875 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
876 | QRhiCommandBuffer *cb = rhiCtx->commandBuffer(); |
877 | |
878 | cb->debugMarkBegin(QByteArrayLiteral("Quick3D render 2D sub-scene" )); |
879 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
880 | Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render 2D sub-scene" )); |
881 | for (const auto &item : std::as_const(t&: item2Ds)) { |
882 | QSSGRenderItem2D *item2D = static_cast<QSSGRenderItem2D *>(item); |
883 | if (item2D->m_renderer && item2D->m_renderer->currentRhi() == renderer.contextInterface()->rhi()) |
884 | item2D->m_renderer->renderSceneInline(); |
885 | } |
886 | cb->debugMarkEnd(); |
887 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("2D_sub_scene" )); |
888 | } |
889 | |
890 | void Item2DPass::release() |
891 | { |
892 | item2Ds.clear(); |
893 | ps = {}; |
894 | } |
895 | |
896 | void InfiniteGridPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data) |
897 | { |
898 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
899 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
900 | auto camera = data.camera; |
901 | QSSG_ASSERT(camera, return); |
902 | layer = &data.layer; |
903 | QSSG_ASSERT(layer, return); |
904 | |
905 | ps = data.getPipelineState(); |
906 | ps.blendEnable = true; |
907 | RenderHelpers::rhiPrepareGrid(rhiCtx: rhiCtx.get(), passKey: this, layer&: *layer, inCamera&: *camera, renderer); |
908 | } |
909 | |
910 | void InfiniteGridPass::renderPass(QSSGRenderer &renderer) |
911 | { |
912 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
913 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
914 | QRhiCommandBuffer *cb = rhiCtx->commandBuffer(); |
915 | |
916 | cb->debugMarkBegin(QByteArrayLiteral("Quick3D render grid" )); |
917 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
918 | Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick3D render grid" )); |
919 | const auto &shaderPipeline = renderer.getRhiGridShader(); |
920 | Q_ASSERT(shaderPipeline); |
921 | ps.shaderPipeline = shaderPipeline.get(); |
922 | QRhiShaderResourceBindings *srb = layer->gridSrb; |
923 | QRhiRenderPassDescriptor *rpDesc = rhiCtx->mainRenderPassDescriptor(); |
924 | renderer.rhiQuadRenderer()->recordRenderQuad(rhiCtx: rhiCtx.get(), ps: &ps, srb, rpDesc, flags: { QSSGRhiQuadRenderer::DepthTest }); |
925 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("render_grid" )); |
926 | } |
927 | |
928 | void InfiniteGridPass::release() |
929 | { |
930 | ps = {}; |
931 | layer = nullptr; |
932 | } |
933 | |
934 | void DebugDrawPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data) |
935 | { |
936 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
937 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
938 | auto camera = data.camera; |
939 | QSSG_ASSERT(camera, return); |
940 | |
941 | ps = data.getPipelineState(); |
942 | |
943 | // debug objects |
944 | const auto &debugDraw = renderer.contextInterface()->debugDrawSystem(); |
945 | if (debugDraw && debugDraw->hasContent()) { |
946 | QRhiResourceUpdateBatch *rub = rhiCtx->rhi()->nextResourceUpdateBatch(); |
947 | debugDraw->prepareGeometry(rhiCtx: rhiCtx.get(), rub); |
948 | QSSGRhiDrawCallData &dcd = rhiCtx->drawCallData(key: { .cid: this, .model: nullptr, .entry: nullptr, .entryIdx: 0 }); |
949 | if (!dcd.ubuf) { |
950 | dcd.ubuf = rhiCtx->rhi()->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: 64); |
951 | dcd.ubuf->create(); |
952 | } |
953 | char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame(); |
954 | QMatrix4x4 viewProjection(Qt::Uninitialized); |
955 | camera->calculateViewProjectionMatrix(outMatrix&: viewProjection); |
956 | viewProjection = rhiCtx->rhi()->clipSpaceCorrMatrix() * viewProjection; |
957 | memcpy(dest: ubufData, src: viewProjection.constData(), n: 64); |
958 | dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame(); |
959 | |
960 | QSSGRhiShaderResourceBindingList bindings; |
961 | bindings.addUniformBuffer(binding: 0, stage: QRhiShaderResourceBinding::VertexStage, buf: dcd.ubuf); |
962 | dcd.srb = rhiCtx->srb(bindings); |
963 | |
964 | rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates: rub); |
965 | } |
966 | } |
967 | |
968 | void DebugDrawPass::renderPass(QSSGRenderer &renderer) |
969 | { |
970 | const auto &rhiCtx = renderer.contextInterface()->rhiContext(); |
971 | QSSG_ASSERT(rhiCtx->rhi()->isRecordingFrame(), return); |
972 | QRhiCommandBuffer *cb = rhiCtx->commandBuffer(); |
973 | |
974 | const auto &debugDraw = renderer.contextInterface()->debugDrawSystem(); |
975 | if (debugDraw && debugDraw->hasContent()) { |
976 | cb->debugMarkBegin(QByteArrayLiteral("Quick 3D debug objects" )); |
977 | Q_TRACE_SCOPE(QSSG_renderPass, QStringLiteral("Quick 3D debug objects" )); |
978 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
979 | auto shaderPipeline = renderer.getRhiDebugObjectShader(); |
980 | QSSG_CHECK(shaderPipeline); |
981 | ps.shaderPipeline = shaderPipeline.get(); |
982 | QSSGRhiDrawCallData &dcd = rhiCtx->drawCallData(key: { .cid: this, .model: nullptr, .entry: nullptr, .entryIdx: 0 }); |
983 | QRhiShaderResourceBindings *srb = dcd.srb; |
984 | QRhiRenderPassDescriptor *rpDesc = rhiCtx->mainRenderPassDescriptor(); |
985 | debugDraw->recordRenderDebugObjects(rhiCtx: rhiCtx.get(), ps: &ps, srb, rpDesc); |
986 | cb->debugMarkEnd(); |
987 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("debug_objects" )); |
988 | } |
989 | } |
990 | |
991 | void DebugDrawPass::release() |
992 | { |
993 | ps = {}; |
994 | } |
995 | |
996 | void UserPass::renderPrep(QSSGRenderer &renderer, QSSGLayerRenderData &data) |
997 | { |
998 | auto &frameData = data.getFrameData(); |
999 | for (const auto &p : std::as_const(t&: extensions)) { |
1000 | p->prepareRender(renderer, data&: frameData); |
1001 | if (p->type() == QSSGRenderExtension::Type::Standalone) |
1002 | p->render(renderer); |
1003 | } |
1004 | } |
1005 | |
1006 | void UserPass::renderPass(QSSGRenderer &renderer) |
1007 | { |
1008 | for (const auto &p : std::as_const(t&: extensions)) { |
1009 | if (p->type() == QSSGRenderExtension::Type::Main) |
1010 | p->render(renderer); |
1011 | } |
1012 | } |
1013 | |
1014 | void UserPass::release() |
1015 | { |
1016 | for (const auto &p : std::as_const(t&: extensions)) |
1017 | p->release(); |
1018 | |
1019 | // TODO: We should track if we need to update this list. |
1020 | extensions.clear(); |
1021 | } |
1022 | |
1023 | QT_END_NAMESPACE |
1024 | |