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.
o3de/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderOptionGroupLayout.cpp

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