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

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