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

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