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
/* clang-format off */
#include <QtQuick3DRuntimeRender/private/qssgshadermaterialadapter_p.h>
#include "qssgrendercontextcore.h"
#include <QtQuick3DRuntimeRender/private/qssgrhicustommaterialsystem_p.h>
QT_BEGIN_NAMESPACE
QSSGShaderMaterialAdapter::~QSSGShaderMaterialAdapter() = default;
QSSGShaderMaterialAdapter *QSSGShaderMaterialAdapter::create(const QSSGRenderGraphObject &materialNode)
{
switch (materialNode.type) {
case QSSGRenderGraphObject::Type::DefaultMaterial:
case QSSGRenderGraphObject::Type::PrincipledMaterial:
case QSSGRenderGraphObject::Type::SpecularGlossyMaterial:
return new QSSGShaderDefaultMaterialAdapter(static_cast<const QSSGRenderDefaultMaterial &>(materialNode));
case QSSGRenderGraphObject::Type::CustomMaterial:
return new QSSGShaderCustomMaterialAdapter(static_cast<const QSSGRenderCustomMaterial &>(materialNode));
default:
break;
}
return nullptr;
}
bool QSSGShaderMaterialAdapter::isUnshaded()
{
return false;
}
bool QSSGShaderMaterialAdapter::hasCustomShaderSnippet(QSSGShaderCache::ShaderType)
{
return false;
}
QByteArray QSSGShaderMaterialAdapter::customShaderSnippet(QSSGShaderCache::ShaderType,
QSSGShaderLibraryManager &,
bool)
{
return QByteArray();
}
bool QSSGShaderMaterialAdapter::hasCustomShaderFunction(QSSGShaderCache::ShaderType,
const QByteArray &,
QSSGShaderLibraryManager &)
{
return false;
}
void QSSGShaderMaterialAdapter::setCustomPropertyUniforms(char *,
QSSGRhiShaderPipeline &,
const QSSGRenderContextInterface &)
{
}
bool QSSGShaderMaterialAdapter::usesSharedVariables()
{
return false;
}
QSSGShaderDefaultMaterialAdapter::QSSGShaderDefaultMaterialAdapter(const QSSGRenderDefaultMaterial &material)
: m_material(material)
{
}
bool QSSGShaderDefaultMaterialAdapter::isPrincipled()
{
return m_material.type == QSSGRenderGraphObject::Type::PrincipledMaterial;
}
bool QSSGShaderDefaultMaterialAdapter::isSpecularGlossy()
{
return m_material.type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial;
}
bool QSSGShaderDefaultMaterialAdapter::isMetalnessEnabled()
{
return m_material.isMetalnessEnabled();
}
bool QSSGShaderDefaultMaterialAdapter::isSpecularEnabled()
{
return m_material.isSpecularEnabled();
}
bool QSSGShaderDefaultMaterialAdapter::isVertexColorsEnabled()
{
return m_material.isVertexColorsEnabled();
}
bool QSSGShaderDefaultMaterialAdapter::isVertexColorsMaskEnabled()
{
return m_material.isVertexColorsMaskEnabled();
}
bool QSSGShaderDefaultMaterialAdapter::isInvertOpacityMapValue()
{
return m_material.isInvertOpacityMapValue();
}
bool QSSGShaderDefaultMaterialAdapter::isBaseColorSingleChannelEnabled()
{
return m_material.isBaseColorSingleChannelEnabled();
}
bool QSSGShaderDefaultMaterialAdapter::isSpecularAmountSingleChannelEnabled()
{
return m_material.isSpecularAmountSingleChannelEnabled();
}
bool QSSGShaderDefaultMaterialAdapter::isEmissiveSingleChannelEnabled()
{
return m_material.isEmissiveSingleChannelEnabled();
}
bool QSSGShaderDefaultMaterialAdapter::isClearcoatEnabled()
{
return m_material.isClearcoatEnabled();
}
bool QSSGShaderDefaultMaterialAdapter::isSheenEnabled()
{
return m_material.isSheenEnabled();
}
bool QSSGShaderDefaultMaterialAdapter::isAnisotropyEnabled()
{
return m_material.isAnisotropyEnabled();
}
bool QSSGShaderDefaultMaterialAdapter::isIridescenceEnabled()
{
return m_material.isIridescenceEnabled();
}
bool QSSGShaderDefaultMaterialAdapter::isDispersionEnabled()
{
return m_material.isDispersionEnabled();
}
bool QSSGShaderDefaultMaterialAdapter::isTransmissionEnabled()
{
return m_material.isTransmissionEnabled();
}
bool QSSGShaderDefaultMaterialAdapter::hasLighting()
{
return m_material.hasLighting();
}
bool QSSGShaderDefaultMaterialAdapter::usesCustomSkinning()
{
return false;
}
bool QSSGShaderDefaultMaterialAdapter::usesCustomMorphing()
{
return false;
}
QSSGRenderDefaultMaterial::MaterialSpecularModel QSSGShaderDefaultMaterialAdapter::specularModel()
{
return m_material.specularModel;
}
QSSGRenderDefaultMaterial::MaterialAlphaMode QSSGShaderDefaultMaterialAdapter::alphaMode()
{
return m_material.alphaMode;
}
QSSGRenderDefaultMaterial::VertexColorMaskFlags QSSGShaderDefaultMaterialAdapter::vertexColorRedMask()
{
return m_material.vertexColorRedMask;
}
QSSGRenderDefaultMaterial::VertexColorMaskFlags QSSGShaderDefaultMaterialAdapter::vertexColorGreenMask()
{
return m_material.vertexColorGreenMask;
}
QSSGRenderDefaultMaterial::VertexColorMaskFlags QSSGShaderDefaultMaterialAdapter::vertexColorBlueMask()
{
return m_material.vertexColorBlueMask;
}
QSSGRenderDefaultMaterial::VertexColorMaskFlags QSSGShaderDefaultMaterialAdapter::vertexColorAlphaMask()
{
return m_material.vertexColorAlphaMask;
}
QSSGRenderImage *QSSGShaderDefaultMaterialAdapter::iblProbe()
{
return m_material.iblProbe;
}
QVector3D QSSGShaderDefaultMaterialAdapter::emissiveColor()
{
return m_material.emissiveColor;
}
QVector4D QSSGShaderDefaultMaterialAdapter::color()
{
return m_material.color;
}
QVector3D QSSGShaderDefaultMaterialAdapter::specularTint()
{
return m_material.specularTint;
}
float QSSGShaderDefaultMaterialAdapter::ior()
{
return m_material.ior;
}
bool QSSGShaderDefaultMaterialAdapter::isFresnelScaleBiasEnabled()
{
return m_material.fresnelScaleBiasEnabled;
}
float QSSGShaderDefaultMaterialAdapter::fresnelScale()
{
return m_material.fresnelScale;
}
float QSSGShaderDefaultMaterialAdapter::fresnelBias()
{
return m_material.fresnelBias;
}
float QSSGShaderDefaultMaterialAdapter::fresnelPower()
{
return m_material.fresnelPower;
}
bool QSSGShaderDefaultMaterialAdapter::isClearcoatFresnelScaleBiasEnabled()
{
return m_material.clearcoatFresnelScaleBiasEnabled;
}
float QSSGShaderDefaultMaterialAdapter::clearcoatFresnelScale()
{
return m_material.clearcoatFresnelScale;
}
float QSSGShaderDefaultMaterialAdapter::clearcoatFresnelBias()
{
return m_material.clearcoatFresnelBias;
}
float QSSGShaderDefaultMaterialAdapter::clearcoatFresnelPower()
{
return m_material.clearcoatFresnelPower;
}
float QSSGShaderDefaultMaterialAdapter::metalnessAmount()
{
return m_material.metalnessAmount;
}
float QSSGShaderDefaultMaterialAdapter::specularAmount()
{
return m_material.specularAmount;
}
float QSSGShaderDefaultMaterialAdapter::specularRoughness()
{
return m_material.specularRoughness;
}
float QSSGShaderDefaultMaterialAdapter::bumpAmount()
{
return m_material.bumpAmount;
}
float QSSGShaderDefaultMaterialAdapter::translucentFallOff()
{
return m_material.translucentFalloff;
}
float QSSGShaderDefaultMaterialAdapter::diffuseLightWrap()
{
return m_material.diffuseLightWrap;
}
float QSSGShaderDefaultMaterialAdapter::occlusionAmount()
{
return m_material.occlusionAmount;
}
float QSSGShaderDefaultMaterialAdapter::alphaCutOff()
{
return m_material.alphaCutoff;
}
float QSSGShaderDefaultMaterialAdapter::pointSize()
{
return m_material.pointSize;
}
float QSSGShaderDefaultMaterialAdapter::lineWidth()
{
return m_material.lineWidth;
}
float QSSGShaderDefaultMaterialAdapter::heightAmount()
{
return m_material.heightAmount;
}
float QSSGShaderDefaultMaterialAdapter::minHeightSamples()
{
return m_material.minHeightSamples;
}
float QSSGShaderDefaultMaterialAdapter::maxHeightSamples()
{
return m_material.maxHeightSamples;
}
float QSSGShaderDefaultMaterialAdapter::clearcoatAmount()
{
return m_material.clearcoatAmount;
}
float QSSGShaderDefaultMaterialAdapter::clearcoatRoughnessAmount()
{
return m_material.clearcoatRoughnessAmount;
}
float QSSGShaderDefaultMaterialAdapter::clearcoatNormalStrength()
{
return m_material.clearcoatNormalStrength;
}
QVector3D QSSGShaderDefaultMaterialAdapter::sheenColor()
{
return m_material.sheenColor;
}
float QSSGShaderDefaultMaterialAdapter::sheenRoughness()
{
return m_material.sheenRoughness;
}
float QSSGShaderDefaultMaterialAdapter::anisotropyStrength()
{
return m_material.anisotropyStrength;
}
float QSSGShaderDefaultMaterialAdapter::anisotropyRotation()
{
return m_material.anisotropyRotation;
}
float QSSGShaderDefaultMaterialAdapter::iridescenceFactor()
{
return m_material.iridescenceFactor;
}
float QSSGShaderDefaultMaterialAdapter::iridescenceIndexOfRefraction()
{
return m_material.iridescenceIndexOfRefraction;
}
float QSSGShaderDefaultMaterialAdapter::iridescenceThicknessMinimum()
{
return m_material.iridescenceThicknessMinimum;
}
float QSSGShaderDefaultMaterialAdapter::iridescenceThicknessMaximum()
{
return m_material.iridescenceThicknessMaximum;
}
float QSSGShaderDefaultMaterialAdapter::dispersion()
{
return m_material.dispersion;
}
float QSSGShaderDefaultMaterialAdapter::transmissionFactor()
{
return m_material.transmissionFactor;
}
float QSSGShaderDefaultMaterialAdapter::thicknessFactor()
{
return m_material.thicknessFactor;
}
float QSSGShaderDefaultMaterialAdapter::attenuationDistance()
{
return m_material.attenuationDistance;
}
QVector3D QSSGShaderDefaultMaterialAdapter::attenuationColor()
{
return m_material.attenuationColor;
}
QSSGShaderCustomMaterialAdapter::QSSGShaderCustomMaterialAdapter(const QSSGRenderCustomMaterial &material)
: m_material(material)
{
}
// Act like Principled. Lighting is always on, specular, metalness, etc. support should all be enabled.
// Unlike Principled, the *enabled values do not depend on the metalness or specularAmount values
// (we cannot tell what those are if they are written in the shader).
bool QSSGShaderCustomMaterialAdapter::isPrincipled()
{
return true;
}
bool QSSGShaderCustomMaterialAdapter::isSpecularGlossy()
{
return false;
}
bool QSSGShaderCustomMaterialAdapter::isMetalnessEnabled()
{
return true;
}
bool QSSGShaderCustomMaterialAdapter::isSpecularEnabled()
{
return true;
}
bool QSSGShaderCustomMaterialAdapter::isVertexColorsEnabled()
{
return m_material.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::VarColor);
}
bool QSSGShaderCustomMaterialAdapter::isVertexColorsMaskEnabled()
{
return false;
}
bool QSSGShaderCustomMaterialAdapter::isInvertOpacityMapValue()
{
return false;
}
bool QSSGShaderCustomMaterialAdapter::isBaseColorSingleChannelEnabled()
{
return false;
}
bool QSSGShaderCustomMaterialAdapter::isSpecularAmountSingleChannelEnabled()
{
return false;
}
bool QSSGShaderCustomMaterialAdapter::isEmissiveSingleChannelEnabled()
{
return false;
}
bool QSSGShaderCustomMaterialAdapter::isClearcoatEnabled()
{
return m_material.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Clearcoat);
}
bool QSSGShaderCustomMaterialAdapter::isSheenEnabled()
{
return m_material.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Sheen);
}
bool QSSGShaderCustomMaterialAdapter::isAnisotropyEnabled()
{
return m_material.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Anisotropy);
}
bool QSSGShaderCustomMaterialAdapter::isIridescenceEnabled()
{
return m_material.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Iridescence);
}
bool QSSGShaderCustomMaterialAdapter::isDispersionEnabled()
{
// Only declares the keyword; it is applied when transmission is used too
return m_material.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Dispersion);
}
bool QSSGShaderCustomMaterialAdapter::isTransmissionEnabled()
{
return m_material.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Transmission);
}
bool QSSGShaderCustomMaterialAdapter::hasLighting()
{
return true;
}
bool QSSGShaderCustomMaterialAdapter::usesCustomSkinning()
{
return m_material.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Skinning);
}
bool QSSGShaderCustomMaterialAdapter::usesCustomMorphing()
{
return m_material.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Morphing);
}
QSSGRenderDefaultMaterial::MaterialSpecularModel QSSGShaderCustomMaterialAdapter::specularModel()
{
return QSSGRenderDefaultMaterial::MaterialSpecularModel::SchlickGGX;
}
QSSGRenderDefaultMaterial::MaterialAlphaMode QSSGShaderCustomMaterialAdapter::alphaMode()
{
return QSSGRenderDefaultMaterial::MaterialAlphaMode::Default;
}
QSSGRenderDefaultMaterial::VertexColorMaskFlags QSSGShaderCustomMaterialAdapter::vertexColorRedMask()
{
return QSSGRenderDefaultMaterial::NoMask;
}
QSSGRenderDefaultMaterial::VertexColorMaskFlags QSSGShaderCustomMaterialAdapter::vertexColorGreenMask()
{
return QSSGRenderDefaultMaterial::NoMask;
}
QSSGRenderDefaultMaterial::VertexColorMaskFlags QSSGShaderCustomMaterialAdapter::vertexColorBlueMask()
{
return QSSGRenderDefaultMaterial::NoMask;
}
QSSGRenderDefaultMaterial::VertexColorMaskFlags QSSGShaderCustomMaterialAdapter::vertexColorAlphaMask()
{
return QSSGRenderDefaultMaterial::NoMask;
}
QSSGRenderImage *QSSGShaderCustomMaterialAdapter::iblProbe()
{
return m_material.m_iblProbe;
}
// The following are the values that get set into uniforms such as
// qt_material_properties etc. When a custom shader is present, these values
// are not used at all. However, a CustomMaterial is also valid without a
// vertex/fragment shader, or with no custom shaders at all. Therefore the
// values here must match the defaults of PrincipledMaterial, in order to make
// PrincipledMaterial { } and CustomMaterial { } identical.
QVector3D QSSGShaderCustomMaterialAdapter::emissiveColor()
{
return QVector3D(0, 0, 0);
}
QVector4D QSSGShaderCustomMaterialAdapter::color()
{
return QVector4D(1, 1, 1, 1);
}
QVector3D QSSGShaderCustomMaterialAdapter::specularTint()
{
return QVector3D(1, 1, 1);
}
float QSSGShaderCustomMaterialAdapter::ior()
{
return 1.45f;
}
bool QSSGShaderCustomMaterialAdapter::isFresnelScaleBiasEnabled()
{
return m_material.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::FresnelScaleBias);
}
float QSSGShaderCustomMaterialAdapter::fresnelScale()
{
return 1.0f;
}
float QSSGShaderCustomMaterialAdapter::fresnelBias()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::fresnelPower()
{
return 0.0f;
}
bool QSSGShaderCustomMaterialAdapter::isClearcoatFresnelScaleBiasEnabled()
{
return m_material.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::ClearcoatFresnelScaleBias);
}
float QSSGShaderCustomMaterialAdapter::clearcoatFresnelScale()
{
return 1.0f;
}
float QSSGShaderCustomMaterialAdapter::clearcoatFresnelBias()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::clearcoatFresnelPower()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::metalnessAmount()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::specularAmount()
{
return 0.5f;
}
float QSSGShaderCustomMaterialAdapter::specularRoughness()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::bumpAmount()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::translucentFallOff()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::diffuseLightWrap()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::occlusionAmount()
{
return 1.0f;
}
float QSSGShaderCustomMaterialAdapter::alphaCutOff()
{
return 0.5f;
}
float QSSGShaderCustomMaterialAdapter::pointSize()
{
return 1.0f;
}
float QSSGShaderCustomMaterialAdapter::lineWidth()
{
return m_material.m_lineWidth;
}
float QSSGShaderCustomMaterialAdapter::heightAmount()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::minHeightSamples()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::maxHeightSamples()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::clearcoatAmount()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::clearcoatRoughnessAmount()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::clearcoatNormalStrength()
{
return 1.0f;
}
QVector3D QSSGShaderCustomMaterialAdapter::sheenColor()
{
// Custom materials provide the value through the SHEEN_COLOR keyword
return { 0.0f, 0.0f, 0.0f };
}
float QSSGShaderCustomMaterialAdapter::sheenRoughness()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::anisotropyStrength()
{
// Custom materials provide the value through the ANISOTROPY_STRENGTH keyword
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::anisotropyRotation()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::iridescenceFactor()
{
// Custom materials provide the value through the IRIDESCENCE_FACTOR keyword
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::iridescenceIndexOfRefraction()
{
return 1.3f;
}
float QSSGShaderCustomMaterialAdapter::iridescenceThicknessMinimum()
{
return 100.0f;
}
float QSSGShaderCustomMaterialAdapter::iridescenceThicknessMaximum()
{
return 400.0f;
}
float QSSGShaderCustomMaterialAdapter::dispersion()
{
// Custom materials provide the value through the DISPERSION keyword
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::transmissionFactor()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::thicknessFactor()
{
return 0.0f;
}
float QSSGShaderCustomMaterialAdapter::attenuationDistance()
{
return std::numeric_limits<float>::infinity();
}
QVector3D QSSGShaderCustomMaterialAdapter::attenuationColor()
{
return { 1.0f, 1.0f, 1.0f };
}
bool QSSGShaderCustomMaterialAdapter::isUnshaded()
{
return m_material.m_shadingMode == QSSGRenderCustomMaterial::ShadingMode::Unshaded;
}
bool QSSGShaderCustomMaterialAdapter::hasCustomShaderSnippet(QSSGShaderCache::ShaderType type)
{
if (type == QSSGShaderCache::ShaderType::Vertex)
return m_material.m_customShaderPresence.testFlag(QSSGRenderCustomMaterial::CustomShaderPresenceFlag::Vertex);
return m_material.m_customShaderPresence.testFlag(QSSGRenderCustomMaterial::CustomShaderPresenceFlag::Fragment);
}
QByteArray QSSGShaderCustomMaterialAdapter::customShaderSnippet(QSSGShaderCache::ShaderType type,
QSSGShaderLibraryManager &shaderLibraryManager,
bool multiViewCompatible)
{
if (hasCustomShaderSnippet(type)) {
const QByteArray shaderPathKey = m_material.m_shaderPathKey[multiViewCompatible ? QSSGRenderCustomMaterial::MultiViewShaderPathKeyIndex
: QSSGRenderCustomMaterial::RegularShaderPathKeyIndex];
return shaderLibraryManager.getShaderSource(shaderPathKey, type);
}
return QByteArray();
}
bool QSSGShaderCustomMaterialAdapter::hasCustomShaderFunction(QSSGShaderCache::ShaderType shaderType,
const QByteArray &funcName,
QSSGShaderLibraryManager &shaderLibraryManager)
{
if (hasCustomShaderSnippet(shaderType))
return shaderLibraryManager.getShaderMetaData(m_material.m_shaderPathKey[QSSGRenderCustomMaterial::RegularShaderPathKeyIndex],
shaderType).customFunctions.contains(funcName);
return false;
}
void QSSGShaderCustomMaterialAdapter::setCustomPropertyUniforms(char *ubufData,
QSSGRhiShaderPipeline &shaderPipeline,
const QSSGRenderContextInterface &context)
{
context.customMaterialSystem()->applyRhiShaderPropertyValues(ubufData, m_material, shaderPipeline);
}
bool QSSGShaderCustomMaterialAdapter::usesSharedVariables()
{
return m_material.m_usesSharedVariables;
}
namespace {
// Custom material shader substitution table.
// Must be in sync with the shader generator.
static const QSSGCustomMaterialVariableSubstitution qssg_var_subst_tab[] = {
// uniform (block members)
{ "MODELVIEWPROJECTION_MATRIX", "qt_modelViewProjection", true },
{ "VIEWPROJECTION_MATRIX", "qt_viewProjectionMatrix", true },
{ "MODEL_MATRIX", "qt_modelMatrix", false },
{ "VIEW_MATRIX", "qt_viewMatrix", true },
{ "NORMAL_MATRIX", "qt_normalMatrix", false },
{ "BONE_TRANSFORMS", "qt_getTexMatrix", false },
{ "BONE_NORMAL_TRANSFORMS", "qt_getTexMatrix", false },
{ "PROJECTION_MATRIX", "qt_projectionMatrix", true },
{ "INVERSE_PROJECTION_MATRIX", "qt_inverseProjectionMatrix", true },
{ "CAMERA_POSITION", "qt_cameraPosition", true },
{ "CAMERA_DIRECTION", "qt_cameraDirection", true },
{ "CAMERA_PROPERTIES", "qt_cameraProperties", false },
{ "FRAMEBUFFER_Y_UP", "qt_rhi_properties.x", false },
{ "NDC_Y_UP", "qt_rhi_properties.y", false },
{ "NEAR_CLIP_VALUE", "qt_rhi_properties.z", false },
{ "IBL_MAXMIPMAP", "qt_lightProbeProperties.y", false },
{ "IBL_HORIZON", "qt_lightProbeProperties.z", false },
{ "IBL_EXPOSE", "qt_lightProbeProperties.w", false },
// outputs
{ "POSITION", "gl_Position", false },
{ "FRAGCOLOR", "fragOutput", false },
{ "POINT_SIZE", "gl_PointSize", false },
// fragment inputs
{ "FRAGCOORD", "gl_FragCoord", false },
// functions
{ "DIRECTIONAL_LIGHT", "qt_directionalLightProcessor", false },
{ "POINT_LIGHT", "qt_pointLightProcessor", false },
{ "SPOT_LIGHT", "qt_spotLightProcessor", false },
{ "AMBIENT_LIGHT", "qt_ambientLightProcessor", false },
{ "SPECULAR_LIGHT", "qt_specularLightProcessor", false },
{ "MAIN", "qt_customMain", false },
{ "POST_PROCESS", "qt_customPostProcessor", false },
{ "IBL_PROBE", "qt_iblProbeProcessor", false },
// textures
{ "SCREEN_TEXTURE", "qt_screenTexture", true },
{ "SCREEN_MIP_TEXTURE", "qt_screenTexture", true }, // same resource as SCREEN_TEXTURE under the hood
{ "DEPTH_TEXTURE", "qt_depthTexture", true },
{ "AO_TEXTURE", "qt_aoTexture", true },
{ "IBL_TEXTURE", "qt_lightProbe", false },
{ "LIGHTMAP", "qt_lightmap", false },
{ "NORMAL_ROUGHNESS_TEXTURE", "qt_normalTexture", false },
{ "MOTION_VECTOR_TEXTURE", "qt_motionVectorTexture", false },
// For shaded only: vertex outputs, for convenience and perf. (only those
// that are always present when lighting is enabled) The custom vertex main
// can also calculate on its own and pass them on with VARYING but that's a
// bit wasteful since we calculate these anyways.
{ "VAR_WORLD_NORMAL", "qt_varNormal", false },
{ "VAR_WORLD_TANGENT", "qt_varTangent", false },
{ "VAR_WORLD_BINORMAL", "qt_varBinormal", false },
{ "VAR_WORLD_POSITION", "qt_varWorldPos", false },
// vertex color is always enabled for custom materials (shaded)
{ "VAR_COLOR", "qt_varColor", false },
// effects
{ "INPUT", "qt_inputTexture", true },
{ "INPUT_UV", "qt_inputUV", false },
{ "TEXTURE_UV", "qt_textureUV", false },
{ "INPUT_SIZE", "qt_inputSize", false },
{ "OUTPUT_SIZE", "qt_outputSize", false },
{ "FRAME", "qt_frame_num", false },
// instancing
{ "INSTANCE_COLOR", "qt_instanceColor", false },
{ "INSTANCE_DATA", "qt_instanceData", false },
{ "INSTANCE_INDEX", "gl_InstanceIndex", false },
// morphing
{ "MORPH_POSITION", "qt_getTargetPositionFromTargetId", false },
{ "MORPH_NORMAL", "qt_getTargetNormalFromTargetId", false },
{ "MORPH_TANGENT", "qt_getTargetTangentFromTargetId", false },
{ "MORPH_BINORMAL", "qt_getTargetBinormalFromTargetId", false },
{ "MORPH_WEIGHTS", "qt_morphWeights", false },
// custom variables
{ "SHARED_VARS", "struct QT_SHARED_VARS", false },
// multiview
{ "VIEW_INDEX", "qt_viewIndex", false }
};
// Functions that, if present, get an argument list injected.
static const QByteArrayView qssg_func_injectarg_tab[] = {
"DIRECTIONAL_LIGHT",
"POINT_LIGHT",
"SPOT_LIGHT",
"AMBIENT_LIGHT",
"SPECULAR_LIGHT",
"MAIN",
"POST_PROCESS",
"IBL_PROBE"
};
// This is based on the Qt Quick shader rewriter (with fixes)
struct Tokenizer {
enum Token {
Token_Comment,
Token_OpenBrace,
Token_CloseBrace,
Token_OpenParen,
Token_CloseParen,
Token_SemiColon,
Token_Identifier,
Token_OpenBraket,
Token_CloseBraket,
Token_Unspecified,
Token_EOF
};
void initialize(const QByteArray &input);
Token next();
const char *stream;
const char *pos;
const char *identifier;
};
void Tokenizer::initialize(const QByteArray &input)
{
stream = input.constData();
pos = input;
identifier = input;
}
Tokenizer::Token Tokenizer::next()
{
while (*pos) {
char c = *pos++;
switch (c) {
case '/':
if (*pos == '/') {
// '//' comment
++pos;
while (*pos && *pos != '\n') ++pos;
return Token_Comment;
} else if (*pos == '*') {
// /* */ comment
++pos;
while (*pos && (*pos != '*' || pos[1] != '/')) ++pos;
if (*pos) pos += 2;
return Token_Comment;
}
return Token_Unspecified;
case ';': return Token_SemiColon;
case '\0': return Token_EOF;
case '{': return Token_OpenBrace;
case '}': return Token_CloseBrace;
case '(': return Token_OpenParen;
case ')': return Token_CloseParen;
case '[': return Token_OpenBraket;
case ']': return Token_CloseBraket;
case ' ':
case '\n':
case '\r': break;
default:
// Identifier...
if ((c >= 'a' && c <= 'z' ) || (c >= 'A' && c <= 'Z' ) || c == '_') {
identifier = pos - 1;
while (*pos && ((*pos >= 'a' && *pos <= 'z')
|| (*pos >= 'A' && *pos <= 'Z')
|| *pos == '_'
|| (*pos >= '0' && *pos <= '9'))) {
++pos;
}
return Token_Identifier;
} else {
return Token_Unspecified;
}
}
}
return Token_EOF;
}
} // namespace
void QSSGShaderCustomMaterialAdapter::beginPrepareCustomShader(
CustomShaderPrepWorkData *workData,
ShaderCodeAndMetaData *codeAndMetaData,
const QByteArray &shaderCode,
QSSGShaderCache::ShaderType type,
bool multiViewCompatible)
{
QByteArrayList inputs;
QByteArrayList outputs;
Tokenizer tok;
tok.initialize(shaderCode);
QSSGCustomShaderMetaData md = {};
QByteArray result;
result.reserve(1024);
// If shader debugging is not enabled we reset the line count to make error message
// when a shader fails more useful. When shader debugging is enabled the whole shader
// will be printed and not just the user written part, so in that case we do not want
// to adjust the line numbers.
//
// NOTE: This is not perfect, we do expend the custom material and effect shaders, so
// there cane still be cases where the reported line numbers are slightly off.
if (!QSSGRhiContextPrivate::shaderDebuggingEnabled())
result.prepend("#line 1\n");
const char *lastPos = shaderCode.constData();
int funcFinderState = 0;
int useJointTexState = -1;
int useJointNormalTexState = -1;
QByteArray currentShadedFunc;
Tokenizer::Token t = tok.next();
while (t != Tokenizer::Token_EOF) {
switch (t) {
case Tokenizer::Token_Comment:
break;
case Tokenizer::Token_Identifier:
{
QByteArray id = QByteArray::fromRawData(lastPos, tok.pos - lastPos);
if (id.trimmed() == QByteArrayLiteral("VARYING")) {
QByteArray vtype;
QByteArray vname;
bool vflat = false;
lastPos = tok.pos;
t = tok.next();
while (t != Tokenizer::Token_EOF) {
QByteArray data = QByteArray::fromRawData(lastPos, tok.pos - lastPos);
if (t == Tokenizer::Token_Identifier) {
if (vtype.isEmpty()) {
vtype = data.trimmed();
if (vtype == QByteArrayLiteral("flat")) {
vflat = true;
vtype.clear();
}
} else if (vname.isEmpty()) {
vname = data.trimmed();
}
}
if (t == Tokenizer::Token_SemiColon)
break;
lastPos = tok.pos;
t = tok.next();
}
if (type == QSSGShaderCache::ShaderType::Vertex)
outputs.append((vflat ? "flat " : "") + vtype + " " + vname);
else
inputs.append((vflat ? "flat " : "") + vtype + " " + vname);
} else {
const QByteArray trimmedId = id.trimmed();
if (funcFinderState == 0 && trimmedId == QByteArrayLiteral("void")) {
funcFinderState += 1;
} else if (funcFinderState == 1) {
auto begin = qssg_func_injectarg_tab;
const auto end = qssg_func_injectarg_tab + (sizeof(qssg_func_injectarg_tab) / sizeof(qssg_func_injectarg_tab[0]));
const auto foundIt = std::find_if(begin, end, [trimmedId](const QByteArrayView &entry) { return entry == trimmedId; });
if (foundIt != end) {
currentShadedFunc = trimmedId;
funcFinderState += 1;
}
} else {
funcFinderState = 0;
}
if (trimmedId == QByteArrayLiteral("SCREEN_TEXTURE"))
md.flags |= QSSGCustomShaderMetaData::UsesScreenTexture;
else if (trimmedId == QByteArrayLiteral("SCREEN_MIP_TEXTURE"))
md.flags |= QSSGCustomShaderMetaData::UsesScreenMipTexture;
else if (trimmedId == QByteArrayLiteral("DEPTH_TEXTURE"))
md.flags |= QSSGCustomShaderMetaData::UsesDepthTexture;
else if (trimmedId == QByteArrayLiteral("NORMAL_ROUGHNESS_TEXTURE"))
md.flags |= QSSGCustomShaderMetaData::UsesNormalTexture;
else if (trimmedId == QByteArrayLiteral("AO_TEXTURE"))
md.flags |= QSSGCustomShaderMetaData::UsesAoTexture;
else if (trimmedId == QByteArrayLiteral("POSITION"))
md.flags |= QSSGCustomShaderMetaData::OverridesPosition;
else if (trimmedId == QByteArrayLiteral("PROJECTION_MATRIX"))
md.flags |= QSSGCustomShaderMetaData::UsesProjectionMatrix;
else if (trimmedId == QByteArrayLiteral("INVERSE_PROJECTION_MATRIX"))
md.flags |= QSSGCustomShaderMetaData::UsesInverseProjectionMatrix;
else if (trimmedId == QByteArrayLiteral("VIEW_MATRIX"))
md.flags |= QSSGCustomShaderMetaData::UsesViewMatrix;
else if (trimmedId == QByteArrayLiteral("VAR_COLOR"))
md.flags |= QSSGCustomShaderMetaData::UsesVarColor;
else if (trimmedId == QByteArrayLiteral("SHARED_VARS"))
md.flags |= QSSGCustomShaderMetaData::UsesSharedVars;
else if (trimmedId == QByteArrayLiteral("IBL_ORIENTATION"))
md.flags |= QSSGCustomShaderMetaData::UsesIblOrientation;
else if (trimmedId == QByteArrayLiteral("LIGHTMAP"))
md.flags |= QSSGCustomShaderMetaData::UsesLightmap;
else if (trimmedId == QByteArrayLiteral("VIEW_INDEX"))
md.flags |= QSSGCustomShaderMetaData::UsesViewIndex;
else if (trimmedId == QByteArrayLiteral("INPUT"))
md.flags |= QSSGCustomShaderMetaData::UsesInputTexture;
else if (trimmedId == QByteArrayLiteral("CLEARCOAT_AMOUNT"))
md.flags |= QSSGCustomShaderMetaData::UsesClearcoat;
else if (trimmedId == QByteArrayLiteral("SHEEN_COLOR")
|| trimmedId == QByteArrayLiteral("SHEEN_ROUGHNESS"))
md.flags |= QSSGCustomShaderMetaData::UsesSheen;
else if (trimmedId == QByteArrayLiteral("ANISOTROPY_STRENGTH")
|| trimmedId == QByteArrayLiteral("ANISOTROPY_ROTATION"))
md.flags |= QSSGCustomShaderMetaData::UsesAnisotropy;
else if (trimmedId == QByteArrayLiteral("IRIDESCENCE_FACTOR")
|| trimmedId == QByteArrayLiteral("IRIDESCENCE_IOR")
|| trimmedId == QByteArrayLiteral("IRIDESCENCE_THICKNESS"))
md.flags |= QSSGCustomShaderMetaData::UsesIridescence;
else if (trimmedId == QByteArrayLiteral("DISPERSION"))
md.flags |= QSSGCustomShaderMetaData::UsesDispersion;
else if (trimmedId == QByteArrayLiteral("CLEARCOAT_FRESNEL_SCALE") ||
trimmedId == QByteArrayLiteral("CLEARCOAT_FRESNEL_BIAS"))
md.flags |= QSSGCustomShaderMetaData::UsesClearcoatFresnelScaleBias;
else if (trimmedId == QByteArrayLiteral("FRESNEL_SCALE") ||
trimmedId == QByteArrayLiteral("FRESNEL_BIAS"))
md.flags |= QSSGCustomShaderMetaData::UsesFresnelScaleBias;
else if (trimmedId == QByteArrayLiteral("MOTION_VECTOR_TEXTURE"))
md.flags |= QSSGCustomShaderMetaData::UsesMotionVectorTexture;
else if (trimmedId == QByteArrayLiteral("TRANSMISSION_FACTOR")) {
md.flags |= QSSGCustomShaderMetaData::UsesTransmission;
md.flags |= QSSGCustomShaderMetaData::UsesScreenTexture;
md.flags |= QSSGCustomShaderMetaData::UsesScreenMipTexture;
}
for (const QSSGCustomMaterialVariableSubstitution &subst : qssg_var_subst_tab) {
if (trimmedId == subst.builtin) {
QByteArray newExpr;
newExpr.assign(subst.actualName);
if (subst.multiViewDependent && multiViewCompatible) {
if (subst.builtin.endsWith(QByteArrayLiteral("_TEXTURE"))
|| subst.builtin == QByteArrayLiteral("INPUT"))
{
newExpr += QByteArrayLiteral("Array"); // e.g. qt_depthTexture -> qt_depthTextureArray
} else {
newExpr += QByteArrayLiteral("[qt_viewIndex]"); // e.g. qt_viewProjectionMatrix -> qt_viewProjectionMatrix[qt_viewIndex]
}
}
id.replace(subst.builtin, newExpr); // replace, not assignment, to keep whitespace etc.
if (trimmedId == QByteArrayLiteral("BONE_TRANSFORMS")) {
useJointTexState = 0;
md.flags |= QSSGCustomShaderMetaData::UsesSkinning;
} else if (trimmedId == QByteArrayLiteral("BONE_NORMAL_TRANSFORMS")) {
useJointNormalTexState = 0;
md.flags |= QSSGCustomShaderMetaData::UsesSkinning;
}
if (trimmedId == QByteArrayLiteral("MORPH_POSITION") ||
trimmedId == QByteArrayLiteral("MORPH_NORMAL") ||
trimmedId == QByteArrayLiteral("MORPH_TANGENT") ||
trimmedId == QByteArrayLiteral("MORPH_BINORMAL"))
md.flags |= QSSGCustomShaderMetaData::UsesMorphing;
break;
}
}
result += id;
}
}
break;
case Tokenizer::Token_OpenParen:
result += QByteArray::fromRawData(lastPos, tok.pos - lastPos);
if (funcFinderState == 2) {
result += QByteArrayLiteral("/*%QT_ARGS_");
result += currentShadedFunc;
result += QByteArrayLiteral("%*/");
for (const QSSGCustomMaterialVariableSubstitution &subst : qssg_var_subst_tab) {
if (currentShadedFunc == subst.builtin) {
currentShadedFunc = subst.actualName.toByteArray();
break;
}
}
md.customFunctions.insert(currentShadedFunc);
currentShadedFunc.clear();
}
funcFinderState = 0;
break;
case Tokenizer::Token_OpenBraket:
// copy everything as-is up to the [
result += QByteArray::fromRawData(lastPos, tok.pos - lastPos - 1);
if (useJointTexState == 0) {
result += QByteArrayLiteral("(2 * (");
++useJointTexState;
break;
} else if (useJointNormalTexState == 0) {
result += QByteArrayLiteral("(1 + 2 * (");
++useJointNormalTexState;
break;
}
if (useJointTexState >= 0)
++useJointTexState;
else if (useJointNormalTexState >= 0)
++useJointNormalTexState;
result += QByteArrayLiteral("[");
break;
case Tokenizer::Token_CloseBraket:
// copy everything as-is up to the ]
result += QByteArray::fromRawData(lastPos, tok.pos - lastPos - 1);
// This implementation will not allow mixed usages of BONE_TRANSFORMS and
// BONE_NORMAL_TRANSFORMS.
// For example, BONE_TRANSFORM[int(BONE_NORMAL_TRANFORMS[i][0].x)]
// cannot be compiled successfully.
if (useJointTexState <= 0 && useJointNormalTexState <= 0) {
result += QByteArrayLiteral("]");
break;
}
if (useJointTexState > 1) {
result += QByteArrayLiteral("]");
--useJointTexState;
break;
} else if (useJointNormalTexState > 1) {
result += QByteArrayLiteral("]");
--useJointNormalTexState;
break;
}
result += QByteArrayLiteral("))");
useJointTexState = -1;
useJointNormalTexState = -1;
break;
default:
result += QByteArray::fromRawData(lastPos, tok.pos - lastPos);
break;
}
lastPos = tok.pos;
t = tok.next();
}
result += '\n';
workData->inputs = inputs;
workData->outputs = outputs;
if (md.flags.testFlag(QSSGCustomShaderMetaData::UsesDispersion)
&& !md.flags.testFlag(QSSGCustomShaderMetaData::UsesTransmission)) {
qWarning("Custom material writes DISPERSION but never sets TRANSMISSION_FACTOR; "
"dispersion requires transmission and will have no effect");
}
*codeAndMetaData = { result, md };
}
void QSSGShaderCustomMaterialAdapter::finishPrepareCustomShader(
QByteArray *dst,
const CustomShaderPrepWorkData &workData,
const ShaderCodeAndMetaData &codeAndMetaData,
QSSGShaderCache::ShaderType type,
bool multiViewCompatible,
const StringPairList &baseUniforms,
const StringPairList &baseInputs,
const StringPairList &baseOutputs,
const StringPairList &multiViewDependentSamplers,
const StringPairList &multiViewDependentUniforms)
{
StringPairList allUniforms = baseUniforms;
for (const StringPair &samplerTypeAndName : multiViewDependentSamplers) {
if (multiViewCompatible)
allUniforms.append({ "sampler2DArray", samplerTypeAndName.second });
else
allUniforms.append(samplerTypeAndName);
}
const QSSGCustomShaderMetaData &md(codeAndMetaData.second);
// We either have qt_depthTexture or qt_depthTextureArray (or none of them),
// but never both. We do not generally support binding a 2D texture to a
// sampler2DArray binding point and vice versa. Therefore it is up to the
// shader snippet to ifdef with QSHADER_VIEW_COUNT if it wants to support
// both multiview and non-multiview rendering.
if (md.flags.testFlag(QSSGCustomShaderMetaData::UsesDepthTexture)) {
if (multiViewCompatible)
allUniforms.append({ "sampler2DArray", "qt_depthTextureArray" });
else
allUniforms.append({ "sampler2D", "qt_depthTexture" });
}
// And the same pattern for qt_screenTexture(Array).
if ((md.flags.testFlag(QSSGCustomShaderMetaData::UsesScreenTexture) || md.flags.testFlag(QSSGCustomShaderMetaData::UsesScreenMipTexture))) {
if (multiViewCompatible)
allUniforms.append({ "sampler2DArray", "qt_screenTextureArray" });
else
allUniforms.append({ "sampler2D", "qt_screenTexture" });
}
// And for SSAO.
if (md.flags.testFlag(QSSGCustomShaderMetaData::UsesAoTexture)) {
if (multiViewCompatible)
allUniforms.append({ "sampler2DArray", "qt_aoTextureArray" });
else
allUniforms.append({ "sampler2D", "qt_aoTexture" });
}
if (md.flags.testFlag(QSSGCustomShaderMetaData::UsesNormalTexture))
allUniforms.append({ "sampler2D", "qt_normalTexture" });
// Input texture for post-processing effects.
if (md.flags.testFlag(QSSGCustomShaderMetaData::UsesInputTexture)) {
if (multiViewCompatible)
allUniforms.append({ "sampler2DArray", "qt_inputTextureArray" });
else
allUniforms.append({ "sampler2D", "qt_inputTexture" });
}
if (md.flags.testFlag(QSSGCustomShaderMetaData::UsesLightmap))
allUniforms.append({ "sampler2D", "qt_lightmap" });
if (md.flags.testFlag(QSSGCustomShaderMetaData::UsesMotionVectorTexture))
allUniforms.append({ "sampler2D", "qt_motionVectorTexture" });
static const char *metaStart = "#ifdef QQ3D_SHADER_META\n/*{\n \"uniforms\": [\n";
static const char *metaEnd = " ]\n}*/\n#endif\n";
dst->append(metaStart);
for (int i = 0, count = allUniforms.size(); i < count; ++i) {
const auto &typeAndName(allUniforms[i]);
dst->append(" { \"type\": \"" + typeAndName.first + "\", \"name\": \"" + typeAndName.second + "\" }");
if (i < count - 1 || !multiViewDependentUniforms.isEmpty())
dst->append(",");
dst->append("\n");
}
// multiViewDependentUniforms are not in allUniforms. For these we leave it to the
// metadata processor to add an array suffix (e.g., qt_projectionMatrix[2]) when needed.
for (int i = 0, count = multiViewDependentUniforms.size(); i < count; ++i) {
const auto &typeAndName(multiViewDependentUniforms[i]);
dst->append(" { \"type\": \"" + typeAndName.first + "\", \"name\": \"" + typeAndName.second + "\", \"multiview_dependent\": true }");
if (i < count - 1)
dst->append(",");
dst->append("\n");
}
dst->append(metaEnd);
const char *stageStr = type == QSSGShaderCache::ShaderType::Vertex ? "vertex" : "fragment";
StringPairList allInputs = baseInputs;
QVarLengthArray<bool, 16> inputIsFlat(allInputs.count(), false);
for (const QByteArray &inputTypeAndName : workData.inputs) {
const QByteArrayList typeAndName = inputTypeAndName.split(' ');
if (typeAndName.size() == 2) {
allInputs.append({ typeAndName[0].trimmed(), typeAndName[1].trimmed() });
inputIsFlat.append(false);
} else if (typeAndName.size() == 3 && typeAndName[0].startsWith("flat")) {
allInputs.append({ typeAndName[1].trimmed(), typeAndName[2].trimmed() });
inputIsFlat.append(true);
}
}
if (!allInputs.isEmpty()) {
static const char *metaStart = "#ifdef QQ3D_SHADER_META\n/*{\n \"inputs\": [\n";
static const char *metaEnd = " ]\n}*/\n#endif\n";
dst->append(metaStart);
for (int i = 0, count = allInputs.size(); i < count; ++i) {
dst->append(" { \"type\": \"" + allInputs[i].first
+ "\", \"name\": \"" + allInputs[i].second
+ "\", \"stage\": \"" + stageStr
+ (inputIsFlat[i] ? "\", \"flat\": true" : "\"")
+ " }");
if (i < count - 1)
dst->append(",");
dst->append("\n");
}
dst->append(metaEnd);
}
StringPairList allOutputs = baseOutputs;
QVarLengthArray<bool, 16> outputIsFlat(allOutputs.count(), false);
for (const QByteArray &outputTypeAndName : workData.outputs) {
const QByteArrayList typeAndName = outputTypeAndName.split(' ');
if (typeAndName.size() == 2) {
allOutputs.append({ typeAndName[0].trimmed(), typeAndName[1].trimmed() });
outputIsFlat.append(false);
} else if (typeAndName.size() == 3 && typeAndName[0].startsWith("flat")) {
allOutputs.append({ typeAndName[1].trimmed(), typeAndName[2].trimmed() });
outputIsFlat.append(true);
}
}
if (!allOutputs.isEmpty()) {
static const char *metaStart = "#ifdef QQ3D_SHADER_META\n/*{\n \"outputs\": [\n";
static const char *metaEnd = " ]\n}*/\n#endif\n";
dst->append(metaStart);
for (int i = 0, count = allOutputs.size(); i < count; ++i) {
dst->append(" { \"type\": \"" + allOutputs[i].first
+ "\", \"name\": \"" + allOutputs[i].second
+ "\", \"stage\": \"" + stageStr
+ (outputIsFlat[i] ? "\", \"flat\": true" : "\"")
+ " }");
if (i < count - 1)
dst->append(",");
dst->append("\n");
}
dst->append(metaEnd);
}
}
QList<QByteArrayView> QtQuick3DEditorHelpers::CustomMaterial::preprocessorVars()
{
QList<QByteArrayView> k;
k.reserve(std::size(qssg_var_subst_tab));
for (const auto &v : qssg_var_subst_tab)
k.push_back(v.builtin);
return k;
}
QT_END_NAMESPACE