1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtQuick3DRuntimeRender/private/qssgrenderreflectionprobe_p.h>
5#include <QtQuick3DRuntimeRender/private/qssgrenderlayer_p.h>
6#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
7#include <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h>
8#include <QtQuick3DUtils/private/qssgrenderbasetypes_p.h>
9
10QT_BEGIN_NAMESPACE
11
12const int prefilterSampleCount = 16;
13
14QSSGRenderReflectionMap::QSSGRenderReflectionMap(const QSSGRenderContextInterface &inContext)
15 : m_context(inContext)
16{
17}
18
19QSSGRenderReflectionMap::~QSSGRenderReflectionMap()
20{
21 releaseCachedResources();
22}
23
24void QSSGRenderReflectionMap::releaseCachedResources()
25{
26 for (QSSGReflectionMapEntry &entry : m_reflectionMapList)
27 entry.destroyRhiResources();
28
29 m_reflectionMapList.clear();
30}
31
32static QRhiTexture *allocateRhiReflectionTexture(QRhi *rhi,
33 QRhiTexture::Format format,
34 const QSize &size,
35 QRhiTexture::Flags flags = {})
36{
37 auto texture = rhi->newTexture(format, pixelSize: size, sampleCount: 1, flags);
38 if (!texture->create())
39 qWarning(msg: "Failed to create reflection map texture of size %dx%d", size.width(), size.height());
40 return texture;
41}
42
43static QRhiRenderBuffer *allocateRhiReflectionRenderBuffer(QRhi *rhi,
44 QRhiRenderBuffer::Type type,
45 const QSize &size)
46{
47 auto renderBuffer = rhi->newRenderBuffer(type, pixelSize: size, sampleCount: 1);
48 if (!renderBuffer->create())
49 qWarning(msg: "Failed to build depth-stencil buffer of size %dx%d", size.width(), size.height());
50 return renderBuffer;
51}
52
53
54void QSSGRenderReflectionMap::addReflectionMapEntry(qint32 probeIdx, const QSSGRenderReflectionProbe &probe)
55{
56 QRhi *rhi = m_context.rhiContext()->rhi();
57 // Bail out if there is no QRhi, since we can't add entries without it
58 if (!rhi)
59 return;
60
61 QRhiTexture::Format rhiFormat = QRhiTexture::RGBA16F;
62
63 const QByteArray rtName = probe.debugObjectName.toLatin1();
64
65 const int mapRes = 1 << probe.reflectionMapRes;
66 QSize pixelSize(mapRes, mapRes);
67 QSSGReflectionMapEntry *pEntry = reflectionMapEntry(probeIdx);
68
69 if (!pEntry) {
70 QRhiRenderBuffer *depthStencil = allocateRhiReflectionRenderBuffer(rhi, type: QRhiRenderBuffer::DepthStencil, size: pixelSize);
71 QRhiTexture *map = allocateRhiReflectionTexture(rhi, format: rhiFormat, size: pixelSize, flags: QRhiTexture::RenderTarget | QRhiTexture::CubeMap
72 | QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips);
73 QRhiTexture *prefiltered = allocateRhiReflectionTexture(rhi, format: rhiFormat, size: pixelSize, flags: QRhiTexture::RenderTarget | QRhiTexture::CubeMap
74 | QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips);
75 m_reflectionMapList.push_back(t: QSSGReflectionMapEntry::withRhiCubeMap(probeIdx, cube: map, prefiltered, depthStencil));
76
77 pEntry = &m_reflectionMapList.back();
78 }
79
80 if (pEntry) {
81 pEntry->m_needsRender = true;
82
83 if (probe.hasScheduledUpdate)
84 pEntry->m_rendered = false;
85
86 if (!pEntry->m_rhiDepthStencil || mapRes != pEntry->m_rhiCube->pixelSize().width()) {
87 pEntry->destroyRhiResources();
88 pEntry->m_rhiDepthStencil = allocateRhiReflectionRenderBuffer(rhi, type: QRhiRenderBuffer::DepthStencil, size: pixelSize);
89 pEntry->m_rhiCube = allocateRhiReflectionTexture(rhi, format: rhiFormat, size: pixelSize, flags: QRhiTexture::RenderTarget | QRhiTexture::CubeMap
90 | QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips);
91 pEntry->m_rhiPrefilteredCube = allocateRhiReflectionTexture(rhi, format: rhiFormat, size: pixelSize, flags: QRhiTexture::RenderTarget | QRhiTexture::CubeMap
92 | QRhiTexture::MipMapped | QRhiTexture::UsedWithGenerateMips);
93 }
94
95 // Additional graphics resources: samplers, render targets.
96 if (pEntry->m_rhiRenderTargets.isEmpty()) {
97 pEntry->m_rhiRenderTargets.resize(sz: 6);
98 for (int i = 0; i < 6; ++i)
99 pEntry->m_rhiRenderTargets[i] = nullptr;
100 }
101 Q_ASSERT(pEntry->m_rhiRenderTargets.size() == 6);
102
103 if (pEntry->m_skyBoxSrbs.isEmpty()) {
104 pEntry->m_skyBoxSrbs.resize(sz: 6);
105 for (int i = 0; i < 6; ++i)
106 pEntry->m_skyBoxSrbs[i] = nullptr;
107 }
108
109
110 for (const auto face : QSSGRenderTextureCubeFaces) {
111 QRhiTextureRenderTarget *&rt(pEntry->m_rhiRenderTargets[quint8(face)]);
112 if (!rt) {
113 QRhiColorAttachment att(pEntry->m_rhiCube);
114 att.setLayer(quint8(face)); // 6 render targets, each referencing one face of the cubemap
115 QRhiTextureRenderTargetDescription rtDesc;
116 rtDesc.setColorAttachments({ att });
117 rtDesc.setDepthStencilBuffer(pEntry->m_rhiDepthStencil);
118 rt = rhi->newTextureRenderTarget(desc: rtDesc);
119 rt->setDescription(rtDesc);
120 if (!pEntry->m_rhiRenderPassDesc)
121 pEntry->m_rhiRenderPassDesc = rt->newCompatibleRenderPassDescriptor();
122 rt->setRenderPassDescriptor(pEntry->m_rhiRenderPassDesc);
123 if (!rt->create())
124 qWarning(msg: "Failed to build reflection map render target");
125 }
126 rt->setName(rtName + QByteArrayLiteral(" reflection cube face: ") + QSSGBaseTypeHelpers::displayName(face));
127 }
128
129 if (!pEntry->m_prefilterPipeline) {
130 const QSize mapSize = pEntry->m_rhiCube->pixelSize();
131
132 int mipmapCount = rhi->mipLevelsForSize(size: mapSize);
133 mipmapCount = qMin(a: mipmapCount, b: 6); // don't create more than 6 mip levels
134
135 // Create a renderbuffer for each mip level
136 for (int mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) {
137 const QSize levelSize = QSize(mapSize.width() * std::pow(x: 0.5, y: mipLevel),
138 mapSize.height() * std::pow(x: 0.5, y: mipLevel));
139 pEntry->m_prefilterMipLevelSizes.insert(key: mipLevel, value: levelSize);
140 // Setup Render targets (6 * mipmapCount)
141 QVarLengthArray<QRhiTextureRenderTarget *, 6> renderTargets;
142 for (const auto face : QSSGRenderTextureCubeFaces) {
143 QRhiColorAttachment att(pEntry->m_rhiPrefilteredCube);
144 att.setLayer(quint8(face));
145 att.setLevel(mipLevel);
146 QRhiTextureRenderTargetDescription rtDesc;
147 rtDesc.setColorAttachments({att});
148 auto renderTarget = rhi->newTextureRenderTarget(desc: rtDesc);
149 renderTarget->setName(rtName + QByteArrayLiteral(" reflection prefilter mip/face ")
150 + QByteArray::number(mipLevel) + QByteArrayLiteral("/") + QSSGBaseTypeHelpers::displayName(face));
151 renderTarget->setDescription(rtDesc);
152 if (!pEntry->m_rhiPrefilterRenderPassDesc)
153 pEntry->m_rhiPrefilterRenderPassDesc = renderTarget->newCompatibleRenderPassDescriptor();
154 renderTarget->setRenderPassDescriptor(pEntry->m_rhiPrefilterRenderPassDesc);
155 if (!renderTarget->create())
156 qWarning(msg: "Failed to build prefilter cube map render target");
157 renderTargets << renderTarget;
158 }
159 pEntry->m_rhiPrefilterRenderTargetsMap.insert(key: mipLevel, value: renderTargets);
160 }
161
162 const auto &prefilterShaderStages = m_context.shaderCache()->loadBuiltinForRhi(inKey: "reflectionprobeprefilter");
163
164 const QSSGRhiSamplerDescription samplerMipMapDesc {
165 .minFilter: QRhiSampler::Linear,
166 .magFilter: QRhiSampler::Linear,
167 .mipmap: QRhiSampler::Linear,
168 .hTiling: QRhiSampler::ClampToEdge,
169 .vTiling: QRhiSampler::ClampToEdge,
170 .zTiling: QRhiSampler::Repeat
171 };
172
173 const QSSGRhiSamplerDescription samplerDesc {
174 .minFilter: QRhiSampler::Linear,
175 .magFilter: QRhiSampler::Linear,
176 .mipmap: QRhiSampler::None,
177 .hTiling: QRhiSampler::ClampToEdge,
178 .vTiling: QRhiSampler::ClampToEdge,
179 .zTiling: QRhiSampler::Repeat
180 };
181
182 QRhiSampler *sampler = m_context.rhiContext()->sampler(samplerDescription: samplerDesc);
183 QRhiSampler *cubeSampler = m_context.rhiContext()->sampler(samplerDescription: samplerMipMapDesc);
184
185 QRhiVertexInputLayout inputLayout;
186 inputLayout.setBindings({
187 { 3 * sizeof(float) }
188 });
189 inputLayout.setAttributes({
190 { 0, 0, QRhiVertexInputAttribute::Float3, 0 }
191 });
192
193 int ubufElementSize = rhi->ubufAligned(v: 128);
194 pEntry->m_prefilterVertBuffer = rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: ubufElementSize * 6);
195 pEntry->m_prefilterVertBuffer->create();
196
197 const int uBufSamplesSize = 16 * prefilterSampleCount + 8;
198 int uBufSamplesElementSize = rhi->ubufAligned(v: uBufSamplesSize);
199 pEntry->m_prefilterFragBuffer = rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: uBufSamplesElementSize * mipmapCount);
200 pEntry->m_prefilterFragBuffer->create();
201
202 pEntry->m_prefilterPipeline = rhi->newGraphicsPipeline();
203 pEntry->m_prefilterPipeline->setCullMode(QRhiGraphicsPipeline::Front);
204 pEntry->m_prefilterPipeline->setFrontFace(QRhiGraphicsPipeline::CCW);
205 pEntry->m_prefilterPipeline->setDepthOp(QRhiGraphicsPipeline::LessOrEqual);
206 pEntry->m_prefilterPipeline->setShaderStages({
207 *prefilterShaderStages->vertexStage(),
208 *prefilterShaderStages->fragmentStage()
209 });
210
211 pEntry->m_prefilterSrb = rhi->newShaderResourceBindings();
212 pEntry->m_prefilterSrb->setBindings({
213 QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(binding: 0, stage: QRhiShaderResourceBinding::VertexStage, buf: pEntry->m_prefilterVertBuffer, size: 128),
214 QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(binding: 2, stage: QRhiShaderResourceBinding::FragmentStage, buf: pEntry->m_prefilterFragBuffer, size: uBufSamplesSize),
215 QRhiShaderResourceBinding::sampledTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: pEntry->m_rhiCube, sampler: cubeSampler)
216 });
217 pEntry->m_prefilterSrb->create();
218
219 pEntry->m_prefilterPipeline->setVertexInputLayout(inputLayout);
220 pEntry->m_prefilterPipeline->setShaderResourceBindings(pEntry->m_prefilterSrb);
221 pEntry->m_prefilterPipeline->setRenderPassDescriptor(pEntry->m_rhiPrefilterRenderPassDesc);
222 if (!pEntry->m_prefilterPipeline->create())
223 qWarning(msg: "failed to create pre-filter reflection map pipeline state");
224
225 const auto &irradianceShaderStages = m_context.shaderCache()->loadBuiltinForRhi(inKey: "environmentmapprefilter");
226
227 pEntry->m_irradiancePipeline = rhi->newGraphicsPipeline();
228 pEntry->m_irradiancePipeline->setCullMode(QRhiGraphicsPipeline::Front);
229 pEntry->m_irradiancePipeline->setFrontFace(QRhiGraphicsPipeline::CCW);
230 pEntry->m_irradiancePipeline->setDepthOp(QRhiGraphicsPipeline::LessOrEqual);
231 pEntry->m_irradiancePipeline->setShaderStages({
232 *irradianceShaderStages->vertexStage(),
233 *irradianceShaderStages->fragmentStage()
234 });
235
236 int ubufIrradianceSize = rhi->ubufAligned(v: 20);
237 pEntry->m_irradianceFragBuffer = rhi->newBuffer(type: QRhiBuffer::Dynamic, usage: QRhiBuffer::UniformBuffer, size: ubufIrradianceSize);
238 pEntry->m_irradianceFragBuffer->create();
239
240 pEntry->m_irradianceSrb = rhi->newShaderResourceBindings();
241 pEntry->m_irradianceSrb->setBindings({
242 QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(binding: 0, stage: QRhiShaderResourceBinding::VertexStage, buf: pEntry->m_prefilterVertBuffer, size: 128),
243 QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(binding: 2, stage: QRhiShaderResourceBinding::FragmentStage, buf: pEntry->m_irradianceFragBuffer, size: 20),
244 QRhiShaderResourceBinding::sampledTexture(binding: 1, stage: QRhiShaderResourceBinding::FragmentStage, tex: pEntry->m_rhiCube, sampler)
245 });
246 pEntry->m_irradianceSrb->create();
247
248 pEntry->m_irradiancePipeline->setShaderResourceBindings(pEntry->m_irradianceSrb);
249 pEntry->m_irradiancePipeline->setVertexInputLayout(inputLayout);
250 pEntry->m_irradiancePipeline->setRenderPassDescriptor(pEntry->m_rhiPrefilterRenderPassDesc);
251 if (!pEntry->m_irradiancePipeline->create())
252 qWarning(msg: "failed to create irradiance reflection map pipeline state");
253 }
254
255 pEntry->m_timeSlicing = probe.timeSlicing;
256 pEntry->m_probeIndex = probeIdx;
257 Q_QUICK3D_PROFILE_ASSIGN_ID(&probe, pEntry);
258 }
259}
260
261void QSSGRenderReflectionMap::addTexturedReflectionMapEntry(qint32 probeIdx, const QSSGRenderReflectionProbe &probe)
262{
263 QSSGReflectionMapEntry *pEntry = reflectionMapEntry(probeIdx);
264 const QSSGRenderImageTexture probeTexture = m_context.bufferManager()->loadRenderImage(image: probe.texture, inMipMode: QSSGBufferManager::MipModeFollowRenderImage);
265 if (!pEntry) {
266 if (probeTexture.m_texture)
267 m_reflectionMapList.push_back(t: QSSGReflectionMapEntry::withRhiTexturedCubeMap(probeIdx, preFiltered: probeTexture.m_texture));
268 else
269 addReflectionMapEntry(probeIdx, probe);
270 } else {
271 if (pEntry->m_rhiDepthStencil)
272 pEntry->destroyRhiResources();
273 if (probeTexture.m_texture)
274 pEntry->m_rhiPrefilteredCube = probeTexture.m_texture;
275 }
276}
277
278QSSGReflectionMapEntry *QSSGRenderReflectionMap::reflectionMapEntry(int probeIdx)
279{
280 Q_ASSERT(probeIdx >= 0);
281
282 for (int i = 0; i < m_reflectionMapList.size(); i++) {
283 QSSGReflectionMapEntry *pEntry = &m_reflectionMapList[i];
284 if (pEntry->m_probeIndex == quint32(probeIdx))
285 return pEntry;
286 }
287
288 return nullptr;
289}
290
291QSSGReflectionMapEntry::QSSGReflectionMapEntry()
292 : m_probeIndex(std::numeric_limits<quint32>::max())
293{
294}
295
296// Vertex data for rendering reflection cube map
297static const float cube[] = {
298 -1.0f,-1.0f,-1.0f, // -X side
299 -1.0f,-1.0f, 1.0f,
300 -1.0f, 1.0f, 1.0f,
301 -1.0f, 1.0f, 1.0f,
302 -1.0f, 1.0f,-1.0f,
303 -1.0f,-1.0f,-1.0f,
304
305 -1.0f,-1.0f,-1.0f, // -Z side
306 1.0f, 1.0f,-1.0f,
307 1.0f,-1.0f,-1.0f,
308 -1.0f,-1.0f,-1.0f,
309 -1.0f, 1.0f,-1.0f,
310 1.0f, 1.0f,-1.0f,
311
312 -1.0f,-1.0f,-1.0f, // -Y side
313 1.0f,-1.0f,-1.0f,
314 1.0f,-1.0f, 1.0f,
315 -1.0f,-1.0f,-1.0f,
316 1.0f,-1.0f, 1.0f,
317 -1.0f,-1.0f, 1.0f,
318
319 -1.0f, 1.0f,-1.0f, // +Y side
320 -1.0f, 1.0f, 1.0f,
321 1.0f, 1.0f, 1.0f,
322 -1.0f, 1.0f,-1.0f,
323 1.0f, 1.0f, 1.0f,
324 1.0f, 1.0f,-1.0f,
325
326 1.0f, 1.0f,-1.0f, // +X side
327 1.0f, 1.0f, 1.0f,
328 1.0f,-1.0f, 1.0f,
329 1.0f,-1.0f, 1.0f,
330 1.0f,-1.0f,-1.0f,
331 1.0f, 1.0f,-1.0f,
332
333 -1.0f, 1.0f, 1.0f, // +Z side
334 -1.0f,-1.0f, 1.0f,
335 1.0f, 1.0f, 1.0f,
336 -1.0f,-1.0f, 1.0f,
337 1.0f,-1.0f, 1.0f,
338 1.0f, 1.0f, 1.0f,
339
340 0.0f, 1.0f, // -X side
341 1.0f, 1.0f,
342 1.0f, 0.0f,
343 1.0f, 0.0f,
344 0.0f, 0.0f,
345 0.0f, 1.0f,
346
347 1.0f, 1.0f, // -Z side
348 0.0f, 0.0f,
349 0.0f, 1.0f,
350 1.0f, 1.0f,
351 1.0f, 0.0f,
352 0.0f, 0.0f,
353
354 1.0f, 0.0f, // -Y side
355 1.0f, 1.0f,
356 0.0f, 1.0f,
357 1.0f, 0.0f,
358 0.0f, 1.0f,
359 0.0f, 0.0f,
360
361 1.0f, 0.0f, // +Y side
362 0.0f, 0.0f,
363 0.0f, 1.0f,
364 1.0f, 0.0f,
365 0.0f, 1.0f,
366 1.0f, 1.0f,
367
368 1.0f, 0.0f, // +X side
369 0.0f, 0.0f,
370 0.0f, 1.0f,
371 0.0f, 1.0f,
372 1.0f, 1.0f,
373 1.0f, 0.0f,
374
375 0.0f, 0.0f, // +Z side
376 0.0f, 1.0f,
377 1.0f, 0.0f,
378 0.0f, 1.0f,
379 1.0f, 1.0f,
380 1.0f, 0.0f,
381};
382
383float radicalInverseVdC(uint bits)
384{
385 bits = (bits << 16u) | (bits >> 16u);
386 bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
387 bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
388 bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
389 bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
390 return float(bits) * 2.3283064365386963e-10; // / 0x100000000
391}
392
393QVector2D hammersley(uint i, uint N)
394{
395 return QVector2D(float(i) / float(N), radicalInverseVdC(bits: i));
396}
397
398QVector3D importanceSampleGGX(QVector2D xi, float roughness)
399{
400 float a = roughness*roughness;
401
402 float phi = 2.0f * M_PI * xi.x();
403 float cosTheta = sqrt(x: (1.0f - xi.y()) / (1.0f + (a*a - 1.0f) * xi.y()));
404 float sinTheta = sqrt(x: 1.0f - cosTheta * cosTheta);
405
406 // from spherical coordinates to cartesian coordinates
407 return QVector3D(cos(x: phi) * sinTheta, sin(x: phi) * sinTheta, cosTheta);
408}
409
410float distributionGGX(float nDotH, float roughness)
411{
412 float a = roughness * roughness;
413 float a2 = a * a;
414 float nDotH2 = nDotH * nDotH;
415
416 float nom = a2;
417 float denom = nDotH2 * (a2 - 1.0f) + 1.0f;
418 denom = M_PI * denom * denom;
419
420 return nom / denom;
421}
422
423void fillPrefilterValues(float roughness, float resolution,
424 QVarLengthArray<QVector4D, prefilterSampleCount> &sampleDirections,
425 float &invTotalWeight, uint &sampleCount)
426{
427 for (int i = 0; i < prefilterSampleCount * 8; ++i) {
428 const QVector2D xi = hammersley(i, N: prefilterSampleCount);
429 const QVector3D half = importanceSampleGGX(xi, roughness);
430 QVector3D light = 2.0f * half.z() * half - QVector3D(0, 0, 1);
431 light.normalize();
432 const float D = distributionGGX(nDotH: half.z(), roughness);
433 const float pdf = D * half.z() / (4.0f * half.z()) + 0.0001f;
434 const float saTexel = 4.0f * M_PI / (6.0f * resolution * resolution);
435 const float saSample = 1.0f / (float(prefilterSampleCount) * pdf + 0.0001f);
436 float mipLevel = roughness == 0.0f ? 0.0f : 0.5f * log2(x: saSample / saTexel);
437 if (light.z() > 0) {
438 sampleDirections.append(t: QVector4D(light, mipLevel));
439 invTotalWeight += light.z();
440 sampleCount++;
441 if (sampleCount >= prefilterSampleCount)
442 break;
443 }
444 }
445 invTotalWeight = 1.0f / invTotalWeight;
446}
447
448void QSSGReflectionMapEntry::renderMips(QSSGRhiContext *context)
449{
450 auto *rhi = context->rhi();
451 auto *cb = context->commandBuffer();
452
453 auto *rub = rhi->nextResourceUpdateBatch();
454 rub->generateMips(tex: m_rhiCube);
455 QRhiBuffer *vertexBuffer = rhi->newBuffer(type: QRhiBuffer::Immutable, usage: QRhiBuffer::VertexBuffer, size: sizeof(cube));
456 vertexBuffer->create();
457 vertexBuffer->deleteLater();
458 rub->uploadStaticBuffer(buf: vertexBuffer, data: cube);
459 cb->resourceUpdate(resourceUpdates: rub);
460
461 const QRhiCommandBuffer::VertexInput vbufBinding(vertexBuffer, 0);
462
463 int ubufElementSize = rhi->ubufAligned(v: 128);
464
465 const int uBufSamplesSize = 16 * prefilterSampleCount + 8;
466 int uBufSamplesElementSize = rhi->ubufAligned(v: uBufSamplesSize);
467 int uBufIrradianceElementSize = rhi->ubufAligned(v: 20);
468
469 // Uniform Data
470 QMatrix4x4 mvp = rhi->clipSpaceCorrMatrix();
471 mvp.perspective(verticalAngle: 90.0f, aspectRatio: 1.0f, nearPlane: 0.1f, farPlane: 10.0f);
472
473 auto lookAt = [](const QVector3D &eye, const QVector3D &center, const QVector3D &up) {
474 QMatrix4x4 viewMatrix;
475 viewMatrix.lookAt(eye, center, up);
476 return viewMatrix;
477 };
478 QVarLengthArray<QMatrix4x4, 6> views;
479 views.append(t: lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(1.0, 0.0, 0.0), QVector3D(0.0f, -1.0f, 0.0f)));
480 views.append(t: lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(-1.0, 0.0, 0.0), QVector3D(0.0f, -1.0f, 0.0f)));
481 if (rhi->isYUpInFramebuffer()) {
482 views.append(t: lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 1.0, 0.0), QVector3D(0.0f, 0.0f, 1.0f)));
483 views.append(t: lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, -1.0, 0.0), QVector3D(0.0f, 0.0f, -1.0f)));
484 } else {
485 views.append(t: lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, -1.0, 0.0), QVector3D(0.0f, 0.0f, -1.0f)));
486 views.append(t: lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 1.0, 0.0), QVector3D(0.0f, 0.0f, 1.0f)));
487 }
488 views.append(t: lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 0.0, 1.0), QVector3D(0.0f, -1.0f, 0.0f)));
489 views.append(t: lookAt(QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0, 0.0, -1.0), QVector3D(0.0f, -1.0f, 0.0f)));
490
491 rub = rhi->nextResourceUpdateBatch();
492 for (const auto face : QSSGRenderTextureCubeFaces) {
493 rub->updateDynamicBuffer(buf: m_prefilterVertBuffer, offset: quint8(face) * ubufElementSize, size: 64, data: mvp.constData());
494 rub->updateDynamicBuffer(buf: m_prefilterVertBuffer, offset: quint8(face) * ubufElementSize + 64, size: 64, data: views[quint8(face)].constData());
495 }
496
497 const QSize mapSize = m_rhiCube->pixelSize();
498
499 int mipmapCount = rhi->mipLevelsForSize(size: mapSize);
500 mipmapCount = qMin(a: mipmapCount, b: 6);
501
502 const float resolution = mapSize.width();
503 QVarLengthArray<QVector4D, prefilterSampleCount> sampleDirections;
504
505 // set the samples uniform buffer data
506 for (int mipLevel = 0; mipLevel < mipmapCount - 1; ++mipLevel) {
507 Q_ASSERT(mipmapCount - 2);
508 const float roughness = float(mipLevel) / float(mipmapCount - 2);
509 float invTotalWeight = 0.0f;
510 uint sampleCount = 0;
511
512 sampleDirections.clear();
513 fillPrefilterValues(roughness, resolution, sampleDirections, invTotalWeight, sampleCount);
514
515 rub->updateDynamicBuffer(buf: m_prefilterFragBuffer, offset: mipLevel * uBufSamplesElementSize, size: 16 * prefilterSampleCount, data: sampleDirections.constData());
516 rub->updateDynamicBuffer(buf: m_prefilterFragBuffer, offset: mipLevel * uBufSamplesElementSize + 16 * prefilterSampleCount, size: 4, data: &invTotalWeight);
517 rub->updateDynamicBuffer(buf: m_prefilterFragBuffer, offset: mipLevel * uBufSamplesElementSize + 16 * prefilterSampleCount + 4, size: 4, data: &sampleCount);
518 }
519 {
520 const float roughness = 0.0f; // doesn't matter for irradiance
521 const float lodBias = 0.0f;
522 const int distribution = 0;
523 const int sampleCount = resolution / 4;
524
525 rub->updateDynamicBuffer(buf: m_irradianceFragBuffer, offset: 0, size: 4, data: &roughness);
526 rub->updateDynamicBuffer(buf: m_irradianceFragBuffer, offset: 4, size: 4, data: &resolution);
527 rub->updateDynamicBuffer(buf: m_irradianceFragBuffer, offset: 4 + 4, size: 4, data: &lodBias);
528 rub->updateDynamicBuffer(buf: m_irradianceFragBuffer, offset: 4 + 4 + 4, size: 4, data: &sampleCount);
529 rub->updateDynamicBuffer(buf: m_irradianceFragBuffer, offset: 4 + 4 + 4 + 4, size: 4, data: &distribution);
530 }
531
532 cb->resourceUpdate(resourceUpdates: rub);
533
534 // Render
535 for (int mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) {
536 if (mipLevel > 0 && m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::AllFacesAtOnce)
537 mipLevel = m_timeSliceFrame;
538
539 for (auto face : QSSGRenderTextureCubeFaces) {
540 if (m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
541 face = m_timeSliceFace;
542
543 cb->beginPass(rt: m_rhiPrefilterRenderTargetsMap[mipLevel][quint8(face)], colorClearValue: QColor(0, 0, 0, 1), depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: nullptr, flags: QSSGRhiContext::commonPassFlags());
544 QSSGRHICTX_STAT(context, beginRenderPass(m_rhiPrefilterRenderTargetsMap[mipLevel][quint8(face)]));
545 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
546 if (mipLevel < mipmapCount - 1) {
547 // Specular pre-filtered Cube Map levels
548 cb->setGraphicsPipeline(m_prefilterPipeline);
549 cb->setVertexInput(startBinding: 0, bindingCount: 1, bindings: &vbufBinding);
550 cb->setViewport(QRhiViewport(0, 0, m_prefilterMipLevelSizes[mipLevel].width(), m_prefilterMipLevelSizes[mipLevel].height()));
551 QVector<QPair<int, quint32>> dynamicOffsets = {
552 { 0, quint32(ubufElementSize * quint8(face)) },
553 { 2, quint32(uBufSamplesElementSize * mipLevel) }
554 };
555 cb->setShaderResources(srb: m_prefilterSrb, dynamicOffsetCount: 2, dynamicOffsets: dynamicOffsets.constData());
556 } else {
557 // Diffuse Irradiance
558 cb->setGraphicsPipeline(m_irradiancePipeline);
559 cb->setVertexInput(startBinding: 0, bindingCount: 1, bindings: &vbufBinding);
560 cb->setViewport(QRhiViewport(0, 0, m_prefilterMipLevelSizes[mipLevel].width(), m_prefilterMipLevelSizes[mipLevel].height()));
561 QVector<QPair<int, quint32>> dynamicOffsets = {
562 { 0, quint32(ubufElementSize * quint8(face)) },
563 { 2, quint32(uBufIrradianceElementSize) }
564 };
565 cb->setShaderResources(srb: m_irradianceSrb, dynamicOffsetCount: 1, dynamicOffsets: dynamicOffsets.constData());
566 }
567 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
568 cb->draw(vertexCount: 36);
569 QSSGRHICTX_STAT(context, draw(36, 1));
570 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DRenderCall, 36llu | (1llu << 32), profilingId);
571 cb->endPass();
572 QSSGRHICTX_STAT(context, endRenderPass());
573 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QSSG_RENDERPASS_NAME("reflection_map", mipLevel, face));
574
575 if (m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::IndividualFaces)
576 break;
577 }
578
579 if (mipLevel > 0 && m_timeSlicing == QSSGRenderReflectionProbe::ReflectionTimeSlicing::AllFacesAtOnce) {
580 m_timeSliceFrame++;
581 if (m_timeSliceFrame >= mipmapCount)
582 m_timeSliceFrame = 1;
583 break;
584 }
585 }
586 cb->debugMarkEnd();
587}
588
589QSSGReflectionMapEntry QSSGReflectionMapEntry::withRhiTexturedCubeMap(quint32 probeIdx, QRhiTexture *prefiltered)
590{
591 QSSGReflectionMapEntry e;
592 e.m_probeIndex = probeIdx;
593 e.m_rhiPrefilteredCube = prefiltered;
594 return e;
595}
596
597QSSGReflectionMapEntry QSSGReflectionMapEntry::withRhiCubeMap(quint32 probeIdx,
598 QRhiTexture *cube,
599 QRhiTexture *prefiltered,
600 QRhiRenderBuffer *depthStencil)
601{
602 QSSGReflectionMapEntry e;
603 e.m_probeIndex = probeIdx;
604 e.m_rhiCube = cube;
605 e.m_rhiPrefilteredCube = prefiltered;
606 e.m_rhiDepthStencil = depthStencil;
607 return e;
608}
609
610void QSSGReflectionMapEntry::destroyRhiResources()
611{
612 delete m_rhiCube;
613 m_rhiCube = nullptr;
614 // Without depth stencil the prefiltered cubemap is assumed to be not owned here and shouldn't be deleted
615 if (m_rhiDepthStencil)
616 delete m_rhiPrefilteredCube;
617 m_rhiPrefilteredCube = nullptr;
618 delete m_rhiDepthStencil;
619 m_rhiDepthStencil = nullptr;
620
621 qDeleteAll(c: m_rhiRenderTargets);
622 m_rhiRenderTargets.clear();
623 delete m_rhiRenderPassDesc;
624 m_rhiRenderPassDesc = nullptr;
625
626 delete m_prefilterPipeline;
627 m_prefilterPipeline = nullptr;
628 delete m_irradiancePipeline;
629 m_irradiancePipeline = nullptr;
630 delete m_prefilterSrb;
631 m_prefilterSrb = nullptr;
632 delete m_irradianceSrb;
633 m_irradianceSrb = nullptr;
634 delete m_prefilterVertBuffer;
635 m_prefilterVertBuffer = nullptr;
636 delete m_prefilterFragBuffer;
637 m_prefilterFragBuffer = nullptr;
638 delete m_irradianceFragBuffer;
639 m_irradianceFragBuffer = nullptr;
640 delete m_rhiPrefilterRenderPassDesc;
641 m_rhiPrefilterRenderPassDesc = nullptr;
642 for (const auto &e : std::as_const(t&: m_rhiPrefilterRenderTargetsMap))
643 qDeleteAll(c: e);
644 m_rhiPrefilterRenderTargetsMap.clear();
645 m_prefilterMipLevelSizes.clear();
646}
647
648QT_END_NAMESPACE
649

source code of qtquick3d/src/runtimerender/qssgrenderreflectionmap.cpp