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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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