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 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | QSSGRenderShadowMap::QSSGRenderShadowMap(const QSSGRenderContextInterface &inContext) |
13 | : m_context(inContext) |
14 | { |
15 | } |
16 | |
17 | QSSGRenderShadowMap::~QSSGRenderShadowMap() |
18 | { |
19 | releaseCachedResources(); |
20 | } |
21 | |
22 | void QSSGRenderShadowMap::releaseCachedResources() |
23 | { |
24 | for (QSSGShadowMapEntry &entry : m_shadowMapList) |
25 | entry.destroyRhiResources(); |
26 | |
27 | m_shadowMapList.clear(); |
28 | } |
29 | |
30 | static 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 | |
41 | static 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 | |
51 | static 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 | |
61 | static 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 | |
71 | void 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 | |
251 | QSSGShadowMapEntry *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 | |
264 | QSSGShadowMapEntry::QSSGShadowMapEntry() |
265 | : m_lightIndex(std::numeric_limits<quint32>::max()) |
266 | , m_shadowMapMode(ShadowMapModes::VSM) |
267 | { |
268 | } |
269 | |
270 | QSSGShadowMapEntry 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 | |
285 | QSSGShadowMapEntry 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 | |
300 | void 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 | |
325 | QT_END_NAMESPACE |
326 | |