Revision control

Copy as Markdown

// Copyright (C) 2008-2012 NVIDIA Corporation.
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qssgrhicustommaterialsystem_p.h"
#include <QtQuick3DUtils/private/qssgutils_p.h>
#include "qssgrendercontextcore.h"
#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendermesh_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendercamera_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderlayer_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderableimage_p.h>
#include <QtQuick3DRuntimeRender/private/qssgvertexpipelineimpl_p.h>
#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendermodel_p.h>
#include <QtQuick3DRuntimeRender/private/qssgruntimerenderlogging_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrhiparticles_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderhelpers_p.h>
#include <qtquick3d_tracepoints_p.h>
#include <QtCore/qbitarray.h>
QT_BEGIN_NAMESPACE
Q_TRACE_POINT(qtquick3d, QSSG_generateShader_entry)
Q_TRACE_POINT(qtquick3d, QSSG_generateShader_exit)
QSSGCustomMaterialSystem::QSSGCustomMaterialSystem() = default;
QSSGCustomMaterialSystem::~QSSGCustomMaterialSystem()
{
}
bool QSSGCustomMaterialSystem::prepareForRender(const QSSGRenderModel &,
const QSSGRenderSubset &,
QSSGRenderCustomMaterial &inMaterial)
{
return inMaterial.isDirty();
}
void QSSGCustomMaterialSystem::setRenderContextInterface(QSSGRenderContextInterface *inContext)
{
context = inContext;
}
void QSSGCustomMaterialSystem::releaseCachedResources()
{
shaderMap.clear();
}
QSSGRhiShaderPipelinePtr QSSGCustomMaterialSystem::shadersForCustomMaterial(QSSGRhiGraphicsPipelineState *ps,
const QSSGRenderCustomMaterial &material,
QSSGSubsetRenderable &renderable,
const QSSGShaderDefaultMaterialKeyProperties &defaultMaterialShaderKeyProperties,
const QSSGShaderFeatures &featureSet,
const QSSGUserShaderAugmentation &shaderAugmentation)
{
QElapsedTimer timer;
timer.start();
QSSGRhiShaderPipelinePtr shaderPipeline;
const bool multiView = featureSet.isSet(QSSGShaderFeatures::Feature::DisableMultiView)
? false
: defaultMaterialShaderKeyProperties.m_viewCount.getValue(renderable.shaderDescription) >= 2;
const QByteArray &baseShaderPathKey = material.m_shaderPathKey[multiView ? QSSGRenderCustomMaterial::MultiViewShaderPathKeyIndex
: QSSGRenderCustomMaterial::RegularShaderPathKeyIndex];
const QByteArray shaderPathKey = shaderAugmentation.hasUserAugmentation()
? baseShaderPathKey + ':' + shaderAugmentation.hash
: baseShaderPathKey;
// This just references inFeatureSet and inRenderable.shaderDescription -
// cheap to construct and is good enough for the find(). This is the first
// level, fast lookup. (equivalent to what
// QSSGRenderer::getShaderPipelineForDefaultMaterial does for the
// default/principled material)
QSSGShaderMapKey skey = QSSGShaderMapKey(shaderPathKey,
featureSet,
renderable.shaderDescription);
auto it = shaderMap.find(skey);
if (it == shaderMap.end()) {
// This is also passed to generateMaterialRhiShader() so that newly
// generated shaders get stored in the caches under the same key.
QByteArray shaderString = shaderPathKey;
QSSGShaderDefaultMaterialKey matKey(renderable.shaderDescription);
matKey.toString(shaderString, defaultMaterialShaderKeyProperties);
for (const auto &def : shaderAugmentation.defines)
shaderString.append(def.name).append(';').append(def.value).append(';');
// Try the persistent (disk-based) cache.
const QByteArray qsbcKey = QQsbCollection::EntryDesc::generateSha(shaderString, QQsbCollection::toFeatureSet(featureSet));
shaderPipeline = context->shaderCache()->tryNewPipelineFromPersistentCache(qsbcKey, shaderString, featureSet);
if (!shaderPipeline) {
// Have to generate the shaders and send it all through the shader conditioning pipeline.
Q_TRACE_SCOPE(QSSG_generateShader);
Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DGenerateShader);
QSSGMaterialVertexPipeline vertexPipeline(*context->shaderProgramGenerator(),
defaultMaterialShaderKeyProperties,
material.adapter);
shaderPipeline = QSSGMaterialShaderGenerator::generateMaterialRhiShader(shaderPathKey,
shaderString,
vertexPipeline,
renderable.shaderDescription,
defaultMaterialShaderKeyProperties,
featureSet,
material,
*context->shaderLibraryManager(),
*context->shaderCache(),
shaderAugmentation);
Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DGenerateShader, 0, material.profilingId);
}
// make skey useable as a key for the QHash (makes a copy of the materialKey, instead of just referencing)
skey.detach();
// insert it no matter what, no point in trying over and over again
shaderMap.insert(skey, shaderPipeline);
} else {
shaderPipeline = it.value();
}
if (shaderPipeline) {
QSSGRhiGraphicsPipelineStatePrivate::setShaderPipeline(*ps, shaderPipeline.get());
shaderPipeline->resetExtraTextures();
}
QSSGRhiContextStats::get(*context->rhiContext()).registerMaterialShaderGenerationTime(timer.elapsed());
return shaderPipeline;
}
void QSSGCustomMaterialSystem::updateUniformsForCustomMaterial(QSSGRhiShaderPipeline &shaderPipeline,
QSSGRhiContext *rhiCtx,
const QSSGLayerRenderData &inData,
char *ubufData,
QSSGRhiGraphicsPipelineState *ps,
const QSSGRenderCustomMaterial &material,
QSSGSubsetRenderable &renderable,
const QSSGRenderCameraList &cameras,
const QVector2D *depthAdjust,
const QMatrix4x4 *alteredModelViewProjection)
{
QSSGRenderMvpArray alteredMvpList;
if (alteredModelViewProjection)
alteredMvpList[0] = *alteredModelViewProjection;
const QMatrix4x4 clipSpaceCorrMatrix = rhiCtx->rhi()->clipSpaceCorrMatrix();
QRhiTexture *lightmapTexture = inData.getLightmapTexture(renderable.modelContext);
const auto &modelNode = renderable.modelContext.model;
const auto &[localInstanceTransform, globalInstanceTransform] = inData.getInstanceTransforms(modelNode);
const auto &defaultMaterialShaderKeyProperties = inData.getDefaultMaterialPropertyTable();
const QMatrix4x4 &modelMatrix(modelNode.usesBoneTexture() ? QMatrix4x4() : renderable.modelContext.globalTransform);
QSSGMaterialShaderGenerator::setRhiMaterialProperties(*context,
shaderPipeline,
ubufData,
ps,
material,
renderable.shaderDescription,
defaultMaterialShaderKeyProperties,
cameras,
alteredModelViewProjection ? alteredMvpList : renderable.modelContext.modelViewProjections,
renderable.modelContext.normalMatrix,
modelMatrix,
clipSpaceCorrMatrix,
localInstanceTransform,
globalInstanceTransform,
toDataView(modelNode.morphWeights),
renderable.firstImage,
renderable.opacity,
inData,
renderable.lights,
renderable.reflectionProbe,
true,
renderable.renderableFlags.receivesReflections(),
depthAdjust,
lightmapTexture);
}
static const QRhiShaderResourceBinding::StageFlags CUSTOM_MATERIAL_VISIBILITY_ALL =
QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage;
void QSSGCustomMaterialSystem::rhiPrepareRenderable(QSSGRhiGraphicsPipelineState *ps,
QSSGPassKey passKey,
QSSGSubsetRenderable &renderable,
const QSSGShaderFeatures &featureSet,
const QSSGRenderCustomMaterial &material,
const QSSGLayerRenderData &layerData,
QRhiRenderPassDescriptor *renderPassDescriptor,
int samples,
int viewCount,
bool screenMapPass,
QSSGRenderCamera *alteredCamera,
QSSGRenderTextureCubeFace cubeFace,
QMatrix4x4 *alteredModelViewProjection,
QSSGReflectionMapEntry *entry,
bool oit)
{
QSSGRhiContext *rhiCtx = context->rhiContext().get();
QRhiGraphicsPipeline::TargetBlend blend; // no blending by default
if (material.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Blending)) {
blend.enable = true;
blend.srcColor = material.m_srcBlend;
blend.srcAlpha = material.m_srcAlphaBlend;
blend.dstColor = material.m_dstBlend;
blend.dstAlpha = material.m_dstAlphaBlend;
}
const QSSGCullFaceMode cullMode = material.m_cullMode;
const auto &defaultMaterialShaderKeyProperties = layerData.getDefaultMaterialPropertyTable();
const bool blendParticles = defaultMaterialShaderKeyProperties.m_blendParticles.getValue(renderable.shaderDescription);
const auto &shaderPipeline = shadersForCustomMaterial(ps, material, renderable, defaultMaterialShaderKeyProperties, featureSet);
if (shaderPipeline) {
QSSGRhiShaderResourceBindingList bindings;
const auto &modelNode = renderable.modelContext.model;
// NOTE:
// - entryIdx should 0 for QSSGRenderTextureCubeFaceNone (and a null entry).
// In all other cases the entryIdx is a combination of the cubeface idx and the subset
// offset, where the lower bits are the cubeface idx. The per-probe reflection map
// entry, if any, goes in the key's mapEntry slot. The key's resource must be the
// material itself, and nothing but the material, since
// cleanupDrawCallDataForResource() releases the entries for a to-be-destroyed
// material by matching on the resource.
const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
const quintptr entryIdx = quintptr(cubeFace != QSSGRenderTextureCubeFaceNone)
* (cubeFaceIdx + (quintptr(renderable.subset.offset) << 3));
// cleanupDrawCallDataForResource() is handed a QSSGRenderGraphObject pointer, so
// that is the identity the key has to carry.
const auto *materialResource = static_cast<const QSSGRenderGraphObject *>(&material);
QSSGRhiDrawCallData &dcd = QSSGRhiContextPrivate::get(rhiCtx)->drawCallData({ passKey, &modelNode,
materialResource, entry,
entryIdx });
shaderPipeline->ensureCombinedUniformBuffer(&dcd.ubuf);
char *ubufData = dcd.ubuf->beginFullDynamicBufferUpdateForCurrentFrame();
if (!alteredCamera) {
updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, layerData, ubufData, ps, material, renderable, layerData.renderedCameras, nullptr, nullptr);
} else {
QSSGRenderCameraList cameras({ alteredCamera });
updateUniformsForCustomMaterial(*shaderPipeline, rhiCtx, layerData, ubufData, ps, material, renderable, cameras, nullptr, alteredModelViewProjection);
}
if (blendParticles)
QSSGParticleRenderer::updateUniformsForParticleModel(*shaderPipeline, ubufData, &renderable.modelContext.model, renderable.subset.offset);
dcd.ubuf->endFullDynamicBufferUpdateForCurrentFrame();
if (blendParticles)
QSSGParticleRenderer::prepareParticlesForModel(*shaderPipeline, rhiCtx, bindings, &renderable.modelContext.model);
bool instancing = false;
if (!alteredCamera) {
const QSSGRenderCameraDataList &cameraDatas(*layerData.renderedCameraData);
instancing = QSSGLayerRenderData::prepareInstancing(rhiCtx, &renderable, cameraDatas[0].direction, cameraDatas[0].position, renderable.instancingLodMin, renderable.instancingLodMax);
} else {
const auto &altCamTransform = layerData.getGlobalTransform(*alteredCamera);
instancing = QSSGLayerRenderData::prepareInstancing(rhiCtx, &renderable, QSSGRenderNode::getScalingCorrectDirection(altCamTransform), QSSGRenderNode::getGlobalPos(altCamTransform), renderable.instancingLodMin, renderable.instancingLodMax);
}
ps->samples = samples;
ps->viewCount = viewCount;
ps->cullMode = QSSGRhiHelpers::toCullMode(cullMode);
if (!oit)
ps->targetBlend[0] = blend;
auto &ia = QSSGRhiInputAssemblerStatePrivate::get(*ps);
ia = renderable.subset.rhi.ia;
//### Copied code from default materials
int instanceBufferBinding = 0;
if (instancing) {
// Need to setup new bindings for instanced buffers
const quint32 stride = renderable.modelContext.model.instanceTable->stride();
QVarLengthArray<QRhiVertexInputBinding, 8> bindings;
std::copy(ia.inputLayout.cbeginBindings(),
ia.inputLayout.cendBindings(),
std::back_inserter(bindings));
bindings.append({ stride, QRhiVertexInputBinding::PerInstance });
instanceBufferBinding = bindings.size() - 1;
ia.inputLayout.setBindings(bindings.cbegin(), bindings.cend());
}
QSSGRhiHelpers::bakeVertexInputLocations(&ia, *shaderPipeline, instanceBufferBinding);
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
QRhiTexture *dummyTexture = rhiCtx->dummyTexture({}, resourceUpdates);
QRhiTexture *dummyTextureArray = rhiCtx->dummyTexture({}, resourceUpdates, QSize(64, 64), Qt::black, qMax(2, viewCount));
QRhiTexture *dummyTexture3D = rhiCtx->dummyTexture(QRhiTexture::ThreeDimensional, resourceUpdates);
QRhiTexture *dummyCubeTexture = rhiCtx->dummyTexture(QRhiTexture::CubeMap, resourceUpdates);
QRhiTexture *dummyCubeTextureArray = rhiCtx->dummyTexture(QRhiTexture::CubeMap, resourceUpdates, QSize(64, 64), Qt::black, qMax(2, viewCount));
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
bindings.addUniformBuffer(0, CUSTOM_MATERIAL_VISIBILITY_ALL, dcd.ubuf, 0, shaderPipeline->ub0Size());
bindings.addUniformBuffer(1, CUSTOM_MATERIAL_VISIBILITY_ALL, dcd.ubuf, shaderPipeline->ub0LightDataOffset(), sizeof(QSSGShaderLightsUniformData));
bindings.addUniformBuffer(2, CUSTOM_MATERIAL_VISIBILITY_ALL, dcd.ubuf, shaderPipeline->ub0DirectionalLightDataOffset(), sizeof(QSSGShaderDirectionalLightsUniformData));
QVector<QShaderDescription::InOutVariable> samplerVars =
shaderPipeline->fragmentStage()->shader().description().combinedImageSamplers();
const auto combinedSamplers = shaderPipeline->vertexStage()->shader().description().combinedImageSamplers();
for (const QShaderDescription::InOutVariable &var : combinedSamplers) {
auto it = std::find_if(samplerVars.cbegin(), samplerVars.cend(),
[&var](const QShaderDescription::InOutVariable &v) { return var.binding == v.binding; });
if (it == samplerVars.cend())
samplerVars.append(var);
}
int maxSamplerBinding = -1;
for (const QShaderDescription::InOutVariable &var : std::as_const(samplerVars))
maxSamplerBinding = qMax(maxSamplerBinding, var.binding);
// Will need to set unused image-samplers to something dummy
// because the shader code contains all custom property textures,
// and not providing a binding for all of them is invalid with some
// graphics APIs (and will need a real texture because setting a
// null handle or similar is not permitted with some of them so the
// srb does not accept null QRhiTextures either; but first let's
// figure out what bindings are unused in this frame)
QBitArray samplerBindingsSpecified(maxSamplerBinding + 1);
if (blendParticles)
samplerBindingsSpecified.setBit(shaderPipeline->bindingForTexture("qt_particleTexture"));
// Skinning
if (QRhiTexture *boneTexture = layerData.getBonemapTexture(renderable.modelContext)) {
int binding = shaderPipeline->bindingForTexture("qt_boneTexture");
if (binding >= 0) {
QRhiSampler *boneSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::Repeat
});
bindings.addTexture(binding,
QRhiShaderResourceBinding::VertexStage,
boneTexture,
boneSampler);
samplerBindingsSpecified.setBit(binding);
}
}
// Morphing
auto *targetsTexture = renderable.subset.rhi.targetsTexture;
if (targetsTexture) {
int binding = shaderPipeline->bindingForTexture("qt_morphTargetTexture");
if (binding >= 0) {
QRhiSampler *targetsSampler = rhiCtx->sampler({ QRhiSampler::Nearest,
QRhiSampler::Nearest,
QRhiSampler::None,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge,
QRhiSampler::ClampToEdge
});
bindings.addTexture(binding, QRhiShaderResourceBinding::VertexStage, renderable.subset.rhi.targetsTexture, targetsSampler);
samplerBindingsSpecified.setBit(binding);
}
}
// Prioritize reflection texture over Light Probe texture because
// reflection texture also contains the irradiance and pre filtered
// values for the light probe.
if (featureSet.isSet(QSSGShaderFeatures::Feature::ReflectionProbe)) {
int reflectionSampler = shaderPipeline->bindingForTexture("qt_reflectionMap");
QRhiTexture* reflectionTexture = layerData.getReflectionMapManager()->reflectionMapEntry(renderable.reflectionProbeIndex)->m_rhiPrefilteredCube;
const auto mipMapFilter = reflectionTexture && reflectionTexture->flags().testFlag(QRhiTexture::Flag::MipMapped)
? QRhiSampler::Linear
: QRhiSampler::None;
QRhiSampler *sampler = rhiCtx->sampler(
{ QRhiSampler::Linear, QRhiSampler::Linear, mipMapFilter, QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
if (reflectionSampler >= 0 && reflectionTexture) {
bindings.addTexture(reflectionSampler, QRhiShaderResourceBinding::FragmentStage, reflectionTexture, sampler);
samplerBindingsSpecified.setBit(reflectionSampler);
}
}
if (int binding = shaderPipeline->bindingForTexture("qt_lightProbe", int(QSSGRhiSamplerBindingHints::LightProbe));
binding >= 0) {
auto texture = shaderPipeline->lightProbeTexture();
if (!texture)
texture = rhiCtx->dummyTexture(QRhiTexture::CubeMap, resourceUpdates);
Q_ASSERT(texture);
samplerBindingsSpecified.setBit(binding);
QPair<QSSGRenderTextureCoordOp, QSSGRenderTextureCoordOp> tiling = shaderPipeline->lightProbeTiling();
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear,
QRhiSampler::Linear,
QRhiSampler::Linear, // enables mipmapping
QSSGRhiHelpers::toRhi(tiling.first),
QSSGRhiHelpers::toRhi(tiling.second),
QRhiSampler::Repeat });
bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
}
if (shaderPipeline->screenTexture()) {
const int screenTextureBinding = shaderPipeline->bindingForTexture("qt_screenTexture", int(QSSGRhiSamplerBindingHints::ScreenTexture));
const int screenTextureArrayBinding = shaderPipeline->bindingForTexture("qt_screenTextureArray", int(QSSGRhiSamplerBindingHints::ScreenTextureArray));
if (screenTextureBinding >= 0 || screenTextureArrayBinding >= 0) {
QRhiTexture *screenTexture = shaderPipeline->screenTexture();
// If we're called as part of the screen map pass there's obviously no screen texture available, but the shader may still expect it, so bind a dummy texture.
if (screenMapPass) {
QRhiResourceUpdateBatch *resourceUpdates = rhiCtx->rhi()->nextResourceUpdateBatch();
// Just mirror what is set up by the ScreenMap pass.
const QRhiTexture::Flags flags = screenTexture->flags();
// and set the dummy texture so there's something for the shader to sample.
screenTexture = rhiCtx->dummyTexture(flags, resourceUpdates);
rhiCtx->commandBuffer()->resourceUpdate(resourceUpdates);
}
// linear min/mag, mipmap filtering depends on the
// texture, with SCREEN_TEXTURE there are no mipmaps, but
// once SCREEN_MIP_TEXTURE is seen the texture (the same
// one) has mipmaps generated.
QRhiSampler::Filter mipFilter = shaderPipeline->screenTexture()->flags().testFlag(QRhiTexture::MipMapped)
? QRhiSampler::Linear : QRhiSampler::None;
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, mipFilter,
QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
if (screenTextureBinding >= 0) {
samplerBindingsSpecified.setBit(screenTextureBinding);
bindings.addTexture(screenTextureBinding,
QRhiShaderResourceBinding::FragmentStage,
screenTexture, sampler);
}
if (screenTextureArrayBinding >= 0) {
samplerBindingsSpecified.setBit(screenTextureArrayBinding);
bindings.addTexture(screenTextureArrayBinding,
QRhiShaderResourceBinding::FragmentStage,
screenTexture, sampler);
}
} // else ignore, not an error
}
if (shaderPipeline->depthTexture()) {
const int depthTextureBinding = shaderPipeline->bindingForTexture("qt_depthTexture", int(QSSGRhiSamplerBindingHints::DepthTexture));
const int depthTextureArrayBinding = shaderPipeline->bindingForTexture("qt_depthTextureArray", int(QSSGRhiSamplerBindingHints::DepthTextureArray));
if (depthTextureBinding >= 0 || depthTextureArrayBinding >= 0) {
// nearest min/mag, no mipmap
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
if (depthTextureBinding >= 0) {
samplerBindingsSpecified.setBit(depthTextureBinding);
bindings.addTexture(depthTextureBinding,
CUSTOM_MATERIAL_VISIBILITY_ALL,
shaderPipeline->depthTexture(), sampler);
}
if (depthTextureArrayBinding >= 0) {
samplerBindingsSpecified.setBit(depthTextureArrayBinding);
bindings.addTexture(depthTextureArrayBinding,
CUSTOM_MATERIAL_VISIBILITY_ALL,
shaderPipeline->depthTexture(), sampler);
}
} // else ignore, not an error
}
if (shaderPipeline->normalTexture()) {
const int normalTextureBinding = shaderPipeline->bindingForTexture("qt_normalTexture", int(QSSGRhiSamplerBindingHints::NormalTexture));
if (normalTextureBinding >= 0) {
// nearest min/mag, no mipmap
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
samplerBindingsSpecified.setBit(normalTextureBinding);
bindings.addTexture(normalTextureBinding, CUSTOM_MATERIAL_VISIBILITY_ALL, shaderPipeline->normalTexture(), sampler);
} // else ignore, not an error
}
if (shaderPipeline->ssaoTexture()) {
const int ssaoTextureBinding = shaderPipeline->bindingForTexture("qt_aoTexture", int(QSSGRhiSamplerBindingHints::AoTexture));
const int ssaoTextureArrayBinding = shaderPipeline->bindingForTexture("qt_aoTextureArray", int(QSSGRhiSamplerBindingHints::AoTextureArray));
if (ssaoTextureBinding >= 0 || ssaoTextureArrayBinding >= 0) {
// linear min/mag, no mipmap
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
if (ssaoTextureBinding >= 0) {
samplerBindingsSpecified.setBit(ssaoTextureBinding);
bindings.addTexture(ssaoTextureBinding,
QRhiShaderResourceBinding::FragmentStage,
shaderPipeline->ssaoTexture(), sampler);
}
if (ssaoTextureArrayBinding >= 0) {
samplerBindingsSpecified.setBit(ssaoTextureArrayBinding);
bindings.addTexture(ssaoTextureArrayBinding,
QRhiShaderResourceBinding::FragmentStage,
shaderPipeline->ssaoTexture(), sampler);
}
} // else ignore, not an error
}
if (shaderPipeline->lightmapTexture()) {
int binding = shaderPipeline->bindingForTexture("qt_lightmap", int(QSSGRhiSamplerBindingHints::LightmapTexture));
if (binding >= 0) {
samplerBindingsSpecified.setBit(binding);
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
bindings.addTexture(binding,
QRhiShaderResourceBinding::FragmentStage,
shaderPipeline->lightmapTexture(), sampler);
} // else ignore, not an error
}
if (shaderPipeline->MotionVectorTexture()) {
int binding = shaderPipeline->bindingForTexture("qt_motionVectorTexture", int(QSSGRhiSamplerBindingHints::MotionVectorTexture));
if (binding >= 0) {
samplerBindingsSpecified.setBit(binding);
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
bindings.addTexture(binding,
QRhiShaderResourceBinding::FragmentStage,
shaderPipeline->MotionVectorTexture(), sampler);
}
}
// Shadow map atlas
auto shadowMapAtlas = shaderPipeline->shadowMapAtlasTexture();
if (shadowMapAtlas) {
int binding = shaderPipeline->bindingForTexture("qt_shadowmap_texture");
if (binding >= 0) {
samplerBindingsSpecified.setBit(binding);
QRhiTexture *texture = shadowMapAtlas;
QRhiSampler *sampler = rhiCtx->sampler({ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
Q_ASSERT(texture && sampler);
bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
}
}
// Shadow map blue noise
if (auto shadowMapBlueNoise = shaderPipeline->shadowMapBlueNoiseTexture()) {
int binding = shaderPipeline->bindingForTexture("qt_shadowmap_blue_noise_texture");
if (binding >= 0) {
samplerBindingsSpecified.setBit(binding);
QRhiTexture *texture = shadowMapBlueNoise;
QRhiSampler *sampler = rhiCtx->sampler(
{ QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, QRhiSampler::Repeat, QRhiSampler::Repeat, QRhiSampler::Repeat });
Q_ASSERT(texture && sampler);
bindings.addTexture(binding, QRhiShaderResourceBinding::FragmentStage, texture, sampler);
}
}
QSSGRenderableImage *renderableImage = renderable.firstImage;
while (renderableImage) {
const char *samplerName = QSSGMaterialShaderGenerator::getSamplerName(renderableImage->m_mapType);
const int samplerHint = int(renderableImage->m_mapType);
int samplerBinding = shaderPipeline->bindingForTexture(samplerName, samplerHint);
if (samplerBinding >= 0) {
QRhiTexture *texture = renderableImage->m_texture.m_texture;
if (samplerBinding >= 0 && texture) {
const bool mipmapped = texture->flags().testFlag(QRhiTexture::MipMapped);
QSSGRhiSamplerDescription samplerDesc = {
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_minFilterType),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_magFilterType),
mipmapped ? QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_mipFilterType) : QRhiSampler::None,
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_horizontalTilingMode),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_verticalTilingMode),
QSSGRhiHelpers::toRhi(renderableImage->m_imageNode.m_depthTilingMode)
};
rhiCtx->checkAndAdjustForNPoT(texture, &samplerDesc);
QRhiSampler *sampler = rhiCtx->sampler(samplerDesc);
samplerBindingsSpecified.setBit(samplerBinding);
bindings.addTexture(samplerBinding,
CUSTOM_MATERIAL_VISIBILITY_ALL,
texture, sampler);
}
} // else this is not necessarily an error, e.g. having metalness/roughness maps with metalness disabled
renderableImage = renderableImage->m_nextImage;
}
if (maxSamplerBinding >= 0) {
// custom property textures
int customTexCount = shaderPipeline->extraTextureCount();
for (int i = 0; i < customTexCount; ++i) {
QSSGRhiTexture &t(shaderPipeline->extraTextureAt(i));
const int samplerBinding = shaderPipeline->bindingForTexture(t.name);
if (samplerBinding >= 0) {
samplerBindingsSpecified.setBit(samplerBinding);
rhiCtx->checkAndAdjustForNPoT(t.texture, &t.samplerDesc);
QRhiSampler *sampler = rhiCtx->sampler(t.samplerDesc);
bindings.addTexture(samplerBinding,
CUSTOM_MATERIAL_VISIBILITY_ALL,
t.texture,
sampler);
}
}
// use a dummy texture for the unused samplers in the shader
QRhiSampler *dummySampler = rhiCtx->sampler({ QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge, QRhiSampler::Repeat });
for (const QShaderDescription::InOutVariable &var : std::as_const(samplerVars)) {
if (!samplerBindingsSpecified.testBit(var.binding)) {
QRhiTexture *t = nullptr;
if (var.type == QShaderDescription::SamplerCube)
t = dummyCubeTexture;
else if (var.type == QShaderDescription::Sampler3D)
t = dummyTexture3D;
else if (var.type == QShaderDescription::Sampler2DArray)
t = dummyTextureArray;
else if (var.type == QShaderDescription::SamplerCubeArray)
t = dummyCubeTextureArray;
else
t = dummyTexture;
bindings.addTexture(var.binding, CUSTOM_MATERIAL_VISIBILITY_ALL, t, dummySampler);
}
}
}
QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
if (oit && layerData.layer.oitMethod == QSSGRenderLayer::OITMethod::LinkedList)
RenderHelpers::addAccumulatorImageBindings(shaderPipeline.get(), bindings);
// do the same srb lookup acceleration as default materials
QRhiShaderResourceBindings *&srb = dcd.srb;
bool srbChanged = false;
if (!srb || bindings != dcd.bindings) {
srb = rhiCtxD->srb(bindings);
rhiCtxD->releaseCachedSrb(dcd.bindings);
dcd.bindings = bindings;
srbChanged = true;
}
if (cubeFace == QSSGRenderTextureCubeFaceNone)
renderable.rhiRenderData.mainPass.srb = srb;
else
renderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx] = srb;
const auto pipelineKey = QSSGGraphicsPipelineStateKey::create(*ps, renderPassDescriptor, srb);
if (dcd.pipeline
&& !srbChanged
&& dcd.renderTargetDescriptionHash == pipelineKey.extra.renderTargetDescriptionHash
&& dcd.renderTargetDescription == pipelineKey.renderTargetDescription
&& dcd.ps == *ps)
{
if (cubeFace == QSSGRenderTextureCubeFaceNone)
renderable.rhiRenderData.mainPass.pipeline = dcd.pipeline;
else
renderable.rhiRenderData.reflectionPass.pipeline = dcd.pipeline;
} else {
if (cubeFace == QSSGRenderTextureCubeFaceNone) {
renderable.rhiRenderData.mainPass.pipeline = rhiCtxD->pipeline(pipelineKey,
renderPassDescriptor,
srb);
dcd.pipeline = renderable.rhiRenderData.mainPass.pipeline;
} else {
renderable.rhiRenderData.reflectionPass.pipeline = rhiCtxD->pipeline(pipelineKey,
renderPassDescriptor,
srb);
dcd.pipeline = renderable.rhiRenderData.reflectionPass.pipeline;
}
dcd.renderTargetDescriptionHash = pipelineKey.extra.renderTargetDescriptionHash;
dcd.renderTargetDescription = pipelineKey.renderTargetDescription;
dcd.ps = *ps;
}
}
}
void QSSGCustomMaterialSystem::setShaderResources(char *ubufData,
const QSSGRenderCustomMaterial &inMaterial,
const QByteArray &inPropertyName,
const QVariant &propertyValue,
QSSGRenderShaderValue::Type inPropertyType,
QSSGRhiShaderPipeline &shaderPipeline)
{
Q_UNUSED(inMaterial);
if (inPropertyType == QSSGRenderShaderValue::Texture) {
QSSGRenderCustomMaterial::TextureProperty *textureProperty =
reinterpret_cast<QSSGRenderCustomMaterial::TextureProperty *>(propertyValue.value<void *>());
QSSGRenderImage *image = textureProperty->texImage;
if (image) {
const auto &theBufferManager(context->bufferManager());
const QSSGRenderImageTexture texture = theBufferManager->loadRenderImage(image);
if (texture.m_texture) {
const QSSGRhiTexture t = {
inPropertyName,
texture.m_texture,
{ 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)
}
};
shaderPipeline.addExtraTexture(t);
}
}
} else {
shaderPipeline.setUniformValue(ubufData, inPropertyName, propertyValue, inPropertyType);
}
}
void QSSGCustomMaterialSystem::applyRhiShaderPropertyValues(char *ubufData,
const QSSGRenderCustomMaterial &material,
QSSGRhiShaderPipeline &shaderPipeline)
{
const auto &properties = material.m_properties;
for (const auto &prop : properties)
setShaderResources(ubufData, material, prop.name, prop.value, prop.shaderDataType, shaderPipeline);
const auto textProps = material.m_textureProperties;
for (const auto &prop : textProps)
setShaderResources(ubufData, material, prop.name, QVariant::fromValue((void *)&prop), prop.shaderDataType, shaderPipeline);
}
void QSSGCustomMaterialSystem::rhiRenderRenderable(QSSGRhiContext *rhiCtx,
QSSGSubsetRenderable &renderable,
bool *needsSetViewport,
QSSGRenderTextureCubeFace cubeFace,
const QSSGRhiGraphicsPipelineState &state,
qsizetype userPassIndex)
{
QRhiGraphicsPipeline *ps = (userPassIndex >= 0) ? renderable.rhiRenderData.userPassData[userPassIndex].pipeline : renderable.rhiRenderData.mainPass.pipeline;
QRhiShaderResourceBindings *srb = (userPassIndex >= 0) ? renderable.rhiRenderData.userPassData[userPassIndex].srb : renderable.rhiRenderData.mainPass.srb;
if (cubeFace != QSSGRenderTextureCubeFaceNone) {
const auto cubeFaceIdx = QSSGBaseTypeHelpers::indexOfCubeFace(cubeFace);
ps = renderable.rhiRenderData.reflectionPass.pipeline;
srb = renderable.rhiRenderData.reflectionPass.srb[cubeFaceIdx];
}
if (!ps || !srb)
return;
Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DRenderCall);
QRhiBuffer *vertexBuffer = renderable.subset.rhi.vertexBuffer->buffer();
QRhiBuffer *indexBuffer = renderable.subset.rhi.indexBuffer ? renderable.subset.rhi.indexBuffer->buffer() : nullptr;
QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
cb->setGraphicsPipeline(ps);
cb->setShaderResources(srb);
if (*needsSetViewport) {
cb->setViewport(state.viewport);
*needsSetViewport = false;
}
QRhiCommandBuffer::VertexInput vertexBuffers[2];
int vertexBufferCount = 1;
vertexBuffers[0] = QRhiCommandBuffer::VertexInput(vertexBuffer, 0);
quint32 instances = 1;
if (renderable.modelContext.model.instancing()) {
instances = renderable.modelContext.model.instanceCount();
vertexBuffers[1] = QRhiCommandBuffer::VertexInput(renderable.instanceBuffer, 0);
vertexBufferCount = 2;
}
if (indexBuffer) {
cb->setVertexInput(0, vertexBufferCount, vertexBuffers, indexBuffer, 0, renderable.subset.rhi.indexBuffer->indexFormat());
cb->drawIndexed(renderable.subset.lodCount(renderable.subsetLevelOfDetail), instances, renderable.subset.lodOffset(renderable.subsetLevelOfDetail));
QSSGRHICTX_STAT(rhiCtx, drawIndexed(renderable.subset.lodCount(renderable.subsetLevelOfDetail), instances));
} else {
cb->setVertexInput(0, vertexBufferCount, vertexBuffers);
cb->draw(renderable.subset.count, instances, renderable.subset.offset);
QSSGRHICTX_STAT(rhiCtx, draw(renderable.subset.count, instances));
}
Q_QUICK3D_PROFILE_END_WITH_IDS(QQuick3DProfiler::Quick3DRenderCall, (renderable.subset.count | quint64(instances) << 32),
QVector<int>({renderable.modelContext.model.profilingId,
renderable.material.profilingId}));
}
QT_END_NAMESPACE