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