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 <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h>
9
10QT_BEGIN_NAMESPACE
11
12QSSGRenderShadowMap::QSSGRenderShadowMap(const QSSGRenderContextInterface &inContext)
13 : m_context(inContext)
14{
15}
16
17QSSGRenderShadowMap::~QSSGRenderShadowMap()
18{
19 releaseCachedResources();
20}
21
22void QSSGRenderShadowMap::releaseCachedResources()
23{
24 for (QSSGShadowMapEntry &entry : m_shadowMapList)
25 entry.destroyRhiResources();
26
27 m_shadowMapList.clear();
28}
29
30static QRhiTexture *allocateRhiShadowTexture(QRhi *rhi,
31 QRhiTexture::Format format,
32 const QSize &size,
33 QRhiTexture::Flags flags = {})
34{
35 auto texture = rhi->newTexture(format, pixelSize: size, sampleCount: 1, flags);
36 if (!texture->create())
37 qWarning(msg: "Failed to create shadow map texture of size %dx%d", size.width(), size.height());
38 return texture;
39}
40
41static QRhiRenderBuffer *allocateRhiShadowRenderBuffer(QRhi *rhi,
42 QRhiRenderBuffer::Type type,
43 const QSize &size)
44{
45 auto renderBuffer = rhi->newRenderBuffer(type, pixelSize: size, sampleCount: 1);
46 if (!renderBuffer->create())
47 qWarning(msg: "Failed to build depth-stencil buffer of size %dx%d", size.width(), size.height());
48 return renderBuffer;
49}
50
51static inline void setupForRhiDepthCube(QRhi *rhi,
52 QSSGShadowMapEntry *entry,
53 const QSize &size,
54 QRhiTexture::Format format)
55{
56 entry->m_rhiDepthCube = allocateRhiShadowTexture(rhi, format, size, flags: QRhiTexture::RenderTarget | QRhiTexture::CubeMap);
57 entry->m_rhiCubeCopy = allocateRhiShadowTexture(rhi, format, size, flags: QRhiTexture::RenderTarget | QRhiTexture::CubeMap);
58 entry->m_rhiDepthStencil = allocateRhiShadowRenderBuffer(rhi, type: QRhiRenderBuffer::DepthStencil, size);
59}
60
61static inline void setupForRhiDepth(QRhi *rhi,
62 QSSGShadowMapEntry *entry,
63 const QSize &size,
64 QRhiTexture::Format format)
65{
66 entry->m_rhiDepthMap = allocateRhiShadowTexture(rhi, format, size, flags: QRhiTexture::RenderTarget);
67 entry->m_rhiDepthCopy = allocateRhiShadowTexture(rhi, format, size, flags: QRhiTexture::RenderTarget);
68 entry->m_rhiDepthStencil = allocateRhiShadowRenderBuffer(rhi, type: QRhiRenderBuffer::DepthStencil, size);
69}
70
71void QSSGRenderShadowMap::addShadowMapEntry(qint32 lightIdx,
72 qint32 width,
73 qint32 height,
74 ShadowMapModes mode,
75 const QString &renderNodeObjName)
76{
77 QRhi *rhi = m_context.rhiContext()->rhi();
78 // Bail out if there is no QRhi, since we can't add entries without it
79 if (!rhi)
80 return;
81
82
83 const QSize pixelSize(width, height);
84
85 QRhiTexture::Format rhiFormat = QRhiTexture::R16F;
86 if (!rhi->isTextureFormatSupported(format: rhiFormat))
87 rhiFormat = QRhiTexture::R16;
88
89 const QByteArray rtName = renderNodeObjName.toLatin1();
90
91 // This function is called once per shadow casting light on every layer
92 // prepare (i.e. once per frame). We must avoid creating resources as much
93 // as possible: if the shadow mode, dimensions, etc. are all the same as in
94 // the previous prepare round, then reuse the existing resources.
95
96 QSSGShadowMapEntry *pEntry = shadowMapEntry(lightIdx);
97 if (pEntry) {
98 if (pEntry->m_rhiDepthMap && mode == ShadowMapModes::CUBE) {
99 // previously VSM now CUBE
100 pEntry->destroyRhiResources();
101 setupForRhiDepthCube(rhi, entry: pEntry, size: pixelSize, format: rhiFormat);
102 } else if (pEntry->m_rhiDepthCube && mode != ShadowMapModes::CUBE) {
103 // previously CUBE now VSM
104 pEntry->destroyRhiResources();
105 setupForRhiDepth(rhi, entry: pEntry, size: pixelSize, format: rhiFormat);
106 } else if (pEntry->m_rhiDepthMap) {
107 // VSM before and now, see if size has changed
108 if (pEntry->m_rhiDepthMap->pixelSize() != pixelSize) {
109 pEntry->destroyRhiResources();
110 setupForRhiDepth(rhi, entry: pEntry, size: pixelSize, format: rhiFormat);
111 }
112 } else if (pEntry->m_rhiDepthCube) {
113 // CUBE before and now, see if size has changed
114 if (pEntry->m_rhiDepthCube->pixelSize() != pixelSize) {
115 pEntry->destroyRhiResources();
116 setupForRhiDepthCube(rhi, entry: pEntry, size: pixelSize, format: rhiFormat);
117 }
118 }
119 pEntry->m_shadowMapMode = mode;
120 } else if (mode == ShadowMapModes::CUBE) {
121 QRhiTexture *depthMap = allocateRhiShadowTexture(rhi, format: rhiFormat, size: pixelSize, flags: QRhiTexture::RenderTarget | QRhiTexture::CubeMap);
122 QRhiTexture *depthCopy = allocateRhiShadowTexture(rhi, format: rhiFormat, size: pixelSize, flags: QRhiTexture::RenderTarget | QRhiTexture::CubeMap);
123 QRhiRenderBuffer *depthStencil = allocateRhiShadowRenderBuffer(rhi, type: QRhiRenderBuffer::DepthStencil, size: pixelSize);
124 m_shadowMapList.push_back(t: QSSGShadowMapEntry::withRhiDepthCubeMap(lightIdx, mode, depthCube: depthMap, cubeCopy: depthCopy, depthStencil));
125
126 pEntry = &m_shadowMapList.back();
127 } else { // VSM
128 Q_ASSERT(mode == ShadowMapModes::VSM);
129 QRhiTexture *depthMap = allocateRhiShadowTexture(rhi, format: rhiFormat, size: QSize(width, height), flags: QRhiTexture::RenderTarget);
130 QRhiTexture *depthCopy = allocateRhiShadowTexture(rhi, format: rhiFormat, size: QSize(width, height), flags: QRhiTexture::RenderTarget);
131 QRhiRenderBuffer *depthStencil = allocateRhiShadowRenderBuffer(rhi, type: QRhiRenderBuffer::DepthStencil, size: pixelSize);
132 m_shadowMapList.push_back(t: QSSGShadowMapEntry::withRhiDepthMap(lightIdx, mode, depthMap, depthCopy, depthStencil));
133
134 pEntry = &m_shadowMapList.back();
135 }
136
137 if (pEntry) {
138 // Additional graphics resources: samplers, render targets.
139 if (mode == ShadowMapModes::VSM) {
140 if (pEntry->m_rhiRenderTargets.isEmpty()) {
141 pEntry->m_rhiRenderTargets.resize(sz: 1);
142 pEntry->m_rhiRenderTargets[0] = nullptr;
143 }
144 Q_ASSERT(pEntry->m_rhiRenderTargets.size() == 1);
145
146 QRhiTextureRenderTarget *&rt(pEntry->m_rhiRenderTargets[0]);
147 if (!rt) {
148 QRhiTextureRenderTargetDescription rtDesc;
149 rtDesc.setColorAttachments({ pEntry->m_rhiDepthMap });
150 rtDesc.setDepthStencilBuffer(pEntry->m_rhiDepthStencil);
151 rt = rhi->newTextureRenderTarget(desc: rtDesc);
152 rt->setDescription(rtDesc);
153 // The same renderpass descriptor can be reused since the
154 // format, load/store ops are the same regardless of the shadow mode.
155 if (!pEntry->m_rhiRenderPassDesc)
156 pEntry->m_rhiRenderPassDesc = rt->newCompatibleRenderPassDescriptor();
157 rt->setRenderPassDescriptor(pEntry->m_rhiRenderPassDesc);
158 if (!rt->create())
159 qWarning(msg: "Failed to build shadow map render target");
160 }
161 rt->setName(rtName + QByteArrayLiteral(" shadow map"));
162
163 if (!pEntry->m_rhiBlurRenderTarget0) {
164 // blur X: depthMap -> depthCopy
165 pEntry->m_rhiBlurRenderTarget0 = rhi->newTextureRenderTarget(desc: { pEntry->m_rhiDepthCopy });
166 if (!pEntry->m_rhiBlurRenderPassDesc)
167 pEntry->m_rhiBlurRenderPassDesc = pEntry->m_rhiBlurRenderTarget0->newCompatibleRenderPassDescriptor();
168 pEntry->m_rhiBlurRenderTarget0->setRenderPassDescriptor(pEntry->m_rhiBlurRenderPassDesc);
169 pEntry->m_rhiBlurRenderTarget0->create();
170 }
171 pEntry->m_rhiBlurRenderTarget0->setName(rtName + QByteArrayLiteral(" shadow blur X"));
172 if (!pEntry->m_rhiBlurRenderTarget1) {
173 // blur Y: depthCopy -> depthMap
174 pEntry->m_rhiBlurRenderTarget1 = rhi->newTextureRenderTarget(desc: { pEntry->m_rhiDepthMap });
175 pEntry->m_rhiBlurRenderTarget1->setRenderPassDescriptor(pEntry->m_rhiBlurRenderPassDesc);
176 pEntry->m_rhiBlurRenderTarget1->create();
177 }
178 pEntry->m_rhiBlurRenderTarget1->setName(rtName + QByteArrayLiteral(" shadow blur Y"));
179 } else {
180 if (pEntry->m_rhiRenderTargets.isEmpty()) {
181 pEntry->m_rhiRenderTargets.resize(sz: 6);
182 for (int i = 0; i < 6; ++i)
183 pEntry->m_rhiRenderTargets[i] = nullptr;
184 }
185 Q_ASSERT(pEntry->m_rhiRenderTargets.size() == 6);
186
187 for (const auto face : QSSGRenderTextureCubeFaces) {
188 QRhiTextureRenderTarget *&rt(pEntry->m_rhiRenderTargets[quint8(face)]);
189 if (!rt) {
190 QRhiColorAttachment att(pEntry->m_rhiDepthCube);
191 att.setLayer(quint8(face)); // 6 render targets, each referencing one face of the cubemap
192 QRhiTextureRenderTargetDescription rtDesc;
193 rtDesc.setColorAttachments({ att });
194 rtDesc.setDepthStencilBuffer(pEntry->m_rhiDepthStencil);
195 rt = rhi->newTextureRenderTarget(desc: rtDesc);
196 rt->setDescription(rtDesc);
197 if (!pEntry->m_rhiRenderPassDesc)
198 pEntry->m_rhiRenderPassDesc = rt->newCompatibleRenderPassDescriptor();
199 rt->setRenderPassDescriptor(pEntry->m_rhiRenderPassDesc);
200 if (!rt->create())
201 qWarning(msg: "Failed to build shadow map render target");
202 }
203 rt->setName(rtName + QByteArrayLiteral(" shadow cube face: ") + QSSGBaseTypeHelpers::displayName(face));
204 }
205
206 // blurring cubemap happens via multiple render targets (all faces attached to COLOR0..5)
207 if (rhi->resourceLimit(limit: QRhi::MaxColorAttachments) >= 6) {
208 if (!pEntry->m_rhiBlurRenderTarget0) {
209 // blur X: depthCube -> cubeCopy
210 QRhiColorAttachment att[6];
211 for (const auto face : QSSGRenderTextureCubeFaces) {
212 att[quint8(face)].setTexture(pEntry->m_rhiCubeCopy);
213 att[quint8(face)].setLayer(quint8(face));
214 }
215 QRhiTextureRenderTargetDescription rtDesc;
216 rtDesc.setColorAttachments(first: att, last: att + 6);
217 pEntry->m_rhiBlurRenderTarget0 = rhi->newTextureRenderTarget(desc: rtDesc);
218 if (!pEntry->m_rhiBlurRenderPassDesc)
219 pEntry->m_rhiBlurRenderPassDesc = pEntry->m_rhiBlurRenderTarget0->newCompatibleRenderPassDescriptor();
220 pEntry->m_rhiBlurRenderTarget0->setRenderPassDescriptor(pEntry->m_rhiBlurRenderPassDesc);
221 pEntry->m_rhiBlurRenderTarget0->create();
222 }
223 pEntry->m_rhiBlurRenderTarget0->setName(rtName + QByteArrayLiteral(" shadow cube blur X"));
224 if (!pEntry->m_rhiBlurRenderTarget1) {
225 // blur Y: cubeCopy -> depthCube
226 QRhiColorAttachment att[6];
227 for (const auto face : QSSGRenderTextureCubeFaces) {
228 att[quint8(face)].setTexture(pEntry->m_rhiDepthCube);
229 att[quint8(face)].setLayer(quint8(face));
230 }
231 QRhiTextureRenderTargetDescription rtDesc;
232 rtDesc.setColorAttachments(first: att, last: att + 6);
233 pEntry->m_rhiBlurRenderTarget1 = rhi->newTextureRenderTarget(desc: rtDesc);
234 pEntry->m_rhiBlurRenderTarget1->setRenderPassDescriptor(pEntry->m_rhiBlurRenderPassDesc);
235 pEntry->m_rhiBlurRenderTarget1->create();
236 }
237 pEntry->m_rhiBlurRenderTarget1->setName(rtName + QByteArrayLiteral(" shadow cube blur Y"));
238 } else {
239 static bool warned = false;
240 if (!warned) {
241 warned = true;
242 qWarning(msg: "Cubemap-based shadow maps will not be blurred because MaxColorAttachments is less than 6");
243 }
244 }
245 }
246
247 pEntry->m_lightIndex = lightIdx;
248 }
249}
250
251QSSGShadowMapEntry *QSSGRenderShadowMap::shadowMapEntry(int lightIdx)
252{
253 Q_ASSERT(lightIdx >= 0);
254
255 for (int i = 0; i < m_shadowMapList.size(); i++) {
256 QSSGShadowMapEntry *pEntry = &m_shadowMapList[i];
257 if (pEntry->m_lightIndex == quint32(lightIdx))
258 return pEntry;
259 }
260
261 return nullptr;
262}
263
264QSSGShadowMapEntry::QSSGShadowMapEntry()
265 : m_lightIndex(std::numeric_limits<quint32>::max())
266 , m_shadowMapMode(ShadowMapModes::VSM)
267{
268}
269
270QSSGShadowMapEntry QSSGShadowMapEntry::withRhiDepthMap(quint32 lightIdx,
271 ShadowMapModes mode,
272 QRhiTexture *depthMap,
273 QRhiTexture *depthCopy,
274 QRhiRenderBuffer *depthStencil)
275{
276 QSSGShadowMapEntry e;
277 e.m_lightIndex = lightIdx;
278 e.m_shadowMapMode = mode;
279 e.m_rhiDepthMap = depthMap;
280 e.m_rhiDepthCopy = depthCopy;
281 e.m_rhiDepthStencil = depthStencil;
282 return e;
283}
284
285QSSGShadowMapEntry QSSGShadowMapEntry::withRhiDepthCubeMap(quint32 lightIdx,
286 ShadowMapModes mode,
287 QRhiTexture *depthCube,
288 QRhiTexture *cubeCopy,
289 QRhiRenderBuffer *depthStencil)
290{
291 QSSGShadowMapEntry e;
292 e.m_lightIndex = lightIdx;
293 e.m_shadowMapMode = mode;
294 e.m_rhiDepthCube = depthCube;
295 e.m_rhiCubeCopy = cubeCopy;
296 e.m_rhiDepthStencil = depthStencil;
297 return e;
298}
299
300void QSSGShadowMapEntry::destroyRhiResources()
301{
302 delete m_rhiDepthMap;
303 m_rhiDepthMap = nullptr;
304 delete m_rhiDepthCopy;
305 m_rhiDepthCopy = nullptr;
306 delete m_rhiDepthCube;
307 m_rhiDepthCube = nullptr;
308 delete m_rhiCubeCopy;
309 m_rhiCubeCopy = nullptr;
310 delete m_rhiDepthStencil;
311 m_rhiDepthStencil = nullptr;
312
313 qDeleteAll(c: m_rhiRenderTargets);
314 m_rhiRenderTargets.clear();
315 delete m_rhiRenderPassDesc;
316 m_rhiRenderPassDesc = nullptr;
317 delete m_rhiBlurRenderTarget0;
318 m_rhiBlurRenderTarget0 = nullptr;
319 delete m_rhiBlurRenderTarget1;
320 m_rhiBlurRenderTarget1 = nullptr;
321 delete m_rhiBlurRenderPassDesc;
322 m_rhiBlurRenderPassDesc = nullptr;
323}
324
325QT_END_NAMESPACE
326

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