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