1 | // Copyright (C) 2008-2012 NVIDIA Corporation. |
2 | // Copyright (C) 2020 The Qt Company Ltd. |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
4 | |
5 | #include <QtQuick3DRuntimeRender/private/qssgrhieffectsystem_p.h> |
6 | #include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h> |
7 | #include <QtQuick3DRuntimeRender/private/qssgrhiquadrenderer_p.h> |
8 | #include <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h> |
9 | #include <qtquick3d_tracepoints_p.h> |
10 | |
11 | #include <QtQuick3DUtils/private/qssgassert_p.h> |
12 | |
13 | #include <QtCore/qloggingcategory.h> |
14 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | Q_DECLARE_LOGGING_CATEGORY(lcEffectSystem); |
18 | Q_LOGGING_CATEGORY(lcEffectSystem, "qt.quick3d.effects" ); |
19 | |
20 | struct QSSGRhiEffectTexture |
21 | { |
22 | QRhiTexture *texture = nullptr; |
23 | QRhiRenderPassDescriptor *renderPassDescriptor = nullptr; |
24 | QRhiTextureRenderTarget *renderTarget = nullptr; |
25 | QByteArray name; |
26 | |
27 | QSSGRhiSamplerDescription desc; |
28 | QSSGAllocateBufferFlags flags; |
29 | |
30 | ~QSSGRhiEffectTexture() |
31 | { |
32 | delete texture; |
33 | delete renderPassDescriptor; |
34 | delete renderTarget; |
35 | } |
36 | QSSGRhiEffectTexture &operator=(const QSSGRhiEffectTexture &) = delete; |
37 | }; |
38 | |
39 | QSSGRhiEffectSystem::QSSGRhiEffectSystem(const std::shared_ptr<QSSGRenderContextInterface> &sgContext) |
40 | : m_sgContext(sgContext) |
41 | { |
42 | } |
43 | |
44 | QSSGRhiEffectSystem::~QSSGRhiEffectSystem() |
45 | { |
46 | releaseResources(); |
47 | } |
48 | |
49 | void QSSGRhiEffectSystem::setup(QSize outputSize) |
50 | { |
51 | if (outputSize.isEmpty()) { |
52 | releaseResources(); |
53 | return; |
54 | } |
55 | m_outSize = outputSize; |
56 | } |
57 | |
58 | QSSGRhiEffectTexture *QSSGRhiEffectSystem::findTexture(const QByteArray &bufferName) |
59 | { |
60 | auto findTexture = [bufferName](const QSSGRhiEffectTexture *rt){ return rt->name == bufferName; }; |
61 | const auto foundIt = std::find_if(first: m_textures.cbegin(), last: m_textures.cend(), pred: findTexture); |
62 | QSSGRhiEffectTexture *result = foundIt == m_textures.cend() ? nullptr : *foundIt; |
63 | return result; |
64 | } |
65 | |
66 | QSSGRhiEffectTexture *QSSGRhiEffectSystem::getTexture(const QByteArray &bufferName, |
67 | const QSize &size, |
68 | QRhiTexture::Format format, |
69 | bool isFinalOutput, |
70 | const QSSGRenderEffect *inEffect) |
71 | { |
72 | QSSGRhiEffectTexture *result = findTexture(bufferName); |
73 | const bool gotMatch = result != nullptr; |
74 | |
75 | // If not found, look for an unused texture |
76 | if (!result) { |
77 | // ### This could be enhanced to try to find a texture with the right |
78 | // size/format/flags first. It is not essential because the texture will be |
79 | // recreated below if the size or format does not match, but it would |
80 | // be more optimal (for Effects with Buffers with sizeMultipliers on |
81 | // them, or ones that use different formats) to look for a matching |
82 | // size/format too instead of picking the first unused texture. |
83 | auto findUnused = [](const QSSGRhiEffectTexture *rt){ return rt->name.isEmpty(); }; |
84 | const auto found = std::find_if(first: m_textures.cbegin(), last: m_textures.cend(), pred: findUnused); |
85 | if (found != m_textures.cend()) { |
86 | result = *found; |
87 | result->desc = {}; |
88 | } |
89 | } |
90 | |
91 | if (!result) { |
92 | result = new QSSGRhiEffectTexture {}; |
93 | m_textures.append(t: result); |
94 | } |
95 | |
96 | QRhi *rhi = m_sgContext->rhi(); |
97 | const bool formatChanged = result->texture && result->texture->format() != format; |
98 | const bool needsRebuild = result->texture && (result->texture->pixelSize() != size || formatChanged); |
99 | |
100 | QRhiTexture::Flags flags = QRhiTexture::RenderTarget; |
101 | if (isFinalOutput) // play nice with progressive/temporal AA |
102 | flags |= QRhiTexture::UsedAsTransferSource; |
103 | |
104 | if (!result->texture) { |
105 | result->texture = rhi->newTexture(format, pixelSize: size, sampleCount: 1, flags); |
106 | result->texture->create(); |
107 | } else if (needsRebuild) { |
108 | result->texture->setFlags(flags); |
109 | result->texture->setPixelSize(size); |
110 | result->texture->setFormat(format); |
111 | result->texture->create(); |
112 | } |
113 | |
114 | if (!result->renderTarget) { |
115 | result->renderTarget = rhi->newTextureRenderTarget(desc: { result->texture }); |
116 | result->renderPassDescriptor = result->renderTarget->newCompatibleRenderPassDescriptor(); |
117 | result->renderTarget->setRenderPassDescriptor(result->renderPassDescriptor); |
118 | result->renderTarget->create(); |
119 | m_pendingClears.insert(value: result->renderTarget); |
120 | } else if (needsRebuild) { |
121 | if (formatChanged) { |
122 | delete result->renderPassDescriptor; |
123 | result->renderPassDescriptor = result->renderTarget->newCompatibleRenderPassDescriptor(); |
124 | result->renderTarget->setRenderPassDescriptor(result->renderPassDescriptor); |
125 | } |
126 | result->renderTarget->create(); |
127 | m_pendingClears.insert(value: result->renderTarget); |
128 | } |
129 | |
130 | if (!gotMatch) { |
131 | QByteArray rtName = inEffect->debugObjectName.toLatin1(); |
132 | rtName += QByteArrayLiteral(" effect pass " ); |
133 | rtName += bufferName; |
134 | result->renderTarget->setName(rtName); |
135 | } |
136 | |
137 | result->name = bufferName; |
138 | return result; |
139 | } |
140 | |
141 | void QSSGRhiEffectSystem::releaseTexture(QSSGRhiEffectTexture *texture) |
142 | { |
143 | // Mark as unused by setting the name to empty, unless the Buffer had scene |
144 | // lifetime on it (then it needs to live on for ever). |
145 | if (!texture->flags.isSceneLifetime()) |
146 | texture->name = {}; |
147 | } |
148 | |
149 | void QSSGRhiEffectSystem::releaseTextures() |
150 | { |
151 | for (auto *t : std::as_const(t&: m_textures)) |
152 | releaseTexture(texture: t); |
153 | } |
154 | |
155 | QRhiTexture *QSSGRhiEffectSystem::process(const QSSGRenderEffect &firstEffect, |
156 | QRhiTexture *inTexture, |
157 | QRhiTexture *inDepthTexture, |
158 | QVector2D cameraClipRange) |
159 | { |
160 | QSSG_ASSERT(m_sgContext != nullptr, return inTexture); |
161 | const auto &rhiContext = m_sgContext->rhiContext(); |
162 | const auto &renderer = m_sgContext->renderer(); |
163 | QSSG_ASSERT(rhiContext && renderer, return inTexture); |
164 | |
165 | m_depthTexture = inDepthTexture; |
166 | m_cameraClipRange = cameraClipRange; |
167 | |
168 | m_currentUbufIndex = 0; |
169 | auto *currentEffect = &firstEffect; |
170 | QSSGRhiEffectTexture firstTex{ .texture: inTexture, .renderPassDescriptor: nullptr, .renderTarget: nullptr, .name: {}, .desc: {}, .flags: {} }; |
171 | auto *latestOutput = doRenderEffect(inEffect: currentEffect, inTexture: &firstTex); |
172 | firstTex.texture = nullptr; // make sure we don't delete inTexture when we go out of scope |
173 | |
174 | while ((currentEffect = currentEffect->m_nextEffect)) { |
175 | auto *effectOut = doRenderEffect(inEffect: currentEffect, inTexture: latestOutput); |
176 | releaseTexture(texture: latestOutput); |
177 | latestOutput = effectOut; |
178 | } |
179 | |
180 | releaseTextures(); |
181 | return latestOutput ? latestOutput->texture : nullptr; |
182 | } |
183 | |
184 | void QSSGRhiEffectSystem::releaseResources() |
185 | { |
186 | qDeleteAll(c: m_textures); |
187 | m_textures.clear(); |
188 | |
189 | m_shaderPipelines.clear(); |
190 | } |
191 | |
192 | QSSGRenderTextureFormat::Format QSSGRhiEffectSystem::overriddenOutputFormat(const QSSGRenderEffect *inEffect) |
193 | { |
194 | QSSGRenderTextureFormat::Format format = QSSGRenderTextureFormat::Unknown; |
195 | for (const QSSGRenderEffect::Command &c : inEffect->commands) { |
196 | QSSGCommand *cmd = c.command; |
197 | if (cmd->m_type == CommandType::BindTarget) { |
198 | QSSGBindTarget *targetCmd = static_cast<QSSGBindTarget *>(cmd); |
199 | format = targetCmd->m_outputFormat == QSSGRenderTextureFormat::Unknown |
200 | ? inEffect->outputFormat : targetCmd->m_outputFormat.format; |
201 | } |
202 | } |
203 | return format; |
204 | } |
205 | |
206 | QSSGRhiEffectTexture *QSSGRhiEffectSystem::doRenderEffect(const QSSGRenderEffect *inEffect, |
207 | QSSGRhiEffectTexture *inTexture) |
208 | { |
209 | // Run through the effect commands and render the effect. |
210 | qCDebug(lcEffectSystem) << "START effect " << inEffect->className; |
211 | QSSGRhiEffectTexture *finalOutputTexture = nullptr; |
212 | QSSGRhiEffectTexture *currentOutput = nullptr; |
213 | QSSGRhiEffectTexture *currentInput = inTexture; |
214 | for (const QSSGRenderEffect::Command &c : inEffect->commands) { |
215 | QSSGCommand *theCommand = c.command; |
216 | qCDebug(lcEffectSystem).noquote() << " >" << theCommand->typeAsString() << "--" << theCommand->debugString(); |
217 | |
218 | switch (theCommand->m_type) { |
219 | case CommandType::AllocateBuffer: |
220 | allocateBufferCmd(inCmd: static_cast<QSSGAllocateBuffer *>(theCommand), inTexture, inEffect); |
221 | break; |
222 | |
223 | case CommandType::ApplyBufferValue: { |
224 | auto *applyCommand = static_cast<QSSGApplyBufferValue *>(theCommand); |
225 | |
226 | /* |
227 | BufferInput { buffer: buf } |
228 | -> INPUT (qt_inputTexture) in the shader samples the texture for Buffer buf in the pass |
229 | BufferInput { sampler: "ttt" } |
230 | -> ttt in the shader samples the input texture for the pass |
231 | (ttt also needs to be a TextureInput with a Texture{} to get the sampler declared in the shader code, |
232 | beware that without the BufferInput the behavior would change: ttt would then sample a dummy texture) |
233 | BufferInput { buffer: buf; sampler: "ttt" } |
234 | -> ttt in the shader samples the texture for Buffer buf in the pass |
235 | */ |
236 | |
237 | auto *buffer = applyCommand->m_bufferName.isEmpty() ? inTexture : findTexture(bufferName: applyCommand->m_bufferName); |
238 | if (applyCommand->m_samplerName.isEmpty()) |
239 | currentInput = buffer; |
240 | else |
241 | addTextureToShaderPipeline(name: applyCommand->m_samplerName, texture: buffer->texture, samplerDesc: buffer->desc); |
242 | break; |
243 | } |
244 | |
245 | case CommandType::ApplyInstanceValue: |
246 | applyInstanceValueCmd(inCmd: static_cast<QSSGApplyInstanceValue *>(theCommand), inEffect); |
247 | break; |
248 | |
249 | case CommandType::ApplyValue: |
250 | applyValueCmd(inCmd: static_cast<QSSGApplyValue *>(theCommand), inEffect); |
251 | break; |
252 | |
253 | case CommandType::BindBuffer: { |
254 | auto *bindCmd = static_cast<QSSGBindBuffer *>(theCommand); |
255 | currentOutput = findTexture(bufferName: bindCmd->m_bufferName); |
256 | break; |
257 | } |
258 | |
259 | case CommandType::BindShader: |
260 | bindShaderCmd(inCmd: static_cast<QSSGBindShader *>(theCommand), inEffect); |
261 | break; |
262 | |
263 | case CommandType::BindTarget: { |
264 | auto targetCmd = static_cast<QSSGBindTarget*>(theCommand); |
265 | // matches overriddenOutputFormat() |
266 | QSSGRenderTextureFormat::Format f = targetCmd->m_outputFormat == QSSGRenderTextureFormat::Unknown ? |
267 | inEffect->outputFormat : targetCmd->m_outputFormat.format; |
268 | // f is now either Unknown (common case), or if the effect overrides the output format, then that |
269 | QRhiTexture::Format rhiFormat = f == QSSGRenderTextureFormat::Unknown ? |
270 | currentInput->texture->format() : QSSGBufferManager::toRhiFormat(format: f); |
271 | qCDebug(lcEffectSystem) << " Target format override" << QSSGBaseTypeHelpers::toString(value: f) << "Effective RHI format" << rhiFormat; |
272 | // Make sure we use different names for each effect inside one frame |
273 | QByteArray tmpName = QByteArrayLiteral("__output_" ).append(a: QByteArray::number(m_currentUbufIndex)); |
274 | currentOutput = getTexture(bufferName: tmpName, size: m_outSize, format: rhiFormat, isFinalOutput: true, inEffect); |
275 | finalOutputTexture = currentOutput; |
276 | break; |
277 | } |
278 | |
279 | case CommandType::Render: |
280 | renderCmd(inTexture: currentInput, target: currentOutput); |
281 | currentInput = inTexture; // default input for each new pass is defined to be original input |
282 | break; |
283 | |
284 | default: |
285 | qWarning() << "Effect command" << theCommand->typeAsString() << "not implemented" ; |
286 | break; |
287 | } |
288 | } |
289 | // TODO: release textures used by this effect now, instead of after processing all the effects |
290 | qCDebug(lcEffectSystem) << "END effect " << inEffect->className; |
291 | return finalOutputTexture; |
292 | } |
293 | |
294 | void QSSGRhiEffectSystem::allocateBufferCmd(const QSSGAllocateBuffer *inCmd, QSSGRhiEffectTexture *inTexture, const QSSGRenderEffect *inEffect) |
295 | { |
296 | // Note: Allocate is used both to allocate new, and refer to buffer created earlier |
297 | QSize bufferSize(m_outSize * qreal(inCmd->m_sizeMultiplier)); |
298 | |
299 | QSSGRenderTextureFormat f = inCmd->m_format; |
300 | QRhiTexture::Format rhiFormat = (f == QSSGRenderTextureFormat::Unknown) ? inTexture->texture->format() |
301 | : QSSGBufferManager::toRhiFormat(format: f); |
302 | |
303 | QSSGRhiEffectTexture *buf = getTexture(bufferName: inCmd->m_name, size: bufferSize, format: rhiFormat, isFinalOutput: false, inEffect); |
304 | auto filter = toRhi(op: inCmd->m_filterOp); |
305 | auto tiling = toRhi(tiling: inCmd->m_texCoordOp); |
306 | buf->desc = { .minFilter: filter, .magFilter: filter, .mipmap: QRhiSampler::None, .hTiling: tiling, .vTiling: tiling, .zTiling: QRhiSampler::Repeat }; |
307 | buf->flags = inCmd->m_bufferFlags; |
308 | } |
309 | |
310 | void QSSGRhiEffectSystem::applyInstanceValueCmd(const QSSGApplyInstanceValue *inCmd, const QSSGRenderEffect *inEffect) |
311 | { |
312 | if (!m_currentShaderPipeline) |
313 | return; |
314 | |
315 | const bool setAll = inCmd->m_propertyName.isEmpty(); |
316 | for (const QSSGRenderEffect::Property &property : std::as_const(t: inEffect->properties)) { |
317 | if (setAll || property.name == inCmd->m_propertyName) { |
318 | m_currentShaderPipeline->setUniformValue(ubufData: m_currentUBufData, name: property.name, value: property.value, type: property.shaderDataType); |
319 | //qCDebug(lcEffectSystem) << "setUniformValue" << property.name << toString(property.shaderDataType) << "to" << property.value; |
320 | } |
321 | } |
322 | for (const QSSGRenderEffect::TextureProperty &textureProperty : std::as_const(t: inEffect->textureProperties)) { |
323 | if (setAll || textureProperty.name == inCmd->m_propertyName) { |
324 | bool texAdded = false; |
325 | QSSGRenderImage *image = textureProperty.texImage; |
326 | if (image) { |
327 | const auto &theBufferManager(m_sgContext->bufferManager()); |
328 | const QSSGRenderImageTexture texture = theBufferManager->loadRenderImage(image); |
329 | if (texture.m_texture) { |
330 | const QSSGRhiSamplerDescription desc{ |
331 | .minFilter: toRhi(op: textureProperty.minFilterType), |
332 | .magFilter: toRhi(op: textureProperty.magFilterType), |
333 | .mipmap: textureProperty.mipFilterType != QSSGRenderTextureFilterOp::None ? toRhi(op: textureProperty.mipFilterType) : QRhiSampler::None, |
334 | .hTiling: toRhi(tiling: textureProperty.horizontalClampType), |
335 | .vTiling: toRhi(tiling: textureProperty.verticalClampType), |
336 | .zTiling: QRhiSampler::Repeat |
337 | }; |
338 | addTextureToShaderPipeline(name: textureProperty.name, texture: texture.m_texture, samplerDesc: desc); |
339 | texAdded = true; |
340 | } |
341 | } |
342 | if (!texAdded) { |
343 | // Something went wrong, e.g. image file not found. Still need to add a dummy texture for the shader |
344 | qCDebug(lcEffectSystem) << "Using dummy texture for property" << textureProperty.name; |
345 | addTextureToShaderPipeline(name: textureProperty.name, texture: nullptr, samplerDesc: {}); |
346 | } |
347 | } |
348 | } |
349 | } |
350 | |
351 | void QSSGRhiEffectSystem::applyValueCmd(const QSSGApplyValue *inCmd, const QSSGRenderEffect *inEffect) |
352 | { |
353 | if (!m_currentShaderPipeline) |
354 | return; |
355 | |
356 | const auto &properties = inEffect->properties; |
357 | const auto foundIt = std::find_if(first: properties.cbegin(), last: properties.cend(), pred: [inCmd](const QSSGRenderEffect::Property &prop) { |
358 | return (prop.name == inCmd->m_propertyName); |
359 | }); |
360 | |
361 | if (foundIt != properties.cend()) |
362 | m_currentShaderPipeline->setUniformValue(ubufData: m_currentUBufData, name: inCmd->m_propertyName, value: inCmd->m_value, type: foundIt->shaderDataType); |
363 | else |
364 | qWarning() << "Could not find effect property" << inCmd->m_propertyName; |
365 | } |
366 | |
367 | static const char *effect_builtin_textureMapUV = |
368 | "vec2 qt_effectTextureMapUV(vec2 uv)\n" |
369 | "{\n" |
370 | " return uv;\n" |
371 | "}\n" ; |
372 | |
373 | static const char *effect_builtin_textureMapUVFlipped = |
374 | "vec2 qt_effectTextureMapUV(vec2 uv)\n" |
375 | "{\n" |
376 | " return vec2(uv.x, 1.0 - uv.y);\n" |
377 | "}\n" ; |
378 | |
379 | QSSGRhiShaderPipelinePtr QSSGRhiEffectSystem::buildShaderForEffect(const QSSGBindShader &inCmd, |
380 | QSSGProgramGenerator &generator, |
381 | QSSGShaderLibraryManager &shaderLib, |
382 | QSSGShaderCache &shaderCache, |
383 | bool isYUpInFramebuffer) |
384 | { |
385 | const auto &key = inCmd.m_shaderPathKey; |
386 | qCDebug(lcEffectSystem) << " generating new shader pipeline for: " << key; |
387 | |
388 | generator.beginProgram(); |
389 | |
390 | { |
391 | const QByteArray src = shaderLib.getShaderSource(inShaderPathKey: inCmd.m_shaderPathKey, type: QSSGShaderCache::ShaderType::Vertex); |
392 | QSSGStageGeneratorBase *vStage = generator.getStage(inStage: QSSGShaderGeneratorStage::Vertex); |
393 | // The variation based on isYUpInFramebuffer is captured in 'key' as |
394 | // well, so it is safe to vary the source code here. |
395 | vStage->append(data: isYUpInFramebuffer ? effect_builtin_textureMapUV : effect_builtin_textureMapUVFlipped); |
396 | vStage->append(data: src); |
397 | } |
398 | { |
399 | const QByteArray src = shaderLib.getShaderSource(inShaderPathKey: inCmd.m_shaderPathKey, type: QSSGShaderCache::ShaderType::Fragment); |
400 | QSSGStageGeneratorBase *fStage = generator.getStage(inStage: QSSGShaderGeneratorStage::Fragment); |
401 | fStage->append(data: src); |
402 | } |
403 | |
404 | return generator.compileGeneratedRhiShader(inMaterialInfoString: key, |
405 | inFeatureSet: shaderLib.getShaderMetaData(inShaderPathKey: inCmd.m_shaderPathKey, type: QSSGShaderCache::ShaderType::Fragment).features, |
406 | shaderLibraryManager&: shaderLib, |
407 | theCache&: shaderCache, |
408 | stageFlags: QSSGRhiShaderPipeline::UsedWithoutIa); |
409 | } |
410 | |
411 | void QSSGRhiEffectSystem::bindShaderCmd(const QSSGBindShader *inCmd, const QSSGRenderEffect *inEffect) |
412 | { |
413 | QElapsedTimer timer; |
414 | timer.start(); |
415 | |
416 | m_currentTextures.clear(); |
417 | m_pendingClears.clear(); |
418 | m_currentShaderPipeline = nullptr; |
419 | |
420 | QRhi *rhi = m_sgContext->rhi(); |
421 | const auto &shaderLib = m_sgContext->shaderLibraryManager(); |
422 | const auto &shaderCache = m_sgContext->shaderCache(); |
423 | |
424 | // Now we need a proper unique key (unique in the scene), the filenames are |
425 | // not sufficient. This means that using the same shader source files in |
426 | // multiple Effects in the same scene will work. It wouldn't if all those |
427 | // Effects reused the same QSSGRhiShaderPipeline (i.e. if the only cache |
428 | // key was the m_shaderPathKey). |
429 | QSSGEffectSceneCacheKey cacheKey; |
430 | cacheKey.m_shaderPathKey = inCmd->m_shaderPathKey; |
431 | cacheKey.m_cmd = quintptr(inCmd); |
432 | cacheKey.m_ubufIndex = m_currentUbufIndex; |
433 | cacheKey.updateHashCode(); |
434 | |
435 | // look for a runtime pipeline |
436 | const auto it = m_shaderPipelines.constFind(key: cacheKey); |
437 | if (it != m_shaderPipelines.cend()) |
438 | m_currentShaderPipeline = (*it).get(); |
439 | |
440 | QByteArray qsbcKey; |
441 | QSSGShaderFeatures features; |
442 | if (!m_currentShaderPipeline) { // don't spend time if already got the pipeline |
443 | features = shaderLib->getShaderMetaData(inShaderPathKey: inCmd->m_shaderPathKey, type: QSSGShaderCache::ShaderType::Fragment).features; |
444 | qsbcKey = QQsbCollection::EntryDesc::generateSha(materialKey: inCmd->m_shaderPathKey, featureSet: QQsbCollection::toFeatureSet(ssgFeatureSet: features)); |
445 | } |
446 | |
447 | // Check if there's a build-time generated entry for this effect |
448 | if (!m_currentShaderPipeline && !shaderLib->m_preGeneratedShaderEntries.isEmpty()) { |
449 | const QQsbCollection::EntryMap &pregenEntries = shaderLib->m_preGeneratedShaderEntries; |
450 | const auto foundIt = pregenEntries.constFind(value: QQsbCollection::Entry(qsbcKey)); |
451 | if (foundIt != pregenEntries.cend()) { |
452 | // The result here is always a new QSSGRhiShaderPipeline, which |
453 | // fulfills the requirements of our local cache (cmd/ubufIndex in |
454 | // cacheKey, not needed here since the result is a new object). |
455 | const auto &shader = shaderCache->newPipelineFromPregenerated(inKey: inCmd->m_shaderPathKey, |
456 | inFeatures: features, |
457 | entry: *foundIt, |
458 | obj: *inEffect, |
459 | stageFlags: QSSGRhiShaderPipeline::UsedWithoutIa); |
460 | m_shaderPipelines.insert(key: cacheKey, value: shader); |
461 | m_currentShaderPipeline = shader.get(); |
462 | } |
463 | } |
464 | |
465 | if (!m_currentShaderPipeline) { |
466 | // Try the persistent (disk-based) cache then. The result here is |
467 | // always a new QSSGRhiShaderPipeline, which fulfills the requirements |
468 | // of our local cache (cmd/ubufIndex in cacheKey, not needed here since |
469 | // the result is a new object). Alternatively, the result may be null |
470 | // if there was no hit. |
471 | const auto &shaderPipeline = shaderCache->tryNewPipelineFromPersistentCache(qsbcKey, |
472 | inKey: inCmd->m_shaderPathKey, |
473 | inFeatures: features, |
474 | stageFlags: QSSGRhiShaderPipeline::UsedWithoutIa); |
475 | if (shaderPipeline) { |
476 | m_shaderPipelines.insert(key: cacheKey, value: shaderPipeline); |
477 | m_currentShaderPipeline = shaderPipeline.get(); |
478 | } |
479 | } |
480 | |
481 | if (!m_currentShaderPipeline) { |
482 | // Final option, generate the shader pipeline |
483 | Q_TRACE_SCOPE(QSSG_generateShader); |
484 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DGenerateShader); |
485 | const auto &generator = m_sgContext->shaderProgramGenerator(); |
486 | if (auto stages = buildShaderForEffect(inCmd: *inCmd, generator&: *generator, shaderLib&: *shaderLib, shaderCache&: *shaderCache, isYUpInFramebuffer: rhi->isYUpInFramebuffer())) { |
487 | m_shaderPipelines.insert(key: cacheKey, value: stages); |
488 | m_currentShaderPipeline = stages.get(); |
489 | } |
490 | Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DGenerateShader, 0, inEffect->profilingId); |
491 | } |
492 | |
493 | const auto &rhiContext = m_sgContext->rhiContext(); |
494 | |
495 | if (m_currentShaderPipeline) { |
496 | const void *cacheKey1 = reinterpret_cast<const void *>(this); |
497 | const void *cacheKey2 = reinterpret_cast<const void *>(qintptr(m_currentUbufIndex)); |
498 | QSSGRhiDrawCallData &dcd = rhiContext->drawCallData(key: { .cid: cacheKey1, .model: cacheKey2, .entry: nullptr, .entryIdx: 0 }); |
499 | m_currentShaderPipeline->ensureCombinedMainLightsUniformBuffer(ubuf: &dcd.ubuf); |
500 | m_currentUBufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame(); |
501 | } else { |
502 | m_currentUBufData = nullptr; |
503 | } |
504 | |
505 | rhiContext->stats().registerEffectShaderGenerationTime(ms: timer.elapsed()); |
506 | } |
507 | |
508 | void QSSGRhiEffectSystem::renderCmd(QSSGRhiEffectTexture *inTexture, QSSGRhiEffectTexture *target) |
509 | { |
510 | if (!m_currentShaderPipeline) |
511 | return; |
512 | |
513 | if (!target) { |
514 | qWarning(msg: "No effect render target?" ); |
515 | return; |
516 | } |
517 | |
518 | addTextureToShaderPipeline(QByteArrayLiteral("qt_inputTexture" ), texture: inTexture->texture, samplerDesc: inTexture->desc); |
519 | |
520 | const auto &rhiContext = m_sgContext->rhiContext(); |
521 | const auto &renderer = m_sgContext->renderer(); |
522 | |
523 | QRhiCommandBuffer *cb = rhiContext->commandBuffer(); |
524 | cb->debugMarkBegin(QByteArrayLiteral("Post-processing effect" )); |
525 | Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass); |
526 | |
527 | for (QRhiTextureRenderTarget *rt : m_pendingClears) { |
528 | // Effects like motion blur use an accumulator texture that should |
529 | // start out empty (and they are sampled in the first pass), so such |
530 | // textures need an explicit clear. It is not applicable for the common |
531 | // case of outputting into a texture because that will get a clear |
532 | // anyway when rendering the quad. |
533 | if (rt != target->renderTarget) { |
534 | cb->beginPass(rt, colorClearValue: Qt::transparent, depthStencilClearValue: { 1.0f, 0 }, resourceUpdates: nullptr, flags: QSSGRhiContext::commonPassFlags()); |
535 | QSSGRHICTX_STAT(rhiContext, beginRenderPass(rt)); |
536 | cb->endPass(); |
537 | QSSGRHICTX_STAT(rhiContext, endRenderPass()); |
538 | } |
539 | } |
540 | m_pendingClears.clear(); |
541 | |
542 | const QSize inputSize = inTexture->texture->pixelSize(); |
543 | const QSize outputSize = target->texture->pixelSize(); |
544 | addCommonEffectUniforms(inputSize, outputSize); |
545 | |
546 | const void *cacheKey1 = reinterpret_cast<const void *>(this); |
547 | const void *cacheKey2 = reinterpret_cast<const void *>(qintptr(m_currentUbufIndex)); |
548 | QSSGRhiDrawCallData &dcd = rhiContext->drawCallData(key: { .cid: cacheKey1, .model: cacheKey2, .entry: nullptr, .entryIdx: 0 }); |
549 | dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame(); |
550 | m_currentUBufData = nullptr; |
551 | |
552 | QRhiResourceUpdateBatch *rub = rhiContext->rhi()->nextResourceUpdateBatch(); |
553 | renderer->rhiQuadRenderer()->prepareQuad(rhiCtx: rhiContext.get(), maybeRub: rub); |
554 | |
555 | // do resource bindings |
556 | const QRhiShaderResourceBinding::StageFlags VISIBILITY_ALL = |
557 | QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage; |
558 | QSSGRhiShaderResourceBindingList bindings; |
559 | for (const QSSGRhiTexture &rhiTex : m_currentTextures) { |
560 | int binding = m_currentShaderPipeline->bindingForTexture(name: rhiTex.name); |
561 | if (binding < 0) // may not be used in the shader (think qt_inputTexture, it's not given a shader samples INPUT) |
562 | continue; |
563 | qCDebug(lcEffectSystem) << " -> texture binding" << binding << "for" << rhiTex.name; |
564 | // Make sure to bind all samplers even if the texture is missing, otherwise we can get crash on some graphics APIs |
565 | QRhiTexture *texture = rhiTex.texture ? rhiTex.texture : rhiContext->dummyTexture(flags: {}, rub); |
566 | bindings.addTexture(binding, |
567 | stage: QRhiShaderResourceBinding::FragmentStage, |
568 | tex: texture, |
569 | sampler: rhiContext->sampler(samplerDescription: rhiTex.samplerDesc)); |
570 | } |
571 | bindings.addUniformBuffer(binding: 0, stage: VISIBILITY_ALL, buf: dcd.ubuf); |
572 | QRhiShaderResourceBindings *srb = rhiContext->srb(bindings); |
573 | |
574 | QSSGRhiGraphicsPipelineState ps; |
575 | ps.viewport = QRhiViewport(0, 0, float(outputSize.width()), float(outputSize.height())); |
576 | ps.shaderPipeline = m_currentShaderPipeline; |
577 | |
578 | renderer->rhiQuadRenderer()->recordRenderQuadPass(rhiCtx: rhiContext.get(), ps: &ps, srb, rt: target->renderTarget, flags: QSSGRhiQuadRenderer::UvCoords); |
579 | m_currentUbufIndex++; |
580 | cb->debugMarkEnd(); |
581 | Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("post_processing_effect" )); |
582 | } |
583 | |
584 | void QSSGRhiEffectSystem::addCommonEffectUniforms(const QSize &inputSize, const QSize &outputSize) |
585 | { |
586 | const auto &rhiContext = m_sgContext->rhiContext(); |
587 | QRhi *rhi = rhiContext->rhi(); |
588 | |
589 | QMatrix4x4 mvp; |
590 | if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC()) |
591 | mvp.data()[5] = -1.0f; |
592 | m_currentShaderPipeline->setUniformValue(ubufData: m_currentUBufData, name: "qt_modelViewProjection" , value: mvp, type: QSSGRenderShaderValue::Matrix4x4); |
593 | |
594 | QVector2D size(inputSize.width(), inputSize.height()); |
595 | m_currentShaderPipeline->setUniformValue(ubufData: m_currentUBufData, name: "qt_inputSize" , value: size, type: QSSGRenderShaderValue::Vec2); |
596 | |
597 | size = QVector2D(outputSize.width(), outputSize.height()); |
598 | m_currentShaderPipeline->setUniformValue(ubufData: m_currentUBufData, name: "qt_outputSize" , value: size, type: QSSGRenderShaderValue::Vec2); |
599 | |
600 | float fc = float(m_sgContext->frameCount()); |
601 | m_currentShaderPipeline->setUniformValue(ubufData: m_currentUBufData, name: "qt_frame_num" , value: fc, type: QSSGRenderShaderValue::Float); |
602 | |
603 | // Bames and values for uniforms that are also used by default and/or |
604 | // custom materials must always match, effects must not deviate. |
605 | |
606 | m_currentShaderPipeline->setUniformValue(ubufData: m_currentUBufData, name: "qt_cameraProperties" , value: m_cameraClipRange, type: QSSGRenderShaderValue::Vec2); |
607 | |
608 | float vp = rhi->isYUpInFramebuffer() ? 1.0f : -1.0f; |
609 | m_currentShaderPipeline->setUniformValue(ubufData: m_currentUBufData, name: "qt_normalAdjustViewportFactor" , value: vp, type: QSSGRenderShaderValue::Float); |
610 | |
611 | const float nearClip = rhi->isClipDepthZeroToOne() ? 0.0f : -1.0f; |
612 | m_currentShaderPipeline->setUniformValue(ubufData: m_currentUBufData, name: "qt_nearClipValue" , value: nearClip, type: QSSGRenderShaderValue::Float); |
613 | |
614 | if (m_depthTexture) { |
615 | static const QSSGRhiSamplerDescription depthSamplerDesc { |
616 | .minFilter: QRhiSampler::Nearest, .magFilter: QRhiSampler::Nearest, |
617 | .mipmap: QRhiSampler::None, |
618 | .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat |
619 | }; |
620 | addTextureToShaderPipeline(name: "qt_depthTexture" , texture: m_depthTexture, samplerDesc: depthSamplerDesc); |
621 | } |
622 | } |
623 | |
624 | void QSSGRhiEffectSystem::addTextureToShaderPipeline(const QByteArray &name, |
625 | QRhiTexture *texture, |
626 | const QSSGRhiSamplerDescription &samplerDescription) |
627 | { |
628 | if (!m_currentShaderPipeline) |
629 | return; |
630 | |
631 | static const QSSGRhiSamplerDescription defaultDescription { .minFilter: QRhiSampler::Linear, .magFilter: QRhiSampler::Linear, .mipmap: QRhiSampler::None, |
632 | .hTiling: QRhiSampler::ClampToEdge, .vTiling: QRhiSampler::ClampToEdge, .zTiling: QRhiSampler::Repeat }; |
633 | bool validDescription = samplerDescription.magFilter != QRhiSampler::None; |
634 | |
635 | // This is a map for a reason: there can be multiple calls to this function |
636 | // for the same 'name', with a different 'texture', take the last value |
637 | // into account only. |
638 | m_currentTextures.insert(key: name, value: { .name: name, .texture: texture, .samplerDesc: validDescription ? samplerDescription : defaultDescription}); |
639 | } |
640 | |
641 | QT_END_NAMESPACE |
642 | |