Files
UnrealEngine/Engine/Source/Runtime/RenderCore/Public/ShaderPermutation.h
2025-05-18 13:04:45 +08:00

523 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
ShaderPermutation.h: All shader permutation's compile time API.
=============================================================================*/
#pragma once
#include "CoreMinimal.h"
#include "ShaderCore.h"
// Enable this to log the parameters of each compiled permutation
inline constexpr bool bLogPermutations = false;
struct FShaderCompilerEnvironment;
// Flags that can specialize shader permutations compiled for specific platforms
enum class EShaderPermutationFlags : uint32
{
None = 0u,
HasEditorOnlyData = (1u << 0),
IsODSCOnly = (1u << 1),
};
ENUM_CLASS_FLAGS(EShaderPermutationFlags);
struct FShaderPermutationParameters
{
// Shader platform to compile to.
const EShaderPlatform Platform;
// Unique permutation identifier of the material shader type.
const int32 PermutationId;
// Flags that describe the permutation
const EShaderPermutationFlags Flags;
// Default to include editor-only shaders, to maintain backwards-compatibility
explicit FShaderPermutationParameters(EShaderPlatform InPlatform, int32 InPermutationId = 0, EShaderPermutationFlags InFlags = EShaderPermutationFlags::HasEditorOnlyData)
: Platform(InPlatform)
, PermutationId(InPermutationId)
, Flags(InFlags)
{
}
};
/** Defines at compile time a boolean permutation dimension. */
struct FShaderPermutationBool
{
/** Setup the dimension's type in permutation domain as boolean. */
using Type = bool;
/** Setup the dimension's number of permutation. */
static constexpr int32 PermutationCount = 2;
/** Setup the dimension as non multi-dimensional, so that the ModifyCompilationEnvironement's
* define can conventily be set up in SHADER_PERMUTATION_BOOL.
*/
static constexpr bool IsMultiDimensional = false;
/** Converts dimension boolean value to dimension's value id. */
static int32 ToDimensionValueId(Type E)
{
return E ? 1 : 0;
}
/** Pass down a boolean to FShaderCompilerEnvironment::SetDefine(). */
static bool ToDefineValue(Type E)
{
return E;
}
/** Converts dimension's value id to dimension boolean value (exact reciprocal of ToDimensionValueId). */
static Type FromDimensionValueId(int32 PermutationId)
{
checkf(PermutationId == 0 || PermutationId == 1, TEXT("Invalid shader permutation dimension id %i."), PermutationId);
return PermutationId == 1;
}
};
/** Defines at compile time a permutation dimension made of int32 from 0 to N -1. */
template <typename TType, int32 TDimensionSize, int32 TFirstValue=0>
struct TShaderPermutationInt
{
/** Setup the dimension's type in permutation domain as integer. */
using Type = TType;
/** Setup the dimension's number of permutation. */
static constexpr int32 PermutationCount = TDimensionSize;
/** Setup the dimension as non multi-dimensional, so that the ModifyCompilationEnvironement's
* define can conventily be set up in SHADER_PERMUTATION_INT.
*/
static constexpr bool IsMultiDimensional = false;
/** Min and max values. */
static constexpr Type MinValue = static_cast<Type>(TFirstValue);
static constexpr Type MaxValue = static_cast<Type>(TFirstValue + TDimensionSize - 1);
/** Converts dimension's integer value to dimension's value id. */
static int32 ToDimensionValueId(Type E)
{
int32 PermutationId = static_cast<int32>(E) - TFirstValue;
checkf(PermutationId < PermutationCount && PermutationId >= 0, TEXT("Unknown shader permutation dimension value id %i."), PermutationId);
return PermutationId;
}
/** Pass down a int32 to FShaderCompilerEnvironment::SetDefine() even for contiguous enum classes. */
static int32 ToDefineValue(Type E)
{
return ToDimensionValueId(E) + TFirstValue;
}
/** Converts dimension's value id to dimension's integer value (exact reciprocal of ToDimensionValueId). */
static Type FromDimensionValueId(int32 PermutationId)
{
checkf(PermutationId < PermutationCount && PermutationId >= 0, TEXT("Invalid shader permutation dimension value id %i."), PermutationId);
return static_cast<Type>(PermutationId + TFirstValue);
}
};
/** Defines at compile time a permutation dimension made of specific int32. */
template <int32... Ts>
struct TShaderPermutationSparseInt
{
/** Setup the dimension's type in permutation domain as integer. */
using Type = int32;
/** Setup the dimension's number of permutation. */
static constexpr int32 PermutationCount = 0;
/** Setup the dimension as non multi-dimensional, so that the ModifyCompilationEnvironement's
* define can conventily be set up in SHADER_PERMUTATION_SPARSE_INT.
*/
static constexpr bool IsMultiDimensional = false;
/** Converts dimension's integer value to dimension's value id, bu in this case fail because the dimension value was wrong. */
static int32 ToDimensionValueId(Type E)
{
checkf(false, TEXT("Unknown shader permutation dimension value %i."), E);
return int32(0);
}
/** Converts dimension's value id to dimension's integer value (exact reciprocal of ToDimensionValueId). */
static Type FromDimensionValueId(int32 PermutationId)
{
checkf(false, TEXT("Invalid shader permutation dimension id %i."), PermutationId);
return Type(0);
}
};
template <int32 TUniqueValue, int32... Ts>
struct TShaderPermutationSparseInt<TUniqueValue, Ts...>
{
/** Setup the dimension's type in permutation domain as integer. */
using Type = int32;
/** Setup the dimension's number of permutation. */
static constexpr int32 PermutationCount = TShaderPermutationSparseInt<Ts...>::PermutationCount + 1;
/** Setup the dimension as non multi-dimensional, so that the ModifyCompilationEnvironement's
* define can conventily be set up in SHADER_PERMUTATION_SPARSE_INT.
*/
static constexpr bool IsMultiDimensional = false;
/** Converts dimension's integer value to dimension's value id. */
static int32 ToDimensionValueId(Type E)
{
if (E == TUniqueValue)
{
return PermutationCount - 1;
}
return TShaderPermutationSparseInt<Ts...>::ToDimensionValueId(E);
}
/** Pass down a int32 to FShaderCompilerEnvironment::SetDefine(). */
static int32 ToDefineValue(Type E)
{
return int32(E);
}
/** Converts dimension's value id to dimension's integer value (exact reciprocal of ToDimensionValueId). */
static Type FromDimensionValueId(int32 PermutationId)
{
if (PermutationId == PermutationCount - 1)
{
return TUniqueValue;
}
return TShaderPermutationSparseInt<Ts...>::FromDimensionValueId(PermutationId);
}
};
/** Variadic template that defines an arbitrary multi-dimensional permutation domain, that can be instantiated to represent
* a vector within the domain.
*
* // Defines a permutation domain with arbitrary number of dimensions. Dimensions can themselves be domains.
* // It is totally legal to have a domain with no dimensions.
* class FMyPermutationDomain = TShaderPermutationDomain<FMyDimensionA, FMyDimensionB, FMyDimensionC>;
*
* // ...
*
* // Create a permutation vector to be initialized. By default a vector is set at the origin of the domain.
* // The origin of the domain is the ShaderPermutationId == 0.
* FMyPermutationDomain PermutationVector;
*
* // Set the permutation vector's dimensions.
* PermutationVector.Set<FMyDimensionA>(MyDimensionValueA);
* PermutationVector.Set<FMyDimensionB>(MyDimensionValueB);
* PermutationVector.Set<FMyDimensionC>(MyDimensionValueC);
*
* // Get the permutation id from the permutation vector for shader compiler.
* int32 ShaderPermutationId = PermutationVector.ToDimensionValueId();
*
* // Reconstruct the permutation vector from shader permutation id.
* FMyPermutationDomain PermutationVector2(ShaderPermutationId);
*
* // Get permutation vector's dimension.
* if (PermutationVector2.Get<FMyDimensionA>())
* { }
*/
template <typename... Ts>
struct TShaderPermutationDomain
{
/** Setup the dimension's type in permutation domain as itself so that a permutation domain can be
* used as a dimension of another domain.
*/
using Type = TShaderPermutationDomain<Ts...>;
/** Define a domain as a multidimensional dimension so that ModifyCompilationEnvironment() is getting used. */
static constexpr bool IsMultiDimensional = true;
/** Total number of permutation within the domain is one if no dimension at all. */
static constexpr int32 PermutationCount = 1;
/** Constructors. */
TShaderPermutationDomain<Ts...>() {}
explicit TShaderPermutationDomain<Ts...>(int32 PermutationId)
{
checkf(PermutationId == 0, TEXT("Invalid shader permutation id %i."), PermutationId);
}
/** Set dimension's value, but in this case emit compile time error if could not find the dimension to set. */
template<class DimensionToSet>
void Set(typename DimensionToSet::Type)
{
// On clang, we can't do static_assert(false), because is evaluated even when method is not used. So
// we test sizeof(DimensionToSet::Type) == 0 to make the static assert depend on the DimensionToSet
// template parameter.
static_assert(sizeof(typename DimensionToSet::Type) == 0, "Unknown shader permutation dimension.");
}
/** get dimension's value, but in this case emit compile time error if could not find the dimension to get. */
template<class DimensionToGet>
const typename DimensionToGet::Type Get() const
{
// On clang, we can't do static_assert(false), because is evaluated even when method is not used. So
// we test sizeof(DimensionToSet::Type) == 0 to make the static assert depend on the DimensionToGet
// template parameter.
static_assert(sizeof(typename DimensionToGet::Type) == 0, "Unknown shader permutation dimension.");
return DimensionToGet::Type();
}
/** Modify compilation environment. */
void ModifyCompilationEnvironment(FShaderCompilerEnvironment& OutEnvironment) const {}
/** Converts domain permutation vector to domain's value id. */
static int32 ToDimensionValueId(const Type& PermutationVector)
{
return 0;
}
int32 ToDimensionValueId() const
{
return ToDimensionValueId(*this);
}
/** Returns the permutation domain from the unique ID. */
static Type FromDimensionValueId(const int32 PermutationId)
{
return Type(PermutationId);
}
/** Test if equal. */
bool operator==(const Type& Other) const
{
return true;
}
};
// C++11 doesn't allow partial specialization of templates method or function. So we spetialise class that have
// non spetialised static method, but leave templated static function.
template<bool BooleanSpecialization>
class TShaderPermutationDomainSpetialization
{
public:
template<typename TPermutationVector, typename TDimension>
static void ModifyCompilationEnvironment(const TPermutationVector& PermutationVector, FShaderCompilerEnvironment& OutEnvironment)
{
if constexpr (bLogPermutations)
{
UE_LOG(LogShaders, Verbose, TEXT(" %s = %d"), TDimension::DefineName, TDimension::ToDefineValue(PermutationVector.DimensionValue));
}
OutEnvironment.SetDefine(TDimension::DefineName, TDimension::ToDefineValue(PermutationVector.DimensionValue));
return PermutationVector.Tail.ModifyCompilationEnvironment(OutEnvironment);
}
template<typename TPermutationVector, typename TDimensionToGet>
static const typename TDimensionToGet::Type& GetDimension(const TPermutationVector& PermutationVector)
{
return PermutationVector.Tail.template Get<TDimensionToGet>();
}
template<typename TPermutationVector, typename TDimensionToSet>
static void SetDimension(TPermutationVector& PermutationVector, const typename TDimensionToSet::Type& Value)
{
return PermutationVector.Tail.template Set<TDimensionToSet>(Value);
}
};
template<>
class TShaderPermutationDomainSpetialization<true>
{
public:
template<typename TPermutationVector, typename TDimension>
static void ModifyCompilationEnvironment(const TPermutationVector& PermutationVector, FShaderCompilerEnvironment& OutEnvironment)
{
PermutationVector.DimensionValue.ModifyCompilationEnvironment(OutEnvironment);
return PermutationVector.Tail.ModifyCompilationEnvironment(OutEnvironment);
}
template<typename TPermutationVector, typename TDimensionToGet>
static const typename TDimensionToGet::Type& GetDimension(const TPermutationVector& PermutationVector)
{
return PermutationVector.DimensionValue;
}
template<typename TPermutationVector, typename TDimensionToSet>
static void SetDimension(TPermutationVector& PermutationVector, const typename TDimensionToSet::Type& Value)
{
PermutationVector.DimensionValue = Value;
}
};
template <typename TDimension, typename... Ts>
struct TShaderPermutationDomain<TDimension, Ts...>
{
/** Setup the dimension's type in permutation domain as itself so that a permutation domain can be
* used as a dimension of another domain.
*/
using Type = TShaderPermutationDomain<TDimension, Ts...>;
/** Define a domain as a multidimensional dimension so that ModifyCompilationEnvironment() is used. */
static constexpr bool IsMultiDimensional = true;
/** Parent type in the variadic template to reduce code. */
using Super = TShaderPermutationDomain<Ts...>;
/** Total number of permutation within the domain. */
static constexpr int32 PermutationCount = Super::PermutationCount * TDimension::PermutationCount;
/** Constructors. */
TShaderPermutationDomain<TDimension, Ts...>()
: DimensionValue(TDimension::FromDimensionValueId(0))
{
}
explicit TShaderPermutationDomain<TDimension, Ts...>(int32 PermutationId)
: DimensionValue(TDimension::FromDimensionValueId(PermutationId % TDimension::PermutationCount))
, Tail(PermutationId / TDimension::PermutationCount)
{
checkf(PermutationId >= 0 && PermutationId < PermutationCount, TEXT("Invalid shader permutation id %i."), PermutationId);
}
/** Set dimension's value. */
template<class DimensionToSet>
void Set(typename DimensionToSet::Type Value)
{
return TShaderPermutationDomainSpetialization<std::is_same_v<TDimension, DimensionToSet>>::template SetDimension<Type, DimensionToSet>(*this, Value);
}
/** Get dimension's value. */
template<class DimensionToGet>
const typename DimensionToGet::Type& Get() const
{
return TShaderPermutationDomainSpetialization<std::is_same_v<TDimension, DimensionToGet>>::template GetDimension<Type, DimensionToGet>(*this);
}
/** Get the tail of the dimensions. */
FORCEINLINE const typename Super::Type& GetTail() const
{
return Tail;
}
/** Modify the shader's compilation environment. */
void ModifyCompilationEnvironment(FShaderCompilerEnvironment& OutEnvironment) const
{
TShaderPermutationDomainSpetialization<TDimension::IsMultiDimensional>::template ModifyCompilationEnvironment<Type, TDimension>(*this, OutEnvironment);
}
/** Converts domain permutation vector to domain's value id. */
static int32 ToDimensionValueId(const Type& PermutationVector)
{
return PermutationVector.ToDimensionValueId();
}
int32 ToDimensionValueId() const
{
return TDimension::ToDimensionValueId(DimensionValue) + TDimension::PermutationCount * Tail.ToDimensionValueId();
}
/** Returns the permutation domain from the unique ID. */
static Type FromDimensionValueId(const int32 PermutationId)
{
return Type(PermutationId);
}
/** Test if equal. */
bool operator==(const Type& Other) const
{
return DimensionValue == Other.DimensionValue && Tail == Other.Tail;
}
/** Test if not equal. */
bool operator!=(const Type& Other) const
{
return !(*this == Other);
}
private:
template<bool BooleanSpecialization>
friend class TShaderPermutationDomainSpetialization;
typename TDimension::Type DimensionValue;
Super Tail;
};
/** Global shader permutation domain with no dimension. */
using FShaderPermutationNone = TShaderPermutationDomain<>;
// Internal implementation of non multi-dimensional shader permutation dimension.
#define DECLARE_SHADER_PERMUTATION_IMPL(InDefineName,PermutationMetaType,...) \
public PermutationMetaType<__VA_ARGS__> { \
public: \
static constexpr const TCHAR* DefineName = TEXT(InDefineName); \
}
/** Implements a boolean shader permutation dimensions. Meant to be used like so:
*
* class FMyShaderDim : SHADER_PERMUTATION_BOOL("MY_SHADER_DEFINE_NAME");
*/
#define SHADER_PERMUTATION_BOOL(InDefineName) \
public FShaderPermutationBool { \
public: \
static constexpr const TCHAR* DefineName = TEXT(InDefineName); \
}
/** Implements an integer shader permutation dimensions with N permutation values from [[0; N[[. Meant to be used like so:
*
* class FMyShaderDim : SHADER_PERMUTATION_INT("MY_SHADER_DEFINE_NAME", N);
*/
#define SHADER_PERMUTATION_INT(InDefineName, Count) \
DECLARE_SHADER_PERMUTATION_IMPL(InDefineName, TShaderPermutationInt, int32, Count)
/** Implements an integer shader permutation dimensions with N permutation values from [[X; X+N[[. Meant to be used like so:
*
* class FMyShaderDim : SHADER_PERMUTATION_RANGE_INT("MY_SHADER_DEFINE_NAME", X, N);
*/
#define SHADER_PERMUTATION_RANGE_INT(InDefineName, Start, Count) \
DECLARE_SHADER_PERMUTATION_IMPL(InDefineName, TShaderPermutationInt, int32, Count, Start)
/** Implements an integer shader permutation dimensions with non contiguous permutation values. Meant to be used like so:
*
* class FMyShaderDim : SHADER_PERMUTATION_SPARSE_INT("MY_SHADER_DEFINE_NAME", 1, 2, 4, 8);
*/
#define SHADER_PERMUTATION_SPARSE_INT(InDefineName,...) \
DECLARE_SHADER_PERMUTATION_IMPL(InDefineName, TShaderPermutationSparseInt, __VA_ARGS__)
/** Implements an shader permutation dimensions with an enum class assumed to have contiguous integer values. Meant to be used like so:
*
* enum class EMyEnum
* {
* Hello,
* World,
* // [...]
* MAX
* };
*
* class FMyShaderDim : SHADER_PERMUTATION_ENUM_CLASS("MY_SHADER_DEFINE_NAME", EMyEnum);
*/
#define SHADER_PERMUTATION_ENUM_CLASS(InDefineName, EnumName) \
DECLARE_SHADER_PERMUTATION_IMPL(InDefineName, TShaderPermutationInt, EnumName, static_cast<int32>(EnumName::MAX))