You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
590 lines
22 KiB
C++
590 lines
22 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroupLayout.h>
|
|
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
|
|
|
|
#include <Atom/RHI.Reflect/Bits.h>
|
|
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <AzCore/std/string/conversions.h>
|
|
#include <AzCore/std/sort.h>
|
|
#include <AzCore/Utils/TypeHash.h>
|
|
|
|
#include <AzFramework/StringFunc/StringFunc.h>
|
|
|
|
namespace AZ
|
|
{
|
|
namespace RPI
|
|
{
|
|
const char* ShaderOptionDescriptor::DebugCategory = "ShaderOption";
|
|
const char* ShaderOptionGroupLayout::DebugCategory = "ShaderOption";
|
|
|
|
const char* ToString(ShaderOptionType shaderOptionType)
|
|
{
|
|
switch (shaderOptionType)
|
|
{
|
|
case ShaderOptionType::Boolean: return "Boolean";
|
|
case ShaderOptionType::Enumeration: return "Enumeration";
|
|
case ShaderOptionType::IntegerRange: return "IntegerRange";
|
|
default: return "<Unknown>";
|
|
}
|
|
}
|
|
|
|
void ShaderOptionGroupHints::Reflect(ReflectContext* context)
|
|
{
|
|
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
|
|
{
|
|
serializeContext->Class<ShaderOptionGroupHints>()
|
|
->Version(4)
|
|
->Field("BakePrecedingVariants", &ShaderOptionGroupHints::m_bakePrecedingVariants)
|
|
->Field("BakeEmptyAsDefault", &ShaderOptionGroupHints::m_bakeEmptyAsDefault)
|
|
;
|
|
}
|
|
}
|
|
|
|
void ShaderOptionDescriptor::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
ShaderOptionValue::Reflect(context);
|
|
NameReflectionMapForValues::Reflect(context);
|
|
|
|
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
|
|
{
|
|
serializeContext->Class<ShaderOptionDescriptor>()
|
|
->Version(4)
|
|
->Field("m_name", &ShaderOptionDescriptor::m_name)
|
|
->Field("m_type", &ShaderOptionDescriptor::m_type)
|
|
->Field("m_defaultValue", &ShaderOptionDescriptor::m_defaultValue)
|
|
->Field("m_minValue", &ShaderOptionDescriptor::m_minValue)
|
|
->Field("m_maxValue", &ShaderOptionDescriptor::m_maxValue)
|
|
->Field("m_bitOffset", &ShaderOptionDescriptor::m_bitOffset)
|
|
->Field("m_bitCount", &ShaderOptionDescriptor::m_bitCount)
|
|
->Field("m_bitMask", &ShaderOptionDescriptor::m_bitMask)
|
|
->Field("m_bitMaskNot", &ShaderOptionDescriptor::m_bitMaskNot)
|
|
->Field("m_hash", &ShaderOptionDescriptor::m_hash)
|
|
->Field("m_nameReflectionForValues", &ShaderOptionDescriptor::m_nameReflectionForValues)
|
|
;
|
|
}
|
|
|
|
if (BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context))
|
|
{
|
|
behaviorContext->Class<ShaderOptionDescriptor>()
|
|
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
|
|
->Attribute(AZ::Script::Attributes::Category, "Shader")
|
|
->Attribute(AZ::Script::Attributes::Module, "shader")
|
|
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::RuntimeOwn)
|
|
->Method("GetName", &ShaderOptionDescriptor::GetName)
|
|
->Method("GetDefaultValue", &ShaderOptionDescriptor::GetDefaultValue)
|
|
->Method("GetValueName", &ShaderOptionDescriptor::GetValueName)
|
|
->Method("FindValue", &ShaderOptionDescriptor::FindValue)
|
|
;
|
|
}
|
|
}
|
|
|
|
ShaderOptionDescriptor::ShaderOptionDescriptor(const Name& name,
|
|
const ShaderOptionType& optionType,
|
|
uint32_t bitOffset,
|
|
uint32_t order,
|
|
const AZStd::vector<RPI::ShaderOptionValuePair>& nameIndexList,
|
|
const Name& defaultValue)
|
|
|
|
: m_name{name}
|
|
, m_type{optionType}
|
|
, m_bitOffset{bitOffset}
|
|
, m_order{order}
|
|
, m_defaultValue{defaultValue}
|
|
{
|
|
for (auto pair : nameIndexList)
|
|
{ // Registers the pair in the lookup table
|
|
AddValue(pair.first, pair.second);
|
|
}
|
|
|
|
uint32_t numValues = (m_type == ShaderOptionType::IntegerRange) ? (m_maxValue.GetIndex() - m_minValue.GetIndex() + 1) : (uint32_t) nameIndexList.size();
|
|
numValues = RHI::NextPowerOfTwo(numValues) - 1;
|
|
m_bitCount = RHI::CountBitsSet(numValues);
|
|
|
|
ShaderVariantKey bitMask;
|
|
bitMask = AZ_BIT_MASK(m_bitCount);
|
|
bitMask <<= m_bitOffset;
|
|
m_bitMask = bitMask;
|
|
m_bitMaskNot = ~bitMask;
|
|
|
|
m_hash = TypeHash64(m_bitMask, static_cast<HashValue64>(m_name.GetHash()));
|
|
}
|
|
|
|
const Name& ShaderOptionDescriptor::GetName() const
|
|
{
|
|
return m_name;
|
|
}
|
|
|
|
uint32_t ShaderOptionDescriptor::GetBitOffset() const
|
|
{
|
|
return m_bitOffset;
|
|
}
|
|
|
|
uint32_t ShaderOptionDescriptor::GetBitCount() const
|
|
{
|
|
return m_bitCount;
|
|
}
|
|
|
|
uint32_t ShaderOptionDescriptor::GetOrder() const
|
|
{
|
|
return m_order;
|
|
}
|
|
|
|
|
|
ShaderVariantKey ShaderOptionDescriptor::GetBitMask() const
|
|
{
|
|
return m_bitMask;
|
|
}
|
|
|
|
ShaderVariantKey ShaderOptionDescriptor::GetBitMaskNot() const
|
|
{
|
|
return m_bitMaskNot;
|
|
}
|
|
|
|
HashValue64 ShaderOptionDescriptor::GetHash() const
|
|
{
|
|
return m_hash;
|
|
}
|
|
|
|
bool ShaderOptionDescriptor::Set(ShaderOptionGroup& group, const Name& valueName) const
|
|
{
|
|
auto valueIndex = FindValue(valueName);
|
|
if (valueIndex.IsValid())
|
|
{
|
|
return Set(group, valueIndex);
|
|
}
|
|
else
|
|
{
|
|
AZ_Error(DebugCategory, false, "ShaderOption value '%s' does not exist", valueName.GetCStr());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ShaderOptionDescriptor::Set(ShaderOptionGroup& group, const ShaderOptionValue valueIndex) const
|
|
{
|
|
if (valueIndex.IsNull())
|
|
{
|
|
AZ_Error(DebugCategory, false, "Invalid ShaderOption value");
|
|
return false;
|
|
}
|
|
|
|
if (m_type == ShaderOptionType::Unknown)
|
|
{
|
|
group.GetShaderVariantMask() &= m_bitMaskNot;
|
|
}
|
|
else
|
|
{
|
|
if (!(m_minValue.GetIndex() <= valueIndex.GetIndex() && valueIndex.GetIndex() <= m_maxValue.GetIndex()))
|
|
{
|
|
AZ_Error(DebugCategory, false, "%s ShaderOption value [%d] is out of range [%d,%d].",
|
|
ToString(m_type), valueIndex.GetIndex(), m_minValue.GetIndex(), m_maxValue.GetIndex());
|
|
return false;
|
|
}
|
|
|
|
EncodeBits(group.GetShaderVariantKey(), valueIndex.GetIndex() - m_minValue.GetIndex());
|
|
group.GetShaderVariantMask() |= m_bitMask;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ShaderOptionDescriptor::Set(ShaderVariantKey& key, const ShaderOptionValue valueIndex) const
|
|
{
|
|
if (valueIndex.IsNull())
|
|
{
|
|
AZ_Error(DebugCategory, false, "Invalid ShaderOption value");
|
|
return false;
|
|
}
|
|
|
|
if (m_type != ShaderOptionType::Unknown)
|
|
{
|
|
if (!(m_minValue.GetIndex() <= valueIndex.GetIndex() && valueIndex.GetIndex() <= m_maxValue.GetIndex()))
|
|
{
|
|
AZ_Error(DebugCategory, false, "%s ShaderOption value [%d] is out of range [%d,%d].",
|
|
ToString(m_type), valueIndex.GetIndex(), m_minValue.GetIndex(), m_maxValue.GetIndex());
|
|
return false;
|
|
}
|
|
|
|
EncodeBits(key, valueIndex.GetIndex() - m_minValue.GetIndex());
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
ShaderOptionValue ShaderOptionDescriptor::Get(const ShaderOptionGroup& group) const
|
|
{
|
|
if (group.GetShaderVariantMask().test(m_bitOffset))
|
|
{
|
|
return ShaderOptionValue(DecodeBits(group.GetShaderVariantKey()) + m_minValue.GetIndex());
|
|
}
|
|
|
|
return ShaderOptionValue();
|
|
}
|
|
|
|
void ShaderOptionDescriptor::Clear(ShaderOptionGroup& group) const
|
|
{
|
|
group.GetShaderVariantMask() &= m_bitMaskNot;
|
|
}
|
|
|
|
void ShaderOptionDescriptor::AddValue(const Name& valueName, const ShaderOptionValue valueIndex)
|
|
{
|
|
AZ_Assert(m_type != ShaderOptionType::IntegerRange || valueIndex.GetIndex() == ShaderOptionValue((uint32_t)AZStd::stoll(AZStd::string{valueName.GetCStr()})).GetIndex(), "By convention, IntegerRange's values' ids must be equal to their numerical value!");
|
|
|
|
m_nameReflectionForValues.Insert(valueName, valueIndex);
|
|
|
|
if (m_minValue.IsNull() || m_minValue.GetIndex() > valueIndex.GetIndex())
|
|
{
|
|
m_minValue = valueIndex;
|
|
}
|
|
if (m_maxValue.IsNull() || m_maxValue.GetIndex() < valueIndex.GetIndex())
|
|
{
|
|
m_maxValue = valueIndex;
|
|
}
|
|
}
|
|
|
|
void ShaderOptionDescriptor::SetDefaultValue(const Name& valueName)
|
|
{
|
|
AZ_Assert(!valueName.IsEmpty(), "The default value cannot be empty!");
|
|
auto valueIter = m_nameReflectionForValues.Find(valueName);
|
|
if (valueIter.IsNull())
|
|
{
|
|
AZ_Assert(false, "ShaderOption [%s] has no member value [%s] so this cannot be the default!", m_name.GetCStr(), valueName.GetCStr());
|
|
return;
|
|
}
|
|
|
|
m_defaultValue = valueName;
|
|
}
|
|
|
|
const Name& ShaderOptionDescriptor::GetDefaultValue() const
|
|
{
|
|
return m_defaultValue;
|
|
}
|
|
|
|
uint32_t ShaderOptionDescriptor::GetValuesCount() const
|
|
{
|
|
return static_cast<uint32_t>(m_maxValue.GetIndex() - m_minValue.GetIndex() + 1);
|
|
}
|
|
|
|
/// Sets the hint type for the shader option
|
|
void ShaderOptionDescriptor::SetType(ShaderOptionType optionType)
|
|
{
|
|
m_type = optionType;
|
|
}
|
|
|
|
/// Gets the hint type for the shader option
|
|
const ShaderOptionType& ShaderOptionDescriptor::GetType() const
|
|
{
|
|
return m_type;
|
|
}
|
|
|
|
ShaderOptionValue ShaderOptionDescriptor::GetMinValue() const
|
|
{
|
|
return m_minValue;
|
|
}
|
|
|
|
ShaderOptionValue ShaderOptionDescriptor::GetMaxValue() const
|
|
{
|
|
return m_maxValue;
|
|
}
|
|
|
|
ShaderOptionValue ShaderOptionDescriptor::FindValue(const Name& valueName) const
|
|
{
|
|
switch (m_type)
|
|
{
|
|
case ShaderOptionType::Boolean:
|
|
// This is better than hardcoding True, or On, or Enabled:
|
|
return m_nameReflectionForValues.Find(valueName);
|
|
case ShaderOptionType::Enumeration:
|
|
return m_nameReflectionForValues.Find(valueName);
|
|
|
|
case ShaderOptionType::IntegerRange:
|
|
{
|
|
int asInt;
|
|
if (AzFramework::StringFunc::LooksLikeInt(valueName.GetCStr(), &asInt))
|
|
{
|
|
if (aznumeric_cast<int64_t>(m_minValue.GetIndex()) <= asInt && asInt <= aznumeric_cast<int64_t>(m_maxValue.GetIndex()))
|
|
{
|
|
return ShaderOptionValue(asInt);
|
|
}
|
|
}
|
|
}
|
|
return ShaderOptionValue();
|
|
|
|
default:
|
|
AZ_Assert(false, "Unhandled case for ShaderOptionType! We should not break here!");
|
|
}
|
|
|
|
// Unreachable code
|
|
return ShaderOptionValue();
|
|
}
|
|
|
|
Name ShaderOptionDescriptor::GetValueName(ShaderOptionValue value) const
|
|
{
|
|
auto name = m_nameReflectionForValues.Find(value);
|
|
return name;
|
|
}
|
|
|
|
void ShaderOptionDescriptor::EncodeBits(ShaderVariantKey& shaderVariantKey, uint32_t value) const
|
|
{
|
|
if (value < AZ_BIT(m_bitCount))
|
|
{
|
|
ShaderVariantKey valueBits = (value & AZ_BIT_MASK(m_bitCount));
|
|
valueBits <<= m_bitOffset;
|
|
shaderVariantKey &= m_bitMaskNot;
|
|
shaderVariantKey |= valueBits;
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(false, "Exceeded maximum number of bits allocated for option.");
|
|
}
|
|
}
|
|
|
|
uint32_t ShaderOptionDescriptor::DecodeBits(ShaderVariantKey shaderVariantKey) const
|
|
{
|
|
shaderVariantKey >>= m_bitOffset;
|
|
shaderVariantKey &= AZ_BIT_MASK(m_bitCount);
|
|
uint32_t value = shaderVariantKey.to_ulong();
|
|
return value;
|
|
}
|
|
|
|
bool ShaderOptionDescriptor::operator==(const ShaderOptionDescriptor& rhs) const
|
|
{
|
|
return m_hash == rhs.m_hash;
|
|
}
|
|
|
|
bool ShaderOptionDescriptor::operator!=(const ShaderOptionDescriptor& rhs) const
|
|
{
|
|
return m_hash != rhs.m_hash;
|
|
}
|
|
|
|
bool ShaderOptionDescriptor::CompareOrder(const ShaderOptionDescriptor& first, const ShaderOptionDescriptor& second)
|
|
{
|
|
return first.GetOrder() < second.GetOrder();
|
|
}
|
|
|
|
bool ShaderOptionDescriptor::SameOrder(const ShaderOptionDescriptor& first, const ShaderOptionDescriptor& second)
|
|
{
|
|
return first.GetOrder() == second.GetOrder();
|
|
}
|
|
|
|
void ShaderOptionGroupLayout::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
ShaderOptionIndex::Reflect(context);
|
|
NameReflectionMapForOptions::Reflect(context);
|
|
|
|
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
|
|
{
|
|
serializeContext->Class<ShaderOptionGroupLayout>()
|
|
->Version(2)
|
|
->Field("m_bitMask", &ShaderOptionGroupLayout::m_bitMask)
|
|
->Field("m_options", &ShaderOptionGroupLayout::m_options)
|
|
->Field("m_nameReflectionForOptions", &ShaderOptionGroupLayout::m_nameReflectionForOptions)
|
|
->Field("m_hash", &ShaderOptionGroupLayout::m_hash)
|
|
;
|
|
}
|
|
|
|
if (BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context))
|
|
{
|
|
behaviorContext->Class<ShaderOptionGroupLayout>()
|
|
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
|
|
->Attribute(AZ::Script::Attributes::Category, "Shader")
|
|
->Attribute(AZ::Script::Attributes::Module, "shader")
|
|
->Method("GetShaderOptions", &ShaderOptionGroupLayout::GetShaderOptions)
|
|
;
|
|
}
|
|
}
|
|
|
|
Ptr<ShaderOptionGroupLayout> ShaderOptionGroupLayout::Create()
|
|
{
|
|
return aznew ShaderOptionGroupLayout;
|
|
}
|
|
|
|
bool ShaderOptionGroupLayout::IsFinalized() const
|
|
{
|
|
return m_hash != HashValue64{ 0 };
|
|
}
|
|
|
|
HashValue64 ShaderOptionGroupLayout::GetHash() const
|
|
{
|
|
return m_hash;
|
|
}
|
|
|
|
void ShaderOptionGroupLayout::Clear()
|
|
{
|
|
m_options.clear();
|
|
m_nameReflectionForOptions.Clear();
|
|
m_bitMask = {};
|
|
m_hash = HashValue64{ 0 };
|
|
}
|
|
|
|
void ShaderOptionGroupLayout::Finalize()
|
|
{
|
|
AZStd::sort(m_options.begin(), m_options.end(), ShaderOptionDescriptor::CompareOrder);
|
|
|
|
// Start with a hash of the size so that m_hash!=0 will mean the group is finalized, even if the options list is empty.
|
|
HashValue64 hash = TypeHash64(m_options.size());
|
|
|
|
for (const ShaderOptionDescriptor& option : m_options)
|
|
{
|
|
hash = TypeHash64(option.GetHash(), hash);
|
|
}
|
|
m_hash = hash;
|
|
}
|
|
|
|
bool ShaderOptionGroupLayout::ValidateIsFinalized() const
|
|
{
|
|
if (!IsFinalized())
|
|
{
|
|
AZ_Assert(false, "ShaderOptionGroupLayout is not finalized! This operation is only permitted on a finalized layout.");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ShaderOptionGroupLayout::ValidateIsNotFinalized() const
|
|
{
|
|
if (IsFinalized())
|
|
{
|
|
AZ_Assert(false, "ShaderOptionGroupLayout is finalized! This operation is only permitted on a non-finalized layout.");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ShaderOptionGroupLayout::AddShaderOption(const ShaderOptionDescriptor& option)
|
|
{
|
|
if (!ValidateIsNotFinalized())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const Name& optionName = option.GetName();
|
|
const ShaderVariantKey bitMask = option.GetBitMask();
|
|
|
|
if ((m_bitMask & bitMask).any())
|
|
{
|
|
AZ_Error(DebugCategory, false, "ShaderOptionBinding '%s': mask overlaps with previously added masks.", optionName.GetCStr());
|
|
return false;
|
|
}
|
|
|
|
if (option.GetName().IsEmpty())
|
|
{
|
|
AZ_Error(DebugCategory, false, "ShaderOptionBinding added with empty name.");
|
|
return false;
|
|
}
|
|
|
|
if (option.GetBitCount() == 0)
|
|
{
|
|
AZ_Error(DebugCategory, false, "ShaderOptionBinding '%s' has zero bits.", optionName.GetCStr());
|
|
return false;
|
|
}
|
|
|
|
if (option.GetBitOffset() + option.GetBitCount() > bitMask.size())
|
|
{
|
|
AZ_Error(DebugCategory, false, "ShaderOptionBinding '%s' exceeds size of mask.", optionName.GetCStr());
|
|
return false;
|
|
}
|
|
|
|
if (AZStd::any_of(m_options.begin(), m_options.end(),
|
|
[&option](const ShaderOptionDescriptor& other) { return ShaderOptionDescriptor::SameOrder(option, other); }))
|
|
{
|
|
AZ_Error(DebugCategory, false, "ShaderOption '%s' has the same order (%d) as another shader option.", optionName.GetCStr(), option.GetOrder());
|
|
return false;
|
|
}
|
|
|
|
if (!option.FindValue(option.GetDefaultValue()).IsValid())
|
|
{
|
|
AZ_Error(DebugCategory, false, "ShaderOption '%s' has invalid default value '%s'.", optionName.GetCStr(), option.GetDefaultValue().GetCStr());
|
|
return false;
|
|
}
|
|
|
|
const ShaderOptionIndex optionIndex(m_options.size());
|
|
if (!m_nameReflectionForOptions.Insert(optionName, optionIndex))
|
|
{
|
|
AZ_Error(DebugCategory, false, "ShaderOptionBinding '%s': name already exists.", optionName.GetCStr());
|
|
return false;
|
|
}
|
|
|
|
m_bitMask |= bitMask;
|
|
|
|
m_options.push_back(option);
|
|
return true;
|
|
}
|
|
|
|
ShaderOptionIndex ShaderOptionGroupLayout::FindShaderOptionIndex(const Name& optionName) const
|
|
{
|
|
if (ValidateIsFinalized())
|
|
{
|
|
return m_nameReflectionForOptions.Find(optionName);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
ShaderOptionValue ShaderOptionGroupLayout::FindValue(const Name& optionName, const Name& valueName) const
|
|
{
|
|
return FindValue(FindShaderOptionIndex(optionName), valueName);
|
|
}
|
|
|
|
ShaderOptionValue ShaderOptionGroupLayout::FindValue(const ShaderOptionIndex& optionIndex, const Name& valueName) const
|
|
{
|
|
if (optionIndex.IsValid() && optionIndex.GetIndex() < m_options.size())
|
|
{
|
|
return m_options[optionIndex.GetIndex()].FindValue(valueName);
|
|
}
|
|
else
|
|
{
|
|
return ShaderOptionValue{};
|
|
}
|
|
}
|
|
|
|
uint32_t ShaderOptionGroupLayout::GetBitSize() const
|
|
{
|
|
if (m_options.empty())
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return m_options.back().GetBitOffset() + m_options.back().GetBitCount();
|
|
}
|
|
}
|
|
|
|
const AZStd::vector<ShaderOptionDescriptor>& ShaderOptionGroupLayout::GetShaderOptions() const
|
|
{
|
|
return m_options;
|
|
}
|
|
|
|
const ShaderOptionDescriptor& ShaderOptionGroupLayout::GetShaderOption(ShaderOptionIndex optionIndex) const
|
|
{
|
|
return m_options[optionIndex.GetIndex()];
|
|
}
|
|
|
|
size_t ShaderOptionGroupLayout::GetShaderOptionCount() const
|
|
{
|
|
return m_options.size();
|
|
}
|
|
|
|
ShaderVariantKey ShaderOptionGroupLayout::GetBitMask() const
|
|
{
|
|
return m_bitMask;
|
|
}
|
|
|
|
bool ShaderOptionGroupLayout::IsValidShaderVariantKey(const ShaderVariantKey& shaderVariantKey) const
|
|
{
|
|
if (ValidateIsFinalized())
|
|
{
|
|
return (m_bitMask & shaderVariantKey) == shaderVariantKey;
|
|
}
|
|
return false;
|
|
}
|
|
} // namespace RPI
|
|
} // namespace AZ
|