1// Copyright (C) 2008-2012 NVIDIA Corporation.
2// Copyright (C) 2019 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4
5#include <QtQuick3DRuntimeRender/private/qssgrenderlayer_p.h>
6#include <QtQuick3DRuntimeRender/private/qssgrendershadowmap_p.h>
7#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
8#include "qssgrendercontextcore.h"
9
10QT_BEGIN_NAMESPACE
11
12static constexpr quint32 NUM_TEXTURE_SIZES = 5;
13
14static QRhiTexture *allocateRhiShadowTexture(QRhi *rhi, QRhiTexture::Format format, const QSize &size, quint32 numLayers, QRhiTexture::Flags flags)
15{
16 auto texture = rhi->newTexture(format, pixelSize: size, sampleCount: 1, flags);
17 if (flags & QRhiTexture::TextureArray)
18 texture->setArraySize(numLayers);
19 if (!texture->create())
20 qWarning(msg: "Failed to create shadow map texture of size %dx%d", size.width(), size.height());
21 return texture;
22}
23
24static QRhiRenderBuffer *allocateRhiShadowRenderBuffer(QRhi *rhi, QRhiRenderBuffer::Type type, const QSize &size)
25{
26 auto renderBuffer = rhi->newRenderBuffer(type, pixelSize: size, sampleCount: 1);
27 if (!renderBuffer->create())
28 qWarning(msg: "Failed to build depth-stencil buffer of size %dx%d", size.width(), size.height());
29 return renderBuffer;
30}
31
32static QRhiTexture::Format getShadowMapTextureFormat(QRhi *rhi, bool use32bit)
33{
34 if (use32bit && rhi->isTextureFormatSupported(format: QRhiTexture::R32F))
35 return QRhiTexture::R32F;
36 if (rhi->isTextureFormatSupported(format: QRhiTexture::R16F))
37 return QRhiTexture::R16F;
38 return QRhiTexture::R16;
39}
40
41static quint8 mapSizeToIndex(quint32 mapSize)
42{
43 Q_ASSERT(!(mapSize & (mapSize - 1)) && "shadow map resolution is power of 2");
44 Q_ASSERT(mapSize >= 256);
45 quint8 index = qCountTrailingZeroBits(v: mapSize) - 8;
46 Q_ASSERT(index < NUM_TEXTURE_SIZES);
47 return index;
48}
49
50static quint32 indexToMapSize(quint8 index)
51{
52 Q_ASSERT(index < NUM_TEXTURE_SIZES);
53 return 1 << (index + 8);
54}
55
56QSSGRenderShadowMap::QSSGRenderShadowMap(const QSSGRenderContextInterface &inContext)
57 : m_context(inContext)
58{
59}
60
61QSSGRenderShadowMap::~QSSGRenderShadowMap()
62{
63 releaseCachedResources();
64}
65
66void QSSGRenderShadowMap::releaseCachedResources()
67{
68 for (QSSGShadowMapEntry &entry : m_shadowMapList)
69 entry.destroyRhiResources();
70
71 for (auto &hash : m_depthTextureArrays) {
72 for (auto &textureArray : hash)
73 delete textureArray;
74 hash.clear();
75 }
76 m_shadowMapList.clear();
77}
78
79void QSSGRenderShadowMap::addShadowMaps(const QSSGShaderLightList &renderableLights)
80{
81 QRhi *rhi = m_context.rhiContext()->rhi();
82 // Bail out if there is no QRhi, since we can't add entries without it
83 if (!rhi)
84 return;
85
86 constexpr quint32 MAX_SPLITS = 4;
87 const quint32 numLights = renderableLights.size();
88 qsizetype numShadows = 0;
89 std::array<std::array<quint8, NUM_TEXTURE_SIZES>, 2> textureSizeLayerCount = {}; // 0: 16 bit, 1: 32bit
90 QVarLengthArray<quint8, 16> lightIndexToLayerStartIndex;
91 lightIndexToLayerStartIndex.resize(sz: numLights * MAX_SPLITS);
92
93 // NOTE: This is a quite ugly workaround. If 32bit is not supported then go through all lights and disable it.
94 const bool supports32BitTextures = rhi->isTextureFormatSupported(format: QRhiTexture::R32F);
95 if (!supports32BitTextures) {
96 bool any32bit = false;
97 for (quint32 lightIndex = 0; lightIndex < numLights; ++lightIndex) {
98 QSSGRenderLight *light = renderableLights.at(idx: lightIndex).light;
99 any32bit = any32bit || light->m_use32BitShadowmap;
100 light->m_use32BitShadowmap = false;
101 }
102 static bool warned32bit = false;
103 if (!warned32bit && any32bit) {
104 qWarning() << "WARN: 32 bit shadow maps are unsupported, falling back to 16 bit.";
105 warned32bit = true;
106 }
107 }
108
109 for (quint32 lightIndex = 0; lightIndex < numLights; ++lightIndex) {
110 const QSSGShaderLight &shaderLight = renderableLights.at(idx: lightIndex);
111 ShadowMapModes mapMode = (shaderLight.light->type == QSSGRenderLight::Type::PointLight) ? ShadowMapModes::CUBE
112 : ShadowMapModes::VSM;
113 if (shaderLight.shadows)
114 numShadows += 1;
115 if (!shaderLight.shadows || mapMode == ShadowMapModes::CUBE)
116 continue;
117
118 quint32 mapSize = shaderLight.light->m_shadowMapRes;
119 quint8 &layerCount = textureSizeLayerCount[shaderLight.light->m_use32BitShadowmap ? 1 : 0][mapSizeToIndex(mapSize)];
120 quint8 layerIndex = layerCount;
121 layerCount += shaderLight.light->m_csmNumSplits + 1;
122 lightIndexToLayerStartIndex[lightIndex] = layerIndex;
123 }
124
125 // Only recreate shadow assets if something has changed
126 bool needsRebuild = numShadows != shadowMapEntryCount();
127 if (!needsRebuild) {
128 // Check if relevant shadow properties has changed
129 for (quint32 lightIndex = 0; lightIndex < numLights; ++lightIndex) {
130 const QSSGShaderLight &shaderLight = renderableLights.at(idx: lightIndex);
131 if (!shaderLight.shadows)
132 continue;
133
134 QSSGShadowMapEntry *pEntry = shadowMapEntry(lightIdx: lightIndex);
135 if (!pEntry) {
136 needsRebuild = true;
137 break;
138 }
139
140 QRhiTexture::Format textureFormat = getShadowMapTextureFormat(rhi, use32bit: shaderLight.light->m_use32BitShadowmap);
141 ShadowMapModes mapMode = (shaderLight.light->type == QSSGRenderLight::Type::PointLight) ? ShadowMapModes::CUBE
142 : ShadowMapModes::VSM;
143 quint32 mapSize = shaderLight.light->m_shadowMapRes;
144 quint32 csmNumSplits = shaderLight.light->m_csmNumSplits;
145 quint32 layerIndex = mapMode == ShadowMapModes::VSM ? lightIndexToLayerStartIndex[lightIndex] : 0;
146 if (!pEntry->isCompatible(mapSize: QSize(mapSize, mapSize), layerIndex, csmNumSplits, mapMode, textureFormat)) {
147 needsRebuild = true;
148 break;
149 }
150 }
151 }
152
153 if (!needsRebuild)
154 return;
155
156 releaseCachedResources();
157
158 // Create VSM texture arrays
159 for (quint32 hashI = 0; hashI < 2; ++hashI) {
160 const bool use32bit = hashI == 1;
161 QRhiTexture::Format rhiFormat = getShadowMapTextureFormat(rhi, use32bit);
162 for (quint32 sizeI = 0; sizeI < NUM_TEXTURE_SIZES; sizeI++) {
163 const quint32 numLayers = textureSizeLayerCount[hashI][sizeI];
164 if (numLayers == 0)
165 continue;
166
167 const quint32 mapSize = indexToMapSize(index: sizeI);
168 QSize texSize = QSize(mapSize, mapSize);
169 auto texture = allocateRhiShadowTexture(rhi, format: rhiFormat, size: texSize, numLayers, flags: QRhiTexture::RenderTarget | QRhiTexture::TextureArray);
170 m_depthTextureArrays[hashI].insert(key: texSize, value: texture);
171 }
172 }
173
174 // Setup render targets
175 for (quint32 lightIdx = 0; lightIdx < numLights; ++lightIdx) {
176 const auto &shaderLight = renderableLights.at(idx: lightIdx);
177 if (!shaderLight.shadows)
178 continue;
179
180 const bool use32bit = shaderLight.light->m_use32BitShadowmap;
181 QSize mapSize = QSize(shaderLight.light->m_shadowMapRes, shaderLight.light->m_shadowMapRes);
182 ShadowMapModes mapMode = (shaderLight.light->type == QSSGRenderLight::Type::PointLight) ? ShadowMapModes::CUBE
183 : ShadowMapModes::VSM;
184 switch (mapMode) {
185 case ShadowMapModes::VSM: {
186 quint32 layerStartIndex = lightIndexToLayerStartIndex.value(i: lightIdx);
187 quint32 csmNumSplits = shaderLight.light->m_csmNumSplits;
188 addDirectionalShadowMap(lightIdx, size: mapSize, use32bit, layerStartIndex, csmNumSplits, renderNodeObjName: shaderLight.light->debugObjectName);
189 break;
190 }
191 case ShadowMapModes::CUBE: {
192 addCubeShadowMap(lightIdx, size: mapSize, use32bit, renderNodeObjName: shaderLight.light->debugObjectName);
193 break;
194 }
195 default:
196 Q_UNREACHABLE();
197 break;
198 }
199 }
200}
201
202QSSGShadowMapEntry *QSSGRenderShadowMap::addDirectionalShadowMap(qint32 lightIdx,
203 QSize size,
204 bool use32bit,
205 quint32 layerStartIndex,
206 quint32 csmNumSplits,
207 const QString &renderNodeObjName)
208{
209 QRhi *rhi = m_context.rhiContext()->rhi();
210 QSSGShadowMapEntry *pEntry = shadowMapEntry(lightIdx);
211
212 Q_ASSERT(rhi);
213 Q_ASSERT(!pEntry);
214
215 auto texture = m_depthTextureArrays[use32bit ? 1 : 0].value(key: size);
216 Q_ASSERT(texture);
217 m_shadowMapList.push_back(t: QSSGShadowMapEntry::withRhiDepthMap(lightIdx, mode: ShadowMapModes::VSM, textureArray: texture));
218
219 pEntry = &m_shadowMapList.back();
220 pEntry->m_csmNumSplits = csmNumSplits;
221
222 // Additional graphics resources: samplers, render targets.
223 for (quint32 splitIndex = 0; splitIndex < csmNumSplits + 1; splitIndex++) {
224 QRhiTextureRenderTarget *&rt(pEntry->m_rhiRenderTargets[splitIndex]);
225 Q_ASSERT(!rt);
226
227 pEntry->m_rhiDepthStencil[splitIndex] = allocateRhiShadowRenderBuffer(rhi, type: QRhiRenderBuffer::DepthStencil, size);
228
229 QRhiTextureRenderTargetDescription rtDesc;
230 QRhiColorAttachment attachment(pEntry->m_rhiDepthTextureArray);
231 attachment.setLayer(layerStartIndex + splitIndex);
232 rtDesc.setColorAttachments({ attachment });
233 rtDesc.setDepthStencilBuffer(pEntry->m_rhiDepthStencil[splitIndex]);
234 rt = rhi->newTextureRenderTarget(desc: rtDesc);
235 rt->setDescription(rtDesc);
236 // The same renderpass descriptor can be reused since the
237 // format, load/store ops are the same regardless of the shadow mode.
238 if (!pEntry->m_rhiRenderPassDesc[splitIndex])
239 pEntry->m_rhiRenderPassDesc[splitIndex] = rt->newCompatibleRenderPassDescriptor();
240 rt->setRenderPassDescriptor(pEntry->m_rhiRenderPassDesc[splitIndex]);
241 if (!rt->create())
242 qWarning(msg: "Failed to build shadow map render target");
243
244 const QByteArray rtName = renderNodeObjName.toLatin1();
245 rt->setName(rtName + QByteArrayLiteral(" shadow map"));
246 }
247
248 pEntry->m_lightIndex = lightIdx;
249 pEntry->m_depthArrayIndex = layerStartIndex;
250
251 return pEntry;
252}
253
254QSSGShadowMapEntry *QSSGRenderShadowMap::addCubeShadowMap(qint32 lightIdx, QSize size, bool use32bit, const QString &renderNodeObjName)
255{
256 QRhi *rhi = m_context.rhiContext()->rhi();
257 QSSGShadowMapEntry *pEntry = shadowMapEntry(lightIdx);
258
259 Q_ASSERT(rhi);
260 Q_ASSERT(!pEntry);
261
262 QRhiTexture::Format rhiFormat = getShadowMapTextureFormat(rhi, use32bit);
263 QRhiTexture *depthMap = allocateRhiShadowTexture(rhi, format: rhiFormat, size, numLayers: 0, flags: QRhiTexture::RenderTarget | QRhiTexture::CubeMap);
264 QRhiRenderBuffer *depthStencil = allocateRhiShadowRenderBuffer(rhi, type: QRhiRenderBuffer::DepthStencil, size);
265 m_shadowMapList.push_back(t: QSSGShadowMapEntry::withRhiDepthCubeMap(lightIdx, mode: ShadowMapModes::CUBE, depthCube: depthMap, depthStencil));
266 pEntry = &m_shadowMapList.back();
267
268 const QByteArray rtName = renderNodeObjName.toLatin1();
269
270 for (const auto face : QSSGRenderTextureCubeFaces) {
271 QRhiTextureRenderTarget *&rt(pEntry->m_rhiRenderTargets[quint8(face)]);
272 Q_ASSERT(!rt);
273 QRhiColorAttachment att(pEntry->m_rhiDepthCube);
274 att.setLayer(quint8(face)); // 6 render targets, each referencing one face of the cubemap
275 QRhiTextureRenderTargetDescription rtDesc;
276 rtDesc.setColorAttachments({ att });
277 rtDesc.setDepthStencilBuffer(pEntry->m_rhiDepthStencil[0]);
278 rt = rhi->newTextureRenderTarget(desc: rtDesc);
279 rt->setDescription(rtDesc);
280 if (!pEntry->m_rhiRenderPassDesc[0])
281 pEntry->m_rhiRenderPassDesc[0] = rt->newCompatibleRenderPassDescriptor();
282 rt->setRenderPassDescriptor(pEntry->m_rhiRenderPassDesc[0]);
283 if (!rt->create())
284 qWarning(msg: "Failed to build shadow map render target");
285 rt->setName(rtName + QByteArrayLiteral(" shadow cube face: ") + QSSGBaseTypeHelpers::displayName(face));
286 }
287
288 return pEntry;
289}
290
291QSSGShadowMapEntry *QSSGRenderShadowMap::shadowMapEntry(int lightIdx)
292{
293 Q_ASSERT(lightIdx >= 0);
294
295 for (int i = 0; i < m_shadowMapList.size(); i++) {
296 QSSGShadowMapEntry *pEntry = &m_shadowMapList[i];
297 if (pEntry->m_lightIndex == quint32(lightIdx))
298 return pEntry;
299 }
300
301 return nullptr;
302}
303
304QSSGShadowMapEntry::QSSGShadowMapEntry()
305 : m_lightIndex(std::numeric_limits<quint32>::max())
306 , m_shadowMapMode(ShadowMapModes::VSM)
307{
308}
309
310QSSGShadowMapEntry QSSGShadowMapEntry::withRhiDepthMap(quint32 lightIdx, ShadowMapModes mode, QRhiTexture *textureArray)
311{
312 QSSGShadowMapEntry e;
313 e.m_lightIndex = lightIdx;
314 e.m_shadowMapMode = mode;
315 e.m_rhiDepthTextureArray = textureArray;
316 return e;
317}
318
319QSSGShadowMapEntry QSSGShadowMapEntry::withRhiDepthCubeMap(quint32 lightIdx, ShadowMapModes mode, QRhiTexture *depthCube, QRhiRenderBuffer *depthStencil)
320{
321 QSSGShadowMapEntry e;
322 e.m_lightIndex = lightIdx;
323 e.m_shadowMapMode = mode;
324 e.m_rhiDepthCube = depthCube;
325 e.m_rhiDepthStencil[0] = depthStencil;
326 return e;
327}
328
329bool QSSGShadowMapEntry::isCompatible(QSize mapSize, quint32 layerIndex, quint32 csmNumSplits, ShadowMapModes mapMode, QRhiTexture::Format textureFormat)
330{
331 if (csmNumSplits != m_csmNumSplits)
332 return false;
333
334 if (mapMode != m_shadowMapMode)
335 return false;
336
337 switch (mapMode) {
338 case ShadowMapModes::CUBE: {
339 if (mapSize != m_rhiDepthCube->pixelSize()) {
340 return false;
341 }
342 break;
343 }
344 case ShadowMapModes::VSM: {
345 if (mapSize != m_rhiDepthTextureArray->pixelSize() || int(layerIndex) >= m_rhiDepthTextureArray->arraySize()
346 || textureFormat != m_rhiDepthTextureArray->format()) {
347 return false;
348 }
349 break;
350 }
351 default:
352 Q_UNREACHABLE();
353 break;
354 }
355
356 return true;
357}
358
359void QSSGShadowMapEntry::destroyRhiResources()
360{
361 m_rhiDepthTextureArray = nullptr;
362
363 delete m_rhiDepthCube;
364 m_rhiDepthCube = nullptr;
365 qDeleteAll(c: m_rhiDepthStencil);
366 m_rhiDepthStencil.fill(u: nullptr);
367 qDeleteAll(c: m_rhiRenderTargets);
368 m_rhiRenderTargets.fill(u: nullptr);
369 qDeleteAll(c: m_rhiRenderPassDesc);
370 m_rhiRenderPassDesc.fill(u: nullptr);
371}
372
373QT_END_NAMESPACE
374

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