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