Revision control

++ // Copyright (C) 2008-2012 NVIDIA Corporation.
++ // Copyright (C) 2020 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ #include <QtQuick3DRuntimeRender/private/qssgrhieffectsystem_p.h>
++ #include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
++ #include <QtQuick3DRuntimeRender/private/qssgrhiquadrenderer_p.h>
++ #include <QtQuick3DRuntimeRender/private/qssgrendercamera_p.h>
++ #include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
++
++ #include "qssgrendercontextcore.h"
++ #include "qssgrendershadercodegenerator_p.h"
++ #include <qtquick3d_tracepoints_p.h>
++
++ #include <QtQuick3DUtils/private/qssgassert_p.h>
++
++ #include <QtCore/qloggingcategory.h>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_DECLARE_LOGGING_CATEGORY(lcEffectSystem);
++ Q_LOGGING_CATEGORY(lcEffectSystem, "qt.quick3d.effects");
++
++ struct QSSGRhiEffectTexture
++ {
++ QRhiTexture *texture = nullptr;
++ QRhiRenderPassDescriptor *renderPassDescriptor = nullptr;
++ QRhiTextureRenderTarget *renderTarget = nullptr;
++ QByteArray name;
++
++ QSSGRhiSamplerDescription desc;
++ QSSGAllocateBufferFlags flags;
++
++ ~QSSGRhiEffectTexture()
++ {
++ delete texture;
++ delete renderPassDescriptor;
++ delete renderTarget;
++ }
++ QSSGRhiEffectTexture &operator=(const QSSGRhiEffectTexture &) = delete;
++ };
++
++ QSSGRhiEffectSystem::QSSGRhiEffectSystem(const std::shared_ptr<QSSGRenderContextInterface> &sgContext)
++ : m_sgContext(sgContext)
++ {
++ }
++
++ QSSGRhiEffectSystem::~QSSGRhiEffectSystem()
++ {
++ // The uniform buffers for the effect passes are keyed on this object.
++ if (const auto &rhiCtx = m_sgContext->rhiContext(); rhiCtx && rhiCtx->isValid())
++ QSSGRhiContextPrivate::get(rhiCtx.get())->cleanupDrawCallDataForCid(this);
++ releaseResources();
++ }
++
++ void QSSGRhiEffectSystem::setup(QSize outputSize)
++ {
++ if (outputSize.isEmpty()) {
++ releaseResources();
++ return;
++ }
++ m_outSize = outputSize;
++ }
++
++ QSSGRhiEffectTexture *QSSGRhiEffectSystem::findTexture(const QByteArray &bufferName)
++ {
++ auto findTexture = [bufferName](const QSSGRhiEffectTexture *rt){ return rt->name == bufferName; };
++ const auto foundIt = std::find_if(m_textures.cbegin(), m_textures.cend(), findTexture);
++ QSSGRhiEffectTexture *result = foundIt == m_textures.cend() ? nullptr : *foundIt;
++ return result;
++ }
++
++ QSSGRhiEffectTexture *QSSGRhiEffectSystem::getTexture(const QByteArray &bufferName,
++ const QSize &size,
++ QRhiTexture::Format format,
++ bool isFinalOutput,
++ const QSSGRenderEffect *inEffect,
++ quint8 viewCount)
++ {
++ QSSGRhiEffectTexture *result = findTexture(bufferName);
++ const bool gotMatch = result != nullptr;
++
++ // If not found, look for an unused texture
++ if (!result) {
++ // ### This could be enhanced to try to find a texture with the right
++ // size/format/flags first. It is not essential because the texture will be
++ // recreated below if the size or format does not match, but it would
++ // be more optimal (for Effects with Buffers with sizeMultipliers on
++ // them, or ones that use different formats) to look for a matching
++ // size/format too instead of picking the first unused texture.
++ auto findUnused = [](const QSSGRhiEffectTexture *rt){ return rt->name.isEmpty(); };
++ const auto found = std::find_if(m_textures.cbegin(), m_textures.cend(), findUnused);
++ if (found != m_textures.cend()) {
++ result = *found;
++ result->desc = {};
++ }
++ }
++
++ if (!result) {
++ result = new QSSGRhiEffectTexture {};
++ m_textures.append(result);
++ }
++
++ const auto &rhiCtx = m_sgContext->rhiContext();
++ QRhi *rhi = rhiCtx->rhi();
++ const bool formatChanged = result->texture && result->texture->format() != format;
++ const bool needsRebuild = result->texture && (result->texture->pixelSize() != size || formatChanged);
++
++ QRhiTexture::Flags flags = QRhiTexture::RenderTarget;
++ if (isFinalOutput) // play nice with progressive/temporal AA
++ flags |= QRhiTexture::UsedAsTransferSource;
++
++ if (!result->texture) {
++ if (viewCount >= 2)
++ result->texture = rhi->newTextureArray(format, viewCount, size, 1, flags);
++ else
++ result->texture = rhi->newTexture(format, size, 1, flags);
++ result->texture->create();
++ } else if (needsRebuild) {
++ result->texture->setFlags(flags);
++ result->texture->setPixelSize(size);
++ result->texture->setFormat(format);
++ result->texture->create();
++ }
++
++ if (!result->renderTarget) {
++ QRhiColorAttachment colorAttachment(result->texture);
++ colorAttachment.setMultiViewCount(viewCount);
++ QRhiTextureRenderTargetDescription desc(colorAttachment);
++ result->renderTarget = rhi->newTextureRenderTarget(desc);
++ result->renderPassDescriptor = result->renderTarget->newCompatibleRenderPassDescriptor();
++ result->renderTarget->setRenderPassDescriptor(result->renderPassDescriptor);
++ result->renderTarget->create();
++ m_pendingClears.insert(result->renderTarget);
++ } else if (needsRebuild) {
++ if (formatChanged) {
++ delete result->renderPassDescriptor;
++ result->renderPassDescriptor = result->renderTarget->newCompatibleRenderPassDescriptor();
++ result->renderTarget->setRenderPassDescriptor(result->renderPassDescriptor);
++ }
++ result->renderTarget->create();
++ m_pendingClears.insert(result->renderTarget);
++ }
++
++ if (!gotMatch) {
++ QByteArray rtName = inEffect->debugObjectName.toLatin1();
++ rtName += QByteArrayLiteral(" effect pass ");
++ rtName += bufferName;
++ result->renderTarget->setName(rtName);
++ }
++
++ result->name = bufferName;
++ return result;
++ }
++
++ void QSSGRhiEffectSystem::releaseTexture(QSSGRhiEffectTexture *texture)
++ {
++ // Mark as unused by setting the name to empty, unless the Buffer had scene
++ // lifetime on it (then it needs to live on for ever).
++ if (!texture->flags.isSceneLifetime())
++ texture->name = {};
++ }
++
++ void QSSGRhiEffectSystem::releaseTextures()
++ {
++ for (auto *t : std::as_const(m_textures))
++ releaseTexture(t);
++ }
++
++ QRhiTexture *QSSGRhiEffectSystem::process(const QSSGRenderLayer &layer,
++ QRhiTexture *inTexture,
++ QRhiTexture *inDepthTexture,
++ QRhiTexture *inNormalTexture,
++ QRhiTexture *inMotionVectorTexture)
++ {
++ QSSG_ASSERT(m_sgContext != nullptr, return inTexture);
++ QSSG_ASSERT(layer.firstEffect != nullptr, return inTexture);
++ const auto &rhiContext = m_sgContext->rhiContext();
++ const auto &renderer = m_sgContext->renderer();
++ QSSG_ASSERT(rhiContext && renderer, return inTexture);
++
++ const auto viewCount = layer.viewCount;
++
++ m_depthTexture = inDepthTexture;
++ m_normalTexture = inNormalTexture;
++ m_motionVectorTexture = inMotionVectorTexture;
++ m_cameraClipRange = layer.renderedCameras[0]->clipPlanes;
++
++ bool usesProjectionMatrix = false;
++ bool usesViewMatrix = false;
++ for (const QSSGRenderEffect *eff = layer.firstEffect; eff; eff = eff->m_nextEffect) {
++ if (eff->testFlag(QSSGRenderEffect::Flags::UsesProjectionMatrix)
++ || eff->testFlag(QSSGRenderEffect::Flags::UsesInverseProjectionMatrix))
++ {
++ usesProjectionMatrix = true;
++ }
++ if (eff->testFlag(QSSGRenderEffect::Flags::UsesViewMatrix))
++ usesViewMatrix = true;
++ }
++
++ if (usesProjectionMatrix) {
++ m_projectionMatrices.resize(viewCount);
++ const QMatrix4x4 clipSpaceCorrMatrix = rhiContext->rhi()->clipSpaceCorrMatrix();
++ for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
++ m_projectionMatrices[viewIndex] = clipSpaceCorrMatrix * layer.renderedCameras[viewIndex]->projection;
++ }
++
++ if (usesViewMatrix) {
++ QMatrix4x4 camGlobalTransforms[2] { QMatrix4x4{Qt::Uninitialized}, QMatrix4x4{Qt::Uninitialized} };
++ for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
++ camGlobalTransforms[viewIndex] = layer.renderData->getGlobalTransform(*layer.renderedCameras[viewIndex]);
++ m_viewMatrices.resize(viewCount);
++ for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
++ m_viewMatrices[viewIndex] = camGlobalTransforms[viewIndex].inverted();
++ }
++
++ m_currentUbufIndex = 0;
++ // FIXME: Keeping the change minimal for now, but we should avoid the need for this cast.
++ QSSGRenderEffect *currentEffect = const_cast<QSSGRenderEffect *>(layer.firstEffect);
++ QSSGRhiEffectTexture firstTex{ inTexture, nullptr, nullptr, {}, {}, {} };
++ auto *latestOutput = doRenderEffect(currentEffect, &firstTex, viewCount);
++ firstTex.texture = nullptr; // make sure we don't delete inTexture when we go out of scope
++
++ while ((currentEffect = currentEffect->m_nextEffect)) {
++ QSSGRhiEffectTexture *effectOut = doRenderEffect(currentEffect, latestOutput, viewCount);
++ releaseTexture(latestOutput);
++ latestOutput = effectOut;
++ }
++
++ releaseTextures();
++ return latestOutput ? latestOutput->texture : nullptr;
++ }
++
++ void QSSGRhiEffectSystem::releaseResources()
++ {
++ qDeleteAll(m_textures);
++ m_textures.clear();
++
++ m_shaderPipelines.clear();
++ }
++
++ QSSGRenderTextureFormat::Format QSSGRhiEffectSystem::overriddenOutputFormat(const QSSGRenderEffect *inEffect)
++ {
++ QSSGRenderTextureFormat::Format format = QSSGRenderTextureFormat::Unknown;
++ for (const QSSGCommand *cmd : inEffect->commands) {
++ if (cmd->m_type == CommandType::BindTarget) {
++ const QSSGBindTarget *targetCmd = static_cast<const QSSGBindTarget *>(cmd);
++ format = targetCmd->m_outputFormat == QSSGRenderTextureFormat::Unknown
++ ? inEffect->outputFormat : targetCmd->m_outputFormat.format;
++ }
++ }
++ return format;
++ }
++
++ QSSGRhiEffectTexture *QSSGRhiEffectSystem::doRenderEffect(const QSSGRenderEffect *inEffect,
++ QSSGRhiEffectTexture *inTexture,
++ quint8 viewCount)
++ {
++ // Run through the effect commands and render the effect.
++ qCDebug(lcEffectSystem) << "START effect " << inEffect->className;
++ QSSGRhiEffectTexture *finalOutputTexture = nullptr;
++ QSSGRhiEffectTexture *currentOutput = nullptr;
++ QSSGRhiEffectTexture *currentInput = inTexture;
++ for (const QSSGCommand *theCommand : inEffect->commands) {
++ qCDebug(lcEffectSystem).noquote() << " >" << theCommand->typeAsString() << "--" << theCommand->debugString();
++
++ switch (theCommand->m_type) {
++ case CommandType::AllocateBuffer:
++ allocateBufferCmd(static_cast<const QSSGAllocateBuffer *>(theCommand), inTexture, inEffect, viewCount);
++ break;
++
++ case CommandType::ApplyBufferValue: {
++ auto *applyCommand = static_cast<const QSSGApplyBufferValue *>(theCommand);
++
++ /*
++ BufferInput { buffer: buf }
++ -> INPUT (qt_inputTexture) in the shader samples the texture for Buffer buf in the pass
++ BufferInput { sampler: "ttt" }
++ -> ttt in the shader samples the input texture for the pass
++ (ttt also needs to be a TextureInput with a Texture{} to get the sampler declared in the shader code,
++ beware that without the BufferInput the behavior would change: ttt would then sample a dummy texture)
++ BufferInput { buffer: buf; sampler: "ttt" }
++ -> ttt in the shader samples the texture for Buffer buf in the pass
++ */
++
++ auto *buffer = applyCommand->m_bufferName.isEmpty() ? inTexture : findTexture(applyCommand->m_bufferName);
++ if (applyCommand->m_samplerName.isEmpty())
++ currentInput = buffer;
++ else
++ addTextureToShaderPipeline(applyCommand->m_samplerName, buffer->texture, buffer->desc);
++ break;
++ }
++
++ case CommandType::ApplyInstanceValue:
++ applyInstanceValueCmd(static_cast<const QSSGApplyInstanceValue *>(theCommand), inEffect);
++ break;
++
++ case CommandType::ApplyValue:
++ applyValueCmd(static_cast<const QSSGApplyValue *>(theCommand), inEffect);
++ break;
++
++ case CommandType::BindBuffer: {
++ auto *bindCmd = static_cast<const QSSGBindBuffer *>(theCommand);
++ currentOutput = findTexture(bindCmd->m_bufferName);
++ break;
++ }
++
++ case CommandType::BindShader:
++ bindShaderCmd(static_cast<const QSSGBindShader *>(theCommand), inEffect, viewCount);
++ break;
++
++ case CommandType::BindTarget: {
++ auto targetCmd = static_cast<const QSSGBindTarget*>(theCommand);
++ // matches overriddenOutputFormat()
++ QSSGRenderTextureFormat::Format f = targetCmd->m_outputFormat == QSSGRenderTextureFormat::Unknown ?
++ inEffect->outputFormat : targetCmd->m_outputFormat.format;
++ // f is now either Unknown (common case), or if the effect overrides the output format, then that
++ QRhiTexture::Format rhiFormat = f == QSSGRenderTextureFormat::Unknown ?
++ currentInput->texture->format() : QSSGBufferManager::toRhiFormat(f);
++ qCDebug(lcEffectSystem) << " Target format override" << QSSGBaseTypeHelpers::toString(f) << "Effective RHI format" << rhiFormat;
++ // Make sure we use different names for each effect inside one frame
++ QByteArray tmpName = QByteArrayLiteral("__output_").append(QByteArray::number(m_currentUbufIndex));
++ currentOutput = getTexture(tmpName, m_outSize, rhiFormat, true, inEffect, viewCount);
++ finalOutputTexture = currentOutput;
++ break;
++ }
++
++ case CommandType::Render:
++ renderCmd(inEffect, currentInput, currentOutput, viewCount);
++ currentInput = inTexture; // default input for each new pass is defined to be original input
++ break;
++
++ default:
++ qWarning() << "Effect command" << theCommand->typeAsString() << "not implemented";
++ break;
++ }
++ }
++ // TODO: release textures used by this effect now, instead of after processing all the effects
++ qCDebug(lcEffectSystem) << "END effect " << inEffect->className;
++ return finalOutputTexture;
++ }
++
++ void QSSGRhiEffectSystem::allocateBufferCmd(const QSSGAllocateBuffer *inCmd,
++ QSSGRhiEffectTexture *inTexture,
++ const QSSGRenderEffect *inEffect,
++ quint8 viewCount)
++ {
++ // Note: Allocate is used both to allocate new, and refer to buffer created earlier
++ QSize bufferSize(m_outSize * qreal(inCmd->m_sizeMultiplier));
++
++ QSSGRenderTextureFormat f = inCmd->m_format;
++ QRhiTexture::Format rhiFormat = (f == QSSGRenderTextureFormat::Unknown) ? inTexture->texture->format()
++ : QSSGBufferManager::toRhiFormat(f);
++
++ QSSGRhiEffectTexture *buf = getTexture(inCmd->m_name, bufferSize, rhiFormat, false, inEffect, viewCount);
++ auto filter = QSSGRhiHelpers::toRhi(inCmd->m_filterOp);
++ auto tiling = QSSGRhiHelpers::toRhi(inCmd->m_texCoordOp);
++ buf->desc = { filter, filter, QRhiSampler::None, tiling, tiling, QRhiSampler::Repeat };
++ buf->flags = inCmd->m_bufferFlags;
++ }
++
++ void QSSGRhiEffectSystem::applyInstanceValueCmd(const QSSGApplyInstanceValue *inCmd, const QSSGRenderEffect *inEffect)
++ {
++ if (!m_currentShaderPipeline)
++ return;
++
++ const bool setAll = inCmd->m_propertyName.isEmpty();
++ for (const QSSGRenderEffect::Property &property : std::as_const(inEffect->properties)) {
++ if (setAll || property.name == inCmd->m_propertyName) {
++ m_currentShaderPipeline->setUniformValue(m_currentUBufData, property.name, property.value, property.shaderDataType);
++ //qCDebug(lcEffectSystem) << "setUniformValue" << property.name << toString(property.shaderDataType) << "to" << property.value;
++ }
++ }
++ for (const QSSGRenderEffect::TextureProperty &textureProperty : std::as_const(inEffect->textureProperties)) {
++ if (setAll || textureProperty.name == inCmd->m_propertyName) {
++ bool texAdded = false;
++ QSSGRenderImage *image = textureProperty.texImage;
++ if (image) {
++ const auto &theBufferManager(m_sgContext->bufferManager());
++ const QSSGRenderImageTexture texture = theBufferManager->loadRenderImage(image);
++ if (texture.m_texture) {
++ const QSSGRhiSamplerDescription desc{
++ QSSGRhiHelpers::toRhi(textureProperty.minFilterType),
++ QSSGRhiHelpers::toRhi(textureProperty.magFilterType),
++ textureProperty.mipFilterType != QSSGRenderTextureFilterOp::None ? QSSGRhiHelpers::toRhi(textureProperty.mipFilterType) : QRhiSampler::None,
++ QSSGRhiHelpers::toRhi(textureProperty.horizontalClampType),
++ QSSGRhiHelpers::toRhi(textureProperty.verticalClampType),
++ QSSGRhiHelpers::toRhi(textureProperty.zClampType)
++ };
++ addTextureToShaderPipeline(textureProperty.name, texture.m_texture, desc);
++ texAdded = true;
++ }
++ }
++ if (!texAdded) {
++ // Something went wrong, e.g. image file not found. Still need to add a dummy texture for the shader
++ qCDebug(lcEffectSystem) << "Using dummy texture for property" << textureProperty.name;
++ addTextureToShaderPipeline(textureProperty.name, nullptr, {});
++ }
++ }
++ }
++ }
++
++ void QSSGRhiEffectSystem::applyValueCmd(const QSSGApplyValue *inCmd, const QSSGRenderEffect *inEffect)
++ {
++ if (!m_currentShaderPipeline)
++ return;
++
++ const auto &properties = inEffect->properties;
++ const auto foundIt = std::find_if(properties.cbegin(), properties.cend(), [inCmd](const QSSGRenderEffect::Property &prop) {
++ return (prop.name == inCmd->m_propertyName);
++ });
++
++ if (foundIt != properties.cend())
++ m_currentShaderPipeline->setUniformValue(m_currentUBufData, inCmd->m_propertyName, inCmd->m_value, foundIt->shaderDataType);
++ else
++ qWarning() << "Could not find effect property" << inCmd->m_propertyName;
++ }
++
++ static const char *effect_builtin_textureMapUV =
++ "vec2 qt_effectTextureMapUV(vec2 uv)\n"
++ "{\n"
++ " return uv;\n"
++ "}\n";
++
++ static const char *effect_builtin_textureMapUVFlipped =
++ "vec2 qt_effectTextureMapUV(vec2 uv)\n"
++ "{\n"
++ " return vec2(uv.x, 1.0 - uv.y);\n"
++ "}\n";
++
++ QSSGRhiShaderPipelinePtr QSSGRhiEffectSystem::buildShaderForEffect(const QSSGBindShader &inCmd,
++ QSSGProgramGenerator &generator,
++ QSSGShaderLibraryManager &shaderLib,
++ QSSGShaderCache &shaderCache,
++ bool isYUpInFramebuffer,
++ int viewCount)
++ {
++ const auto &key = inCmd.m_shaderPathKey;
++ qCDebug(lcEffectSystem) << " generating new shader pipeline for: " << key;
++
++ generator.beginProgram();
++
++ {
++ const QByteArray src = shaderLib.getShaderSource(inCmd.m_shaderPathKey, QSSGShaderCache::ShaderType::Vertex);
++ QSSGStageGeneratorBase *vStage = generator.getStage(QSSGShaderGeneratorStage::Vertex);
++ // The variation based on isYUpInFramebuffer is captured in 'key' as
++ // well, so it is safe to vary the source code here.
++ vStage->append(isYUpInFramebuffer ? effect_builtin_textureMapUV : effect_builtin_textureMapUVFlipped);
++ vStage->append(src);
++ }
++ {
++ const QByteArray src = shaderLib.getShaderSource(inCmd.m_shaderPathKey, QSSGShaderCache::ShaderType::Fragment);
++ QSSGStageGeneratorBase *fStage = generator.getStage(QSSGShaderGeneratorStage::Fragment);
++ fStage->append(src);
++ }
++
++ return generator.compileGeneratedRhiShader(key,
++ shaderLib.getShaderMetaData(inCmd.m_shaderPathKey, QSSGShaderCache::ShaderType::Fragment).features,
++ shaderLib,
++ shaderCache,
++ QSSGRhiShaderPipeline::UsedWithoutIa,
++ {},
++ viewCount,
++ false);
++ }
++
++ void QSSGRhiEffectSystem::bindShaderCmd(const QSSGBindShader *inCmd, const QSSGRenderEffect *inEffect, quint8 viewCount)
++ {
++ QElapsedTimer timer;
++ timer.start();
++
++ m_currentTextures.clear();
++ m_pendingClears.clear();
++ m_currentShaderPipeline = nullptr;
++
++ const auto &rhiCtx = m_sgContext->rhiContext();
++ QRhi *rhi = rhiCtx->rhi();
++ const auto &shaderLib = m_sgContext->shaderLibraryManager();
++ const auto &shaderCache = m_sgContext->shaderCache();
++
++ // Now we need a proper unique key (unique in the scene), the filenames are
++ // not sufficient. This means that using the same shader source files in
++ // multiple Effects in the same scene will work. It wouldn't if all those
++ // Effects reused the same QSSGRhiShaderPipeline (i.e. if the only cache
++ // key was the m_shaderPathKey).
++ QSSGEffectSceneCacheKey cacheKey;
++ cacheKey.m_shaderPathKey = inCmd->m_shaderPathKey;
++ cacheKey.m_cmd = quintptr(inCmd);
++ cacheKey.m_ubufIndex = m_currentUbufIndex;
++ cacheKey.updateHashCode();
++
++ // look for a runtime pipeline
++ const auto it = m_shaderPipelines.constFind(cacheKey);
++ if (it != m_shaderPipelines.cend())
++ m_currentShaderPipeline = (*it).get();
++
++ QByteArray qsbcKey;
++ QSSGShaderFeatures features;
++ if (!m_currentShaderPipeline) { // don't spend time if already got the pipeline
++ features = shaderLib->getShaderMetaData(inCmd->m_shaderPathKey, QSSGShaderCache::ShaderType::Fragment).features;
++ qsbcKey = QQsbCollection::EntryDesc::generateSha(inCmd->m_shaderPathKey, QQsbCollection::toFeatureSet(features));
++ }
++
++ // Check if there's a build-time generated entry for this effect
++ if (!m_currentShaderPipeline && !shaderLib->m_preGeneratedShaderEntries.isEmpty()) {
++ const QQsbCollection::EntryMap &pregenEntries = shaderLib->m_preGeneratedShaderEntries;
++ const auto foundIt = pregenEntries.constFind(QQsbCollection::Entry(qsbcKey));
++ if (foundIt != pregenEntries.cend()) {
++ // The result here is always a new QSSGRhiShaderPipeline, which
++ // fulfills the requirements of our local cache (cmd/ubufIndex in
++ // cacheKey, not needed here since the result is a new object).
++ const auto &shader = shaderCache->newPipelineFromPregenerated(inCmd->m_shaderPathKey,
++ features,
++ *foundIt,
++ *inEffect,
++ QSSGRhiShaderPipeline::UsedWithoutIa);
++ m_shaderPipelines.insert(cacheKey, shader);
++ m_currentShaderPipeline = shader.get();
++ }
++ }
++
++ if (!m_currentShaderPipeline) {
++ // Try the persistent (disk-based) cache then. The result here is
++ // always a new QSSGRhiShaderPipeline, which fulfills the requirements
++ // of our local cache (cmd/ubufIndex in cacheKey, not needed here since
++ // the result is a new object). Alternatively, the result may be null
++ // if there was no hit.
++ const auto &shaderPipeline = shaderCache->tryNewPipelineFromPersistentCache(qsbcKey,
++ inCmd->m_shaderPathKey,
++ features,
++ QSSGRhiShaderPipeline::UsedWithoutIa);
++ if (shaderPipeline) {
++ m_shaderPipelines.insert(cacheKey, shaderPipeline);
++ m_currentShaderPipeline = shaderPipeline.get();
++ }
++ }
++
++ if (!m_currentShaderPipeline) {
++ // Final option, generate the shader pipeline
++ Q_TRACE_SCOPE(QSSG_generateShader);
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DGenerateShader);
++ const auto &generator = m_sgContext->shaderProgramGenerator();
++ if (auto stages = buildShaderForEffect(*inCmd, *generator, *shaderLib, *shaderCache, rhi->isYUpInFramebuffer(), viewCount)) {
++ m_shaderPipelines.insert(cacheKey, stages);
++ m_currentShaderPipeline = stages.get();
++ }
++ Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DGenerateShader, 0, inEffect->profilingId);
++ }
++
++ const auto &rhiContext = m_sgContext->rhiContext();
++
++ if (m_currentShaderPipeline) {
++ const void *cacheKey1 = reinterpret_cast<const void *>(this);
++ const void *cacheKey2 = reinterpret_cast<const void *>(qintptr(m_currentUbufIndex));
++ QSSGRhiDrawCallData &dcd = QSSGRhiContextPrivate::get(rhiContext.get())->drawCallData({ cacheKey1, cacheKey2,
++ nullptr, nullptr, 0 });
++ m_currentShaderPipeline->ensureCombinedUniformBuffer(&dcd.ubuf);
++ m_currentUBufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
++ } else {
++ m_currentUBufData = nullptr;
++ }
++
++ QSSGRhiContextStats::get(*rhiContext).registerEffectShaderGenerationTime(timer.elapsed());
++ }
++
++ void QSSGRhiEffectSystem::renderCmd(const QSSGRenderEffect *inEffect, QSSGRhiEffectTexture *inTexture, QSSGRhiEffectTexture *target, quint8 viewCount)
++ {
++ if (!m_currentShaderPipeline)
++ return;
++
++ if (!target) {
++ qWarning("No effect render target?");
++ return;
++ }
++
++ // the shader only uses one of these (or none)
++ addTextureToShaderPipeline(QByteArrayLiteral("qt_inputTexture"), inTexture->texture, inTexture->desc);
++ addTextureToShaderPipeline(QByteArrayLiteral("qt_inputTextureArray"), inTexture->texture, inTexture->desc);
++
++ const auto &rhiContext = m_sgContext->rhiContext();
++ const auto &renderer = m_sgContext->renderer();
++
++ QRhiCommandBuffer *cb = rhiContext->commandBuffer();
++ cb->debugMarkBegin(QByteArrayLiteral("Post-processing effect"));
++ Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderPass);
++
++ for (QRhiTextureRenderTarget *rt : std::as_const(m_pendingClears)) {
++ // Effects like motion blur use an accumulator texture that should
++ // start out empty (and they are sampled in the first pass), so such
++ // textures need an explicit clear. It is not applicable for the common
++ // case of outputting into a texture because that will get a clear
++ // anyway when rendering the quad.
++ if (rt != target->renderTarget) {
++ cb->beginPass(rt, Qt::transparent, { 1.0f, 0 }, nullptr, rhiContext->commonPassFlags());
++ QSSGRHICTX_STAT(rhiContext, beginRenderPass(rt));
++ cb->endPass();
++ QSSGRHICTX_STAT(rhiContext, endRenderPass());
++ }
++ }
++ m_pendingClears.clear();
++
++ const QSize inputSize = inTexture->texture->pixelSize();
++ const QSize outputSize = target->texture->pixelSize();
++ addCommonEffectUniforms(inEffect, inputSize, outputSize, viewCount);
++
++ QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiContext.get());
++
++ const void *cacheKey1 = reinterpret_cast<const void *>(this);
++ const void *cacheKey2 = reinterpret_cast<const void *>(qintptr(m_currentUbufIndex));
++ QSSGRhiDrawCallData &dcd = rhiCtxD->drawCallData({ cacheKey1, cacheKey2, nullptr, nullptr, 0 });
++ dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
++ m_currentUBufData = nullptr;
++
++ QRhiResourceUpdateBatch *rub = rhiContext->rhi()->nextResourceUpdateBatch();
++ renderer->rhiQuadRenderer()->prepareQuad(rhiContext.get(), rub);
++
++ // do resource bindings
++ const QRhiShaderResourceBinding::StageFlags VISIBILITY_ALL =
++ QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage;
++ QSSGRhiShaderResourceBindingList bindings;
++ for (const QSSGRhiTexture &rhiTex : std::as_const(m_currentTextures)) {
++ int binding = m_currentShaderPipeline->bindingForTexture(rhiTex.name);
++ if (binding < 0) // may not be used in the shader (think qt_inputTexture, it's not given a shader samples INPUT)
++ continue;
++ qCDebug(lcEffectSystem) << " -> texture binding" << binding << "for" << rhiTex.name;
++ // Make sure to bind all samplers even if the texture is missing, otherwise we can get crash on some graphics APIs
++ QRhiTexture *texture = rhiTex.texture ? rhiTex.texture : rhiContext->dummyTexture({}, rub);
++ bindings.addTexture(binding,
++ QRhiShaderResourceBinding::FragmentStage,
++ texture,
++ rhiContext->sampler(rhiTex.samplerDesc));
++ }
++ bindings.addUniformBuffer(0, VISIBILITY_ALL, dcd.ubuf);
++
++ QRhiShaderResourceBindings *srb = rhiCtxD->srb(bindings);
++
++ QSSGRhiGraphicsPipelineState ps;
++ ps.viewport = QRhiViewport(0, 0, float(outputSize.width()), float(outputSize.height()));
++ ps.samples = target->renderTarget->sampleCount();
++ ps.viewCount = viewCount;
++ QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(ps, m_currentShaderPipeline);
++
++ renderer->rhiQuadRenderer()->recordRenderQuadPass(rhiContext.get(), &ps, srb, target->renderTarget, QSSGRhiQuadRenderer::UvCoords);
++ m_currentUbufIndex++;
++ cb->debugMarkEnd();
++ Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DRenderPass, 0, QByteArrayLiteral("post_processing_effect"));
++ }
++
++ void QSSGRhiEffectSystem::addCommonEffectUniforms(const QSSGRenderEffect *inEffect, const QSize &inputSize, const QSize &outputSize, quint8 viewCount)
++ {
++ const auto &rhiContext = m_sgContext->rhiContext();
++ QRhi *rhi = rhiContext->rhi();
++
++ QMatrix4x4 mvp;
++ if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC())
++ mvp.data()[5] = -1.0f;
++ m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_modelViewProjection", mvp, QSSGRenderShaderValue::Matrix4x4);
++
++ const bool usesProjectionMatrix = inEffect->testFlag(QSSGRenderEffect::Flags::UsesProjectionMatrix);
++ const bool usesInverseProjectionMatrix = inEffect->testFlag(QSSGRenderEffect::Flags::UsesInverseProjectionMatrix);
++ if (usesProjectionMatrix || usesInverseProjectionMatrix) {
++ if (viewCount < 2) {
++ if (usesProjectionMatrix)
++ m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_projectionMatrix", m_projectionMatrices[0], QSSGRenderShaderValue::Matrix4x4);
++ if (usesInverseProjectionMatrix)
++ m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_inverseProjectionMatrix", m_projectionMatrices[0].inverted(), QSSGRenderShaderValue::Matrix4x4);
++ } else {
++ if (usesProjectionMatrix)
++ m_currentShaderPipeline->setUniformArray(m_currentUBufData, "qt_projectionMatrix", m_projectionMatrices.constData(), viewCount, QSSGRenderShaderValue::Matrix4x4);
++ if (usesInverseProjectionMatrix) {
++ QVarLengthArray<QMatrix4x4, 2> invertedProjections(viewCount);
++ for (quint8 viewIndex = 0; viewIndex < viewCount; ++viewIndex)
++ invertedProjections[viewIndex] = m_projectionMatrices[viewIndex].inverted();
++ m_currentShaderPipeline->setUniformArray(m_currentUBufData, "qt_inverseProjectionMatrix", invertedProjections.constData(), viewCount, QSSGRenderShaderValue::Matrix4x4);
++ }
++ }
++ }
++
++ if (inEffect->testFlag(QSSGRenderEffect::Flags::UsesViewMatrix)) {
++ if (viewCount < 2)
++ m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_viewMatrix", m_viewMatrices[0], QSSGRenderShaderValue::Matrix4x4);
++ else
++ m_currentShaderPipeline->setUniformArray(m_currentUBufData, "qt_viewMatrix", m_viewMatrices.constData(), viewCount, QSSGRenderShaderValue::Matrix4x4);
++ }
++
++ QVector2D size(inputSize.width(), inputSize.height());
++ m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_inputSize", size, QSSGRenderShaderValue::Vec2);
++
++ size = QVector2D(outputSize.width(), outputSize.height());
++ m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_outputSize", size, QSSGRenderShaderValue::Vec2);
++
++ float fc = float(m_sgContext->renderer()->frameCount());
++ m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_frame_num", fc, QSSGRenderShaderValue::Float);
++
++ // Bames and values for uniforms that are also used by default and/or
++ // custom materials must always match, effects must not deviate.
++
++ m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_cameraProperties", m_cameraClipRange, QSSGRenderShaderValue::Vec2);
++
++ float vp = rhi->isYUpInFramebuffer() ? 1.0f : -1.0f;
++ m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_normalAdjustViewportFactor", vp, QSSGRenderShaderValue::Float);
++
++ const float nearClip = rhi->isClipDepthZeroToOne() ? 0.0f : -1.0f;
++ m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_nearClipValue", nearClip, QSSGRenderShaderValue::Float);
++
++ const QVector4D rhiProperties(
++ rhi->isYUpInFramebuffer() ? 1.0f : -1.0f,
++ rhi->isYUpInNDC() ? 1.0f : -1.0f,
++ rhi->isClipDepthZeroToOne() ? 0.0f : -1.0f,
++ 0.0f // unused
++ );
++ m_currentShaderPipeline->setUniformValue(m_currentUBufData, "qt_rhi_properties", rhiProperties, QSSGRenderShaderValue::Vec4);
++
++ if (m_depthTexture) {
++ static const QSSGRhiSamplerDescription depthSamplerDesc {
++ QRhiSampler::Nearest, QRhiSampler::Nearest,
++ QRhiSampler::None,
++ QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat
++ };
++ addTextureToShaderPipeline("qt_depthTexture", m_depthTexture, depthSamplerDesc);
++ addTextureToShaderPipeline("qt_depthTextureArray", m_depthTexture, depthSamplerDesc);
++ }
++
++ if (m_normalTexture) {
++ static const QSSGRhiSamplerDescription normalSamplerDesc {
++ QRhiSampler::Nearest, QRhiSampler::Nearest,
++ QRhiSampler::None,
++ QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat
++ };
++ addTextureToShaderPipeline("qt_normalTexture", m_normalTexture, normalSamplerDesc);
++ }
++ if (m_motionVectorTexture) {
++ static const QSSGRhiSamplerDescription motionVectorSamplerDesc {
++ QRhiSampler::Linear, QRhiSampler::Linear,
++ QRhiSampler::None,
++ QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat
++ };
++ addTextureToShaderPipeline("qt_motionVectorTexture", m_motionVectorTexture, motionVectorSamplerDesc);
++ }
++ }
++
++ void QSSGRhiEffectSystem::addTextureToShaderPipeline(const QByteArray &name,
++ QRhiTexture *texture,
++ const QSSGRhiSamplerDescription &samplerDescription)
++ {
++ if (!m_currentShaderPipeline)
++ return;
++
++ static const QSSGRhiSamplerDescription defaultDescription { QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
++ QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat };
++ bool validDescription = samplerDescription.magFilter != QRhiSampler::None;
++
++ // This is a map for a reason: there can be multiple calls to this function
++ // for the same 'name', with a different 'texture', take the last value
++ // into account only.
++ m_currentTextures.insert(name, { name, texture, validDescription ? samplerDescription : defaultDescription});
++ }
++
++ QT_END_NAMESPACE