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