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
16QT_BEGIN_NAMESPACE
17
18Q_DECLARE_LOGGING_CATEGORY(lcEffectSystem);
19Q_LOGGING_CATEGORY(lcEffectSystem, "qt.quick3d.effects");
20
21struct 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
40QSSGRhiEffectSystem::QSSGRhiEffectSystem(const std::shared_ptr<QSSGRenderContextInterface> &sgContext)
41 : m_sgContext(sgContext)
42{
43}
44
45QSSGRhiEffectSystem::~QSSGRhiEffectSystem()
46{
47 releaseResources();
48}
49
50void QSSGRhiEffectSystem::setup(QSize outputSize)
51{
52 if (outputSize.isEmpty()) {
53 releaseResources();
54 return;
55 }
56 m_outSize = outputSize;
57}
58
59QSSGRhiEffectTexture *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
67QSSGRhiEffectTexture *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
149void 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
157void QSSGRhiEffectSystem::releaseTextures()
158{
159 for (auto *t : std::as_const(t&: m_textures))
160 releaseTexture(texture: t);
161}
162
163QRhiTexture *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
192void QSSGRhiEffectSystem::releaseResources()
193{
194 qDeleteAll(c: m_textures);
195 m_textures.clear();
196
197 m_shaderPipelines.clear();
198}
199
200QSSGRenderTextureFormat::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
214QSSGRhiEffectTexture *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
302void 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
318void 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
359void 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
375static const char *effect_builtin_textureMapUV =
376 "vec2 qt_effectTextureMapUV(vec2 uv)\n"
377 "{\n"
378 " return uv;\n"
379 "}\n";
380
381static 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
387QSSGRhiShaderPipelinePtr 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
422void 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
520void 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
603void 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
644void 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
661QT_END_NAMESPACE
662

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