[ATOM-15276] Shader Build Pipeline: Add Shader Supervariant System (#749)

* [ATOM-15276] Shader Build Pipeline: Add Shader Supervariant System.

Added ShaderAssetBuilder2 & ShaderVariantAssetBuilder2.

Added ShaderAsset2, ShaderVariantAsset2.

Eventually they will be the only builders. AzslBuilder &
SrgLayoutBuilder will be removed.
ShaderResourceGroupAsset will be removed. ShaderAssetBuilder &
ShaderVariantAssetBuilder will be replaced.

Signed-off-by: garrieta <garrieta@amazon.com>
main
galibzon 5 years ago committed by GitHub
parent af22a0f44e
commit 256df54575
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -185,7 +185,8 @@ namespace AZ
// we can't use a temporary folder because CreateJobs API does not warrant side effects, and does not prepare a temp folder.
// we can't use the OS temp folder anyway, because many includes (eg #include "../RPI/Shadow.h") are relative and will only work from the original location
AZStd::string prependedPath = ShaderBuilderUtility::DumpAzslPrependedCode(
BuilderName, prependedAzslSourceCode, originalLocation, ShaderBuilderUtility::ExtractStemName(fullPath.c_str()), shaderPlatformInterface->GetAPIName().GetStringView());
BuilderName, prependedAzslSourceCode, originalLocation, ShaderBuilderUtility::ExtractStemName(fullPath.c_str()),
shaderPlatformInterface->GetAPIName().GetStringView());
// run mcpp
PreprocessorData preprocessorData = PreprocessSource(prependedPath, fullPath, buildOptions.m_preprocessorSettings);
jobDescriptor.m_jobParameters[(u32)JobParameterIndices::PreprocessorError] = preprocessorData.diagnostics; // save for ProcessJob
@ -221,7 +222,7 @@ namespace AZ
}
// eg: ("D:/p/x.a", "D:/p/x.b") -> yes
static bool HasSameStemName(const AZStd::string& lhsPath, const AZStd::string& rhsPath)
static bool HasSameFileName(const AZStd::string& lhsPath, const AZStd::string& rhsPath)
{
using namespace StringFunc::Path;
AZStd::string stem1;
@ -307,7 +308,8 @@ namespace AZ
buildOptions.m_compilerArguments.Merge(shaderAssetSource.m_compiler);
// Earlier, we declared a job dependency on the .azsl's job, let's access the produced assets:
uint32_t subId = ShaderBuilderUtility::MakeAzslBuildProductSubId(RPI::ShaderAssetSubId::GeneratedSource, platformInterface->GetAPIType());
uint32_t subId = ShaderBuilderUtility::MakeAzslBuildProductSubId(
RPI::ShaderAssetSubId::GeneratedHlslSource, platformInterface->GetAPIType());
auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(inputFiles->m_azslSourceFullPath, subId);
AZ_Warning(BuilderName, assetIdOutcome.IsSuccess(), "Product of dependency %s not found: this is an oddity but build can continue.", inputFiles->m_azslSourceFullPath.c_str());
if (assetIdOutcome.IsSuccess())
@ -325,7 +327,7 @@ namespace AZ
AZ_TracePrintf(BuilderName, "Product output already built by %s is not reusable because of incompatible azslc CompilerHints: launching independent build", inputFiles->m_azslSourceFullPath.c_str());
}
if (HasSameStemName(fullSourcePath, inputFiles->m_azslSourceFullPath))
if (HasSameFileName(fullSourcePath, inputFiles->m_azslSourceFullPath))
{
// let's add a "distinguisher" to the names of the outproduct artifacts of this build round.*
// Because otherwise the asset processor is not going to accept an overwrite of the ones output by the .azsl job

@ -26,6 +26,7 @@
#include <AtomCore/Serialization/Json/JsonUtils.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/IO/FileOperations.h> // [GFX TODO] Remove when [ATOM-15472]
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/Process/ProcessCommunicator.h>
@ -122,7 +123,7 @@ namespace AZ
namespace SubProducts = ShaderBuilderUtility::AzslSubProducts;
Outcome<SubProducts::Paths> AzslCompiler::EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile /* = ""*/) const
Outcome<SubProducts::Paths> AzslCompiler::EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile /* = ""*/, const char * addSuffix) const
{
bool success = Compile("--full " + parameters, outputFile);
if (!success)
@ -133,11 +134,22 @@ namespace AZ
SubProducts::Paths productPaths = SubProducts::Paths(SubProducts::Paths::capacity());
for (auto subProduct : SubProducts::SuffixListMembers)
{
productPaths[subProduct.m_value] = outputFile.empty() ? m_inputFilePath : outputFile; // that's a reproduction of azslc's behavior (no "-o" = input name is used)
AzFramework::StringFunc::Path::ReplaceExtension(productPaths[subProduct.m_value], subProduct.m_string.data());
AZStd::string subProductFilePath = outputFile.empty() ? m_inputFilePath : outputFile; // that's a reproduction of azslc's behavior (no "-o" = input name is used)
AzFramework::StringFunc::Path::ReplaceExtension(subProductFilePath, subProduct.m_string.data());
// append .json if it's one of those subs:
auto listOfJsons = { SubProducts::ia, SubProducts::om, SubProducts::srg, SubProducts::options, SubProducts::bindingdep };
productPaths[subProduct.m_value] += AZStd::any_of(AZ_BEGIN_END(listOfJsons), [&](auto v) { return v == subProduct.m_value; }) ? ".json" : "";
subProductFilePath += AZStd::any_of(AZ_BEGIN_END(listOfJsons), [&](auto v) { return v == subProduct.m_value; }) ? ".json" : "";
// [GFX TODO] Remove when [ATOM-15472]
if (addSuffix)
{
// Rename the product file.
AZStd::string finalSubProductFilePath = AZStd::string::format("%s%s", subProductFilePath.c_str(), addSuffix);
AZ::IO::Move(subProductFilePath.c_str(), finalSubProductFilePath.c_str());
subProductFilePath = finalSubProductFilePath;
}
productPaths[subProduct.m_value] = subProductFilePath;
}
productPaths[SubProducts::azslin] = GetInputFilePath(); // post-fixup this one after the loop, because it's not an output of azslc, it's an output of the builder though.
return { productPaths };

@ -38,8 +38,9 @@ namespace AZ
//! @param inputFilePath The target input file to compile. Should be a valid AZSL file with no preprocessing directives.
AzslCompiler(const AZStd::string& inputFilePath);
//! [GFX TODO] Remove @addSuffix when [ATOM-15472]
//! compile with --full and generate all .json files
Outcome<ShaderBuilderUtility::AzslSubProducts::Paths> EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile = "") const;
Outcome<ShaderBuilderUtility::AzslSubProducts::Paths> EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile = "", const char * addSuffix = nullptr) const;
//! compile to HLSL independently
bool EmitShader(AZ::IO::GenericStream& outputStream, const AZStd::string& extraCompilerParams) const;
//! compile with --ia independently and populate document @output

@ -83,23 +83,41 @@ namespace AZ
AZStd::string m_azslFileName; //!< Name for the source .azsl file
};
struct AzslCodeTopData
//! DEPRECATED [ATOM-15472]
//! This class is used to collect all the json files produced by the compilation
//! of an AZSL file as objects.
struct AzslData
{
AzslData(const AZStd::shared_ptr<ShaderFiles>& a_sources) : m_sources(a_sources) { }
AZStd::shared_ptr<ShaderFiles> m_sources;
AZStd::string m_preprocessedFullPath; // Full path to a preprocessed version of the original AZSL file
AZStd::string m_shaderCodePrefix; // AssetProcessor generated shader code which is added to the
// AZSLc emitted code prior to invoking the native shader compiler
SrgDataContainer m_srgData;
AzslFunctions m_functions;
StructContainer m_structs;
AzslFunctions m_functions;
StructContainer m_structs;
RootConstantData m_rootConstantData;
};
struct AzslData
//! This class is used to collect all the json files produced by the compilation
//! of an AZSL file as objects.
struct AzslData2
{
AzslData(const AZStd::shared_ptr<ShaderFiles>& a_sources) : m_sources(a_sources) { }
AzslData2(const AZStd::shared_ptr<ShaderFiles>& a_sources)
: m_sources(a_sources)
{
}
AZStd::shared_ptr<ShaderFiles> m_sources;
AZStd::string m_preprocessedFullPath; // Full path to a preprocessed version of the original AZSL file
AZStd::string m_shaderCodePrefix; // AssetProcessor generated shader code which is added to the
// AZSLc emitted code prior to invoking the native shader compiler
AzslCodeTopData m_topData;
AZStd::string m_preprocessedFullPath; // Full path to a preprocessed version of the original AZSL file
SrgDataContainer m_srgData;
AzslFunctions m_functions;
StructContainer m_structs;
RootConstantData m_rootConstantData;
};
} // ShaderBuilder
} // AZ

@ -86,7 +86,7 @@ namespace AZ
// Register AZSL's compilation products Builder
AssetBuilderSDK::AssetBuilderDesc azslBuilderDescriptor;
azslBuilderDescriptor.m_name = "AZSL Builder";
azslBuilderDescriptor.m_version = 7; // LKG Merge
azslBuilderDescriptor.m_version = 8; // ATOM-15276
// register all extensions thay may carry azsl code. header. main shader. or SRG
azslBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
azslBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsl", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
@ -102,7 +102,7 @@ namespace AZ
// Register Shader Resource Group Layout Builder
AssetBuilderSDK::AssetBuilderDesc srgLayoutBuilderDescriptor;
srgLayoutBuilderDescriptor.m_name = "Shader Resource Group Layout Builder";
srgLayoutBuilderDescriptor.m_version = 54; // Enable Null Rhi for AutomatedTesting
srgLayoutBuilderDescriptor.m_version = 55; // ATOM-15276
srgLayoutBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsl", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
srgLayoutBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsli", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
@ -118,7 +118,7 @@ namespace AZ
// Register Shader Asset Builder
AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor;
shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder";
shaderAssetBuilderDescriptor.m_version = 98; // Enable Null Rhi for AutomatedTesting
shaderAssetBuilderDescriptor.m_version = 99; // ATOM-15276
// .shader file changes trigger rebuilds
shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderAssetBuilder>();
@ -133,7 +133,7 @@ namespace AZ
shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder";
// Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update
// ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder".
shaderVariantAssetBuilderDescriptor.m_version = 19; // Enable Null Rhi for AutomatedTesting
shaderVariantAssetBuilderDescriptor.m_version = 20; // ATOM-15276
shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderVariantAssetBuilder>();
shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
@ -145,7 +145,7 @@ namespace AZ
// Register Precompiled Shader Builder
AssetBuilderSDK::AssetBuilderDesc precompiledShaderBuilderDescriptor;
precompiledShaderBuilderDescriptor.m_name = "Precompiled Shader Builder";
precompiledShaderBuilderDescriptor.m_version = 7; // ATOM-14780
precompiledShaderBuilderDescriptor.m_version = 8; // ATOM-15276
precompiledShaderBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", AZ::PrecompiledShaderBuilder::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
precompiledShaderBuilderDescriptor.m_busId = azrtti_typeid<PrecompiledShaderBuilder>();
precompiledShaderBuilderDescriptor.m_createJobFunction = AZStd::bind(&PrecompiledShaderBuilder::CreateJobs, &m_precompiledShaderBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
@ -153,6 +153,43 @@ namespace AZ
m_precompiledShaderBuilder.BusConnect(precompiledShaderBuilderDescriptor.m_busId);
AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, precompiledShaderBuilderDescriptor);
// Register Shader Asset Builder 2
AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilder2Descriptor;
shaderAssetBuilder2Descriptor.m_name = "Shader Asset Builder 2";
shaderAssetBuilder2Descriptor.m_version = 1; // ATOM-15276
// .shader2 file changes trigger rebuilds
shaderAssetBuilder2Descriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(
AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension2),
AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderAssetBuilder2Descriptor.m_busId = azrtti_typeid<ShaderAssetBuilder2>();
shaderAssetBuilder2Descriptor.m_createJobFunction =
AZStd::bind(&ShaderAssetBuilder2::CreateJobs, &m_shaderAssetBuilder2, AZStd::placeholders::_1, AZStd::placeholders::_2);
shaderAssetBuilder2Descriptor.m_processJobFunction =
AZStd::bind(&ShaderAssetBuilder2::ProcessJob, &m_shaderAssetBuilder2, AZStd::placeholders::_1, AZStd::placeholders::_2);
m_shaderAssetBuilder2.BusConnect(shaderAssetBuilder2Descriptor.m_busId);
AssetBuilderSDK::AssetBuilderBus::Broadcast(
&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderAssetBuilder2Descriptor);
// Register Shader Variant Asset Builder 2
AssetBuilderSDK::AssetBuilderDesc shaderVariantAssetBuilder2Descriptor;
shaderVariantAssetBuilder2Descriptor.m_name = "Shader Variant Asset Builder 2";
// Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update
// ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder".
shaderVariantAssetBuilder2Descriptor.m_version = 1; // ATOM-15276
shaderVariantAssetBuilder2Descriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(
AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension2),
AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderVariantAssetBuilder2Descriptor.m_busId = azrtti_typeid<ShaderVariantAssetBuilder2>();
shaderVariantAssetBuilder2Descriptor.m_createJobFunction = AZStd::bind(
&ShaderVariantAssetBuilder2::CreateJobs, &m_shaderVariantAssetBuilder2, AZStd::placeholders::_1, AZStd::placeholders::_2);
shaderVariantAssetBuilder2Descriptor.m_processJobFunction = AZStd::bind(
&ShaderVariantAssetBuilder2::ProcessJob, &m_shaderVariantAssetBuilder2, AZStd::placeholders::_1, AZStd::placeholders::_2);
m_shaderVariantAssetBuilder2.BusConnect(shaderVariantAssetBuilder2Descriptor.m_busId);
AssetBuilderSDK::AssetBuilderBus::Broadcast(
&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderVariantAssetBuilder2Descriptor);
}
void AzslShaderBuilderSystemComponent::Deactivate()
@ -161,6 +198,8 @@ namespace AZ
m_srgLayoutBuilder.BusDisconnect();
m_shaderVariantAssetBuilder.BusDisconnect();
m_precompiledShaderBuilder.BusDisconnect();
m_shaderAssetBuilder2.BusDisconnect();
m_shaderVariantAssetBuilder2.BusDisconnect();
RHI::ShaderPlatformInterfaceRegisterBus::Handler::BusDisconnect();
ShaderPlatformInterfaceRequestBus::Handler::BusDisconnect();

@ -18,12 +18,14 @@
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include <AzslBuilder.h>
#include <SrgLayoutBuilder.h>
#include <ShaderAssetBuilder.h>
#include <ShaderVariantAssetBuilder.h>
#include <PrecompiledShaderBuilder.h>
#include <ShaderPlatformInterfaceRequest.h>
#include "AzslBuilder.h"
#include "SrgLayoutBuilder.h"
#include "ShaderAssetBuilder.h"
#include "ShaderVariantAssetBuilder.h"
#include "PrecompiledShaderBuilder.h"
#include "ShaderPlatformInterfaceRequest.h"
#include "ShaderAssetBuilder2.h"
#include "ShaderVariantAssetBuilder2.h"
namespace AZ
{
@ -71,6 +73,8 @@ namespace AZ
ShaderAssetBuilder m_shaderAssetBuilder;
ShaderVariantAssetBuilder m_shaderVariantAssetBuilder;
PrecompiledShaderBuilder m_precompiledShaderBuilder;
ShaderAssetBuilder2 m_shaderAssetBuilder2;
ShaderVariantAssetBuilder2 m_shaderVariantAssetBuilder2;
/// Contains the ShaderPlatformInterface for all registered RHIs
AZStd::unordered_map<RHI::APIType, RHI::ShaderPlatformInterface*> m_shaderPlatformInterfaces;

@ -62,7 +62,7 @@ namespace AZ
}
}
GlobalBuildOptions ReadBuildOptions(const char* builderName)
GlobalBuildOptions ReadBuildOptions(const char* builderName, const char* optionalIncludeFolder)
{
GlobalBuildOptions output;
// try to parse some config file for eventual additional options
@ -79,7 +79,7 @@ namespace AZ
{
AZ_TracePrintf(builderName, "config file [%s] not found.", globalBuildOption.c_str());
}
InitializePreprocessorOptions(output.m_preprocessorSettings, builderName);
InitializePreprocessorOptions(output.m_preprocessorSettings, builderName, optionalIncludeFolder);
return output;
}
}

@ -33,6 +33,9 @@ namespace AZ
RHI::ShaderCompilerArguments m_compilerArguments;
};
GlobalBuildOptions ReadBuildOptions(const char* builderName);
//! Reads the global options used when compiling shaders. The options are defined in <GameProject>/Config/shader_global_build_options.json
//! @param builderName: A string with the name of the builder calling this API. Used for trace debugging.
//! @param optionalIncludeFolder: An additional directory to add to the list of include folders for the C-preprocessor.
GlobalBuildOptions ReadBuildOptions(const char* builderName, const char* optionalIncludeFolder = nullptr);
}
}

@ -58,6 +58,37 @@ namespace AZ
}
}
void PreprocessorOptions::RemovePredefinedMacros(const AZStd::vector<AZStd::string>& macroNames)
{
m_predefinedMacros.erase(
AZStd::remove_if(
m_predefinedMacros.begin(), m_predefinedMacros.end(),
[&](const AZStd::string& predefinedMacro)
{
for (const auto& macroName : macroNames)
{
// Haystack, needle, bCaseSensitive
if (!AzFramework::StringFunc::StartsWith(predefinedMacro, macroName, true))
{
return false;
}
// If found, let's make sure it is not just a substring.
if (predefinedMacro.size() == macroName.size())
{
return true;
}
// The predefinedMacro can be a string like "macro=value". If we find '=' it is a match.
if (predefinedMacro.c_str()[macroName.size()] == '=')
{
return true;
}
return false;
}
return false;
}),
m_predefinedMacros.end());
}
//! Binder helper to Matsui C-Pre-Processor library
class McppBinder
{
@ -286,7 +317,8 @@ namespace AZ
}
// populate options with scan folders and contents of parsing shader_global_build_options.json
void InitializePreprocessorOptions(PreprocessorOptions& options, [[maybe_unused]] const char* builderName)
void InitializePreprocessorOptions(
PreprocessorOptions& options, [[maybe_unused]] const char* builderName, const char* optionalIncludeFolder)
{
AZ_TraceContext("Init include-paths lookup options", "preprocessor");
@ -303,6 +335,10 @@ namespace AZ
// Add the project path to list of include paths
AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath();
scanFoldersSet.emplace(projectPath.c_str(), projectPath.size());
if (optionalIncludeFolder)
{
scanFoldersSet.emplace(optionalIncludeFolder, strnlen(optionalIncludeFolder, AZ::IO::MaxPathLength));
}
// but while we transfer to the set, we're going to keep only folders where +/ShaderLib exists
for (AZStd::string folder : scanFoldersVector)
{

@ -47,9 +47,13 @@ namespace AZ
//! folders are relative to the dev folder of the project
AZStd::vector<AZStd::string> m_projectIncludePaths;
//! passed as -D macro1[=value1] -D macro2 ...
//! Each string is of the type "name[=value]"
//! passed as -Dmacro1[=value1] -Dmacro2 ... to MCPP.
AZStd::vector<AZStd::string> m_predefinedMacros;
//! Removes all macros from @m_predefinedMacros that appear in @macroNames
void RemovePredefinedMacros(const AZStd::vector<AZStd::string>& macroNames);
//! if needed, we may add configurations like
//! "keep comments" or "don't predefine non-standard macros"
//! or "output diagnostics to std.err" or "enable digraphs/trigraphs"...
@ -59,7 +63,10 @@ namespace AZ
//! It will populate your option with a default base of include folders given by the Asset Processor scan folders.
//! This is going to look for a Config/shader_global_build_options.json in one of the scan folders
//! (that file can specify additional include files and preprocessor macros).
void InitializePreprocessorOptions(PreprocessorOptions& options, const char* builderName);
//! @param options: Outout parameter, will contain the preprocessor options.
//! @param builderName: Used for debugging.
//! @param optionalIncludeFolder: If not null, will be added to the list of include folders for the c-preprocessor in @options.
void InitializePreprocessorOptions(PreprocessorOptions& options, const char* builderName, const char* optionalIncludeFolder = nullptr);
/**
* Runs the preprocessor on the given source file path, and stores results in outputData.

@ -166,17 +166,6 @@ namespace AZ
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
}
static uint32_t GetRootVariantAssetSubId(const RHI::ShaderPlatformInterface& shaderPlatformInterface)
{
//The 2 Most significant bits encode the the RHI::API unique index.
const uint32_t apiUniqueIndex = shaderPlatformInterface.GetAPIUniqueIndex();
AZ_Assert(apiUniqueIndex <= RHI::Limits::APIType::PerPlatformApiUniqueIndexMax,
"Invalid api unique index [%u] from ShaderPlatformInterface [%s]", apiUniqueIndex, shaderPlatformInterface.GetAPIName().GetCStr());
const uint32_t rhiApiSubId = apiUniqueIndex << 30;
const uint32_t productSubID = rhiApiSubId | static_cast<uint32_t>(RPI::ShaderAssetSubId::RootShaderVariantAsset);
return productSubID;
}
static AssetBuilderSDK::ProcessJobResultCode CompileForAPI(
const ShaderBuilderUtility::AzslSubProducts::Paths& pathOfProductFiles,
RPI::ShaderAssetCreator& shaderAssetCreator,
@ -201,7 +190,7 @@ namespace AZ
if (shaderSourceDataDescriptor.m_programSettings.m_entryPoints.empty())
{
AZ_TracePrintf(ShaderAssetBuilderName, "ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n");
ShaderVariantAssetBuilder::GetDefaultEntryPointsFromAzslData(azslData, shaderEntryPoints);
ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslData.m_functions, shaderEntryPoints);
}
else
{
@ -249,7 +238,9 @@ namespace AZ
// so the root ShaderVariantAsset is found when the ShaderAsset is deserialized.
AZStd::string fullSourcePath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), fullSourcePath, true);
const uint32_t productSubID = GetRootVariantAssetSubId(*shaderPlatformInterface);
const uint32_t productSubID = RPI::ShaderAsset::MakeAssetProductSubId(
shaderPlatformInterface->GetAPIUniqueIndex(),
aznumeric_cast<uint32_t>(RPI::ShaderAssetSubId::RootShaderVariantAsset));
auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(fullSourcePath, productSubID);
AZ_Assert(assetIdOutcome.IsSuccess(), "Failed to get AssetId from shader %s", fullSourcePath.c_str());
const Data::AssetId variantAssetId = assetIdOutcome.TakeValue();
@ -288,12 +279,13 @@ namespace AZ
// add byproducts as job output products:
if (variantCreationContext.m_outputByproducts)
{
uint32_t subProductType = aznumeric_cast<uint32_t>(RPI::ShaderAssetSubId::GeneratedHlslSource) + 1;
for (const AZStd::string& byproduct : variantCreationContext.m_outputByproducts->m_intermediatePaths)
{
AssetBuilderSDK::JobProduct jobProduct;
jobProduct.m_productFileName = byproduct;
jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt");
jobProduct.m_productSubID = ShaderBuilderUtility::MakeDebugByproductSubId(shaderPlatformInterface->GetAPIType(), byproduct);
jobProduct.m_productSubID = RPI::ShaderAsset::MakeAssetProductSubId(shaderPlatformInterface->GetAPIUniqueIndex(), subProductType++);
response.m_outputProducts.push_back(AZStd::move(jobProduct));
}
}
@ -305,12 +297,12 @@ namespace AZ
attributeMaps.resize(RHI::ShaderStageCount);
for (const auto& shaderEntry : shaderSourceDataDescriptor.m_programSettings.m_entryPoints)
{
auto findId = AZStd::find_if(AZ_BEGIN_END(azslData.m_topData.m_functions), [&shaderEntry](const auto& func)
auto findId = AZStd::find_if(AZ_BEGIN_END(azslData.m_functions), [&shaderEntry](const auto& func)
{
return func.m_name == shaderEntry.m_name;
});
if (findId == azslData.m_topData.m_functions.end())
if (findId == azslData.m_functions.end())
{
// shaderData.m_functions only contains Vertex, Fragment and Compute entries for now
// Tessellation shaders will need to be handled too

@ -0,0 +1,684 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*/
#include "ShaderAssetBuilder2.h"
#include <CommonFiles/Preprocessor.h>
#include <CommonFiles/GlobalBuildOptions.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderAssetCreator2.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantKey.h>
#include <Atom/RHI.Edit/Utils.h>
#include <Atom/RHI.Edit/ShaderPlatformInterface.h>
#include <Atom/RPI.Edit/Common/JsonReportingHelper.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RHI.Reflect/ConstantsLayout.h>
#include <Atom/RHI.Reflect/PipelineLayoutDescriptor.h>
#include <Atom/RHI.Reflect/ShaderStageFunction.h>
#include <AtomCore/Serialization/Json/JsonUtils.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/Platform/PlatformDefaults.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/JSON/document.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/IOUtils.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/sort.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include "AzslBuilder.h"
#include "ShaderVariantAssetBuilder2.h"
#include "ShaderBuilderUtility.h"
#include "ShaderPlatformInterfaceRequest.h"
#include "AtomShaderConfig.h"
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AssetBuilderSDK/SerializationDependencies.h>
namespace AZ
{
namespace ShaderBuilder
{
static constexpr char ShaderAssetBuilder2Name[] = "ShaderAssetBuilder2";
static constexpr uint32_t ShaderAssetBuildTimestampParam = 0;
void ShaderAssetBuilder2::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const
{
AZStd::string fullPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullPath, true);
AZ_TracePrintf(ShaderAssetBuilder2Name, "CreateJobs for Shader \"%s\"\n", fullPath.data());
// Used to synchronize versions of the ShaderAsset and ShaderVariantTreeAsset, especially during hot-reload.
// Note it's probably important for this to be set once outside the platform loop so every platform's ShaderAsset
// has the same value, because later the ShaderVariantTreeAsset job will fetch this value from the local ShaderAsset
// which could cross platforms (i.e. building an android ShaderVariantTreeAsset on PC would fetch the tiemstamp from
// the PC's ShaderAsset).
AZStd::sys_time_t shaderAssetBuildTimestamp = AZStd::GetTimeNowMicroSecond();
// Need to get the name of the azsl file from the .shader source asset, to be able to declare a dependency to SRG Layout Job.
// and the macro options to preprocess.
auto descriptorParseOutcome = ShaderBuilderUtility::LoadShaderDataJson(fullPath);
if (!descriptorParseOutcome.IsSuccess())
{
AZ_Error(
ShaderAssetBuilder2Name, false, "Failed to parse Shader Descriptor JSON: %s",
descriptorParseOutcome.GetError().c_str());
return;
}
RPI::ShaderSourceData shaderSourceData = descriptorParseOutcome.TakeValue();
AZStd::string azslFullPath;
ShaderBuilderUtility::GetAbsolutePathToAzslFile(fullPath, shaderSourceData.m_source, azslFullPath);
if (!IO::FileIOBase::GetInstance()->Exists(azslFullPath.c_str()))
{
AZ_Error(
ShaderAssetBuilder2Name, false, "Shader program listed as the source entry does not exist: %s.", azslFullPath.c_str());
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Failed;
return;
}
GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderAssetBuilder2Name);
// [GFX TODO] [ATOM-14966] In principle, based on macro definitions, included files can change per supervariant.
// So, the list of source asset dependencies must be collected by running MCPP on each supervariant.
// For now, we will run MCPP only once because CreateJobs() should be as light as possible.
//
// Regardless of the PlatformInfo and enabled ShaderPlatformInterfaces, the azsl file will be preprocessed
// with the sole purpose of extracting all included files. For each included file a SourceDependency will be declared.
PreprocessorData output;
buildOptions.m_compilerArguments.Merge(shaderSourceData.m_compiler);
PreprocessFile(azslFullPath, output, buildOptions.m_preprocessorSettings, true, true);
for (auto includePath : output.includedPaths)
{
// m_sourceFileDependencyList does not support paths with "." or ".." for relative lookup, but the preprocessor
// may produce path strings like "C:/a/b/c/../../d/file.azsli" so we have to normalize
AzFramework::StringFunc::Path::Normalize(includePath);
AssetBuilderSDK::SourceFileDependency includeFileDependency;
includeFileDependency.m_sourceFileDependencyPath = includePath;
response.m_sourceFileDependencyList.emplace_back(includeFileDependency);
}
{
// Add the AZSL as source dependency
AssetBuilderSDK::SourceFileDependency azslFileDependency;
azslFileDependency.m_sourceFileDependencyPath = azslFullPath;
response.m_sourceFileDependencyList.emplace_back(azslFileDependency);
}
for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
{
AZ_TraceContext("For platform", platformInfo.m_identifier.data());
// Get the platform interfaces to be able to access the prepend file
AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces = ShaderBuilderUtility::DiscoverValidShaderPlatformInterfaces(platformInfo);
if (platformInterfaces.empty())
{
continue;
}
AssetBuilderSDK::JobDescriptor jobDescriptor;
jobDescriptor.m_priority = 2;
// [GFX TODO][ATOM-2830] Set 'm_critical' back to 'false' once proper fix for Atom startup issues are in
jobDescriptor.m_critical = true;
jobDescriptor.m_jobKey = ShaderAssetBuilder2JobKey;
jobDescriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
jobDescriptor.m_jobParameters.emplace(ShaderAssetBuildTimestampParam, AZStd::to_string(shaderAssetBuildTimestamp));
response.m_createJobOutputs.push_back(jobDescriptor);
} // for all request.m_enabledPlatforms
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
}
static bool SerializeOutShaderAsset(Data::Asset<RPI::ShaderAsset2> shaderAsset,
const AZStd::string& tempDirPath,
AssetBuilderSDK::ProcessJobResponse& response)
{
AZStd::string shaderAssetFileName = AZStd::string::format("%s.%s", shaderAsset->GetName().GetCStr(), RPI::ShaderAsset2::Extension);
AZStd::string shaderAssetOutputPath;
AzFramework::StringFunc::Path::ConstructFull(tempDirPath.data(), shaderAssetFileName.data(), shaderAssetOutputPath, true);
if (!Utils::SaveObjectToFile(shaderAssetOutputPath, DataStream::ST_BINARY, shaderAsset.Get()))
{
AZ_Error(ShaderAssetBuilder2Name, false, "Failed to output Shader Descriptor");
return false;
}
AssetBuilderSDK::JobProduct shaderJobProduct;
if (!AssetBuilderSDK::OutputObject(shaderAsset.Get(), shaderAssetOutputPath, azrtti_typeid<RPI::ShaderAsset2>(),
aznumeric_cast<uint32_t>(RPI::ShaderAsset2ProductSubId::ShaderAsset2), shaderJobProduct))
{
AZ_Error(ShaderAssetBuilder2Name, false, "Failed to output product dependencies.");
return false;
}
response.m_outputProducts.push_back(AZStd::move(shaderJobProduct));
return true;
}
static AZ::Outcome<RHI::ShaderStageAttributeMapList, AZStd::string> BuildAttributesMap(
const RHI::ShaderPlatformInterface* shaderPlatformInterface,
const AzslData& azslData,
const MapOfStringToStageType& shaderEntryPoints,
bool& hasRasterProgram)
{
hasRasterProgram = false;
bool hasComputeProgram = false;
bool hasRayTracingProgram = false;
RHI::ShaderStageAttributeMapList attributeMaps;
attributeMaps.resize(RHI::ShaderStageCount);
for (const auto& shaderEntryPoint : shaderEntryPoints)
{
auto shaderEntryName = shaderEntryPoint.first;
auto shaderStageType = shaderEntryPoint.second;
auto assetBuilderShaderType = ShaderBuilderUtility::ToAssetBuilderShaderType(shaderStageType);
hasRasterProgram |= shaderPlatformInterface->IsShaderStageForRaster(assetBuilderShaderType);
hasComputeProgram |= shaderPlatformInterface->IsShaderStageForCompute(assetBuilderShaderType);
hasRayTracingProgram |= shaderPlatformInterface->IsShaderStageForRayTracing(assetBuilderShaderType);
auto findId = AZStd::find_if(AZ_BEGIN_END(azslData.m_functions), [&shaderEntryPoint](const auto& func) {
return func.m_name == shaderEntryPoint.first;
});
if (findId == azslData.m_functions.end())
{
// shaderData.m_functions only contains Vertex, Fragment and Compute entries for now
// Tessellation shaders will need to be handled too
continue;
}
const auto shaderStage = ToRHIShaderStage(assetBuilderShaderType);
for (const auto& attr : findId->attributesList)
{
// Some stages like RHI::ShaderStage::Tessellation are compound and consist of two or more shader entries
const Name& attributeName = attr.first;
const RHI::ShaderStageAttributeArguments& args = attr.second;
const auto stageIndex = static_cast<uint32_t>(shaderStage);
AZ_Assert(stageIndex < RHI::ShaderStageCount, "Invalid shader stage specified!");
attributeMaps[stageIndex][attributeName] = args;
}
}
if (hasRasterProgram && hasComputeProgram)
{
return AZ::Failure(AZStd::string(" Shader asset descriptor defines both a raster entry point and a compute entry point."));
}
if (!hasRasterProgram && !hasComputeProgram && !hasRayTracingProgram)
{
AZStd::string entryPointNames = ShaderBuilderUtility::GetAcceptableDefaultEntryPointNames(azslData);
return AZ::Failure(
AZStd::string::format( "Shader asset descriptor has a program variant that does not define any entry points. Either declare entry "
"points in the .shader file, or use one of the available default names (not case-sensitive): [%s]",
entryPointNames.c_str()));
}
return AZ::Success(attributeMaps);
}
void ShaderAssetBuilder2::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
{
const AZStd::sys_time_t startTime = AZStd::GetTimeNowTicks();
AZStd::string shaderFullPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), shaderFullPath, true);
// Save .shader file name (no extension and no parent directory path)
AZStd::string shaderFileName;
AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.c_str(), shaderFileName);
// No error checking because the same calls were already executed during CreateJobs()
auto descriptorParseOutcome = ShaderBuilderUtility::LoadShaderDataJson(shaderFullPath);
RPI::ShaderSourceData shaderSourceData = descriptorParseOutcome.TakeValue();
AZStd::string azslFullPath;
ShaderBuilderUtility::GetAbsolutePathToAzslFile(shaderFullPath, shaderSourceData.m_source, azslFullPath);
AZ_TracePrintf(ShaderAssetBuilder2Name, "Original AZSL File: %s \n", azslFullPath.c_str());
// The directory where the Azsl file was found must be added to the list of include paths
AZStd::string azslFolderPath;
AzFramework::StringFunc::Path::GetFolderPath(azslFullPath.c_str(), azslFolderPath);
GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderAssetBuilder2Name, azslFolderPath.c_str());
// Request the list of valid shader platform interfaces for the target platform.
AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces = ShaderBuilderUtility::DiscoverEnabledShaderPlatformInterfaces(
request.m_platformInfo, shaderSourceData);
if (platformInterfaces.empty())
{
//No work to do. Exit gracefully.
AZ_TracePrintf(ShaderAssetBuilder2Name,
"No azshader is produced on behalf of %s because all valid RHI backends were disabled for this shader.\n",
shaderFullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
return;
}
// Get the time stamp string as sys_time_t, and also convert back to string to make sure it was converted correctly.
AZStd::sys_time_t shaderAssetBuildTimestamp = 0;
auto shaderAssetBuildTimestampIterator = request.m_jobDescription.m_jobParameters.find(ShaderAssetBuildTimestampParam);
if (shaderAssetBuildTimestampIterator != request.m_jobDescription.m_jobParameters.end())
{
shaderAssetBuildTimestamp = AZStd::stoull(shaderAssetBuildTimestampIterator->second);
if (AZStd::to_string(shaderAssetBuildTimestamp) != shaderAssetBuildTimestampIterator->second)
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
AZ_Assert(false, "Incorrect conversion of ShaderAssetBuildTimestampParam");
return;
}
}
auto supervariantList = ShaderBuilderUtility::GetSupervariantListFromShaderSourceData(shaderSourceData);
RPI::ShaderAssetCreator2 shaderAssetCreator;
shaderAssetCreator.Begin(Uuid::CreateRandom());
shaderAssetCreator.SetName(AZ::Name{shaderFileName.c_str()});
shaderAssetCreator.SetDrawListName(Name(shaderSourceData.m_drawListName));
shaderAssetCreator.SetShaderAssetBuildTimestamp(shaderAssetBuildTimestamp);
// The ShaderOptionGroupLayout must be the same across all supervariants because
// there can be only a single ShaderVariantTreeAsset per ShaderAsset.
// We will store here the one that results when the *.azslin file is
// compiled for the default, nameless, supervariant.
// For all other supervariants we just make sure the hashes are the same
// as this one.
RPI::Ptr<RPI::ShaderOptionGroupLayout> finalShaderOptionGroupLayout = nullptr;
// Time to describe the big picture.
// 1- Preprocess an AZSL file with MCPP (a C-Preprocessor), and generate a flat AZSL file without #include lines and any macros in it.
// Let's call it the Flat-AZSL file. There are two levels of macro definition that need to be merged before we can invoke MCPP:
// 1.1- From <GameProject>/Config/shader_global_build_options.json, which we have stored in the local variable @buildOptions.
// 1.2- From the "Supervariant" definition key, which can be different for each supervariant.
// 2- There will be one Flat-AZSL per supervariant. Each Flat-AZSL will be transpiled to HLSL with AZSLc. This means there will be one HLSL file
// per supervariant.
// 3- The generated HLSL (one HLSL per supervariant) file may contain C-Preprocessor Macros inserted by AZSLc. And that file will be given to DXC.
// DXC has a preprocessor embedded in it. DXC will be executed once for each entry function listed in the .shader file.
// There will be one DXIL compiled binary for each entry function. All the DXIL compiled binaries for each supervariant will be combined
// in the ROOT ShaderVariantAsset.
// Remark: In general, the work done by the ShaderVariantAssetBuilder is similar, but it will start from the HLSL file created; in step 2, mentioned above; by this builder,
// for each supervariant.
// At this moment We have global build options that should be merged with the build options that are common
// to all the supervariants of this shader.
buildOptions.m_compilerArguments.Merge(shaderSourceData.m_compiler);
for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces)
{
AZStd::string apiName(shaderPlatformInterface->GetAPIName().GetCStr());
AZ_TraceContext("Platform API", apiName);
// Signal the begin of shader data for an RHI API.
shaderAssetCreator.BeginAPI(shaderPlatformInterface->GetAPIType());
// Each shaderPlatformInterface has its own azsli header that needs to be prepended to the AZSL file before
// preprocessing. We will create a new temporary file that contains the combined data.
RHI::PrependArguments args;
args.m_sourceFile = azslFullPath.c_str();
args.m_prependFile = shaderPlatformInterface->GetAzslHeader(request.m_platformInfo);
args.m_addSuffixToFileName = apiName.c_str();
args.m_destinationFolder = request.m_tempDirPath.c_str();
AZStd::string prependedAzslFilePath = RHI::PrependFile(args);
if (prependedAzslFilePath == azslFullPath)
{
// For some reason the combined azsl file was not created in the temporary
// directory assigned to this job.
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
// Cache common AZSLC invokation arguments related with the current RHI Backend.
// Each supervariant can, optionally, remove or add more arguments for AZSLc.
AZStd::string commonAzslcCompilerParameters =
shaderPlatformInterface->GetAzslCompilerParameters(buildOptions.m_compilerArguments);
commonAzslcCompilerParameters += " ";
commonAzslcCompilerParameters +=
shaderPlatformInterface->GetAzslCompilerWarningParameters(buildOptions.m_compilerArguments);
AtomShaderConfig::AddParametersFromConfigFile(commonAzslcCompilerParameters, request.m_platformInfo);
// The register number only makes sense if the platform uses "spaces",
// since the register Id of the resource will not change even if the pipeline layout changes.
// We can pass in a default ShaderCompilerArguments because all we care about is whether the shaderPlatformInterface
// appends the "--use-spaces" flag.
const bool platformUsesRegisterSpaces =
(AzFramework::StringFunc::Find(commonAzslcCompilerParameters, "--use-spaces") != AZStd::string::npos);
uint32_t supervariantIndex = 0;
for (const auto& supervariantInfo : supervariantList)
{
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
if (jobCancelListener.IsCancelled())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
shaderAssetCreator.BeginSupervariant(supervariantInfo.m_name);
// Let's combine the global macro definitions, with the macro definitions particular to this
// supervariant. Two steps:
// 1- Supervariants can specify which macros to remove from the global definitions.
AZStd::vector<AZStd::string> macroDefinitionNamesToRemove = supervariantInfo.GetCombinedListOfMacroDefinitionNamesToRemove();
PreprocessorOptions preprocessorOptions = buildOptions.m_preprocessorSettings;
preprocessorOptions.RemovePredefinedMacros(macroDefinitionNamesToRemove);
// 2- Supervariants can specify which macros to add.
AZStd::vector<AZStd::string> macroDefinitionsToAdd = supervariantInfo.GetMacroDefinitionsToAdd();
preprocessorOptions.m_predefinedMacros.insert(
preprocessorOptions.m_predefinedMacros.end(), macroDefinitionsToAdd.begin(), macroDefinitionsToAdd.end());
// Run the preprocessor.
PreprocessorData output;
PreprocessFile(prependedAzslFilePath, output, preprocessorOptions, true, true);
RHI::ReportErrorMessages(ShaderAssetBuilder2Name, output.diagnostics);
// Dump the preprocessed string as a flat AZSL file with extension .azslin, which will be given to AZSLc to generate the HLSL file.
AZStd::string superVariantAzslinStemName = shaderFileName;
if (!supervariantInfo.m_name.IsEmpty())
{
superVariantAzslinStemName += AZStd::string::format("-%s", supervariantInfo.m_name.GetCStr());
}
AZStd::string azslinFullPath = ShaderBuilderUtility::DumpPreprocessedCode(
ShaderAssetBuilder2Name, output.code, request.m_tempDirPath, superVariantAzslinStemName,
apiName, true /*add2*/);
if (azslinFullPath.empty())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
AZ_TracePrintf(ShaderAssetBuilder2Name, "Preprocessed AZSL File: %s \n", prependedAzslFilePath.c_str());
// Before transpiling the flat-AZSL(.azslin) file into HLSL it is necessary
// to setup the AZSLc arguments as required by the current supervariant.
AZStd::string azslcCompilerParameters = supervariantInfo.GetCustomizedArgumentsForAzslc(commonAzslcCompilerParameters);
// Ready to transpile the azslin file into HLSL.
ShaderBuilder::AzslCompiler azslc(azslinFullPath);
AZStd::string hlslFullPath = AZStd::string::format("%s_%s.hlsl2", superVariantAzslinStemName.c_str(), apiName.c_str());
AzFramework::StringFunc::Path::Join(request.m_tempDirPath.c_str(), hlslFullPath.c_str(), hlslFullPath, true);
auto emitFullOutcome = azslc.EmitFullData(azslcCompilerParameters, hlslFullPath, "2");
if (!emitFullOutcome.IsSuccess())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
ShaderBuilderUtility::AzslSubProducts::Paths subProductsPaths = emitFullOutcome.TakeValue();
// In addition to the hlsl file, there are other json files that were generated.
// Each output file will become a product.
for (int i = 0; i < subProductsPaths.size(); ++i)
{
AssetBuilderSDK::JobProduct jobProduct;
jobProduct.m_productFileName = subProductsPaths[i];
static const AZ::Uuid AzslOutcomeType = "{6977AEB1-17AD-4992-957B-23BB2E85B18B}";
jobProduct.m_productAssetType = AzslOutcomeType;
// uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, uint32_t subProductType
jobProduct.m_productSubID = RPI::ShaderAsset2::MakeProductAssetSubId(
shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex,
aznumeric_cast<uint32_t>(ShaderBuilderUtility::AzslSubProducts::SubList[i]));
jobProduct.m_dependenciesHandled = true;
// Note that the output products are not traditional product assets that will be used by the game project.
// They are artifacts that are produced once, cached, and used later by other AssetBuilders as a way to centralize
// build organization.
response.m_outputProducts.push_back(AZStd::move(jobProduct));
}
AZStd::shared_ptr<ShaderFiles> files(new ShaderFiles);
AzslData azslData(files);
azslData.m_preprocessedFullPath = azslinFullPath;
RPI::ShaderResourceGroupLayoutList srgLayoutList;
RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout = RPI::ShaderOptionGroupLayout::Create();
BindingDependencies bindingDependencies;
RootConstantData rootConstantData;
AssetBuilderSDK::ProcessJobResultCode azslJsonReadResult = ShaderBuilderUtility::PopulateAzslDataFromJsonFiles(
ShaderAssetBuilder2Name, subProductsPaths, platformUsesRegisterSpaces, azslData, srgLayoutList, shaderOptionGroupLayout,
bindingDependencies, rootConstantData);
if (azslJsonReadResult != AssetBuilderSDK::ProcessJobResult_Success)
{
response.m_resultCode = azslJsonReadResult;
return;
}
shaderAssetCreator.SetSrgLayoutList(srgLayoutList);
if (!finalShaderOptionGroupLayout)
{
finalShaderOptionGroupLayout = shaderOptionGroupLayout;
shaderAssetCreator.SetShaderOptionGroupLayout(finalShaderOptionGroupLayout);
const uint32_t usedShaderOptionBits = shaderOptionGroupLayout->GetBitSize();
AZ_TracePrintf(
ShaderAssetBuilder2Name, "Note: This shader uses %u of %u available shader variant key bits. \n",
usedShaderOptionBits, RPI::ShaderVariantKeyBitCount);
}
else
{
if (finalShaderOptionGroupLayout->GetHash() != shaderOptionGroupLayout->GetHash())
{
AZ_Error(
ShaderAssetBuilder2Name, false, "Supervariant %s has a different ShaderOptionGroupLayout",
supervariantInfo.m_name.GetCStr())
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
}
// Discover entry points & type of programs.
MapOfStringToStageType shaderEntryPoints;
if (shaderSourceData.m_programSettings.m_entryPoints.empty())
{
AZ_TracePrintf(
ShaderAssetBuilder2Name,
"ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n");
ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslData.m_functions, shaderEntryPoints);
}
else
{
for (const auto& entryPoint : shaderSourceData.m_programSettings.m_entryPoints)
{
shaderEntryPoints[entryPoint.m_name] = entryPoint.m_type;
}
}
bool hasRasterProgram = false;
auto attributeMapsOutcome = BuildAttributesMap(shaderPlatformInterface, azslData, shaderEntryPoints, hasRasterProgram);
if (!attributeMapsOutcome.IsSuccess())
{
AZ_Error(ShaderAssetBuilder2Name, false, "%s\n", attributeMapsOutcome.GetError().c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
shaderAssetCreator.SetShaderStageAttributeMapList(attributeMapsOutcome.TakeValue());
// Check if we were canceled before we do any heavy processing of
// the shader data (compiling the shader kernels, processing SRG
// and pipeline layout data, etc.).
if (jobCancelListener.IsCancelled())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
RHI::Ptr<RHI::PipelineLayoutDescriptor> pipelineLayoutDescriptor =
ShaderBuilderUtility::BuildPipelineLayoutDescriptorForApi(
ShaderAssetBuilder2Name, srgLayoutList, shaderEntryPoints, buildOptions.m_compilerArguments, rootConstantData,
shaderPlatformInterface, bindingDependencies);
if (!pipelineLayoutDescriptor)
{
AZ_Error(
ShaderAssetBuilder2Name, false, "Failed to build pipeline layout descriptor for api=[%s]",
shaderPlatformInterface->GetAPIName().GetCStr());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
shaderAssetCreator.SetPipelineLayout(pipelineLayoutDescriptor);
RPI::ShaderInputContract shaderInputContract;
RPI::ShaderOutputContract shaderOutputContract;
size_t colorAttachmentCount = 0;
ShaderBuilderUtility::CreateShaderInputAndOutputContracts(
azslData, shaderEntryPoints, *shaderOptionGroupLayout.get(),
subProductsPaths[ShaderBuilderUtility::AzslSubProducts::om],
subProductsPaths[ShaderBuilderUtility::AzslSubProducts::ia],
shaderInputContract, shaderOutputContract, colorAttachmentCount);
shaderAssetCreator.SetInputContract(shaderInputContract);
shaderAssetCreator.SetOutputContract(shaderOutputContract);
if (hasRasterProgram)
{
// Set the various states to what is in the descriptor.
const RHI::TargetBlendState& targetBlendState = shaderSourceData.m_blendState;
RHI::RenderStates renderStates;
renderStates.m_rasterState = shaderSourceData.m_rasterState;
renderStates.m_depthStencilState = shaderSourceData.m_depthStencilState;
// [GFX TODO][ATOM-930] We should support unique blend states per RT
for (size_t i = 0; i < colorAttachmentCount; ++i)
{
renderStates.m_blendState.m_targets[i] = targetBlendState;
}
shaderAssetCreator.SetRenderStates(renderStates);
}
Outcome<AZStd::string, AZStd::string> hlslSourceCodeOutcome = Utils::ReadFile(hlslFullPath);
if (!hlslSourceCodeOutcome.IsSuccess())
{
AZ_Error(
ShaderAssetBuilder2Name, false, "Failed to obtain shader source from %s. [%s]", hlslFullPath.c_str(),
hlslSourceCodeOutcome.GetError().c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
AZStd::string hlslSourceCode = hlslSourceCodeOutcome.TakeValue();
// The root ShaderVariantAsset needs to be created with the known uuid of the source .shader asset because
// the ShaderAsset owns a Data::Asset<> reference that gets serialized. It must have the correct uuid
// so the root ShaderVariantAsset is found when the ShaderAsset is deserialized.
uint32_t rootVariantProductSubId = RPI::ShaderAsset2::MakeProductAssetSubId(
shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex,
aznumeric_cast<uint32_t>(RPI::ShaderAsset2ProductSubId::RootShaderVariantAsset));
auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(shaderFullPath, rootVariantProductSubId);
AZ_Assert(assetIdOutcome.IsSuccess(), "Failed to get AssetId from shader %s", shaderFullPath.c_str());
const Data::AssetId variantAssetId = assetIdOutcome.TakeValue();
RPI::ShaderVariantListSourceData::VariantInfo rootVariantInfo;
ShaderVariantCreationContext2 shaderVariantCreationContext = {
*shaderPlatformInterface,
request.m_platformInfo,
buildOptions.m_compilerArguments,
request.m_tempDirPath,
startTime,
shaderSourceData,
*shaderOptionGroupLayout.get(),
shaderEntryPoints,
variantAssetId,
superVariantAzslinStemName,
hlslFullPath,
hlslSourceCode};
AZStd::optional<RHI::ShaderPlatformInterface::ByProducts> outputByproducts;
auto rootShaderVariantAssetOutcome = ShaderVariantAssetBuilder2::CreateShaderVariantAsset(rootVariantInfo, shaderVariantCreationContext, outputByproducts);
if (!rootShaderVariantAssetOutcome.IsSuccess())
{
AZ_Error(ShaderAssetBuilder2Name, false, "%s\n", rootShaderVariantAssetOutcome.GetError().c_str())
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
Data::Asset<RPI::ShaderVariantAsset2> rootShaderVariantAsset = rootShaderVariantAssetOutcome.TakeValue();
shaderAssetCreator.SetRootShaderVariantAsset(rootShaderVariantAsset);
if (!shaderAssetCreator.EndSupervariant())
{
AZ_Error(
ShaderAssetBuilder2Name, false, "Failed to create shader asset for supervariant [%s]", supervariantInfo.m_name.GetCStr())
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
// Time to save the root variant related assets in the cache.
AssetBuilderSDK::JobProduct assetProduct;
if (!ShaderVariantAssetBuilder2::SerializeOutShaderVariantAsset(
rootShaderVariantAsset, superVariantAzslinStemName, request.m_tempDirPath, *shaderPlatformInterface,
rootVariantProductSubId,
assetProduct))
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
response.m_outputProducts.push_back(assetProduct);
if (outputByproducts)
{
// add byproducts as job output products:
uint32_t subProductType = aznumeric_cast<uint32_t>(RPI::ShaderAsset2ProductSubId::FirstByProduct);
for (const AZStd::string& byproduct : outputByproducts.value().m_intermediatePaths)
{
AssetBuilderSDK::JobProduct jobProduct;
jobProduct.m_productFileName = byproduct;
jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt");
jobProduct.m_productSubID = RPI::ShaderAsset2::MakeProductAssetSubId(
shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex,
subProductType++);
response.m_outputProducts.push_back(AZStd::move(jobProduct));
}
}
supervariantIndex++;
} // end for the supervariant
shaderAssetCreator.EndAPI();
} // end for all ShaderPlatformInterfaces
Data::Asset<RPI::ShaderAsset2> shaderAsset;
if (!shaderAssetCreator.End(shaderAsset))
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
if (!SerializeOutShaderAsset(shaderAsset, request.m_tempDirPath, response))
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
const AZStd::sys_time_t endTime = AZStd::GetTimeNowTicks();
const AZStd::sys_time_t deltaTime = endTime - startTime;
const float elapsedTimeSeconds = (float)(deltaTime) / (float)AZStd::GetTimeTicksPerSecond();
AZ_TracePrintf(ShaderAssetBuilder2Name, "Finished processing %s in %.2f seconds\n", request.m_sourceFile.c_str(), elapsedTimeSeconds);
ShaderBuilderUtility::LogProfilingData(ShaderAssetBuilder2Name, shaderFileName);
}
} // ShaderBuilder
} // AZ

@ -0,0 +1,60 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/base.h>
#include <AssetBuilderSDK/AssetBuilderBusses.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <Atom/RHI.Reflect/Base.h>
namespace AZ
{
namespace Data
{
class AssetHandler;
}
namespace RHI
{
class ShaderPlatformInterface;
}
namespace ShaderBuilder
{
struct AzslData;
class ShaderAssetBuilder2
: public AssetBuilderSDK::AssetBuilderCommandBus::Handler
{
public:
AZ_TYPE_INFO(ShaderAssetBuilder2, "{C94DA151-82BC-4475-86FA-E6C92A0BD6F8}");
static constexpr const char* ShaderAssetBuilder2JobKey = "Shader Asset 2";
ShaderAssetBuilder2() = default;
~ShaderAssetBuilder2() = default;
// Asset Builder Callback Functions ...
void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const;
void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const;
// AssetBuilderSDK::AssetBuilderCommandBus interface overrides ...
void ShutDown() override { };
private:
AZ_DISABLE_COPY_MOVE(ShaderAssetBuilder2);
};
} // ShaderBuilder
} // AZ

@ -29,7 +29,8 @@
#include <Atom/RPI.Edit/Common/JsonReportingHelper.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h> // DEPRECATED - [ATOM-15472]
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
#include <Atom/RHI.Edit/Utils.h>
@ -41,13 +42,15 @@
#include "ShaderPlatformInterfaceRequest.h"
#include "AtomShaderConfig.h"
#include "SrgLayoutUtility.h"
namespace AZ
{
namespace ShaderBuilder
{
namespace ShaderBuilderUtility
{
static const char* ShaderBuilderUtilityName = "ShaderBuilderUtility";
static constexpr char ShaderBuilderUtilityName[] = "ShaderBuilderUtility";
Outcome<RPI::ShaderSourceData, AZStd::string> LoadShaderDataJson(const AZStd::string& fullPathToJsonFile)
{
@ -84,22 +87,8 @@ namespace AZ
AzFramework::StringFunc::Path::ReplaceExtension(absoluteAzslPath, "azsl");
}
uint32_t MakeDebugByproductSubId(RHI::APIType apiType, const AZStd::string& productFileName)
{
// bits: ----- 24 -----|- 4 -|- 4 -
// fn hash | id + api | 0xF
uint32_t subId = 0xF; // to avoid collisions with subid of other source outputs using RPI::ShaderAssetSubId::GeneratedSource + api
uint32_t id_api = static_cast<uint32_t>(RPI::ShaderAssetSubId::DebugByProduct);
id_api += apiType;
id_api <<= 4;
subId |= id_api;
size_t fnHash = AZStd::hash<AZStd::string>()(productFileName);
subId |= static_cast<uint32_t>(fnHash) & 0xFFFFFF00;
return subId;
}
static bool LoadShaderResourceGroupAssets(
[[maybe_unused]] const char* BuilderName,
[[maybe_unused]] const char* builderName,
const SrgDataContainer& resourceGroups,
ShaderResourceGroupAssets& srgAssets)
{
@ -121,7 +110,7 @@ namespace AZ
if (!assetFound)
{
AZ_Error(BuilderName, false, "Could not find asset identified by path '%s'", srgFilePath.c_str());
AZ_Error(builderName, false, "Could not find asset identified by path '%s'", srgFilePath.c_str());
readSRGsSuccessfuly = false;
continue;
}
@ -139,7 +128,7 @@ namespace AZ
: asset.GetStatus() == Status::ReadyPreNotify ? "ready-pre-notify"
: asset.GetStatus() == Status::Error ? "error" : "not-loaded/ready/unknown";
AZ_Error(BuilderName, false, "Searching SRG [%s]: Could not load SRG asset. (asset status [%s]) AssetId='%s' Path='%s'",
AZ_Error(builderName, false, "Searching SRG [%s]: Could not load SRG asset. (asset status [%s]) AssetId='%s' Path='%s'",
srgData.m_name.c_str(),
statusString.c_str(),
assetId.ToString<AZStd::string>().c_str(), srgFilePath.c_str());
@ -148,7 +137,7 @@ namespace AZ
}
else if (!asset->IsValid())
{
AZ_Error(BuilderName, false, "SRG asset has no layout information. AssetId='%s' Path='%s'",
AZ_Error(builderName, false, "SRG asset has no layout information. AssetId='%s' Path='%s'",
assetId.ToString<AZStd::string>().c_str(), srgFilePath.c_str());
readSRGsSuccessfuly = false;
continue;
@ -182,8 +171,10 @@ namespace AZ
return files;
}
//! [GFX TODO] [ATOM-15472] Deprecated, remove when this ticket is addressed.
AssetBuilderSDK::ProcessJobResultCode PopulateAzslDataFromJsonFiles(
const char* BuilderName,
const char* builderName,
const AzslSubProducts::Paths& pathOfJsonFiles,
AzslData& azslData,
ShaderResourceGroupAssets& srgAssets,
@ -204,7 +195,7 @@ namespace AZ
outcomes[i] = JsonSerializationUtils::ReadJsonFile(pathOfJsonFiles[i]);
if (!outcomes[i].IsSuccess())
{
AZ_Error(BuilderName, false, "%s", outcomes[i].GetError().c_str());
AZ_Error(builderName, false, "%s", outcomes[i].GetError().c_str());
allReadSuccess = false;
}
}
@ -215,22 +206,22 @@ namespace AZ
// Get full list of functions eligible for vertex shader entry points
// along with metadata for constructing the InputAssembly for each of them
if (!azslc.ParseIaPopulateFunctionData(outcomes[AzslSubProducts::ia].GetValue(), azslData.m_topData.m_functions))
if (!azslc.ParseIaPopulateFunctionData(outcomes[AzslSubProducts::ia].GetValue(), azslData.m_functions))
{
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// Each SRG is built as a separate asset in the SrgLayoutBuilder, here we just
// build the list and load the data from multiple dependency assets.
if (!azslc.ParseSrgPopulateSrgData(outcomes[AzslSubProducts::srg].GetValue(), azslData.m_topData.m_srgData))
if (!azslc.ParseSrgPopulateSrgData(outcomes[AzslSubProducts::srg].GetValue(), azslData.m_srgData))
{
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// Add all Shader Resource Group Assets that were defined in the shader code to the shader asset
if (!LoadShaderResourceGroupAssets(BuilderName, azslData.m_topData.m_srgData, srgAssets))
if (!LoadShaderResourceGroupAssets(builderName, azslData.m_srgData, srgAssets))
{
AZ_Error(BuilderName, false, "Failed to obtain shader resource group assets");
AZ_Error(builderName, false, "Failed to obtain shader resource group assets");
return AssetBuilderSDK::ProcessJobResult_Failed;
}
@ -238,7 +229,7 @@ namespace AZ
// for each option and what is its default value.
if (!azslc.ParseOptionsPopulateOptionGroupLayout(outcomes[AzslSubProducts::options].GetValue(), shaderOptionGroupLayout))
{
AZ_Error(BuilderName, false, "Failed to find a valid list of shader options!");
AZ_Error(builderName, false, "Failed to find a valid list of shader options!");
return AssetBuilderSDK::ProcessJobResult_Failed;
}
@ -246,14 +237,100 @@ namespace AZ
// and informs us on register indexes and shader stages using these resources
if (!azslc.ParseBindingdepPopulateBindingDependencies(outcomes[AzslSubProducts::bindingdep].GetValue(), bindingDependencies)) // consuming data from binding-dep
{
AZ_Error(BuilderName, false, "Failed to obtain shader resource binding reflection");
AZ_Error(builderName, false, "Failed to obtain shader resource binding reflection");
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// access the root constants reflection
if (!azslc.ParseSrgPopulateRootConstantData(outcomes[AzslSubProducts::srg].GetValue(), rootConstantData)) // consuming data from --srg ("InlineConstantBuffer" subjson section)
{
AZ_Error(BuilderName, false, "Failed to obtain root constant data reflection");
AZ_Error(builderName, false, "Failed to obtain root constant data reflection");
return AssetBuilderSDK::ProcessJobResult_Failed;
}
return AssetBuilderSDK::ProcessJobResult_Success;
}
AssetBuilderSDK::ProcessJobResultCode PopulateAzslDataFromJsonFiles(
const char* builderName,
const AzslSubProducts::Paths& pathOfJsonFiles,
const bool platformUsesRegisterSpaces,
AzslData& azslData,
RPI::ShaderResourceGroupLayoutList& srgLayoutList,
RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout,
BindingDependencies& bindingDependencies,
RootConstantData& rootConstantData)
{
AzslCompiler azslc(
azslData
.m_preprocessedFullPath); // set the input file for eventual error messages, but the compiler won't be called on it.
bool allReadSuccess = true;
// read: input assembly reflection
// shader resource group reflection
// options reflection
// binding dependencies reflection
int indicesOfInterest[] = {
AzslSubProducts::ia, AzslSubProducts::srg, AzslSubProducts::options, AzslSubProducts::bindingdep};
AZStd::unordered_map<int, Outcome<rapidjson::Document, AZStd::string>> outcomes;
for (int i : indicesOfInterest)
{
outcomes[i] = JsonSerializationUtils::ReadJsonFile(pathOfJsonFiles[i]);
if (!outcomes[i].IsSuccess())
{
AZ_Error(builderName, false, "%s", outcomes[i].GetError().c_str());
allReadSuccess = false;
}
}
if (!allReadSuccess)
{
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// Get full list of functions eligible for vertex shader entry points
// along with metadata for constructing the InputAssembly for each of them
if (!azslc.ParseIaPopulateFunctionData(outcomes[AzslSubProducts::ia].GetValue(), azslData.m_functions))
{
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// Each SRG is built as a separate asset in the SrgLayoutBuilder, here we just
// build the list and load the data from multiple dependency assets.
if (!azslc.ParseSrgPopulateSrgData(outcomes[AzslSubProducts::srg].GetValue(), azslData.m_srgData))
{
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// Add all Shader Resource Group Assets that were defined in the shader code to the shader asset
if (!SrgLayoutUtility::LoadShaderResourceGroupLayouts(builderName, azslData.m_srgData, platformUsesRegisterSpaces, srgLayoutList))
{
AZ_Error(builderName, false, "Failed to obtain shader resource group assets");
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// The shader options define what options are available, what are the allowed values/range
// for each option and what is its default value.
if (!azslc.ParseOptionsPopulateOptionGroupLayout(outcomes[AzslSubProducts::options].GetValue(), shaderOptionGroupLayout))
{
AZ_Error(builderName, false, "Failed to find a valid list of shader options!");
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// It analyzes the shader external bindings (all SRG contents)
// and informs us on register indexes and shader stages using these resources
if (!azslc.ParseBindingdepPopulateBindingDependencies(
outcomes[AzslSubProducts::bindingdep].GetValue(), bindingDependencies)) // consuming data from binding-dep
{
AZ_Error(builderName, false, "Failed to obtain shader resource binding reflection");
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// access the root constants reflection
if (!azslc.ParseSrgPopulateRootConstantData(
outcomes[AzslSubProducts::srg].GetValue(),
rootConstantData)) // consuming data from --srg ("InlineConstantBuffer" subjson section)
{
AZ_Error(builderName, false, "Failed to obtain root constant data reflection");
return AssetBuilderSDK::ProcessJobResult_Failed;
}
@ -312,7 +389,7 @@ namespace AZ
}
RHI::Ptr<RHI::PipelineLayoutDescriptor> BuildPipelineLayoutDescriptorForApi(
[[maybe_unused]] const char* BuilderName,
[[maybe_unused]] const char* builderName,
RHI::ShaderPlatformInterface* shaderPlatformInterface,
BindingDependencies& bindingDependencies /*inout*/,
const ShaderResourceGroupAssets& srgAssets,
@ -356,7 +433,7 @@ namespace AZ
const BindingDependencies::SrgResources* srgResources = bindingDependencies.GetSrg(srgName);
if (!srgResources)
{
AZ_Error(BuilderName, false, "SRG %s not found in the dependency dataset", srgName.data());
AZ_Error(builderName, false, "SRG %s not found in the dependency dataset", srgName.data());
return nullptr;
}
@ -385,23 +462,21 @@ namespace AZ
for (const auto& constantData : rootConstantData->m_constants)
{
RHI::ShaderInputConstantDescriptor rootConstantDesc(
constantData.m_nameId,
constantData.m_constantByteOffset,
constantData.m_constantByteSize,
constantData.m_nameId, constantData.m_constantByteOffset, constantData.m_constantByteSize,
rootConstantData->m_bindingInfo.m_registerId);
rootConstantsLayout->AddShaderInput(rootConstantDesc);
}
}
if (!rootConstantsLayout->Finalize())
{
AZ_Error(BuilderName, false, "Failed to finalize root constants layout");
AZ_Error(builderName, false, "Failed to finalize root constants layout");
return nullptr;
}
pipelineLayoutDescriptor->SetRootConstantsLayout(*rootConstantsLayout);
RHI::ShaderPlatformInterface::RootConstantsInfo rootConstantInfo;
if (rootConstantData)
{
@ -415,14 +490,15 @@ namespace AZ
rootConstantInfo.m_registerId = dummyRootConstantData.m_bindingInfo.m_registerId;
}
rootConstantInfo.m_totalSizeInBytes = rootConstantsLayout->GetDataSize();
// Build platform-specific PipelineLayoutDescriptor data, and finalize
if (!shaderPlatformInterface->BuildPipelineLayoutDescriptor(pipelineLayoutDescriptor, srgInfos, rootConstantInfo, shaderCompilerArguments))
if (!shaderPlatformInterface->BuildPipelineLayoutDescriptor(
pipelineLayoutDescriptor, srgInfos, rootConstantInfo, shaderCompilerArguments))
{
AZ_Error(BuilderName, false, "Failed to build pipeline layout descriptor");
AZ_Error(builderName, false, "Failed to build pipeline layout descriptor");
return nullptr;
}
return pipelineLayoutDescriptor;
}
@ -442,7 +518,7 @@ namespace AZ
}
else
{
formatted = AZStd::string::format("%s.%s.%s", stemName.c_str(), apiTypeString.c_str(), extension.c_str());
formatted = AZStd::string::format("%s_%s.%s", stemName.c_str(), apiTypeString.c_str(), extension.c_str());
}
AzFramework::StringFunc::Path::Join(dumpDirectory.c_str(), formatted.c_str(), finalFilePath, true, true);
AZ::IO::FileIOStream outFileStream(finalFilePath.data(), AZ::IO::OpenMode::ModeWrite);
@ -463,14 +539,20 @@ namespace AZ
return finalFilePath;
}
AZStd::string DumpPreprocessedCode(const char* builderName, const AZStd::string& preprocessedCode, const AZStd::string& tempDirPath, const AZStd::string& stemName, const AZStd::string& apiTypeString)
// [GFX TODO] Remove 'add2' when [ATOM-15472]
AZStd::string DumpPreprocessedCode(const char* builderName, const AZStd::string& preprocessedCode, const AZStd::string& tempDirPath, const AZStd::string& stemName, const AZStd::string& apiTypeString, bool add2)
{
if (add2)
{
return DumpCode(builderName, preprocessedCode, tempDirPath, stemName, apiTypeString, "azslin2");
}
return DumpCode(builderName, preprocessedCode, tempDirPath, stemName, apiTypeString, "azslin");
}
AZStd::string DumpAzslPrependedCode(const char* builderName, const AZStd::string& nonPreprocessedYetAzslSource, const AZStd::string& tempDirPath, const AZStd::string& stemName, const AZStd::string& apiTypeString)
{
return DumpCode(builderName, nonPreprocessedYetAzslSource, tempDirPath, stemName, apiTypeString, "azsl.prepend");
return DumpCode(builderName, nonPreprocessedYetAzslSource, tempDirPath, stemName, apiTypeString, "azslprepend");
}
AZStd::string ExtractStemName(const char* path)
@ -489,6 +571,83 @@ namespace AZ
return platformInterfaces;
}
AZStd::vector<RHI::ShaderPlatformInterface*> DiscoverEnabledShaderPlatformInterfaces(const AssetBuilderSDK::PlatformInfo& info, const RPI::ShaderSourceData& shaderSourceData)
{
// Request the list of valid shader platform interfaces for the target platform.
AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces;
ShaderPlatformInterfaceRequestBus::BroadcastResult(
platformInterfaces, &ShaderPlatformInterfaceRequest::GetShaderPlatformInterface, info);
// Let's remove the unwanted RHI interfaces from the list.
platformInterfaces.erase(
AZStd::remove_if(AZ_BEGIN_END(platformInterfaces),
[&](const RHI::ShaderPlatformInterface* shaderPlatformInterface) {
return !shaderPlatformInterface ||
shaderSourceData.IsRhiBackendDisabled(shaderPlatformInterface->GetAPIName()) ||
(shaderPlatformInterface->GetAPIUniqueIndex() == static_cast<uint32_t>(AZ::RHI::APIIndex::Null));
}),
platformInterfaces.end());
return platformInterfaces;
}
static bool IsValidSupervariantName(const AZStd::string& supervariantName)
{
return AZStd::all_of(AZ_BEGIN_END(supervariantName),
[](AZStd::string::value_type ch)
{
return AZStd::is_alnum(ch); // allow alpha numeric only
}
);
}
AZStd::vector<RPI::ShaderSourceData::SupervariantInfo> GetSupervariantListFromShaderSourceData(
const RPI::ShaderSourceData& shaderSourceData)
{
AZStd::vector<RPI::ShaderSourceData::SupervariantInfo> supervariants;
supervariants.reserve(shaderSourceData.m_supervariants.size() + 1);
// Add the supervariants, always making sure that:
// 1- The default, nameless, supervariant goes to the front.
// 2- Each supervariant has a unique name
AZStd::unordered_set<AZ::Name> uniqueSuperVariants; // This set helps duplicate detection.
// Although it is not common, it is possible to declare a nameless supervariant.
bool addedNamelessSupervariant = false;
for (const auto& supervariantInfo : shaderSourceData.m_supervariants)
{
if (!IsValidSupervariantName(supervariantInfo.m_name.GetStringView()))
{
AZ_Error(
ShaderBuilderUtilityName, false, "The supervariant name: [%s] contains invalid characters. Only [a-zA-Z0-9] are supported",
supervariantInfo.m_name.GetCStr());
return {}; // Return an empty vector.
}
if (uniqueSuperVariants.count(supervariantInfo.m_name))
{
AZ_Error(
ShaderBuilderUtilityName, false, "It is invalid to specify more than one supervariant with the same name: [%s]",
supervariantInfo.m_name.GetCStr());
return {}; // Return an empty vector.
}
uniqueSuperVariants.emplace(supervariantInfo.m_name);
supervariants.push_back(supervariantInfo);
if (supervariantInfo.m_name.IsEmpty())
{
addedNamelessSupervariant = true;
// Always move the default, nameless, variant to the begining of the list.
AZStd::swap(supervariants.front(), supervariants.back());
}
}
if (!addedNamelessSupervariant)
{
supervariants.push_back({});
// Always move the default, nameless, variant to the begining of the list.
AZStd::swap(supervariants.front(), supervariants.back());
}
return supervariants;
}
static void ReadShaderCompilerProfiling([[maybe_unused]] const char* builderName, RHI::ShaderCompilerProfiling& shaderCompilerProfiling, AZStd::string_view shaderPath)
{
AZStd::string folderPath;
@ -561,12 +720,64 @@ namespace AZ
uint32_t MakeAzslBuildProductSubId(RPI::ShaderAssetSubId subId, RHI::APIType apiType)
{
auto subIdMaxEnumerator = RPI::ShaderAssetSubId::GeneratedSource;
auto subIdMaxEnumerator = RPI::ShaderAssetSubId::GeneratedHlslSource;
// separate bit space between subid enum, and api-type:
int shiftLeft = static_cast<uint32_t>(log2(static_cast<uint32_t>(subIdMaxEnumerator))) + 1;
return static_cast<uint32_t>(subId) + (apiType << shiftLeft);
}
Outcome<AZStd::string, AZStd::string> ObtainBuildArtifactPathFromShaderAssetBuilder2(
const uint32_t rhiUniqueIndex, const AZStd::string& platformIdentifier, const AZStd::string& shaderJsonPath,
const uint32_t supervariantIndex, RPI::ShaderAssetSubId shaderAssetSubId)
{
// platform id from identifier
AzFramework::PlatformId platformId = AzFramework::PlatformId::PC;
if (platformIdentifier == "pc")
{
platformId = AzFramework::PlatformId::PC;
}
else if (platformIdentifier == "osx_gl")
{
platformId = AzFramework::PlatformId::OSX;
}
else if (platformIdentifier == "es3")
{
platformId = AzFramework::PlatformId::ES3;
}
else if (platformIdentifier == "ios")
{
platformId = AzFramework::PlatformId::IOS;
}
uint32_t assetSubId = RPI::ShaderAsset2::MakeProductAssetSubId(rhiUniqueIndex, supervariantIndex, aznumeric_cast<uint32_t>(shaderAssetSubId));
auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(shaderJsonPath, assetSubId);
if (!assetIdOutcome.IsSuccess())
{
return Failure(AZStd::string::format(
"Missing ShaderAssetBuilder2 product %s, for sub %d", shaderJsonPath.c_str(), (uint32_t)shaderAssetSubId));
}
Data::AssetId assetId = assetIdOutcome.TakeValue();
// get the relative path:
AZStd::string assetPath;
Data::AssetCatalogRequestBus::BroadcastResult(assetPath, &Data::AssetCatalogRequests::GetAssetPathById, assetId);
// get the root:
AZStd::string assetRoot = AzToolsFramework::PlatformAddressedAssetCatalog::GetAssetRootForPlatform(platformId);
// join
AZStd::string assetFullPath;
AzFramework::StringFunc::Path::Join(assetRoot.c_str(), assetPath.c_str(), assetFullPath);
bool fileExists = IO::FileIOBase::GetInstance()->Exists(assetFullPath.c_str()) &&
!IO::FileIOBase::GetInstance()->IsDirectory(assetFullPath.c_str());
if (!fileExists)
{
return Failure(AZStd::string::format(
"asset [%s] from shader source %s and subId %d doesn't exist", assetFullPath.c_str(), shaderJsonPath.c_str(),
(uint32_t)shaderAssetSubId));
}
return AZ::Success(assetFullPath);
}
Outcome<AzslSubProducts::Paths> ObtainBuildArtifactsFromAzslBuilder([[maybe_unused]] const char* builderName, const AZStd::string& sourceFullPath, RHI::APIType apiType, const AZStd::string& platform)
{
AzslSubProducts::Paths products;
@ -619,6 +830,7 @@ namespace AZ
return AZ::Success(products);
}
// DEPRECATED [ATOM-15472]
// See header for info.
// REMARK: The approach to string searching and matching done in this function is kind of naive
// because the strings can match text within a comment block, etc. So it is not 100% fool proof.
@ -672,6 +884,399 @@ namespace AZ
return SrgSkipFileResult::ContinueProcess;
}
RHI::Ptr<RHI::PipelineLayoutDescriptor> BuildPipelineLayoutDescriptorForApi(
const char* builderName, const RPI::ShaderResourceGroupLayoutList& srgLayoutList, const MapOfStringToStageType& shaderEntryPoints,
const RHI::ShaderCompilerArguments& shaderCompilerArguments, const RootConstantData& rootConstantData,
RHI::ShaderPlatformInterface* shaderPlatformInterface, BindingDependencies& bindingDependencies /*inout*/)
{
PruneNonEntryFunctions(bindingDependencies, shaderEntryPoints);
// Translates from a list of function names that use a resource to a shader stage mask.
auto getRHIShaderStageMask = [&shaderEntryPoints](const BindingDependencies::FunctionsNameVector& functions) {
RHI::ShaderStageMask mask = RHI::ShaderStageMask::None;
// Iterate through all the functions that are using the resource.
for (const auto& functionName : functions)
{
// Search the function name into the list of valid entry points into the shader.
auto findId =
AZStd::find_if(shaderEntryPoints.begin(), shaderEntryPoints.end(), [&functionName, &mask](const auto& item) {
return item.first == functionName;
});
if (findId != shaderEntryPoints.end())
{
// Use the entry point shader stage type to calculate the mask.
RHI::ShaderHardwareStage hardwareStage = ToAssetBuilderShaderType(findId->second);
mask |= static_cast<RHI::ShaderStageMask>(AZ_BIT(static_cast<uint32_t>(RHI::ToRHIShaderStage(hardwareStage))));
}
}
return mask;
};
// Build general PipelineLayoutDescriptor data that is provided for all platforms
RHI::Ptr<RHI::PipelineLayoutDescriptor> pipelineLayoutDescriptor =
shaderPlatformInterface->CreatePipelineLayoutDescriptor();
RHI::ShaderPlatformInterface::ShaderResourceGroupInfoList srgInfos;
for (const auto& srgLayout : srgLayoutList)
{
// Search the binding info for a Shader Resource Group.
AZStd::string_view srgName = srgLayout->GetName().GetStringView();
const BindingDependencies::SrgResources* srgResources = bindingDependencies.GetSrg(srgName);
if (!srgResources)
{
AZ_Error(builderName, false, "SRG %s not found in the dependency dataset", srgName.data());
return nullptr;
}
RHI::ShaderResourceGroupBindingInfo srgBindingInfo;
srgBindingInfo.m_spaceId = srgResources->m_registerSpace;
const RHI::ShaderResourceGroupLayout* layout = srgLayout.get();
// Calculate the binding in for the constant data. All constant data share the same binding info.
srgBindingInfo.m_constantDataBindingInfo = {
getRHIShaderStageMask(srgResources->m_srgConstantsDependencies.m_binding.m_dependentFunctions),
srgResources->m_srgConstantsDependencies.m_binding.m_registerId};
// Calculate the binding info for each resource of the Shader Resource Group.
for (auto const& resource : srgResources->m_resources)
{
auto const& resourceInfo = resource.second;
srgBindingInfo.m_resourcesRegisterMap.insert(
{AZ::Name(resourceInfo.m_selfName),
RHI::ResourceBindingInfo(
getRHIShaderStageMask(resourceInfo.m_dependentFunctions), resourceInfo.m_registerId)});
}
pipelineLayoutDescriptor->AddShaderResourceGroupLayoutInfo(*layout, srgBindingInfo);
srgInfos.push_back(RHI::ShaderPlatformInterface::ShaderResourceGroupInfo{layout, srgBindingInfo});
}
RHI::Ptr<RHI::ConstantsLayout> rootConstantsLayout = RHI::ConstantsLayout::Create();
for (const auto& constantData : rootConstantData.m_constants)
{
RHI::ShaderInputConstantDescriptor rootConstantDesc(
constantData.m_nameId, constantData.m_constantByteOffset, constantData.m_constantByteSize,
rootConstantData.m_bindingInfo.m_registerId);
rootConstantsLayout->AddShaderInput(rootConstantDesc);
}
if (!rootConstantsLayout->Finalize())
{
AZ_Error(builderName, false, "Failed to finalize root constants layout");
return nullptr;
}
pipelineLayoutDescriptor->SetRootConstantsLayout(*rootConstantsLayout);
RHI::ShaderPlatformInterface::RootConstantsInfo rootConstantInfo;
rootConstantInfo.m_spaceId = rootConstantData.m_bindingInfo.m_space;
rootConstantInfo.m_registerId = rootConstantData.m_bindingInfo.m_registerId;
rootConstantInfo.m_totalSizeInBytes = rootConstantsLayout->GetDataSize();
// Build platform-specific PipelineLayoutDescriptor data, and finalize
if (!shaderPlatformInterface->BuildPipelineLayoutDescriptor(
pipelineLayoutDescriptor, srgInfos, rootConstantInfo, shaderCompilerArguments))
{
AZ_Error(builderName, false, "Failed to build pipeline layout descriptor");
return nullptr;
}
return pipelineLayoutDescriptor;
}
static bool IsSystemValueSemantic(const AZStd::string_view semantic)
{
// https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics#system-value-semantics
return AzFramework::StringFunc::StartsWith(semantic, "sv_", false);
}
static bool CreateShaderInputContract(
const AzslData& azslData,
const AZStd::string& vertexShaderName,
const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout,
const AZStd::string& pathToIaJson,
RPI::ShaderInputContract& contract)
{
StructData inputStruct;
inputStruct.m_id = "";
auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToIaJson);
if (!jsonOutcome.IsSuccess())
{
AZ_Error(ShaderBuilderUtilityName, false, "%s", jsonOutcome.GetError().c_str());
return AssetBuilderSDK::ProcessJobResult_Failed;
}
AzslCompiler azslc(azslData.m_preprocessedFullPath);
if (!azslc.ParseIaPopulateStructData(jsonOutcome.GetValue(), vertexShaderName, inputStruct))
{
AZ_Error(ShaderBuilderUtilityName, false, "Failed to parse input layout\n");
return false;
}
if (inputStruct.m_id.empty())
{
AZ_Error(
ShaderBuilderUtilityName, false, "Failed to find the input struct for vertex shader %s.",
vertexShaderName.c_str());
return false;
}
for (const auto& member : inputStruct.m_members)
{
RHI::ShaderSemantic streamChannelSemantic{Name{member.m_semanticText}, static_cast<uint32_t>(member.m_semanticIndex)};
// Semantics that represent a system-generated value do not map to an input stream
if (IsSystemValueSemantic(streamChannelSemantic.m_name.GetStringView()))
{
continue;
}
contract.m_streamChannels.push_back();
contract.m_streamChannels.back().m_semantic = streamChannelSemantic;
if (member.m_variable.m_typeModifier == MatrixMajor::ColumnMajor)
{
contract.m_streamChannels.back().m_componentCount = member.m_variable.m_cols;
}
else
{
contract.m_streamChannels.back().m_componentCount = member.m_variable.m_rows;
}
// [GFX_TODO][ATOM-14475]: Come up with a more elegant way to mark optional channels and their corresponding shader
// option
static const char OptionalInputStreamPrefix[] = "m_optional_";
if (AzFramework::StringFunc::StartsWith(member.m_variable.m_name, OptionalInputStreamPrefix, true))
{
AZStd::string expectedOptionName = AZStd::string::format(
"o_%s_isBound", member.m_variable.m_name.substr(strlen(OptionalInputStreamPrefix)).c_str());
RPI::ShaderOptionIndex shaderOptionIndex = shaderOptionGroupLayout.FindShaderOptionIndex(Name{expectedOptionName});
if (!shaderOptionIndex.IsValid())
{
AZ_Error(
ShaderBuilderUtilityName, false, "Shader option '%s' not found for optional input stream '%s'",
expectedOptionName.c_str(), member.m_variable.m_name.c_str());
return false;
}
const RPI::ShaderOptionDescriptor& option = shaderOptionGroupLayout.GetShaderOption(shaderOptionIndex);
if (option.GetType() != RPI::ShaderOptionType::Boolean)
{
AZ_Error(ShaderBuilderUtilityName, false, "Shader option '%s' must be a bool.", expectedOptionName.c_str());
return false;
}
if (option.GetDefaultValue().GetStringView() != "false")
{
AZ_Error(
ShaderBuilderUtilityName, false, "Shader option '%s' must default to false.",
expectedOptionName.c_str());
return false;
}
contract.m_streamChannels.back().m_isOptional = true;
contract.m_streamChannels.back().m_streamBoundIndicatorIndex = shaderOptionIndex;
}
}
return true;
}
static bool CreateShaderOutputContract(
const AzslData& azslData,
const AZStd::string& fragmentShaderName,
const AZStd::string& pathToOmJson,
RPI::ShaderOutputContract& contract)
{
StructData outputStruct;
outputStruct.m_id = "";
auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToOmJson);
if (!jsonOutcome.IsSuccess())
{
AZ_Error(ShaderBuilderUtilityName, false, "%s", jsonOutcome.GetError().c_str());
return AssetBuilderSDK::ProcessJobResult_Failed;
}
AzslCompiler azslc(azslData.m_preprocessedFullPath);
if (!azslc.ParseOmPopulateStructData(jsonOutcome.GetValue(), fragmentShaderName, outputStruct))
{
AZ_Error(ShaderBuilderUtilityName, false, "Failed to parse output layout\n");
return false;
}
for (const auto& member : outputStruct.m_members)
{
RHI::ShaderSemantic semantic = RHI::ShaderSemantic::Parse(member.m_semanticText);
bool depthFound = false;
if (semantic.m_name.GetStringView() == "SV_Target")
{
contract.m_requiredColorAttachments.push_back();
// Render targets only support 1-D vector types and those are always column-major (per DXC)
contract.m_requiredColorAttachments.back().m_componentCount = member.m_variable.m_cols;
}
else if (
semantic.m_name.GetStringView() == "SV_Depth" || semantic.m_name.GetStringView() == "SV_DepthGreaterEqual" ||
semantic.m_name.GetStringView() == "SV_DepthLessEqual")
{
if (depthFound)
{
AZ_Error(
ShaderBuilderUtilityName, false,
"SV_Depth specified more than once in the fragment shader output structure");
return false;
}
depthFound = true;
}
else
{
AZ_Error(
ShaderBuilderUtilityName, false, "Unsupported shader output semantic '%s'.", semantic.m_name.GetCStr());
return false;
}
}
return true;
}
bool CreateShaderInputAndOutputContracts(
const AzslData& azslData,
const MapOfStringToStageType& shaderEntryPoints,
const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout,
const AZStd::string& pathToOmJson,
const AZStd::string& pathToIaJson,
RPI::ShaderInputContract& shaderInputContract,
RPI::ShaderOutputContract& shaderOutputContract,
size_t& colorAttachmentCount)
{
bool success = true;
for (const auto& shaderEntryPoint : shaderEntryPoints)
{
auto shaderEntryName = shaderEntryPoint.first;
auto shaderStageType = shaderEntryPoint.second;
if (shaderStageType == RPI::ShaderStageType::Vertex)
{
const bool layoutCreated = CreateShaderInputContract(azslData, shaderEntryName, shaderOptionGroupLayout, pathToIaJson, shaderInputContract);
if (!layoutCreated)
{
success = false;
AZ_Error(
ShaderBuilderUtilityName, false, "Could not create the input contract for the vertex function %s",
shaderEntryName.c_str());
continue; // Using continue to report all the errors found
}
}
if (shaderStageType == RPI::ShaderStageType::Fragment)
{
const bool layoutCreated =
CreateShaderOutputContract(azslData, shaderEntryName, pathToOmJson, shaderOutputContract);
if (!layoutCreated)
{
success = false;
AZ_Error(
ShaderBuilderUtilityName, false, "Could not create the output contract for the fragment function %s",
shaderEntryName.c_str());
continue; // Using continue to report all the errors found
}
colorAttachmentCount = shaderOutputContract.m_requiredColorAttachments.size();
}
}
return success;
}
//! Returns a list of acceptable default entry point names
static void GetAcceptableDefaultEntryPoints(
const AZStd::vector<FunctionData>& azslFunctionDataList,
AZStd::unordered_map<AZStd::string, RPI::ShaderStageType>& defaultEntryPoints)
{
for (const auto& func : azslFunctionDataList)
{
if (!func.m_hasShaderStageVaryings)
{
// Not declaring any semantics for a shader entry is valid, but unusual.
// A shader entry with no semantics must be explicitly listed and won't be selected by default.
continue;
}
if (func.m_name.starts_with("VS") || func.m_name.ends_with("VS"))
{
defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Vertex;
AZ_TracePrintf(
ShaderBuilderUtilityName, "Assuming \"%s\" is a valid Vertex shader entry point.\n", func.m_name.c_str());
}
else if (func.m_name.starts_with("PS") || func.m_name.ends_with("PS"))
{
defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Fragment;
AZ_TracePrintf(
ShaderBuilderUtilityName, "Assuming \"%s\" is a valid Fragment shader entry point.\n",
func.m_name.c_str());
}
else if (func.m_name.starts_with("CS") || func.m_name.ends_with("CS"))
{
defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Compute;
AZ_TracePrintf(
ShaderBuilderUtilityName, "Assuming \"%s\" is a valid Compute shader entry point.\n", func.m_name.c_str());
}
}
}
// DEPRECATED [ATOM-15472
//! Returns a list of acceptable default entry point names
//! This function
static void GetAcceptableDefaultEntryPoints(
const AzslData& azslData, AZStd::unordered_map<AZStd::string, RPI::ShaderStageType>& defaultEntryPoints)
{
return GetAcceptableDefaultEntryPoints(azslData.m_functions, defaultEntryPoints);
}
void GetDefaultEntryPointsFromFunctionDataList(
const AZStd::vector<FunctionData> azslFunctionDataList,
AZStd::unordered_map<AZStd::string, RPI::ShaderStageType>& shaderEntryPoints)
{
AZStd::unordered_map<AZStd::string, RPI::ShaderStageType> defaultEntryPoints;
GetAcceptableDefaultEntryPoints(azslFunctionDataList, defaultEntryPoints);
for (const auto& functionData : azslFunctionDataList)
{
for (const auto& defaultEntryPoint : defaultEntryPoints)
{
// Equal defaults to case insensitive compares...
if (AzFramework::StringFunc::Equal(defaultEntryPoint.first.c_str(), functionData.m_name.c_str()))
{
shaderEntryPoints[defaultEntryPoint.first] = defaultEntryPoint.second;
break; // stop looping default entry points and go to the next shader function
}
}
}
}
AZStd::string GetAcceptableDefaultEntryPointNames(const AzslData& azslData)
{
AZStd::unordered_map<AZStd::string, RPI::ShaderStageType> defaultEntryPointList;
GetAcceptableDefaultEntryPoints(azslData, defaultEntryPointList);
AZStd::vector<AZStd::string> defaultEntryPointNamesList;
for (const auto& shaderEntryPoint : defaultEntryPointList)
{
defaultEntryPointNamesList.push_back(shaderEntryPoint.first);
}
AZStd::string shaderEntryPoints;
AzFramework::StringFunc::Join(
shaderEntryPoints, defaultEntryPointNamesList.begin(), defaultEntryPointNamesList.end(), ", ");
return AZStd::move(shaderEntryPoints);
}
} // namespace ShaderBuilderUtility
} // namespace ShaderBuilder
} // AZ

@ -18,8 +18,10 @@
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <Atom/RPI.Edit/Shader/ShaderSourceData.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderResourceGroupAsset.h>
#include "AzslData.h"
namespace AZ
{
@ -27,7 +29,6 @@ namespace AZ
{
class AzslCompiler;
struct ShaderFiles;
struct AzslData;
struct BindingDependencies;
struct RootConstantData;
@ -40,8 +41,6 @@ namespace AZ
void GetAbsolutePathToAzslFile(const AZStd::string& shaderTemplatePathAndFile, AZStd::string specifiedShaderPathAndName, AZStd::string& absoluteShaderPath);
uint32_t MakeDebugByproductSubId(RHI::APIType apiType, const AZStd::string& productFileName);
//! Opens and read the .shader, returns expanded file paths
AZStd::shared_ptr<ShaderFiles> PrepareSourceInput(
const char* builderName,
@ -54,13 +53,20 @@ namespace AZ
using SubId = RPI::ShaderAssetSubId;
// product sub id enumerators:
static constexpr SubId SubList[] = { SubId::PostPreprocessingPureAzsl, SubId::IaJson, SubId::OmJson, SubId::SrgJson, SubId::OptionsJson, SubId::BindingdepJson, SubId::GeneratedSource };
static constexpr SubId SubList[] = {SubId::PostPreprocessingPureAzsl,
SubId::IaJson,
SubId::OmJson,
SubId::SrgJson,
SubId::OptionsJson,
SubId::BindingdepJson,
SubId::GeneratedHlslSource};
// in the same order, their file name suffix (they replicate what's in AzslcMain.cpp. and hlsl corresponds to what's in AzslBuilder.cpp)
// a type to declare variables holding the full paths of their files
using Paths = AZStd::fixed_vector<AZStd::string, AZ_ARRAY_SIZE(SubList)>;
};
//! [GFX TODO] [ATOM-15472] Deprecated, remove when this ticket is addressed.
//! Collects and generates the necessary data for compiling a shader.
//! @azslData must have paths correctly set.
//! shaderOptionGroupLayout, azslData, srgAssets get the output data.
@ -74,6 +80,16 @@ namespace AZ
RootConstantData& rootConstantData
);
//! Collects all the JSON files generated during AZSL compilation and loads the data as objects.
//! @azslData must have paths correctly set.
//! @azslData, @srgLayoutList, @shaderOptionGroupLayout, @bindingDependencies and @rootConstantData get the output data.
AssetBuilderSDK::ProcessJobResultCode PopulateAzslDataFromJsonFiles(
const char* builderName, const AzslSubProducts::Paths& pathOfJsonFiles,
const bool platformUsesRegisterSpaces, AzslData& azslData,
RPI::ShaderResourceGroupLayoutList& srgLayoutList, RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout,
BindingDependencies& bindingDependencies, RootConstantData& rootConstantData);
RHI::ShaderHardwareStage ToAssetBuilderShaderType(RPI::ShaderStageType stageType);
//! Must be called before shaderPlatformInterface->CompilePlatformInternal()
@ -82,7 +98,7 @@ namespace AZ
//! The pipeline layout descriptor is returned, but the same data will also be set into the @shaderPlatformInterface
//! object, which is why it is important to call this method before calling shaderPlatformInterface->CompilePlatformInternal().
RHI::Ptr<RHI::PipelineLayoutDescriptor> BuildPipelineLayoutDescriptorForApi(
const char* BuilderName,
const char* builderName,
RHI::ShaderPlatformInterface* shaderPlatformInterface,
BindingDependencies& bindingDependencies /*inout*/,
const ShaderResourceGroupAssets& srgAssets,
@ -91,6 +107,33 @@ namespace AZ
const RootConstantData* rootConstantData = nullptr
);
//! Must be called before shaderPlatformInterface->CompilePlatformInternal()
//! This function will prune non entry functions from BindingDependencies and use the
//! rest of input data to create a pipeline layout descriptor.
//! The pipeline layout descriptor is returned, but the same data will also be set into the @shaderPlatformInterface
//! object, which is why it is important to call this method before calling shaderPlatformInterface->CompilePlatformInternal().
RHI::Ptr<RHI::PipelineLayoutDescriptor> BuildPipelineLayoutDescriptorForApi(
const char* builderName,
const RPI::ShaderResourceGroupLayoutList& srgLayoutList,
const MapOfStringToStageType& shaderEntryPoints,
const RHI::ShaderCompilerArguments& shaderCompilerArguments,
const RootConstantData& rootConstantData,
RHI::ShaderPlatformInterface* shaderPlatformInterface,
BindingDependencies& bindingDependencies /*inout*/);
bool CreateShaderInputAndOutputContracts(
const AzslData& azslData, const MapOfStringToStageType& shaderEntryPoints,
const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout, const AZStd::string& pathToOmJson,
const AZStd::string& pathToIaJson, RPI::ShaderInputContract& shaderInputContract,
RPI::ShaderOutputContract& shaderOutputContract, size_t& colorAttachmentCount);
//! Returns a list of acceptable default entry point names as a single string for debug messages.
AZStd::string GetAcceptableDefaultEntryPointNames(const AzslData& shaderData);
//! Create a file from a string's content.
//! That file will be named filename.api.azslin
//! This is meant to be used at this stage:
@ -102,7 +145,8 @@ namespace AZ
const AZStd::string& preprocessedCode,
const AZStd::string& tempDirPath,
const AZStd::string& preprocessedFileName,
const AZStd::string& apiTypeString = "");
const AZStd::string& apiTypeString = "",
bool add2 = false); // [GFX TODO] Remove add2 when [ATOM-15472]
//! Create a file from a string's content.
//! That file will be named filename.api.azsl.prepend
@ -121,12 +165,30 @@ namespace AZ
AZStd::string ExtractStemName(const char* path);
AZStd::vector<RHI::ShaderPlatformInterface*> DiscoverValidShaderPlatformInterfaces(const AssetBuilderSDK::PlatformInfo& info);
AZStd::vector<RHI::ShaderPlatformInterface*> DiscoverEnabledShaderPlatformInterfaces(
const AssetBuilderSDK::PlatformInfo& info, const RPI::ShaderSourceData& shaderSourceData);
// The idea is that the "Supervariants" json property is optional in .shader files,
// For cases when it is not specified, this function will return a vector with one item, the default, nameless, supervariant.
// If "Supervariants" is not empty, then this function will make sure the first supervariant in the list
// is the default, nameless, supervariant.
AZStd::vector<RPI::ShaderSourceData::SupervariantInfo> GetSupervariantListFromShaderSourceData(
const RPI::ShaderSourceData& shaderSourceData);
void GetDefaultEntryPointsFromFunctionDataList(
const AZStd::vector<FunctionData> azslFunctionDataList,
AZStd::unordered_map<AZStd::string, RPI::ShaderStageType>& shaderEntryPoints);
void LogProfilingData(const char* builderName, AZStd::string_view shaderPath);
//! Job products sub id generation helper for AzslBuilder
uint32_t MakeAzslBuildProductSubId(RPI::ShaderAssetSubId subId, RHI::APIType apiType);
//! Returns the asset path of a product artifact produced by ShaderAssetBuilder2.
Outcome<AZStd::string, AZStd::string> ObtainBuildArtifactPathFromShaderAssetBuilder2(
const uint32_t rhiUniqueIndex, const AZStd::string& platformIdentifier, const AZStd::string& shaderJsonPath,
const uint32_t supervariantIndex, RPI::ShaderAssetSubId shaderAssetSubId);
//! Reconstructs the expected output product paths of the AzslBuilder (from the 2 arguments @azslSourceFullPath and @apiType)
Outcome<AzslSubProducts::Paths> ObtainBuildArtifactsFromAzslBuilder(const char* builderName, const AZStd::string& azslSourceFullPath, RHI::APIType apiType, const AZStd::string& platform);

@ -276,12 +276,6 @@ namespace AZ
return LoadResult{LoadResult::Code::DeferredError, AZStd::string::format("ShaderSourceData file does not exist: %s.", shaderSourceFileFullPath.c_str())};
}
// Let's open the shader source, because We need the source code of its AZSL file
auto outcomeShaderData = ShaderBuilderUtility::LoadShaderDataJson(shaderSourceFileFullPath);
if (!outcomeShaderData.IsSuccess())
{
return LoadResult{LoadResult::Code::DeferredError, AZStd::string::format("Failed to parse Shader Descriptor JSON: %s", outcomeShaderData.GetError().c_str())};
}
return LoadResult{LoadResult::Code::Success};
} // LoadShaderVariantListAndAzslSource
@ -420,15 +414,6 @@ namespace AZ
return;
}
if (jobParameters.find(ShouldExitEarlyFromProcessJobParam) != jobParameters.end())
{
AZ_TracePrintf(
ShaderVariantAssetBuilderName, "Doing nothing on behalf of [%s] because it's been overriden by game project.",
jobParameters.at(ShaderVariantLoadErrorParam).c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
return;
}
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
if (jobCancelListener.IsCancelled())
{
@ -589,7 +574,7 @@ namespace AZ
if (shaderSourceDataDescriptor.m_programSettings.m_entryPoints.empty())
{
AZ_TracePrintf(ShaderVariantAssetBuilderName, "ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n");
ShaderVariantAssetBuilder::GetDefaultEntryPointsFromAzslData(azslData, shaderEntryPoints);
ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslData.m_functions, shaderEntryPoints);
}
else
{
@ -778,7 +763,7 @@ namespace AZ
}
// Time to save the asset in the cache tmp folder.
const uint32_t productSubID = RPI::ShaderVariantAsset::GetAssetSubId(shaderPlatformInterface->GetAPIUniqueIndex(), shaderVariantAsset->GetStableId());
const uint32_t productSubID = RPI::ShaderVariantAsset::MakeAssetProductSubId(shaderPlatformInterface->GetAPIUniqueIndex(), shaderVariantAsset->GetStableId());
AssetBuilderSDK::JobProduct assetProduct;
if (!SerializeOutShaderVariantAsset(shaderVariantAsset, shaderSourceFileFullPath, request.m_tempDirPath, *shaderPlatformInterface, productSubID, assetProduct))
{
@ -788,12 +773,14 @@ namespace AZ
response.m_outputProducts.push_back(assetProduct);
// add byproducts as job output products:
uint32_t subProductType = aznumeric_cast<uint32_t>(RPI::ShaderAssetSubId::GeneratedHlslSource) + 1;
for (const AZStd::string& byproduct : byproducts.m_intermediatePaths)
{
AssetBuilderSDK::JobProduct jobProduct;
jobProduct.m_productFileName = byproduct;
jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt");
jobProduct.m_productSubID = ShaderBuilderUtility::MakeDebugByproductSubId(shaderPlatformInterface->GetAPIType(), byproduct);
jobProduct.m_productSubID = RPI::ShaderVariantAsset::MakeAssetProductSubId(
shaderPlatformInterface->GetAPIType(), shaderVariantAsset->GetStableId(), subProductType++);
response.m_outputProducts.push_back(AZStd::move(jobProduct));
}
}
@ -801,53 +788,6 @@ namespace AZ
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
}
/// Returns a list of acceptable default entry point names
static void GetAcceptableDefaultEntryPoints(const AzslData& shaderData, AZStd::unordered_map<AZStd::string, RPI::ShaderStageType>& defaultEntryPoints)
{
for (const auto& func : shaderData.m_topData.m_functions)
{
if (!func.m_hasShaderStageVaryings)
{
// Not declaring any semantics for a shader entry is valid, but unusual.
// A shader entry with no semantics must be explicitly listed and won't be selected by default.
continue;
}
if (func.m_name.starts_with("VS") || func.m_name.ends_with("VS"))
{
defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Vertex;
AZ_TracePrintf(ShaderVariantAssetBuilderName, "Assuming \"%s\" is a valid Vertex shader entry point.\n", func.m_name.c_str());
}
else if (func.m_name.starts_with("PS") || func.m_name.ends_with("PS"))
{
defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Fragment;
AZ_TracePrintf(ShaderVariantAssetBuilderName, "Assuming \"%s\" is a valid Fragment shader entry point.\n", func.m_name.c_str());
}
else if (func.m_name.starts_with("CS") || func.m_name.ends_with("CS"))
{
defaultEntryPoints[func.m_name] = RPI::ShaderStageType::Compute;
AZ_TracePrintf(ShaderVariantAssetBuilderName, "Assuming \"%s\" is a valid Compute shader entry point.\n", func.m_name.c_str());
}
}
}
/// Returns a list of acceptable default entry point names as a single string for messages
static AZStd::string GetAcceptableDefaultEntryPointNames(const AzslData& shaderData)
{
AZStd::unordered_map<AZStd::string, RPI::ShaderStageType> defaultEntryPointList;
GetAcceptableDefaultEntryPoints(shaderData, defaultEntryPointList);
AZStd::vector<AZStd::string> defaultEntryPointNamesList;
for (const auto& shaderEntryPoint : defaultEntryPointList)
{
defaultEntryPointNamesList.push_back(shaderEntryPoint.first);
}
AZStd::string shaderEntryPoints;
AzFramework::StringFunc::Join(shaderEntryPoints, defaultEntryPointNamesList.begin(), defaultEntryPointNamesList.end(), ", ");
return AZStd::move(shaderEntryPoints);
}
static bool CreateShaderVariant(
ShaderVariantCreationContext& variantCreationContext,
const AzslData& azslData,
@ -945,7 +885,7 @@ namespace AZ
if (!hasRasterProgram && !hasComputeProgram && !hasRayTracingProgram)
{
AZStd::string entryPointNames = GetAcceptableDefaultEntryPointNames(azslData);
AZStd::string entryPointNames = ShaderBuilderUtility::GetAcceptableDefaultEntryPointNames(azslData);
AZ_Error(ShaderVariantAssetBuilderName, false, "Shader asset descriptor has a program variant that does not define any entry points. Either declare entry points in the .shader file, or use one of the available default names (not case-sensitive): [%s]", entryPointNames.data());
@ -990,198 +930,6 @@ namespace AZ
return isVariantValid;
}
static bool IsSystemValueSemantic(const AZStd::string_view semantic)
{
// https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics#system-value-semantics
return AzFramework::StringFunc::StartsWith(semantic, "sv_", false);
}
static bool CreateShaderInputContract(
const AzslData& azslData,
const AZStd::string& vertexShaderName,
const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout,
RPI::ShaderInputContract& contract,
const AZStd::string& pathToIaJson)
{
StructData inputStruct;
inputStruct.m_id = "";
auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToIaJson);
if (!jsonOutcome.IsSuccess())
{
AZ_Error(ShaderVariantAssetBuilderName, false, "%s", jsonOutcome.GetError().c_str());
return AssetBuilderSDK::ProcessJobResult_Failed;
}
AzslCompiler azslc(azslData.m_preprocessedFullPath);
if (!azslc.ParseIaPopulateStructData(jsonOutcome.GetValue(), vertexShaderName, inputStruct))
{
AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to parse input layout\n");
return false;
}
if (inputStruct.m_id.empty())
{
AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to find the input struct for vertex shader %s.", vertexShaderName.c_str());
return false;
}
for (const auto& member : inputStruct.m_members)
{
RHI::ShaderSemantic streamChannelSemantic{
Name{ member.m_semanticText },
static_cast<uint32_t>(member.m_semanticIndex) };
// Semantics that represent a system-generated value do not map to an input stream
if (IsSystemValueSemantic(streamChannelSemantic.m_name.GetStringView()))
{
continue;
}
contract.m_streamChannels.push_back();
contract.m_streamChannels.back().m_semantic = streamChannelSemantic;
if (member.m_variable.m_typeModifier == MatrixMajor::ColumnMajor)
{
contract.m_streamChannels.back().m_componentCount = member.m_variable.m_cols;
}
else
{
contract.m_streamChannels.back().m_componentCount = member.m_variable.m_rows;
}
// [GFX_TODO][ATOM-14475]: Come up with a more elegant way to mark optional channels and their corresponding shader option
static const char OptionalInputStreamPrefix[] = "m_optional_";
if (AzFramework::StringFunc::StartsWith(member.m_variable.m_name, OptionalInputStreamPrefix, true))
{
AZStd::string expectedOptionName = AZStd::string::format("o_%s_isBound", member.m_variable.m_name.substr(strlen(OptionalInputStreamPrefix)).c_str());
RPI::ShaderOptionIndex shaderOptionIndex = shaderOptionGroupLayout.FindShaderOptionIndex(Name{expectedOptionName});
if (!shaderOptionIndex.IsValid())
{
AZ_Error(ShaderVariantAssetBuilderName, false, "Shader option '%s' not found for optional input stream '%s'", expectedOptionName.c_str(), member.m_variable.m_name.c_str());
return false;
}
const RPI::ShaderOptionDescriptor& option = shaderOptionGroupLayout.GetShaderOption(shaderOptionIndex);
if (option.GetType() != RPI::ShaderOptionType::Boolean)
{
AZ_Error(ShaderVariantAssetBuilderName, false, "Shader option '%s' must be a bool.", expectedOptionName.c_str());
return false;
}
if (option.GetDefaultValue().GetStringView() != "false")
{
AZ_Error(ShaderVariantAssetBuilderName, false, "Shader option '%s' must default to false.", expectedOptionName.c_str());
return false;
}
contract.m_streamChannels.back().m_isOptional = true;
contract.m_streamChannels.back().m_streamBoundIndicatorIndex = shaderOptionIndex;
}
}
return true;
}
static bool CreateShaderOutputContract(
const AzslData& azslData,
const AZStd::string& fragmentShaderName,
RPI::ShaderOutputContract& contract,
const AZStd::string& pathToOmJson)
{
StructData outputStruct;
outputStruct.m_id = "";
auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToOmJson);
if (!jsonOutcome.IsSuccess())
{
AZ_Error(ShaderVariantAssetBuilderName, false, "%s", jsonOutcome.GetError().c_str());
return AssetBuilderSDK::ProcessJobResult_Failed;
}
AzslCompiler azslc(azslData.m_preprocessedFullPath);
if (!azslc.ParseOmPopulateStructData(jsonOutcome.GetValue(), fragmentShaderName, outputStruct))
{
AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to parse output layout\n");
return false;
}
for (const auto& member : outputStruct.m_members)
{
RHI::ShaderSemantic semantic = RHI::ShaderSemantic::Parse(member.m_semanticText);
bool depthFound = false;
if (semantic.m_name.GetStringView() == "SV_Target")
{
contract.m_requiredColorAttachments.push_back();
// Render targets only support 1-D vector types and those are always column-major (per DXC)
contract.m_requiredColorAttachments.back().m_componentCount = member.m_variable.m_cols;
}
else if (semantic.m_name.GetStringView() == "SV_Depth" ||
semantic.m_name.GetStringView() == "SV_DepthGreaterEqual" ||
semantic.m_name.GetStringView() == "SV_DepthLessEqual")
{
if (depthFound)
{
AZ_Error(ShaderVariantAssetBuilderName, false, "SV_Depth specified more than once in the fragment shader output structure");
return false;
}
depthFound = true;
}
else
{
AZ_Error(ShaderVariantAssetBuilderName, false, "Unsupported shader output semantic '%s'.", semantic.m_name.GetCStr());
return false;
}
}
return true;
}
static bool CreateShaderInputAndOutputContracts(
const AzslData& azslData,
const MapOfStringToStageType& shaderEntryPoints,
const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout,
RPI::ShaderInputContract& shaderInputContract,
RPI::ShaderOutputContract& shaderOutputContract,
size_t& colorAttachmentCount,
const AZStd::string& pathToOmJson,
const AZStd::string& pathToIaJson)
{
bool success = true;
for (const auto& shaderEntryPoint : shaderEntryPoints)
{
auto shaderEntryName = shaderEntryPoint.first;
auto shaderStageType = shaderEntryPoint.second;
if (shaderStageType == RPI::ShaderStageType::Vertex)
{
const bool layoutCreated = CreateShaderInputContract(azslData, shaderEntryName, shaderOptionGroupLayout, shaderInputContract, pathToIaJson);
if (!layoutCreated)
{
success = false;
AZ_Error(ShaderVariantAssetBuilderName, false, "Could not create the input contract for the vertex function %s", shaderEntryName.c_str());
continue; // Using continue to report all the errors found
}
}
if (shaderStageType == RPI::ShaderStageType::Fragment)
{
const bool layoutCreated = CreateShaderOutputContract(azslData, shaderEntryName, shaderOutputContract, pathToOmJson);
if (!layoutCreated)
{
success = false;
AZ_Error(ShaderVariantAssetBuilderName, false, "Could not create the output contract for the fragment function %s", shaderEntryName.c_str());
continue; // Using continue to report all the errors found
}
colorAttachmentCount = shaderOutputContract.m_requiredColorAttachments.size();
}
}
return success;
}
AZ::Outcome<Data::Asset<RPI::ShaderVariantAsset>, AZStd::string> ShaderVariantAssetBuilder::CreateShaderVariantAssetForAPI(
const RPI::ShaderVariantListSourceData::VariantInfo& variantInfo,
@ -1195,8 +943,8 @@ namespace AZ
RPI::ShaderInputContract shaderInputContract;
RPI::ShaderOutputContract shaderOutputContract;
size_t colorAttachmentCount = 0;
CreateShaderInputAndOutputContracts(azslData, variantCreationContext.m_shaderEntryPoints, variantCreationContext.m_shaderOptionGroupLayout,
shaderInputContract, shaderOutputContract, colorAttachmentCount, pathToOmJson, pathToIaJson);
ShaderBuilderUtility::CreateShaderInputAndOutputContracts(azslData, variantCreationContext.m_shaderEntryPoints, variantCreationContext.m_shaderOptionGroupLayout, pathToOmJson,
pathToIaJson, shaderInputContract, shaderOutputContract, colorAttachmentCount);
const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout = variantCreationContext.m_shaderOptionGroupLayout;
// Temporary structure used for sorting and caching intermediate results
@ -1284,25 +1032,6 @@ namespace AZ
return AZ::Success(AZStd::move(shaderVariantAsset));
}
void ShaderVariantAssetBuilder::GetDefaultEntryPointsFromAzslData(const AzslData& shaderData, AZStd::unordered_map<AZStd::string, RPI::ShaderStageType>& shaderEntryPoints)
{
AZStd::unordered_map<AZStd::string, RPI::ShaderStageType> defaultEntryPoints;
GetAcceptableDefaultEntryPoints(shaderData, defaultEntryPoints);
for (const auto& functionData : shaderData.m_topData.m_functions)
{
for (const auto& defaultEntryPoint : defaultEntryPoints)
{
// Equal defaults to case insensitive compares...
if (AzFramework::StringFunc::Equal(defaultEntryPoint.first.c_str(), functionData.m_name.c_str()))
{
shaderEntryPoints[defaultEntryPoint.first] = defaultEntryPoint.second;
break; // stop looping default entry points and go to the next shader function
}
}
}
}
bool ShaderVariantAssetBuilder::SerializeOutShaderVariantAsset(const Data::Asset<RPI::ShaderVariantAsset> shaderVariantAsset, const AZStd::string& shaderSourceFileFullPath, const AZStd::string& tempDirPath,
const RHI::ShaderPlatformInterface& shaderPlatformInterface, const uint32_t productSubID, AssetBuilderSDK::JobProduct& assetProduct)
{

@ -73,8 +73,6 @@ namespace AZ
static bool SerializeOutShaderVariantAsset(const Data::Asset<RPI::ShaderVariantAsset> shaderVariantAsset, const AZStd::string& shaderFullPath, const AZStd::string& tempDirPath,
const RHI::ShaderPlatformInterface& shaderPlatformInterface, const uint32_t productSubID, AssetBuilderSDK::JobProduct& assetProduct);
static void GetDefaultEntryPointsFromAzslData(const AzslData& shaderData, AZStd::unordered_map<AZStd::string, RPI::ShaderStageType>& shaderEntryPoints);
// AssetBuilderSDK::AssetBuilderCommandBus interface overrides ...
void ShutDown() override { };

@ -0,0 +1,978 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*/
#include <ShaderVariantAssetBuilder2.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantTreeAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
#include <Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h>
#include <Atom/RPI.Edit/Shader/ShaderVariantAssetCreator2.h>
#include <Atom/RPI.Edit/Shader/ShaderVariantTreeAssetCreator.h>
#include <Atom/RPI.Edit/Common/JsonUtils.h>
#include <AtomCore/Serialization/Json/JsonUtils.h>
#include <Atom/RPI.Reflect/Shader/ShaderResourceGroupAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantKey.h>
#include <Atom/RHI.Edit/Utils.h>
#include <Atom/RHI.Edit/ShaderPlatformInterface.h>
#include <Atom/RPI.Edit/Common/JsonReportingHelper.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RHI.Reflect/ConstantsLayout.h>
#include <Atom/RHI.Reflect/PipelineLayoutDescriptor.h>
#include <Atom/RHI.Reflect/ShaderStageFunction.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/Platform/PlatformDefaults.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/JSON/document.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/IOUtils.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/sort.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include "ShaderAssetBuilder2.h"
#include "ShaderBuilderUtility.h"
#include "AzslData.h"
#include "AzslCompiler.h"
#include "AzslBuilder.h"
#include <CommonFiles/Preprocessor.h>
#include <CommonFiles/GlobalBuildOptions.h>
#include <ShaderPlatformInterfaceRequest.h>
#include "AtomShaderConfig.h"
namespace AZ
{
namespace ShaderBuilder
{
static constexpr char ShaderVariantAssetBuilder2Name[] = "ShaderVariantAssetBuilder2";
static void AddShaderAssetJobDependency2(
AssetBuilderSDK::JobDescriptor& jobDescriptor, const AssetBuilderSDK::PlatformInfo& platformInfo,
const AZStd::string& shaderVariantListFilePath, const AZStd::string& shaderFilePath)
{
AZStd::vector<AZStd::string> possibleDependencies =
AZ::RPI::AssetUtils::GetPossibleDepenencyPaths(shaderVariantListFilePath, shaderFilePath);
for (auto& file : possibleDependencies)
{
AssetBuilderSDK::JobDependency jobDependency;
jobDependency.m_jobKey = ShaderAssetBuilder2::ShaderAssetBuilder2JobKey;
jobDependency.m_platformIdentifier = platformInfo.m_identifier;
jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order;
jobDependency.m_sourceFile.m_sourceFileDependencyPath = file;
jobDescriptor.m_jobDependencyList.push_back(jobDependency);
}
}
//! Returns true if @sourceFileFullPath starts with a valid asset processor scan folder, false otherwise.
//! In case of true, it splits @sourceFileFullPath into @scanFolderFullPath and @filePathFromScanFolder.
//! @sourceFileFullPath The full path to a source asset file.
//! @scanFolderFullPath [out] Gets the full path of the scan folder where the source file is located.
//! @filePathFromScanFolder [out] Get the file path relative to @scanFolderFullPath.
static bool SplitSourceAssetPathIntoScanFolderFullPathAndRelativeFilePath2(const AZStd::string& sourceFileFullPath, AZStd::string& scanFolderFullPath, AZStd::string& filePathFromScanFolder)
{
AZStd::vector<AZStd::string> scanFolders;
bool success = false;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(success, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetAssetSafeFolders, scanFolders);
if (!success)
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Couldn't get the scan folders");
return false;
}
for (AZStd::string scanFolder : scanFolders)
{
AzFramework::StringFunc::Path::Normalize(scanFolder);
if (!AZ::StringFunc::StartsWith(sourceFileFullPath, scanFolder))
{
continue;
}
const size_t scanFolderSize = scanFolder.size();
const size_t sourcePathSize = sourceFileFullPath.size();
scanFolderFullPath = scanFolder;
filePathFromScanFolder = sourceFileFullPath.substr(scanFolderSize + 1, sourcePathSize - scanFolderSize - 1);
return true;
}
return false;
}
//! Validates if a given .shadervariantlist file is located at the correct path for a given .shader full path.
//! There are two valid paths:
//! 1- Lower Precedence: The same folder where the .shader file is located.
//! 2- Higher Precedence: <DEVROOT>/<GAME>/ShaderVariants/<Same Scan Folder Subpath as the .shader file>.
//! The "Higher Precedence" path gives the option to game projects to override what variants to generate. If this
//! file exists then the "Lower Precedence" path is disregarded.
//! A .shader full path is located under an AP scan folder.
//! Example: "<DEVROOT>/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.shader"
//! - In this example the Scan Folder is "<DEVROOT>/Gems/Atom/Feature/Common/Assets", while the subfolder is "Materials/Types".
//! The "Higher Precedence" expected valid location for the .shadervariantlist would be:
//! - <DEVROOT>/<GameProject>/ShaderVariants/Materials/Types/StandardPBR_ForwardPass.shadervariantlist.
//! The "Lower Precedence" valid location would be:
//! - <DEVROOT>/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.shadervariantlist.
//! @shouldExitEarlyFromProcessJob [out] Set to true if ProcessJob should do no work but return successfully.
//! Set to false if ProcessJob should do work and create assets.
//! When @shaderVariantListFileFullPath is provided by a Gem/Feature instead of the Game Project
//! We check if the game project already defined the shader variant list, and if it did it means
//! ProcessJob should do no work, but return successfully nonetheless.
static bool ValidateShaderVariantListLocation2(const AZStd::string& shaderVariantListFileFullPath,
const AZStd::string& shaderFileFullPath, bool& shouldExitEarlyFromProcessJob)
{
AZStd::string scanFolderFullPath;
AZStd::string shaderProductFileRelativePath;
if (!SplitSourceAssetPathIntoScanFolderFullPathAndRelativeFilePath2(shaderFileFullPath, scanFolderFullPath, shaderProductFileRelativePath))
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Couldn't get the scan folder for shader [%s]", shaderFileFullPath.c_str());
return false;
}
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "For shader [%s], Scan folder full path [%s], relative file path [%s]", shaderFileFullPath.c_str(), scanFolderFullPath.c_str(), shaderProductFileRelativePath.c_str());
AZStd::string shaderVariantListFileRelativePath = shaderProductFileRelativePath;
AzFramework::StringFunc::Path::ReplaceExtension(shaderVariantListFileRelativePath, RPI::ShaderVariantListSourceData::Extension);
const char * gameProjectPath = nullptr;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(gameProjectPath, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetAbsoluteDevGameFolderPath);
AZStd::string expectedHigherPrecedenceFileFullPath;
AzFramework::StringFunc::Path::Join(gameProjectPath, RPI::ShaderVariantTreeAsset::CommonSubFolder, expectedHigherPrecedenceFileFullPath, false /* handle directory overlap? */, false /* be case insensitive? */);
AzFramework::StringFunc::Path::Join(expectedHigherPrecedenceFileFullPath.c_str(), shaderProductFileRelativePath.c_str(), expectedHigherPrecedenceFileFullPath, false /* handle directory overlap? */, false /* be case insensitive? */);
AzFramework::StringFunc::Path::ReplaceExtension(expectedHigherPrecedenceFileFullPath, AZ::RPI::ShaderVariantListSourceData::Extension);
AzFramework::StringFunc::Path::Normalize(expectedHigherPrecedenceFileFullPath);
AZStd::string normalizedShaderVariantListFileFullPath = shaderVariantListFileFullPath;
AzFramework::StringFunc::Path::Normalize(normalizedShaderVariantListFileFullPath);
if (expectedHigherPrecedenceFileFullPath == normalizedShaderVariantListFileFullPath)
{
// Whenever the Game Project declares a *.shadervariantlist file we always do work.
shouldExitEarlyFromProcessJob = false;
return true;
}
AZ::Data::AssetInfo assetInfo;
AZStd::string watchFolder;
bool foundHigherPrecedenceAsset = false;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(foundHigherPrecedenceAsset
, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourcePath
, expectedHigherPrecedenceFileFullPath.c_str(), assetInfo, watchFolder);
if (foundHigherPrecedenceAsset)
{
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "The shadervariantlist [%s] has been overriden by the game project with [%s]",
normalizedShaderVariantListFileFullPath.c_str(), expectedHigherPrecedenceFileFullPath.c_str());
shouldExitEarlyFromProcessJob = true;
return true;
}
// Check the "Lower Precedence" case, .shader path == .shadervariantlist path.
AZStd::string normalizedShaderFileFullPath = shaderFileFullPath;
AzFramework::StringFunc::Path::Normalize(normalizedShaderFileFullPath);
AZStd::string normalizedShaderFileFullPathWithoutExtension = normalizedShaderFileFullPath;
AzFramework::StringFunc::Path::StripExtension(normalizedShaderFileFullPathWithoutExtension);
AZStd::string normalizedShaderVariantListFileFullPathWithoutExtension = normalizedShaderVariantListFileFullPath;
AzFramework::StringFunc::Path::StripExtension(normalizedShaderVariantListFileFullPathWithoutExtension);
#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
//In certain circumstances, the capitalization of the drive letter may not match
const bool caseSensitive = false;
#else
//On the other platforms there's no drive letter, so it should be a non-issue.
const bool caseSensitive = true;
#endif
if (!StringFunc::Equal(normalizedShaderFileFullPathWithoutExtension.c_str(), normalizedShaderVariantListFileFullPathWithoutExtension.c_str(), caseSensitive))
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "For shader file at path [%s], the shader variant list [%s] is expected to be located at [%s.%s] or [%s]"
, normalizedShaderFileFullPath.c_str(), normalizedShaderVariantListFileFullPath.c_str(),
normalizedShaderFileFullPathWithoutExtension.c_str(), RPI::ShaderVariantListSourceData::Extension,
expectedHigherPrecedenceFileFullPath.c_str());
return false;
}
shouldExitEarlyFromProcessJob = false;
return true;
}
// We treat some issues as warnings and return "Success" from CreateJobs allows us to report the dependency.
// If/when a valid dependency file appears, that will trigger the ShaderVariantAssetBuilder2 to run again.
// Since CreateJobs will pass, we forward this message to ProcessJob which will report it as an error.
struct LoadResult2
{
enum class Code
{
Error,
DeferredError,
Success
};
Code m_code;
AZStd::string m_deferredMessage; // Only used when m_code == DeferredError
};
static LoadResult2 LoadShaderVariantList2(const AZStd::string& variantListFullPath, RPI::ShaderVariantListSourceData& shaderVariantList, AZStd::string& shaderSourceFileFullPath,
bool& shouldExitEarlyFromProcessJob)
{
// Need to get the name of the shader file from the template so that we can preprocess the shader data and setup
// source file dependencies.
if (!RPI::JsonUtils::LoadObjectFromFile(variantListFullPath, shaderVariantList))
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to parse Shader Variant List Descriptor JSON from [%s]", variantListFullPath.c_str());
return LoadResult2{LoadResult2::Code::Error};
}
const AZStd::string resolvedShaderPath = AZ::RPI::AssetUtils::ResolvePathReference(variantListFullPath, shaderVariantList.m_shaderFilePath);
if (!AZ::IO::LocalFileIO::GetInstance()->Exists(resolvedShaderPath.c_str()))
{
return LoadResult2{LoadResult2::Code::DeferredError, AZStd::string::format("The shader path [%s] was not found.", resolvedShaderPath.c_str())};
}
shaderSourceFileFullPath = resolvedShaderPath;
if (!ValidateShaderVariantListLocation2(variantListFullPath, shaderSourceFileFullPath, shouldExitEarlyFromProcessJob))
{
return LoadResult2{LoadResult2::Code::Error};
}
if (shouldExitEarlyFromProcessJob)
{
return LoadResult2{LoadResult2::Code::Success};
}
auto resultOutcome = RPI::ShaderVariantTreeAssetCreator::ValidateStableIdsAreUnique(shaderVariantList.m_shaderVariants);
if (!resultOutcome.IsSuccess())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Variant info validation error: %s", resultOutcome.GetError().c_str());
return LoadResult2{LoadResult2::Code::Error};
}
if (!IO::FileIOBase::GetInstance()->Exists(shaderSourceFileFullPath.c_str()))
{
return LoadResult2{LoadResult2::Code::DeferredError, AZStd::string::format("ShaderSourceData file does not exist: %s.", shaderSourceFileFullPath.c_str())};
}
return LoadResult2{LoadResult2::Code::Success};
} // LoadShaderVariantListAndAzslSource
void ShaderVariantAssetBuilder2::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const
{
AZStd::string variantListFullPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), variantListFullPath, true);
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "CreateJobs for Shader Variant List \"%s\"\n", variantListFullPath.data());
RPI::ShaderVariantListSourceData shaderVariantList;
AZStd::string shaderSourceFileFullPath;
bool shouldExitEarlyFromProcessJob = false;
const LoadResult2 loadResult = LoadShaderVariantList2(variantListFullPath, shaderVariantList, shaderSourceFileFullPath, shouldExitEarlyFromProcessJob);
if (loadResult.m_code == LoadResult2::Code::Error)
{
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Failed;
return;
}
if (loadResult.m_code == LoadResult2::Code::DeferredError || shouldExitEarlyFromProcessJob)
{
for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms)
{
// Let's create fake jobs that will fail ProcessJob, but are useful to establish dependency on the shader file.
AssetBuilderSDK::JobDescriptor jobDescriptor;
jobDescriptor.m_priority = -5000;
jobDescriptor.m_critical = false;
jobDescriptor.m_jobKey = ShaderVariantAssetBuilder2JobKey;
jobDescriptor.SetPlatformIdentifier(info.m_identifier.data());
AddShaderAssetJobDependency2(jobDescriptor, info, variantListFullPath, shaderVariantList.m_shaderFilePath);
if (loadResult.m_code == LoadResult2::Code::DeferredError)
{
jobDescriptor.m_jobParameters.emplace(ShaderVariantLoadErrorParam, loadResult.m_deferredMessage);
}
if (shouldExitEarlyFromProcessJob)
{
// The value doesn't matter, what matters is the presence of the key which will
// signal that no assets should be produced on behalf of this shadervariantlist because
// the game project overrode it.
jobDescriptor.m_jobParameters.emplace(ShouldExitEarlyFromProcessJobParam, variantListFullPath);
}
response.m_createJobOutputs.push_back(jobDescriptor);
}
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
return;
}
for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms)
{
AZ_TraceContext("For platform", info.m_identifier.data());
// First job is for the ShaderVariantTreeAsset.
{
AssetBuilderSDK::JobDescriptor jobDescriptor;
// The ShaderVariantTreeAsset is high priority, but must be generated after the ShaderAsset
jobDescriptor.m_priority = 1;
jobDescriptor.m_critical = false;
jobDescriptor.m_jobKey = GetShaderVariantTreeAssetJobKey();
jobDescriptor.SetPlatformIdentifier(info.m_identifier.data());
AddShaderAssetJobDependency2(jobDescriptor, info, variantListFullPath, shaderVariantList.m_shaderFilePath);
jobDescriptor.m_jobParameters.emplace(ShaderSourceFilePathJobParam, shaderSourceFileFullPath);
response.m_createJobOutputs.push_back(jobDescriptor);
}
// One job for each variant. Each job will produce one ".azshadervariant" per RHI per supervariant.
for (const AZ::RPI::ShaderVariantListSourceData::VariantInfo& variantInfo : shaderVariantList.m_shaderVariants)
{
AZStd::string variantInfoAsJsonString;
const bool convertSuccess = AZ::RPI::JsonUtils::SaveObjectToJsonString(variantInfo, variantInfoAsJsonString);
AZ_Assert(convertSuccess, "Failed to convert VariantInfo to json string");
AssetBuilderSDK::JobDescriptor jobDescriptor;
// There can be tens/hundreds of thousands of shader variants. By default each shader will get
// a root variant that can be used at runtime. In order to prevent the AssetProcessor from
// being overtaken by shader variant compilation We mark all non-root shader variant generation
// as non critical and very low priority.
jobDescriptor.m_priority = -5000;
jobDescriptor.m_critical = false;
jobDescriptor.m_jobKey = GetShaderVariantAssetJobKey(RPI::ShaderVariantStableId{variantInfo.m_stableId});
jobDescriptor.SetPlatformIdentifier(info.m_identifier.data());
// The ShaderVariantAssets are job dependent on the ShaderVariantTreeAsset.
AssetBuilderSDK::SourceFileDependency fileDependency;
fileDependency.m_sourceFileDependencyPath = variantListFullPath;
AssetBuilderSDK::JobDependency variantTreeJobDependency;
variantTreeJobDependency.m_jobKey = GetShaderVariantTreeAssetJobKey();
variantTreeJobDependency.m_platformIdentifier = info.m_identifier;
variantTreeJobDependency.m_sourceFile = fileDependency;
variantTreeJobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order;
jobDescriptor.m_jobDependencyList.emplace_back(variantTreeJobDependency);
jobDescriptor.m_jobParameters.emplace(ShaderVariantJobVariantParam, variantInfoAsJsonString);
jobDescriptor.m_jobParameters.emplace(ShaderSourceFilePathJobParam, shaderSourceFileFullPath);
response.m_createJobOutputs.push_back(jobDescriptor);
}
}
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
} // CreateJobs
void ShaderVariantAssetBuilder2::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
{
const auto& jobParameters = request.m_jobDescription.m_jobParameters;
if (jobParameters.find(ShaderVariantLoadErrorParam) != jobParameters.end())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Error during CreateJobs: %s", jobParameters.at(ShaderVariantLoadErrorParam).c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
if (jobParameters.find(ShouldExitEarlyFromProcessJobParam) != jobParameters.end())
{
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Doing nothing on behalf of [%s] because it's been overridden by game project.", jobParameters.at(ShaderVariantLoadErrorParam).c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
return;
}
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
if (jobCancelListener.IsCancelled())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
if (request.m_jobDescription.m_jobKey == GetShaderVariantTreeAssetJobKey())
{
ProcessShaderVariantTreeJob(request, response);
}
else
{
ProcessShaderVariantJob(request, response);
}
}
static RPI::Ptr<RPI::ShaderOptionGroupLayout> LoadShaderOptionsGroupLayoutFromShaderAssetBuilder2(
const RHI::ShaderPlatformInterface* shaderPlatformInterface,
const AssetBuilderSDK::PlatformInfo& platformInfo,
const AzslCompiler& azslCompiler,
const AZStd::string& shaderSourceFileFullPath,
const RPI::SupervariantIndex supervariantIndex)
{
auto optionsGroupPathOutcome = ShaderBuilderUtility::ObtainBuildArtifactPathFromShaderAssetBuilder2(
shaderPlatformInterface->GetAPIUniqueIndex(), platformInfo.m_identifier, shaderSourceFileFullPath, supervariantIndex.GetIndex(),
AZ::RPI::ShaderAssetSubId::OptionsJson);
if (!optionsGroupPathOutcome.IsSuccess())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", optionsGroupPathOutcome.GetError().c_str());
return nullptr;
}
auto optionsGroupJsonPath = optionsGroupPathOutcome.TakeValue();
RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout = RPI::ShaderOptionGroupLayout::Create();
// The shader options define what options are available, what are the allowed values/range
// for each option and what is its default value.
auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(optionsGroupJsonPath);
if (!jsonOutcome.IsSuccess())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", jsonOutcome.GetError().c_str());
return nullptr;
}
if (!azslCompiler.ParseOptionsPopulateOptionGroupLayout(jsonOutcome.GetValue(), shaderOptionGroupLayout))
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to find a valid list of shader options!");
return nullptr;
}
return shaderOptionGroupLayout;
}
static void LoadShaderFunctionsFromShaderAssetBuilder2(
const RHI::ShaderPlatformInterface* shaderPlatformInterface, const AssetBuilderSDK::PlatformInfo& platformInfo,
const AzslCompiler& azslCompiler, const AZStd::string& shaderSourceFileFullPath,
const RPI::SupervariantIndex supervariantIndex,
AzslFunctions& functions)
{
auto functionsJsonPathOutcome = ShaderBuilderUtility::ObtainBuildArtifactPathFromShaderAssetBuilder2(
shaderPlatformInterface->GetAPIUniqueIndex(), platformInfo.m_identifier, shaderSourceFileFullPath, supervariantIndex.GetIndex(),
AZ::RPI::ShaderAssetSubId::IaJson);
if (!functionsJsonPathOutcome.IsSuccess())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", functionsJsonPathOutcome.GetError().c_str());
return;
}
auto functionsJsonPath = functionsJsonPathOutcome.TakeValue();
auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(functionsJsonPath);
if (!jsonOutcome.IsSuccess())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", jsonOutcome.GetError().c_str());
return;
}
if (!azslCompiler.ParseIaPopulateFunctionData(jsonOutcome.GetValue(), functions))
{
functions.clear();
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to find shader functions.");
return;
}
}
// Returns the content of the hlsl file for the given supervariant as produced by ShaderAsssetBuilder2.
// In addition to the content it also returns the full path of the hlsl file in @hlslSourcePath.
static AZStd::string LoadHlslFileFromShaderAssetBuilder2(
const RHI::ShaderPlatformInterface* shaderPlatformInterface, const AssetBuilderSDK::PlatformInfo& platformInfo,
const AZStd::string& shaderSourceFileFullPath, const RPI::SupervariantIndex supervariantIndex, AZStd::string& hlslSourcePath)
{
auto hlslSourcePathOutcome = ShaderBuilderUtility::ObtainBuildArtifactPathFromShaderAssetBuilder2(
shaderPlatformInterface->GetAPIUniqueIndex(), platformInfo.m_identifier, shaderSourceFileFullPath, supervariantIndex.GetIndex(),
AZ::RPI::ShaderAssetSubId::GeneratedHlslSource);
if (!hlslSourcePathOutcome.IsSuccess())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", hlslSourcePathOutcome.GetError().c_str());
return "";
}
hlslSourcePath = hlslSourcePathOutcome.TakeValue();
Outcome<AZStd::string, AZStd::string> hlslSourceOutcome = Utils::ReadFile(hlslSourcePath);
if (!hlslSourceOutcome.IsSuccess())
{
AZ_Error(
ShaderVariantAssetBuilder2Name, false, "Failed to obtain shader source from %s. [%s]", hlslSourcePath.c_str(),
hlslSourceOutcome.TakeError().c_str());
return "";
}
return hlslSourceOutcome.TakeValue();
}
void ShaderVariantAssetBuilder2::ProcessShaderVariantTreeJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
{
AZStd::string variantListFullPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), variantListFullPath, true);
RPI::ShaderVariantListSourceData shaderVariantListDescriptor;
if (!RPI::JsonUtils::LoadObjectFromFile(variantListFullPath, shaderVariantListDescriptor))
{
AZ_Assert(false, "Failed to parse Shader Variant List Descriptor JSON [%s]", variantListFullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
const AZStd::string& shaderSourceFileFullPath = request.m_jobDescription.m_jobParameters.at(ShaderSourceFilePathJobParam);
//For debugging purposes will create a dummy azshadervarianttree file.
AZStd::string shaderName;
AzFramework::StringFunc::Path::GetFileName(shaderSourceFileFullPath.c_str(), shaderName);
// No error checking because the same calls were already executed during CreateJobs()
auto descriptorParseOutcome = ShaderBuilderUtility::LoadShaderDataJson(shaderSourceFileFullPath);
RPI::ShaderSourceData shaderSourceDescriptor = descriptorParseOutcome.TakeValue();
RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout;
// Request the list of valid shader platform interfaces for the target platform.
AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces =
ShaderBuilderUtility::DiscoverEnabledShaderPlatformInterfaces(request.m_platformInfo, shaderSourceDescriptor);
if (platformInterfaces.empty())
{
// No work to do. Exit gracefully.
AZ_TracePrintf(
ShaderVariantAssetBuilder2Name,
"No azshadervarianttree is produced on behalf of %s because all valid RHI backends were disabled for this shader.\n",
shaderSourceFileFullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
return;
}
// set the input file for eventual error messages, but the compiler won't be called on it.
AZStd::string azslFullPath;
ShaderBuilderUtility::GetAbsolutePathToAzslFile(shaderSourceFileFullPath, shaderSourceDescriptor.m_source, azslFullPath);
AzslCompiler azslc(azslFullPath);
AZStd::string previousLoopApiName;
for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces)
{
auto thisLoopApiName = shaderPlatformInterface->GetAPIName().GetStringView();
RPI::Ptr<RPI::ShaderOptionGroupLayout> loopLocal_ShaderOptionGroupLayout =
LoadShaderOptionsGroupLayoutFromShaderAssetBuilder2(
shaderPlatformInterface, request.m_platformInfo, azslc, shaderSourceFileFullPath, RPI::DefaultSupervariantIndex);
if (!loopLocal_ShaderOptionGroupLayout)
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
if (shaderOptionGroupLayout && shaderOptionGroupLayout->GetHash() != loopLocal_ShaderOptionGroupLayout->GetHash())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "There was a discrepancy in shader options between %s and %s", previousLoopApiName.c_str(), thisLoopApiName.data());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
shaderOptionGroupLayout = loopLocal_ShaderOptionGroupLayout;
previousLoopApiName = thisLoopApiName;
}
RPI::ShaderVariantTreeAssetCreator shaderVariantTreeAssetCreator;
shaderVariantTreeAssetCreator.Begin(Uuid::CreateRandom());
shaderVariantTreeAssetCreator.SetShaderOptionGroupLayout(*shaderOptionGroupLayout);
shaderVariantTreeAssetCreator.SetVariantInfos(shaderVariantListDescriptor.m_shaderVariants);
Data::Asset<RPI::ShaderVariantTreeAsset> shaderVariantTreeAsset;
if (!shaderVariantTreeAssetCreator.End(shaderVariantTreeAsset))
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to build Shader Variant Tree Asset");
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
AZStd::string filename = AZStd::string::format("%s.%s", shaderName.c_str(), RPI::ShaderVariantTreeAsset::Extension);
AZStd::string assetPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_tempDirPath.c_str(), filename.c_str(), assetPath, true);
if (!AZ::Utils::SaveObjectToFile(assetPath, AZ::DataStream::ST_BINARY, shaderVariantTreeAsset.Get()))
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to save Shader Variant Tree Asset to \"%s\"", assetPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
AssetBuilderSDK::JobProduct assetProduct;
assetProduct.m_productSubID = RPI::ShaderVariantTreeAsset::ProductSubID;
assetProduct.m_productFileName = assetPath;
assetProduct.m_productAssetType = azrtti_typeid<RPI::ShaderVariantTreeAsset>();
assetProduct.m_dependenciesHandled = true; // This builder has no dependencies to output
response.m_outputProducts.push_back(assetProduct);
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Shader Variant Tree Asset [%s] compiled successfully.\n", assetPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
}
void ShaderVariantAssetBuilder2::ProcessShaderVariantJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
{
const AZStd::sys_time_t startTime = AZStd::GetTimeNowTicks();
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
AZStd::string fullPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullPath, true);
const auto& jobParameters = request.m_jobDescription.m_jobParameters;
const AZStd::string& shaderSourceFileFullPath = jobParameters.at(ShaderSourceFilePathJobParam);
AZStd::string shaderFileName;
AzFramework::StringFunc::Path::GetFileName(shaderSourceFileFullPath.c_str(), shaderFileName);
const AZStd::string& variantJsonString = jobParameters.at(ShaderVariantJobVariantParam);
RPI::ShaderVariantListSourceData::VariantInfo variantInfo;
const bool fromJsonStringSuccess = AZ::RPI::JsonUtils::LoadObjectFromJsonString(variantJsonString, variantInfo);
AZ_Assert(fromJsonStringSuccess, "Failed to convert json string to VariantInfo");
RPI::ShaderSourceData shaderSourceDescriptor;
AZStd::shared_ptr<ShaderFiles> sources = ShaderBuilderUtility::PrepareSourceInput(ShaderVariantAssetBuilder2Name, shaderSourceFileFullPath, shaderSourceDescriptor);
// set the input file for eventual error messages, but the compiler won't be called on it.
AzslCompiler azslc(sources->m_azslSourceFullPath);
// Request the list of valid shader platform interfaces for the target platform.
AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces =
ShaderBuilderUtility::DiscoverEnabledShaderPlatformInterfaces(request.m_platformInfo, shaderSourceDescriptor);
if (platformInterfaces.empty())
{
// No work to do. Exit gracefully.
AZ_TracePrintf(ShaderVariantAssetBuilder2Name,
"No azshader is produced on behalf of %s because all valid RHI backends were disabled for this shader.\n",
shaderSourceFileFullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
return;
}
auto supervariantList = ShaderBuilderUtility::GetSupervariantListFromShaderSourceData(shaderSourceDescriptor);
GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderVariantAssetBuilder2Name);
// At this moment We have global build options that should be merged with the build options that are common
// to all the supervariants of this shader.
buildOptions.m_compilerArguments.Merge(shaderSourceDescriptor.m_compiler);
//! The ShaderOptionGroupLayout is common across all RHIs & Supervariants
RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout = nullptr;
// Generate shaders for each of those ShaderPlatformInterfaces.
for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces)
{
AZ_TraceContext("ShaderPlatformInterface", shaderPlatformInterface->GetAPIName().GetCStr());
// Loop through all the Supervariants.
uint32_t supervariantIndexCounter = 0;
for (const auto& supervariantInfo : supervariantList)
{
RPI::SupervariantIndex supervariantIndex(supervariantIndexCounter);
// Check if we were canceled before we do any heavy processing of
// the shader variant data.
if (jobCancelListener.IsCancelled())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
AZStd::string shaderStemNamePrefix = shaderFileName;
if (supervariantIndex.GetIndex() > 0)
{
shaderStemNamePrefix += supervariantInfo.m_name.GetStringView();
}
// We need these additional pieces of information To build a shader variant asset:
// 1- ShaderOptionsGroupLayout (Need to load it once, because it's the same acrosss all supervariants + RHIs)
// 2- entryFunctions
// 3- hlsl code.
// 1- ShaderOptionsGroupLayout
if (!shaderOptionGroupLayout)
{
shaderOptionGroupLayout =
LoadShaderOptionsGroupLayoutFromShaderAssetBuilder2(
shaderPlatformInterface, request.m_platformInfo, azslc, shaderSourceFileFullPath, supervariantIndex);
if (!shaderOptionGroupLayout)
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
}
// 2- entryFunctions.
AzslFunctions azslFunctions;
LoadShaderFunctionsFromShaderAssetBuilder2(
shaderPlatformInterface, request.m_platformInfo, azslc, shaderSourceFileFullPath, supervariantIndex, azslFunctions);
if (azslFunctions.empty())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
MapOfStringToStageType shaderEntryPoints;
if (shaderSourceDescriptor.m_programSettings.m_entryPoints.empty())
{
AZ_TracePrintf(
ShaderVariantAssetBuilder2Name,
"ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n");
ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslFunctions, shaderEntryPoints);
}
else
{
for (const auto& entryPoint : shaderSourceDescriptor.m_programSettings.m_entryPoints)
{
shaderEntryPoints[entryPoint.m_name] = entryPoint.m_type;
}
}
// 3- hlslCode
AZStd::string hlslSourcePath;
AZStd::string hlslCode = LoadHlslFileFromShaderAssetBuilder2(
shaderPlatformInterface, request.m_platformInfo, shaderSourceFileFullPath, supervariantIndex, hlslSourcePath);
if (hlslCode.empty() || hlslSourcePath.empty())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
// Setup the shader variant creation context:
ShaderVariantCreationContext2 shaderVariantCreationContext =
{
*shaderPlatformInterface, request.m_platformInfo, buildOptions.m_compilerArguments, request.m_tempDirPath,
startTime,
shaderSourceDescriptor,
*shaderOptionGroupLayout.get(),
shaderEntryPoints,
Uuid::CreateRandom(),
shaderStemNamePrefix,
hlslSourcePath, hlslCode
};
AZStd::optional<RHI::ShaderPlatformInterface::ByProducts> outputByproducts;
auto shaderVariantAssetOutcome = CreateShaderVariantAsset(variantInfo, shaderVariantCreationContext, outputByproducts);
if (!shaderVariantAssetOutcome.IsSuccess())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s\n", shaderVariantAssetOutcome.GetError().c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
Data::Asset<RPI::ShaderVariantAsset2> shaderVariantAsset = shaderVariantAssetOutcome.TakeValue();
// Time to save the asset in the tmp folder so it ends up in the Cache folder.
const uint32_t productSubID = RPI::ShaderVariantAsset2::MakeAssetProductSubId(
shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex.GetIndex(),
shaderVariantAsset->GetStableId());
AssetBuilderSDK::JobProduct assetProduct;
if (!SerializeOutShaderVariantAsset(shaderVariantAsset, shaderStemNamePrefix,
request.m_tempDirPath, *shaderPlatformInterface, productSubID,
assetProduct))
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
response.m_outputProducts.push_back(assetProduct);
if (outputByproducts)
{
// add byproducts as job output products:
uint32_t subProductType = RPI::ShaderVariantAsset2::ShaderVariantAsset2SubProductType;
for (const AZStd::string& byproduct : outputByproducts.value().m_intermediatePaths)
{
AssetBuilderSDK::JobProduct jobProduct;
jobProduct.m_productFileName = byproduct;
jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt");
jobProduct.m_productSubID = RPI::ShaderVariantAsset2::MakeAssetProductSubId(
shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex.GetIndex(), shaderVariantAsset->GetStableId(),
subProductType++);
response.m_outputProducts.push_back(AZStd::move(jobProduct));
}
}
supervariantIndexCounter++;
} // End of supervariant for block
}
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
}
bool ShaderVariantAssetBuilder2::SerializeOutShaderVariantAsset(
const Data::Asset<RPI::ShaderVariantAsset2> shaderVariantAsset, const AZStd::string& shaderStemNamePrefix,
const AZStd::string& tempDirPath,
const RHI::ShaderPlatformInterface& shaderPlatformInterface, const uint32_t productSubID, AssetBuilderSDK::JobProduct& assetProduct)
{
AZStd::string filename = AZStd::string::format(
"%s_%s_%u.%s", shaderStemNamePrefix.c_str(), shaderPlatformInterface.GetAPIName().GetCStr(),
shaderVariantAsset->GetStableId().GetIndex(), RPI::ShaderVariantAsset2::Extension);
AZStd::string assetPath;
AzFramework::StringFunc::Path::ConstructFull(tempDirPath.c_str(), filename.c_str(), assetPath, true);
if (!AZ::Utils::SaveObjectToFile(assetPath, AZ::DataStream::ST_BINARY, shaderVariantAsset.Get()))
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to save Shader Variant Asset to \"%s\"", assetPath.c_str());
return false;
}
assetProduct.m_productSubID = productSubID;
assetProduct.m_productFileName = assetPath;
assetProduct.m_productAssetType = azrtti_typeid<RPI::ShaderVariantAsset2>();
assetProduct.m_dependenciesHandled = true; // This builder has no dependencies to output
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Shader Variant Asset [%s] compiled successfully.\n", assetPath.c_str());
return true;
}
AZ::Outcome<Data::Asset<RPI::ShaderVariantAsset2>, AZStd::string> ShaderVariantAssetBuilder2::CreateShaderVariantAsset(
const RPI::ShaderVariantListSourceData::VariantInfo& shaderVariantInfo,
ShaderVariantCreationContext2& creationContext,
AZStd::optional<RHI::ShaderPlatformInterface::ByProducts>& outputByproducts)
{
// Temporary structure used for sorting and caching intermediate results
struct OptionCache
{
AZ::Name m_optionName;
AZ::Name m_valueName;
RPI::ShaderOptionIndex m_optionIndex; // Cached m_optionName
RPI::ShaderOptionValue m_value; // Cached m_valueName
};
AZStd::vector<OptionCache> optionList;
// We can not have more options than the number of options in the layout:
optionList.reserve(creationContext.m_shaderOptionGroupLayout.GetShaderOptionCount());
// This loop will validate and cache the indices for each option value:
for (const auto& shaderOption : shaderVariantInfo.m_options)
{
Name optionName{shaderOption.first};
Name optionValue{shaderOption.second};
RPI::ShaderOptionIndex optionIndex = creationContext.m_shaderOptionGroupLayout.FindShaderOptionIndex(optionName);
if (optionIndex.IsNull())
{
return AZ::Failure(AZStd::string::format("Invalid shader option: %s", optionName.GetCStr()));
}
const RPI::ShaderOptionDescriptor& option = creationContext.m_shaderOptionGroupLayout.GetShaderOption(optionIndex);
RPI::ShaderOptionValue value = option.FindValue(optionValue);
if (value.IsNull())
{
return AZ::Failure(
AZStd::string::format("Invalid value (%s) for shader option: %s", optionValue.GetCStr(), optionName.GetCStr()));
}
optionList.push_back(OptionCache{optionName, optionValue, optionIndex, value});
}
// Create one instance of the shader variant
RPI::ShaderOptionGroup optionGroup(&creationContext.m_shaderOptionGroupLayout);
//! Contains the series of #define macro values that define a variant. Can be empty (root variant).
//! If this string is NOT empty, a new temporary hlsl file will be created that will be the combination
//! of this string + @m_hlslSourceContent.
AZStd::string hlslCodeToPrependForVariant;
// We want to go over all options listed in the variant and set their respective values
// This loop will populate the optionGroup and m_shaderCodePrefix in order of the option priority
for (const auto& optionCache : optionList)
{
const RPI::ShaderOptionDescriptor& option = creationContext.m_shaderOptionGroupLayout.GetShaderOption(optionCache.m_optionIndex);
// Assign the option value specified in the variant:
option.Set(optionGroup, optionCache.m_value);
// Populate all shader option defines. We have already confirmed they're valid.
hlslCodeToPrependForVariant += AZStd::string::format(
"#define %s_OPTION_DEF %s\n", optionCache.m_optionName.GetCStr(), optionCache.m_valueName.GetCStr());
}
AZStd::string variantShaderSourcePath;
// Check if we need to prepend any code prefix
if (!hlslCodeToPrependForVariant.empty())
{
// Prepend any shader code prefix that we should apply to this variant
// and save it back to a file.
AZStd::string variantShaderSourceString(hlslCodeToPrependForVariant);
variantShaderSourceString += creationContext.m_hlslSourceContent;
AZStd::string shaderAssetName = AZStd::string::format(
"%s_%s_%u.hlsl", creationContext.m_shaderStemNamePrefix.c_str(),
creationContext.m_shaderPlatformInterface.GetAPIName().GetCStr(), shaderVariantInfo.m_stableId);
AzFramework::StringFunc::Path::Join(
creationContext.m_tempDirPath.c_str(), shaderAssetName.c_str(), variantShaderSourcePath, true, true);
auto outcome = Utils::WriteFile(variantShaderSourceString, variantShaderSourcePath);
if (!outcome.IsSuccess())
{
return AZ::Failure(AZStd::string::format("Failed to create file %s", variantShaderSourcePath.c_str()));
}
}
else
{
variantShaderSourcePath = creationContext.m_hlslSourcePath;
}
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Variant StableId: %u", shaderVariantInfo.m_stableId);
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Variant Shader Options: %s", optionGroup.ToString().c_str());
const RPI::ShaderVariantStableId shaderVariantStableId{shaderVariantInfo.m_stableId};
// By this time the optionGroup was populated with all option values for the variant and
// the m_shaderCodePrefix contains all option related preprocessing macros
// Let's add the requested variant:
RPI::ShaderVariantAssetCreator2 variantCreator;
RPI::ShaderOptionGroup shaderOptions{&creationContext.m_shaderOptionGroupLayout, optionGroup.GetShaderVariantId()};
variantCreator.Begin(
creationContext.m_shaderVariantAssetId, optionGroup.GetShaderVariantId(), shaderVariantStableId,
shaderOptions.IsFullySpecified());
const AZStd::unordered_map<AZStd::string, RPI::ShaderStageType>& shaderEntryPoints = creationContext.m_shaderEntryPoints;
for (const auto& shaderEntryPoint : shaderEntryPoints)
{
auto shaderEntryName = shaderEntryPoint.first;
auto shaderStageType = shaderEntryPoint.second;
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Entry Point: %s", shaderEntryName.c_str());
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Begin compiling shader function \"%s\"", shaderEntryName.c_str());
auto assetBuilderShaderType = ShaderBuilderUtility::ToAssetBuilderShaderType(shaderStageType);
// Compile HLSL to the platform specific shader.
RHI::ShaderPlatformInterface::StageDescriptor descriptor;
bool shaderWasCompiled = creationContext.m_shaderPlatformInterface.CompilePlatformInternal(
creationContext.m_platformInfo, variantShaderSourcePath, shaderEntryName, assetBuilderShaderType,
creationContext.m_tempDirPath, descriptor, creationContext.m_shaderCompilerArguments);
if (!shaderWasCompiled)
{
return AZ::Failure(AZStd::string::format("Could not compile the shader function %s", shaderEntryName.c_str()));
}
// bubble up the byproducts to the caller by moving them to the context.
outputByproducts.emplace(AZStd::move(descriptor.m_byProducts));
RHI::Ptr<RHI::ShaderStageFunction> shaderStageFunction = creationContext.m_shaderPlatformInterface.CreateShaderStageFunction(descriptor);
variantCreator.SetShaderFunction(ToRHIShaderStage(assetBuilderShaderType), shaderStageFunction);
if (descriptor.m_byProducts.m_dynamicBranchCount != AZ::RHI::ShaderPlatformInterface::ByProducts::UnknownDynamicBranchCount)
{
AZ_TracePrintf(
ShaderVariantAssetBuilder2Name, "Finished compiling shader function. Number of dynamic branches: %u",
descriptor.m_byProducts.m_dynamicBranchCount);
}
else
{
AZ_TracePrintf(
ShaderVariantAssetBuilder2Name, "Finished compiling shader function. Number of dynamic branches: unknown");
}
}
Data::Asset<RPI::ShaderVariantAsset2> shaderVariantAsset;
variantCreator.End(shaderVariantAsset);
return AZ::Success(AZStd::move(shaderVariantAsset));
}
} // ShaderBuilder
} // AZ

@ -0,0 +1,107 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/base.h>
#include <AssetBuilderSDK/AssetBuilderBusses.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <Atom/RHI.Reflect/Base.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <Atom/RPI.Edit/Shader/ShaderSourceData.h>
#include <Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h>
#include "ShaderBuilderUtility.h"
namespace AZ
{
namespace ShaderBuilder
{
struct AzslData;
//! This is nothing more than a class to help consolidate all
//! the data needed to generate a shader variant and prevent
//! all the functions involved in the process to have too many
//! arguments.
struct ShaderVariantCreationContext2
{
RHI::ShaderPlatformInterface& m_shaderPlatformInterface;
const AssetBuilderSDK::PlatformInfo& m_platformInfo;
const RHI::ShaderCompilerArguments& m_shaderCompilerArguments;
//! Used to write temporary files during shader compilation, like *.hlsl, or *.air, or *.metallib, etc.
const AZStd::string& m_tempDirPath;
//! Used to synchronize versions of the ShaderAsset and ShaderVariantAsset,
//! especially during hot-reload. A (ShaderVariantAsset.timestamp) >= (ShaderAsset.timestamp).
const AZStd::sys_time_t m_assetBuildTimestamp;
const RPI::ShaderSourceData& m_shaderSourceDataDescriptor;
const RPI::ShaderOptionGroupLayout& m_shaderOptionGroupLayout;
const MapOfStringToStageType& m_shaderEntryPoints;
const Data::AssetId m_shaderVariantAssetId;
const AZStd::string& m_shaderStemNamePrefix; //<shaderName>-<supervariantName>
const AZStd::string& m_hlslSourcePath;
const AZStd::string& m_hlslSourceContent;
};
class ShaderVariantAssetBuilder2
: public AssetBuilderSDK::AssetBuilderCommandBus::Handler
{
public:
AZ_TYPE_INFO(ShaderVariantAssetBuilder2, "{C959AEC2-2083-4488-AD88-F61B1144535B}");
static constexpr char ShaderVariantAssetBuilder2JobKey[] = "Shader Variant Asset 2";
ShaderVariantAssetBuilder2() = default;
~ShaderVariantAssetBuilder2() = default;
// Asset Builder Callback Functions ...
void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const;
void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const;
//! The ShaderVariantAsset returned by this function won't be written to the filesystem.
//! You should call SerializeOutShaderVariantAsset to write it to the temp folder assigned
//! by the asset processor.
static AZ::Outcome<Data::Asset<RPI::ShaderVariantAsset2>, AZStd::string> CreateShaderVariantAsset(
const RPI::ShaderVariantListSourceData::VariantInfo& shaderVariantInfo,
ShaderVariantCreationContext2& creationContext,
AZStd::optional<RHI::ShaderPlatformInterface::ByProducts>& outputByproducts);
static bool SerializeOutShaderVariantAsset(
const Data::Asset<RPI::ShaderVariantAsset2> shaderVariantAsset,
const AZStd::string& shaderStemNamePrefix, const AZStd::string& tempDirPath,
const RHI::ShaderPlatformInterface& shaderPlatformInterface, const uint32_t productSubID, AssetBuilderSDK::JobProduct& assetProduct);
// AssetBuilderSDK::AssetBuilderCommandBus interface overrides ...
void ShutDown() override { };
private:
AZ_DISABLE_COPY_MOVE(ShaderVariantAssetBuilder2);
static constexpr uint32_t ShaderVariantLoadErrorParam = 0;
static constexpr uint32_t ShaderSourceFilePathJobParam = 2;
static constexpr uint32_t ShaderVariantJobVariantParam = 3;
static constexpr uint32_t ShouldExitEarlyFromProcessJobParam = 4;
//! Called from ProcessJob when the job is supposed to create a ShaderVariantTreeAsset.
void ProcessShaderVariantTreeJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const;
//! Called from ProcessJob when the job is supposed to create ShaderVariantAssets. One ShaderVariantAsset will be produced per RHI::APIType
//! supported by the platform.
void ProcessShaderVariantJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const;
static AZStd::string GetShaderVariantTreeAssetJobKey() { return AZStd::string::format("%s_varianttree", ShaderVariantAssetBuilder2JobKey); }
static AZStd::string GetShaderVariantAssetJobKey(RPI::ShaderVariantStableId variantStableId) { return AZStd::string::format("%s_variant_%u", ShaderVariantAssetBuilder2JobKey, variantStableId.GetIndex()); }
};
} // ShaderBuilder
} // AZ

@ -345,17 +345,21 @@ namespace AZ
for(const SrgDataEntry& srgDataEntry : entry.second)
{
RHI::ShaderPlatformInterface* shaderPlatformInterface = srgDataEntry.first;
const SrgData& srgData = srgDataEntry.second;
srgAssetCreator.BeginAPI(shaderPlatformInterface->GetAPIType());
srgAssetCreator.SetBindingSlot(srgData.m_bindingSlot.m_index);
// The register number only makes sense if the platform uses "spaces",
// since the register Id of the resource will not change even if the pipeline layout changes.
// We can pass in a default ShaderCompilerArguments because all we care about is whether the shaderPlatformInterface appends the "--use-spaces" flag.
AZStd::string azslCompilerParameters = shaderPlatformInterface->GetAzslCompilerParameters(RHI::ShaderCompilerArguments{});
// We can pass in a default ShaderCompilerArguments because all we care about is whether the shaderPlatformInterface
// appends the
// "--use-spaces" flag.
AZStd::string azslCompilerParameters =
shaderPlatformInterface->GetAzslCompilerParameters(RHI::ShaderCompilerArguments{});
bool useRegisterId = (AzFramework::StringFunc::Find(azslCompilerParameters, "--use-spaces") != AZStd::string::npos);
const SrgData& srgData = srgDataEntry.second;
srgAssetCreator.BeginAPI(shaderPlatformInterface->GetAPIType());
srgAssetCreator.SetBindingSlot(srgData.m_bindingSlot.m_index);
// Samplers
for (const SamplerSrgData& samplerData : srgData.m_samplers)
{

@ -0,0 +1,231 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SrgLayoutUtility.h"
#include <Atom/RHI.Edit/Utils.h>
namespace AZ
{
namespace ShaderBuilder
{
namespace SrgLayoutUtility
{
static constexpr char SrgLayoutUtilityName[] = "SrgLayoutUtility";
RHI::ShaderInputImageType ToShaderInputImageType(TextureType textureType)
{
switch (textureType)
{
case TextureType::Texture1D:
return RHI::ShaderInputImageType::Image1D;
case TextureType::Texture1DArray:
return RHI::ShaderInputImageType::Image1DArray;
case TextureType::Texture2D:
return RHI::ShaderInputImageType::Image2D;
case TextureType::Texture2DArray:
return RHI::ShaderInputImageType::Image2DArray;
case TextureType::Texture2DMS:
return RHI::ShaderInputImageType::Image2DMultisample;
case TextureType::Texture2DMSArray:
return RHI::ShaderInputImageType::Image2DMultisampleArray;
case TextureType::Texture3D:
return RHI::ShaderInputImageType::Image3D;
case TextureType::TextureCube:
return RHI::ShaderInputImageType::ImageCube;
case TextureType::RwTexture1D:
return RHI::ShaderInputImageType::Image1D;
case TextureType::RwTexture1DArray:
return RHI::ShaderInputImageType::Image1DArray;
case TextureType::RwTexture2D:
return RHI::ShaderInputImageType::Image2D;
case TextureType::RwTexture2DArray:
return RHI::ShaderInputImageType::Image2DArray;
case TextureType::RwTexture3D:
return RHI::ShaderInputImageType::Image3D;
case TextureType::RasterizerOrderedTexture1D:
return RHI::ShaderInputImageType::Image1D;
case TextureType::RasterizerOrderedTexture1DArray:
return RHI::ShaderInputImageType::Image1DArray;
case TextureType::RasterizerOrderedTexture2D:
return RHI::ShaderInputImageType::Image2D;
case TextureType::RasterizerOrderedTexture2DArray:
return RHI::ShaderInputImageType::Image2DArray;
case TextureType::RasterizerOrderedTexture3D:
return RHI::ShaderInputImageType::Image3D;
case TextureType::SubpassInput:
return RHI::ShaderInputImageType::SubpassInput;
default:
AZ_Assert(false, "Unhandled TextureType");
return RHI::ShaderInputImageType::Unknown;
}
}
RHI::ShaderInputBufferType ToShaderInputBufferType(BufferType bufferType)
{
switch (bufferType)
{
case BufferType::Buffer:
case BufferType::RwBuffer:
case BufferType::RasterizerOrderedBuffer:
return RHI::ShaderInputBufferType::Typed;
case BufferType::AppendStructuredBuffer:
case BufferType::ConsumeStructuredBuffer:
case BufferType::RasterizerOrderedStructuredBuffer:
case BufferType::RwStructuredBuffer:
case BufferType::StructuredBuffer:
return RHI::ShaderInputBufferType::Structured;
case BufferType::RasterizerOrderedByteAddressBuffer:
case BufferType::ByteAddressBuffer:
case BufferType::RwByteAddressBuffer:
return RHI::ShaderInputBufferType::Raw;
case BufferType::RaytracingAccelerationStructure:
return RHI::ShaderInputBufferType::AccelerationStructure;
default:
AZ_Assert(false, "Unhandled BufferType");
return RHI::ShaderInputBufferType::Unknown;
}
}
bool LoadShaderResourceGroupLayouts(
[[maybe_unused]] const char* builderName, const SrgDataContainer& resourceGroups,
const bool platformUsesRegisterSpaces, RPI::ShaderResourceGroupLayoutList& srgLayoutList)
{
// The register number only makes sense if the platform uses "spaces",
// since the register Id of the resource will not change even if the pipeline layout changes.
// All we care about is whether the shaderPlatformInterface appends the "--use-spaces" flag.
bool useRegisterId = platformUsesRegisterSpaces;
// Load all SRGs included in source file
for (const SrgData& srgData : resourceGroups)
{
RHI::Ptr<RHI::ShaderResourceGroupLayout> newSrgLayout = RHI::ShaderResourceGroupLayout::Create();
newSrgLayout->SetName(AZ::Name{srgData.m_name.c_str()});
newSrgLayout->SetBindingSlot(srgData.m_bindingSlot.m_index);
// Samplers
for (const SamplerSrgData& samplerData : srgData.m_samplers)
{
if (samplerData.m_isDynamic)
{
newSrgLayout->AddShaderInput(
{samplerData.m_nameId, samplerData.m_count,
useRegisterId ? samplerData.m_registerId : RHI::UndefinedRegisterSlot});
}
else
{
newSrgLayout->AddStaticSampler(
{samplerData.m_nameId, samplerData.m_descriptor,
useRegisterId ? samplerData.m_registerId : RHI::UndefinedRegisterSlot});
}
}
// Images
for (const TextureSrgData& textureData : srgData.m_textures)
{
const RHI::ShaderInputImageAccess imageAccess =
textureData.m_isReadOnlyType ? RHI::ShaderInputImageAccess::Read : RHI::ShaderInputImageAccess::ReadWrite;
const RHI::ShaderInputImageType imageType = SrgLayoutUtility::ToShaderInputImageType(textureData.m_type);
if (imageType != RHI::ShaderInputImageType::Unknown)
{
if (textureData.m_count != aznumeric_cast<uint32_t>(-1))
{
newSrgLayout->AddShaderInput(
{textureData.m_nameId, imageAccess, imageType, textureData.m_count,
useRegisterId ? textureData.m_registerId : RHI::UndefinedRegisterSlot});
}
else
{
// unbounded array
newSrgLayout->AddShaderInput(
{textureData.m_nameId, imageAccess, imageType,
useRegisterId ? textureData.m_registerId : RHI::UndefinedRegisterSlot});
}
}
else
{
AZ_Error(
builderName, false, "Failed to build Shader Resource Group Asset: Image %s has an unknown type.",
textureData.m_nameId.GetCStr());
return false;
}
}
// Buffers
{
for (const ConstantBufferData& cbData : srgData.m_constantBuffers)
{
newSrgLayout->AddShaderInput(
{cbData.m_nameId, RHI::ShaderInputBufferAccess::Constant, RHI::ShaderInputBufferType::Constant,
cbData.m_count, cbData.m_strideSize, useRegisterId ? cbData.m_registerId : RHI::UndefinedRegisterSlot});
}
for (const BufferSrgData& bufferData : srgData.m_buffers)
{
const RHI::ShaderInputBufferAccess bufferAccess =
bufferData.m_isReadOnlyType ? RHI::ShaderInputBufferAccess::Read : RHI::ShaderInputBufferAccess::ReadWrite;
const RHI::ShaderInputBufferType bufferType = SrgLayoutUtility::ToShaderInputBufferType(bufferData.m_type);
if (bufferType != RHI::ShaderInputBufferType::Unknown)
{
if (bufferData.m_count != aznumeric_cast<uint32_t>(-1))
{
newSrgLayout->AddShaderInput(
{bufferData.m_nameId, bufferAccess, bufferType, bufferData.m_count, bufferData.m_strideSize,
useRegisterId ? bufferData.m_registerId : RHI::UndefinedRegisterSlot});
}
else
{
// unbounded array
newSrgLayout->AddShaderInput(
{bufferData.m_nameId, bufferAccess, bufferType, bufferData.m_strideSize,
useRegisterId ? bufferData.m_registerId : RHI::UndefinedRegisterSlot});
}
}
else
{
AZ_Error(
builderName, false,
"Failed to build Shader Resource Group Asset: Buffer %s has un unknown type.",
bufferData.m_nameId.GetCStr());
return false;
}
}
}
// SRG Constants
uint32_t constantDataRegisterId = useRegisterId ? srgData.m_srgConstantDataRegisterId : RHI::UndefinedRegisterSlot;
for (const SrgConstantData& srgConstants : srgData.m_srgConstantData)
{
newSrgLayout->AddShaderInput(
{srgConstants.m_nameId, srgConstants.m_constantByteOffset, srgConstants.m_constantByteSize,
constantDataRegisterId});
}
// Shader Variant Key fallback
if (srgData.m_fallbackSize > 0)
{
// Designates this SRG as a ShaderVariantKey fallback
newSrgLayout->SetShaderVariantKeyFallback(srgData.m_fallbackName, srgData.m_fallbackSize);
}
srgLayoutList.push_back(newSrgLayout);
}
return true;
}
} // namespace SrgLayoutUtility
} // namespace ShaderBuilder
} // AZ

@ -0,0 +1,34 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/base.h>
#include "CommonFiles/CommonTypes.h"
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include "ShaderBuilderUtility.h"
namespace AZ
{
namespace ShaderBuilder
{
namespace SrgLayoutUtility
{
bool LoadShaderResourceGroupLayouts(
[[maybe_unused]] const char* builderName, const SrgDataContainer& resourceGroups, const bool platformUsesRegisterSpaces,
RPI::ShaderResourceGroupLayoutList& srgLayoutList);
} // SrgLayoutUtility namespace
} // ShaderBuilder namespace
} // AZ

@ -34,8 +34,14 @@ set(FILES
Source/Editor/AzslCompiler.h
Source/Editor/ShaderVariantAssetBuilder.cpp
Source/Editor/ShaderVariantAssetBuilder.h
Source/Editor/ShaderVariantAssetBuilder2.cpp
Source/Editor/ShaderVariantAssetBuilder2.h
Source/Editor/AtomShaderConfig.cpp
Source/Editor/AtomShaderConfig.h
Source/Editor/PrecompiledShaderBuilder.cpp
Source/Editor/PrecompiledShaderBuilder.h
Source/Editor/ShaderAssetBuilder2.cpp
Source/Editor/ShaderAssetBuilder2.h
Source/Editor/SrgLayoutUtility.cpp
Source/Editor/SrgLayoutUtility.h
)

@ -75,6 +75,8 @@ namespace AZ
*/
bool Finalize();
void SetName(const Name& name) { m_name = name; }
const Name& GetName() const { return m_name; }
/**
* Designates this SRG as ShaderVariantKey fallback by providing the generated
@ -272,6 +274,9 @@ namespace AZ
AZ_SERIALIZE_FRIEND();
//! Name of the ShaderResourceGroup as specified in the original *.azsl/*.azsli file.
Name m_name;
AZStd::vector<ShaderInputStaticSamplerDescriptor> m_staticSamplers;
AZStd::vector<ShaderInputBufferDescriptor> m_inputsForBuffers;

@ -22,7 +22,8 @@ namespace AZ
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<ShaderResourceGroupLayout>()
->Version(6)
->Version(7)
->Field("m_name", &ShaderResourceGroupLayout::m_name)
->Field("m_staticSamplers", &ShaderResourceGroupLayout::m_staticSamplers)
->Field("m_inputsForBuffers", &ShaderResourceGroupLayout::m_inputsForBuffers)
->Field("m_inputsForImages", &ShaderResourceGroupLayout::m_inputsForImages)

@ -66,21 +66,21 @@ namespace AZ
}
}
RHI::ConstPtr<RHI::ShaderResourceGroupLayout> PipelineLayout::MergeShaderResourceGroupLayouts(const AZStd::vector<const RHI::ShaderResourceGroupLayout*>& srgLayouts) const
RHI::ConstPtr<RHI::ShaderResourceGroupLayout> PipelineLayout::MergeShaderResourceGroupLayouts(const AZStd::vector<const RHI::ShaderResourceGroupLayout*>& srgLayoutList) const
{
if (srgLayouts.empty())
if (srgLayoutList.empty())
{
return nullptr;
}
if (srgLayouts.size() == 1)
if (srgLayoutList.size() == 1)
{
return srgLayouts.front();
return srgLayoutList.front();
}
RHI::Ptr<RHI::ShaderResourceGroupLayout> mergedLayout = RHI::ShaderResourceGroupLayout::Create();
mergedLayout->SetBindingSlot(srgLayouts.front()->GetBindingSlot());
for (const RHI::ShaderResourceGroupLayout* srgLayout : srgLayouts)
mergedLayout->SetBindingSlot(srgLayoutList.front()->GetBindingSlot());
for (const RHI::ShaderResourceGroupLayout* srgLayout : srgLayoutList)
{
const uint32_t bindingSlot = srgLayout->GetBindingSlot();
const auto& srgBindingInfo = m_layoutDescriptor->GetShaderResourceGroupBindingInfo(m_layoutDescriptor->GetShaderResourceGroupIndexFromBindingSlot(bindingSlot));

@ -85,7 +85,7 @@ namespace AZ
RHI::ResultCode BuildMergedShaderResourceGroupPools();
// Creates a merged SRG layout from a list of SRG layouts.
RHI::ConstPtr<RHI::ShaderResourceGroupLayout> MergeShaderResourceGroupLayouts(const AZStd::vector<const RHI::ShaderResourceGroupLayout*>& srgLayouts) const;
RHI::ConstPtr<RHI::ShaderResourceGroupLayout> MergeShaderResourceGroupLayouts(const AZStd::vector<const RHI::ShaderResourceGroupLayout*>& srgLayoutList) const;
VkPipelineLayout m_nativePipelineLayout = VK_NULL_HANDLE;

@ -16,7 +16,7 @@
#include <Atom/RHI.Edit/ShaderPlatformInterface.h>
#include <Atom/RHI.Edit/ShaderCompilerArguments.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderCommonTypes.h>
#include <Atom/RHI.Reflect/Format.h>
#include <Atom/RHI.Reflect/Limits.h>
@ -38,12 +38,13 @@ namespace AZ
AZ_TYPE_INFO(AZ::RPI::ShaderSourceData, "{B7F00402-872B-4F82-A210-E1A79A366686}");
AZ_CLASS_ALLOCATOR(ShaderSourceData, AZ::SystemAllocator, 0);
static const char* Extension;
static constexpr char Extension[] = "shader";
static constexpr char Extension2[] = "shader2";
static void Reflect(ReflectContext* context);
//! Helper function. Returns true if @rhiName is present in m_disabledRhiBackends
bool IsRhiBackendDisabled(const AZ::Name& rhiName);
bool IsRhiBackendDisabled(const AZ::Name& rhiName) const;
struct EntryPoint
{
@ -71,12 +72,49 @@ namespace AZ
RHI::DepthStencilState m_depthStencilState;
RHI::RasterState m_rasterState;
RHI::TargetBlendState m_blendState;
// Hints for building the shader option group layout
RPI::ShaderOptionGroupHints m_shaderOptionGroupHints;
//! List of RHI Backends (aka ShaderPlatformInterface) for which this shader should not be compiled.
AZStd::vector<AZStd::string> m_disabledRhiBackends;
struct SupervariantInfo
{
AZ_TYPE_INFO(AZ::RPI::ShaderSourceData::SupervariantInfo, "{1132CF2A-C8AB-4DD2-AA90-3021D49AB955}");
//! Unique name of the supervariant.
//! If left empty, the data refers to the default supervariant.
AZ::Name m_name;
//! + MCPP Macro definition arguments + AZSLc arguments.
//! These arguments are added after shader_global_build_options.json & m_compiler.m_azslcAdditionalFreeArguments.
//! Arguments that start with "-D" are given to MCPP.
//! Example: "-DMACRO1 -DMACRO2=3".
//! all other arguments are given to AZSLc.
//! Note the arguments are added in addition to the arguments
//! in <GAME_PROJECT>/Config/shader_global_build_options.json
AZStd::string m_plusArguments;
//! Opposite to @m_plusArguments.
//! - MCPP Macro definition arguments - AZSLc arguments.
//! Because there are global compilation arguments, this one is useful to remove some of those arguments
//! in order to customize the compilation of a particular supervariant.
AZStd::string m_minusArguments;
//! Helper function. Parses @m_minusArguments and @m_plusArguments, looks for arguments of type -D<name>[=<value>] and returns
//! a list of <name> to remove.
AZStd::vector<AZStd::string> GetCombinedListOfMacroDefinitionNamesToRemove() const;
//! Helper function. Parses @m_plusArguments, looks for arguments of type "-D<name>[=<value>]" and returns
//! a list of "<name>[=<value>]".
AZStd::vector<AZStd::string> GetMacroDefinitionsToAdd() const;
//! Helper function. Takes AZSLc arguments from @m_minusArguments and @m_plusArguments, removes them from @initialAzslcCompilerArguments.
//! Takes AZSLc arguments from @m_plusArguments and appends them to @initialAzslcCompilerArguments.
//! Returns a new string with customized arguments.
AZStd::string GetCustomizedArgumentsForAzslc(const AZStd::string& initialAzslcCompilerArguments) const;
};
//! Optional list of supervariants.
AZStd::vector<SupervariantInfo> m_supervariants;
};
} // namespace RPI

@ -0,0 +1,54 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Atom/RPI.Reflect/AssetCreator.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroupLayout.h>
namespace AZ
{
namespace RPI
{
//! The "builder" pattern class that creates a ShaderVariantAsset2.
class ShaderVariantAssetCreator2 final
: public AssetCreator<ShaderVariantAsset2>
{
public:
//! Begins construction of the shader variant asset.
//! @param assetId The "initial" assetId that the resulting ShaderVariantAsset will get.
//! "initial" was quoted because in the end the asset processor will assign another assetId
//! because on the UUID of the source asset (a *.shadervariantlist file) and the product subid
//! that gets assign when returning the Job Response.
//! It is still useful, because when creating the Root Variant for the ShaderAsset this assetId should
//! match the value that will be assigned by the asset processor because the Root Variant is serialized
//! as a Data::Asset<ShaderVariantAsset> inside the ShaderAsset.
void Begin(const AZ::Data::AssetId& assetId, const ShaderVariantId& shaderVariantId, RPI::ShaderVariantStableId stableId, bool isFullyBaked);
//! Finalizes and assigns ownership of the asset to result, if successful.
//! Otherwise false is returned and result is left untouched.
bool End(Data::Asset<ShaderVariantAsset2>& result);
/////////////////////////////////////////////////////////////////////
// Methods for all shader variant types
//! Set the timestamp value when the ProcessJob() started.
//! This is needed to synchronize between the ShaderAsset and ShaderVariantAsset when hot-reloading shaders.
//! The idea is that this timestamp must be greater or equal than the ShaderAsset.
void SetBuildTimestamp(AZStd::sys_time_t buildTimestamp);
//! Assigns a shaderStageFunction, which contains the byte code, to the slot dictated by the shader stage.
void SetShaderFunction(RHI::ShaderStage shaderStage, RHI::Ptr<RHI::ShaderStageFunction> shaderStageFunction);
};
} // namespace RPI
} // namespace AZ

@ -30,6 +30,7 @@ namespace AZ
AZ_CLASS_ALLOCATOR(ShaderVariantListSourceData, AZ::SystemAllocator, 0);
static constexpr const char* Extension = "shadervariantlist";
static constexpr const char* Extension2 = "shadervariantlist2";
static void Reflect(ReflectContext* context);

@ -0,0 +1,194 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Atom/RPI.Public/Shader/ShaderVariant2.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
#include <Atom/RPI.Reflect/Shader/IShaderVariantFinder2.h>
#include <Atom/RHI/DrawListTagRegistry.h>
#include <Atom/RHI/PipelineLibrary.h>
#include <AtomCore/Instance/InstanceData.h>
#include <AzCore/Memory/SystemAllocator.h>
namespace AZ
{
namespace RHI
{
class PipelineStateCache;
}
namespace RPI
{
/**
* Shader2 is effectively an 'uber-shader' containing a collection of 'variants'. Variants are
* designed to be 'variations' on the same core shader technique. To enforce this, every variant
* in the shader shares the same pipeline layout (i.e. set of shader resource groups).
*
* A shader owns a library of pipeline states. When a variant is resolved to a pipeline state, its
* lifetime is determined by the lifetime of the Shader2 (unless an explicit reference is taken). If
* an asset reload event occurs, the pipeline state cache is reset.
*
* To use Shader2:
* 1) Construct a ShaderOptionGroup instance using CreateShaderOptionGroup.
* 2) Configure the group by setting values on shader options.
* 3) Find the ShaderVariantStableId using the ShaderVariantId generated from the configured ShaderOptionGroup.
* 4) Acquire the ShaderVariant2 instance using the ShaderVariantStableId.
* 5) Configure a pipeline state descriptor on the variant; make local overrides as necessary (e.g. to configure runtime render state).
* 6) Acquire a RHI::PipelineState instance from the shader using the configured pipeline state descriptor.
*
* Remember that the returned RHI::PipelineState instance lifetime is tied to the Shader2 lifetime.
* If you need guarantee lifetime, it is safe to take a reference on the returned pipeline state.
*/
class Shader2 final
: public Data::InstanceData
, public Data::AssetBus::Handler
, public ShaderVariantFinderNotificationBus2::Handler
{
friend class ShaderSystem;
public:
AZ_INSTANCE_DATA(Shader2, "{232D8BD6-3BD4-4842-ABD2-F380BD5B0863}");
AZ_CLASS_ALLOCATOR(Shader2, SystemAllocator, 0);
/// Returns the shader instance associated with the provided asset.
static Data::Instance<Shader2> FindOrCreate(const Data::Asset<ShaderAsset2>& shaderAsset, const Name& supervariantName);
~Shader2();
AZ_DISABLE_COPY_MOVE(Shader2);
/// Constructs a shader option group suitable to generate a shader variant key for this shader.
ShaderOptionGroup CreateShaderOptionGroup() const;
/// Finds the best matching ShaderVariant2 for the given shaderVariantId,
/// If the variant is loaded and ready it will return the corresponding ShaderVariant2.
/// If the variant is not yet available it will return the root ShaderVariant2.
/// Callers should listen to ShaderReloadNotificationBus to get notified whenever the exact
/// variant is loaded and available or if a variant changes, etc.
/// This function should be your one stop shop to get a ShaderVariant2 from a ShaderVariantId.
/// Alternatively: You can call FindVariantStableId() followed by GetVariant(shaderVariantStableId).
const ShaderVariant2& GetVariant(const ShaderVariantId& shaderVariantId);
/// Finds the best matching shader variant asset and returns its StableId.
/// In cases where you can't cache the ShaderVariant2, and recurrently you may need
/// the same ShaderVariant2 at different times, then it can be convenient (and more performant) to call
/// this method to cache the ShaderVariantStableId and call GetVariant(ShaderVariantStableId)
/// when needed.
/// If the asset is not immediately found in the file system, it will return the StableId
/// of the root variant.
/// Callers should listen to ShaderReloadNotificationBus to get notified whenever the exact
/// variant is loaded and available or if a variant changes, etc.
ShaderVariantSearchResult FindVariantStableId(const ShaderVariantId& shaderVariantId) const;
/// Returns the variant associated with the provided StableId.
/// You should call FindVariantStableId() which caches the variant, later
/// when this function is called the variant is fetched from a local map.
/// If the variant is not found, the root variant is returned.
/// "Alternatively: a more convenient approach is to call GetVariant(ShaderVariantId) which does both, the find and the get."
const ShaderVariant2& GetVariant(ShaderVariantStableId shaderVariantStableId);
/// Convenient function that returns the root variant.
const ShaderVariant2& GetRootVariant();
/// Returns the pipeline state type generated by variants of this shader.
RHI::PipelineStateType GetPipelineStateType() const;
//! Returns the ShaderInputContract which describes which inputs the shader requires
const ShaderInputContract& GetInputContract() const;
//! Returns the ShaderOutputContract which describes which outputs the shader requires
const ShaderOutputContract& GetOutputContract() const;
/// Acquires a pipeline state directly from a descriptor.
const RHI::PipelineState* AcquirePipelineState(const RHI::PipelineStateDescriptor& descriptor) const;
/// Finds and returns the shader resource group asset with the requested name. Returns an empty handle if no matching group was found.
const RHI::Ptr<RHI::ShaderResourceGroupLayout> FindShaderResourceGroupLayout(const Name& shaderResourceGroupName) const;
/// Finds and returns the shader resource group asset associated with the requested binding slot. Returns an empty handle if no matching group was found.
const RHI::Ptr<RHI::ShaderResourceGroupLayout> FindShaderResourceGroupLayout(uint32_t bindingSlot) const;
/// Finds and returns the shader resource group asset designated as a ShaderVariantKey fallback.
const RHI::Ptr<RHI::ShaderResourceGroupLayout> FindFallbackShaderResourceGroupLayout() const;
/// Returns the set of shader resource groups referenced by all variants in the shader asset.
AZStd::array_view<RHI::Ptr<RHI::ShaderResourceGroupLayout>> GetShaderResourceGroupLayouts() const;
/// Returns a reference to the asset used to initialize this shader.
const Data::Asset<ShaderAsset2>& GetAsset() const;
//! Returns the DrawListTag that identifies which Pass and View objects will process this shader.
//! This tag corresponds to the ShaderAsset2 object's DrawListName.
RHI::DrawListTag GetDrawListTag() const;
private:
Shader2() = default;
static Data::Instance<Shader2> CreateInternal(ShaderAsset2& shaderAsset);
bool SelectSupervariant(const Name& supervariantName);
RHI::ResultCode Init(ShaderAsset2& shaderAsset);
void Shutdown();
ConstPtr<RHI::PipelineLibraryData> LoadPipelineLibrary() const;
void SavePipelineLibrary() const;
///////////////////////////////////////////////////////////////////
/// AssetBus overrides
void OnAssetReloaded(Data::Asset<Data::AssetData> asset) override;
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
/// ShaderVariantFinderNotificationBus overrides
void OnShaderVariantTreeAssetReady(Data::Asset<ShaderVariantTreeAsset> /*shaderVariantTreeAsset*/, bool /*isError*/) override {};
void OnShaderVariantAssetReady(Data::Asset<ShaderVariantAsset2> shaderVariantAsset, bool IsError) override;
///////////////////////////////////////////////////////////////////
//! Returns the path to the pipeline library cache file.
AZStd::string GetPipelineLibraryPath() const;
//! A strong reference to the shader asset.
Data::Asset<ShaderAsset2> m_asset;
//! Selects current supervariant to be used.
//! This value is defined at instantiation.
SupervariantIndex m_supervariantIndex;
//! The pipeline state type required by this shader.
RHI::PipelineStateType m_pipelineStateType = RHI::PipelineStateType::Draw;
//! A cached pointer to the pipeline state cache owned by RHISystem.
RHI::PipelineStateCache* m_pipelineStateCache = nullptr;
//! A handle to the pipeline library in the pipeline state cache.
RHI::PipelineLibraryHandle m_pipelineLibraryHandle;
//! Used for thread safety for FindVariantStableId() and GetVariant().
AZStd::shared_mutex m_variantCacheMutex;
//! The root variant always exist.
ShaderVariant2 m_rootVariant;
//! Local cache of ShaderVariants (except for the root variant), searchable by StableId.
//! Gets populated when GetVariant() is called.
AZStd::unordered_map<ShaderVariantStableId, ShaderVariant2> m_shaderVariants;
//! DrawListTag associated with this shader.
RHI::DrawListTag m_drawListTag;
};
}
}

@ -0,0 +1,58 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/Asset/AssetCommon.h>
//#include <Atom/RPI.Reflect/Shader/ShaderCommonTypes.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantKey.h>
namespace AZ
{
namespace RPI
{
class Shader2;
class ShaderAsset2;
/**
* Connect to this EBus to get notifications whenever a Data::Instance<Shader> reloads its ShaderAsset.
* The bus address is the AssetId of the ShaderAsset.
*/
class ShaderReloadNotifications2
: public EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
typedef Data::AssetId BusIdType;
//////////////////////////////////////////////////////////////////////////
virtual ~ShaderReloadNotifications2() {}
//! Called when the ShaderAsset reinitializes itself in response to another asset being reloaded.
virtual void OnShaderAssetReinitialized(const Data::Asset<ShaderAsset2>& shaderAsset) { AZ_UNUSED(shaderAsset); }
//! Called when the Shader instance reinitializes itself in response to the ShaderAsset being reloaded.
virtual void OnShaderReinitialized(const Shader2& shader) { AZ_UNUSED(shader); }
//! Called when a particular shader variant is reinitialized.
virtual void OnShaderVariantReinitialized(const Shader2& shader, const ShaderVariantId& shaderVariantId, ShaderVariantStableId shaderVariantStableId)
{ AZ_UNUSED(shader); AZ_UNUSED(shaderVariantId); AZ_UNUSED(shaderVariantStableId) }
};
typedef EBus<ShaderReloadNotifications2> ShaderReloadNotificationBus2;
} // namespace RPI
} //namespace AZ

@ -13,6 +13,8 @@
#include <Atom/RPI.Reflect/Base.h>
#include <Atom/RPI.Reflect/Shader/ShaderResourceGroupAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantKey.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroupPool.h>
@ -63,6 +65,10 @@ namespace AZ
/// Instantiates a unique shader resource group instance using its paired asset.
static Data::Instance<ShaderResourceGroup> Create(const Data::Asset<ShaderResourceGroupAsset>& srgAsset);
/// [GFX TODO] [ATOM-15472] Shader Build Pipeline: Remove Deprecated Files And Functions That Predate The Shader Supervariants
/// This is a temporary hack to enable integration of the new supervariant system.
bool ReplaceSrgLayoutUsingShaderAsset(Data::Asset<ShaderAsset2> shaderAsset, const Name& supervariantName, const Name& srgName);
/// Queues a request that the underlying hardware shader resource group be compiled.
void Compile();
@ -278,6 +284,7 @@ namespace AZ
ShaderResourceGroup() = default;
RHI::ResultCode Init(ShaderResourceGroupAsset& shaderResourceGroupAsset);
static AZ::Data::Instance<ShaderResourceGroup> CreateInternal(ShaderResourceGroupAsset& srgAsset);
/// A name to be used in error messages
@ -298,9 +305,12 @@ namespace AZ
/// The shader resource group that can be submitted to the renderer
RHI::Ptr<RHI::ShaderResourceGroup> m_shaderResourceGroup;
/// A reference to the parent template asset used to initialize and manipulate this group.
/// A reference to the SRG asset used to initialize and manipulate this group.
AZ::Data::Asset<ShaderResourceGroupAsset> m_asset;
/// A reference to the shader asset used to initialize and manipulate this group.
AZ::Data::Asset<ShaderAsset2> m_shaderAsset;
/// A pointer to the layout inside of m_srgAsset
const RHI::ShaderResourceGroupLayout* m_layout = nullptr;

@ -61,9 +61,6 @@ namespace AZ
const ShaderAsset& shaderAsset,
Data::Asset<ShaderVariantAsset> shaderVariantAsset);
// Returns a shader stage function associated with the provided enum value, or null if no function exists.
const RHI::ShaderStageFunction* GetShaderStageFunction(RHI::ShaderStage shaderStage) const;
// Cached state from the asset to avoid an indirection.
RHI::PipelineStateType m_pipelineStateType = RHI::PipelineStateType::Count;

@ -0,0 +1,71 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <Atom/RHI/DrawListTagRegistry.h>
namespace AZ
{
namespace RPI
{
//! Represents the concrete state to configure a PipelineStateDescriptor. ShaderVariant2's match
//! the RHI::PipelineStateType of the parent Shader instance. For shaders on the raster
//! pipeline, the RHI::DrawFilterTag is also provided.
class ShaderVariant2 final
{
friend class Shader2;
public:
ShaderVariant2() = default;
AZ_DEFAULT_COPY_MOVE(ShaderVariant2);
//! Fills a pipeline state descriptor with settings provided by the ShaderVariant2. (Note that
//! this does not fill the InputStreamLayout or OutputAttachmentLayout as that also requires
//! information from the mesh data and pass system and must be done as a separate step).
void ConfigurePipelineState(RHI::PipelineStateDescriptor& descriptor) const;
const ShaderVariantId& GetShaderVariantId() const { return m_shaderVariantAsset->GetShaderVariantId(); }
//! Returns whether the variant is fully baked variant (all options are static branches), or false if the
//! variant uses dynamic branches for some shader options.
//! If the shader variant is not fully baked, the ShaderVariantKeyFallbackValue must be correctly set when drawing.
bool IsFullyBaked() const { return m_shaderVariantAsset->IsFullyBaked(); }
//! Return the timestamp when this asset was built.
//! This is used to synchronize versions of the ShaderAsset and ShaderVariantAsset, especially during hot-reload.
//! This timestamp must be >= than the ShaderAsset timestamp.
AZStd::sys_time_t GetBuildTimestamp() const { return m_shaderVariantAsset->GetBuildTimestamp(); }
bool IsRootVariant() const { return m_shaderVariantAsset->IsRootVariant(); }
ShaderVariantStableId GetStableId() const { return m_shaderVariantAsset->GetStableId(); }
private:
// Called by Shader. Initializes runtime data from asset data. Returns whether the call succeeded.
bool Init(
const ShaderAsset2& shaderAsset,
Data::Asset<ShaderVariantAsset2> shaderVariantAsset,
SupervariantIndex supervariantIndex);
// Cached state from the asset to avoid an indirection.
RHI::PipelineStateType m_pipelineStateType = RHI::PipelineStateType::Count;
// State assigned to the pipeline state descriptor.
RHI::ConstPtr<RHI::PipelineLayoutDescriptor> m_pipelineLayoutDescriptor;
Data::Asset<ShaderVariantAsset2> m_shaderVariantAsset;
const RHI::RenderStates* m_renderStates = nullptr; // Cached from ShaderAsset2.
};
}
}

@ -0,0 +1,113 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <Atom/RPI.Reflect/Shader/ShaderCommonTypes.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantKey.h>
namespace AZ
{
namespace RPI
{
class ShaderAsset2;
class ShaderVariantTreeAsset;
class ShaderVariantAsset2;
//! This is the AZ::Interface<> declaration for the singleton responsible
//! for finding the best ShaderVariantAsset a shader can use.
//! This interface is public only to the ShaderAsset class.
//! The expectation is that when in need of shader variants the developer
//! should use AZ::RPI::Shader::GetVariant().
class IShaderVariantFinder2
{
public:
AZ_TYPE_INFO(IShaderVariantFinder2, "{4E041C2C-F158-412E-8961-76987EC75692}");
static constexpr const char* LogName = "IShaderVariantFinder2";
virtual ~IShaderVariantFinder2() = default;
//! This function should be your one stop shop.
//! It simply queues the request to load a shader variant asset.
//! This function will automatically queue the ShaderVariantTreeAsset for loading if not available.
//! Afther the ShaderVariantTreeAsset is loaded and ready, it is used to find the best matching ShaderVariantStableId
//! from the given ShaderVariantId. If a valid ShaderVariantStableId is found, it will be queued for loading.
//! Eventually the caller will be notified via ShaderVariantFinderNotificationBus::OnShaderVariantAssetReady()
//! The notification will occur on the Main Thread.
virtual bool QueueLoadShaderVariantAssetByVariantId(
Data::Asset<ShaderAsset2> shaderAsset, const ShaderVariantId& shaderVariantId, SupervariantIndex supervariantIndex) = 0;
//! This function does the first half of the work. It simply queues the loading of the ShaderVariantTreeAsset.
//! Given the AssetId of a ShaderAsset it will try to find and load its corresponding ShaderVariantTreeAsset from
//! the asset cache. If found, the asset will be loaded asynchronously and the caller will be notified via
//! ShaderVariantFinderNotificationBus on main thread when the ShaderVariantTreeAsset is fully loaded.
//! It is possible the requested ShaderVariantTreeAsset will never come into existence and in such
//! case the caller will NEVER be notified.
//! Returns true if the request was queued successfully.
virtual bool QueueLoadShaderVariantTreeAsset(const Data::AssetId& shaderAssetId) = 0;
//! This function does the second half of the work.
//! Given the AssetId of a ShaderVariantTreeAsset and the stable id of a ShaderVariantAsset it will try to
//! find its corresponding ShaderVariantAsset from the asset cache. If found, the asset will be loaded
//! asynchronously and the caller will be notified via ShaderVariantFinderNotificationBus on main thread when the
//! ShaderVariantAsset is fully loaded.
//! Returns true if the request was queued successfully.
virtual bool QueueLoadShaderVariantAsset(
const Data::AssetId& shaderVariantTreeAssetId, ShaderVariantStableId variantStableId,
SupervariantIndex supervariantIndex) = 0;
//! This is a quick blocking call that will return a valid asset only if it's been fully loaded already,
//! Otherwise it returns an invalid asset and the caller is supposed to call QueueLoadShaderVariantAssetByVariantId().
virtual Data::Asset<ShaderVariantAsset2> GetShaderVariantAssetByVariantId(
Data::Asset<ShaderAsset2> shaderAsset, const ShaderVariantId& shaderVariantId, SupervariantIndex supervariantIndex) = 0;
virtual Data::Asset<ShaderVariantAsset2> GetShaderVariantAssetByStableId(
Data::Asset<ShaderAsset2> shaderAsset, ShaderVariantStableId shaderVariantStableId, SupervariantIndex supervariantIndex) = 0;
//! This is a quick blocking call that will return a valid asset only if it's been fully loaded already,
//! Otherwise it returns an invalid asset and the caller is supposed to call QueueLoadShaderVariantTreeAsset().
virtual Data::Asset<ShaderVariantTreeAsset> GetShaderVariantTreeAsset(const Data::AssetId& shaderAssetId) = 0;
//! This is a quick blocking call that will return a valid asset only if i's been fully loaded already,
//! Otherwise it returns an invalid asset and the caller is supposed to call QueueLoadShaderVariantAsset().
virtual Data::Asset<ShaderVariantAsset2> GetShaderVariantAsset(
const Data::AssetId& shaderVariantTreeAssetId, ShaderVariantStableId variantStableId,
SupervariantIndex supervariantIndex) = 0;
//! Clears the cache of loaded ShaderVariantTreeAsset and ShaderVariantAsset objects.
//! This is intended for testing.
virtual void Reset() = 0;
};
//! IShaderVariantFinder2 will call on this notification bus on the main thread.
//! Only the following classes are supposed to register to this notification bus:
//! AZ::RPI::ShaderAsset & AZ::RPI::Shader
class ShaderVariantFinderNotification2
: public EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using MutexType = AZStd::recursive_mutex;
typedef Data::AssetId BusIdType; // The AssetId of the shader asset.
//////////////////////////////////////////////////////////////////////////
virtual void OnShaderVariantTreeAssetReady(Data::Asset<ShaderVariantTreeAsset> shaderVariantTreeAsset, bool isError) = 0;
virtual void OnShaderVariantAssetReady(Data::Asset<ShaderVariantAsset2> shaderVariantAsset, bool isError) = 0;
};
using ShaderVariantFinderNotificationBus2 = AZ::EBus<ShaderVariantFinderNotification2>;
} // namespace RPI
}// namespace AZ

@ -21,6 +21,7 @@
#include <Atom/RPI.Reflect/Shader/ShaderVariantTreeAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderResourceGroupAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderInputContract.h>
#include <Atom/RPI.Reflect/Shader/ShaderCommonTypes.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantKey.h>
#include <Atom/RPI.Reflect/Shader/IShaderVariantFinder.h>
@ -54,6 +55,8 @@ namespace AZ
//! The default shader variant (i.e. the one without any options set).
static const ShaderVariantStableId RootShaderVariantStableId;
// @subProductType is one of ShaderAssetSubId, or (ShaderAssetSubId::GeneratedHlslSource + 1)+
static uint32_t MakeAssetProductSubId(uint32_t rhiApiUniqueIndex, uint32_t subProductType);
ShaderAsset() = default;
~ShaderAsset();
@ -218,83 +221,21 @@ namespace AZ
//////////////////////////////////////////////////////////////////////////
// Deprecated System
enum class ShaderStageType : uint32_t
{
Vertex,
Geometry,
TessellationControl,
TessellationEvaluation,
Fragment,
Compute,
RayTracing
};
const char* ToString(ShaderStageType shaderStageType);
void ReflectShaderStageType(ReflectContext* context);
enum class ShaderAssetSubId : uint32_t
{
ShaderAsset = 0,
StreamLayout,
GraphicsPipelineState,
OutputMergerState,
RootShaderVariantAsset,
//[GFX TODO][LY-82895] (arsentuf) These shader stages are going to get reworked when virtual stages are implemented
AzVertexShader,
AzGeometryShader,
AzTessellationControlShader,
AzTessellationEvaluationShader,
AzFragmentShader,
AzComputeShader,
AzRayTracingShader,
DebugByProduct,
PostPreprocessingPureAzsl, // .azslin
IaJson,
OmJson,
SrgJson,
OptionsJson,
BindingdepJson,
GeneratedSource // This must be last because we use this as a base for adding the RHI::APIType when generating shadersource for multiple RHI APIs.
};
ShaderAssetSubId ShaderStageToSubId(ShaderStageType stageType);
class ShaderStageDescriptor final
{
public:
AZ_TYPE_INFO(ShaderStageDescriptor, "{3E7822F7-B952-4379-B0A0-48507681845A}");
AZ_CLASS_ALLOCATOR(ShaderStageDescriptor, AZ::SystemAllocator, 0);
static void Reflect(ReflectContext* context);
ShaderStageType m_stageType;
AZStd::vector<uint8_t> m_byteCode;
AZStd::vector<char> m_sourceCode;
AZStd::string m_entryFunctionName;
GeneratedHlslSource // This must be last because we use this as a base for adding the RHI::APIType when generating shadersource for multiple RHI APIs.
};
//[GFX TODO][LY-82803] (arsentuf) Remove this when we've fleshed out Virtual Shader stages
class ShaderStageAsset final
: public AZ::Data::AssetData
{
public:
AZ_RTTI(ShaderStageAsset, "{975F48B5-1577-41C9-B8F5-A1024E2D01F1}", AZ::Data::AssetData);
AZ_CLASS_ALLOCATOR(ShaderStageAsset, AZ::SystemAllocator, 0);
static void Reflect(ReflectContext* context);
ShaderStageAsset() = default;
ShaderStageAsset(const ShaderStageAsset&);
ShaderStageAsset& operator= (const ShaderStageAsset&);
ShaderStageAsset(ShaderStageAsset&& rhs);
AZStd::shared_ptr<ShaderStageDescriptor> m_descriptor;
AZStd::vector<Data::AssetId> m_srgLayouts;
};
//////////////////////////////////////////////////////////////////////////
} // namespace RPI
AZ_TYPE_INFO_SPECIALIZE(RPI::ShaderStageType, "{A6408508-748B-4963-B618-E1E6ECA3629A}");
} // namespace AZ

@ -0,0 +1,339 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/optional.h>
#include <AzCore/EBus/Event.h>
#include <Atom/RPI.Reflect/Asset/AssetHandler.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroupLayout.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantTreeAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderInputContract.h>
#include <Atom/RPI.Reflect/Shader/ShaderOutputContract.h>
#include <Atom/RPI.Reflect/Shader/ShaderCommonTypes.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantKey.h>
#include <Atom/RPI.Reflect/Shader/IShaderVariantFinder2.h>
#include <Atom/RHI/PipelineStateDescriptor.h>
#include <Atom/RHI.Reflect/ShaderStages.h>
#include <Atom/RHI.Reflect/PipelineLayoutDescriptor.h>
#include <limits>
namespace AZ
{
namespace RPI
{
using ShaderResourceGroupLayoutList = AZStd::fixed_vector<RHI::Ptr<RHI::ShaderResourceGroupLayout>, RHI::Limits::Pipeline::ShaderResourceGroupCountMax>;
enum class ShaderAsset2ProductSubId : uint32_t
{
ShaderAsset2 = 0, //!< for .azshader file, One per .shader.
RootShaderVariantAsset, //!< for .azshadervariant, one per supervariant and referenced inside the .azshader.
AzslFlat, //!< .azslin, this file contains the result of preprocessing an azsl file with MCPP, along with prepending the per-RHI azsli header.
IaJson, //!< .ia.json, Input Assembly reflection data.
OmJson, //!< .om.json, Output Merger reflection data.
SrgJson, //!< .srg.json, Shader Resource Group reflection data.
OptionsJson, //!< .options.json, Shader Options reflection data.
BindingdepJson, //!<.bindingdep.json, Binding dependencies.
GeneratedHlslSource, //!<.hlsl code generated with AZSLc.
FirstByProduct, //!< This must be last because we use this as a base for adding all the debug byProducts generated
//!< with dxc, or spirv-cross, etc.
};
class ShaderAsset2 final
: public Data::AssetData
, public ShaderVariantFinderNotificationBus2::Handler
, public Data::AssetBus::Handler
{
friend class ShaderAssetCreator2;
friend class ShaderAssetHandler2;
friend class ShaderAssetTester2;
public:
AZ_RTTI(ShaderAsset2, "{823395A3-D570-49F4-99A9-D820CD1DEF98}", Data::AssetData);
static void Reflect(ReflectContext* context);
static constexpr char DisplayName[] = "Shader";
static constexpr char Extension[] = "azshader2";
static constexpr char Group[] = "Shader";
//! The default shader variant (i.e. the one without any options set).
static const ShaderVariantStableId RootShaderVariantStableId;
// @subProductType is one of ShaderAsset2ProductSubId, or ShaderAsset2ProductSubId::FirstByProduct+
static uint32_t MakeProductAssetSubId(uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, uint32_t subProductType);
static SupervariantIndex GetSupervariantIndexFromProductAssetSubId(uint32_t assetProducSubId);
static SupervariantIndex GetSupervariantIndexFromAssetId(const Data::AssetId& assetId);
ShaderAsset2() = default;
~ShaderAsset2();
AZ_DISABLE_COPY_MOVE(ShaderAsset2);
//! Returns the name of the shader.
const Name& GetName() const;
//! Returns the pipeline state type generated by variants of this shader.
RHI::PipelineStateType GetPipelineStateType() const;
//! Returns the draw list tag name.
//! To get the corresponding DrawListTag use DrawListTagRegistry's FindTag() or AcquireTag() (see
//! RHISystemInterface::GetDrawListTagRegistry()). The DrawListTag is also available in the Shader that corresponds to this
//! ShaderAsset2.
const Name& GetDrawListName() const;
//! Return the timestamp when the shader asset was built.
//! This is used to synchronize versions of the ShaderAsset2 and ShaderVariantTreeAsset, especially during hot-reload.
AZStd::sys_time_t GetShaderAssetBuildTimestamp() const;
//! Returns the shader option group layout.
const ShaderOptionGroupLayout* GetShaderOptionGroupLayout() const;
SupervariantIndex GetSupervariantIndex(const AZ::Name& supervariantName) const;
//! This function should be your one stop shop to get a ShaderVariantAsset.
//! Finds and returns the best matching ShaderVariantAsset given a ShaderVariantId.
//! If the ShaderVariantAsset is not fully loaded and ready at the moment, this function
//! will QueueLoad the ShaderVariantTreeAsset and subsequently will QueueLoad the ShaderVariantAsset.
//! The called will be notified via the ShaderVariantFinderNotificationBus when the
//! ShaderVariantAsset is loaded and ready.
//! In the mean time, if the required variant is not available this function
//! returns the Root Variant.
Data::Asset<ShaderVariantAsset2> GetVariant(
const ShaderVariantId& shaderVariantId, SupervariantIndex supervariantIndex);
Data::Asset<ShaderVariantAsset2> GetVariant(const ShaderVariantId& shaderVariantId) { return GetVariant(shaderVariantId, DefaultSupervariantIndex); }
//! Finds the best matching shader variant and returns its StableId.
//! This function first loads and caches the ShaderVariantTreeAsset (if not done before).
//! If the ShaderVariantTreeAsset is not found (either the AssetProcessor has not generated it yet, or it simply doesn't exist), then
//! it returns a search result that identifies the root variant.
//! This function is thread safe.
ShaderVariantSearchResult FindVariantStableId(const ShaderVariantId& shaderVariantId);
//! Returns the variant asset associated with the provided StableId.
//! The user should call FindVariantStableId() first to get a ShaderVariantStableId from a ShaderVariantId,
//! Or better yet, call GetVariant(ShaderVariantId) for maximum convenience.
//! If the requested variant is not found, the root variant will be returned AND the requested variant will be queued for loading.
//! Next time around if the variant has been loaded this function will return it. Alternatively
//! the caller can register with the ShaderVariantFinderNotificationBus to get the asset as soon as is available.
//! This function is thread safe.
Data::Asset<ShaderVariantAsset2> GetVariant(
ShaderVariantStableId shaderVariantStableId, SupervariantIndex supervariantIndex) const;
Data::Asset<ShaderVariantAsset2> GetVariant(ShaderVariantStableId shaderVariantStableId) const { return GetVariant(shaderVariantStableId, DefaultSupervariantIndex); }
Data::Asset<ShaderVariantAsset2> GetRootVariant(SupervariantIndex supervariantIndex) const;
Data::Asset<ShaderVariantAsset2> GetRootVariant() const { return GetRootVariant(DefaultSupervariantIndex); }
//! Finds and returns the shader resource group asset with the requested name. Returns an empty handle if no matching group was
//! found.
const RHI::Ptr<RHI::ShaderResourceGroupLayout> FindShaderResourceGroupLayout(
const Name& shaderResourceGroupName, SupervariantIndex supervariantIndex) const;
const RHI::Ptr<RHI::ShaderResourceGroupLayout> FindShaderResourceGroupLayout(const Name& shaderResourceGroupName) const
{
return FindShaderResourceGroupLayout(shaderResourceGroupName, DefaultSupervariantIndex);
}
//! Finds and returns the shader resource group layout associated with the requested binding slot. Returns an empty handle if no matching srg was found.
const RHI::Ptr<RHI::ShaderResourceGroupLayout> FindShaderResourceGroupLayout(
uint32_t bindingSlot, SupervariantIndex supervariantIndex) const;
const RHI::Ptr<RHI::ShaderResourceGroupLayout> FindShaderResourceGroupLayout(uint32_t bindingSlot) const
{
return FindShaderResourceGroupLayout(bindingSlot, DefaultSupervariantIndex);
}
//! Finds and returns the shader resource group layout designated as a ShaderVariantKey fallback.
const RHI::Ptr<RHI::ShaderResourceGroupLayout> FindFallbackShaderResourceGroupLayout( SupervariantIndex supervariantIndex) const;
const RHI::Ptr<RHI::ShaderResourceGroupLayout> FindFallbackShaderResourceGroupLayout() const
{
return FindFallbackShaderResourceGroupLayout(DefaultSupervariantIndex);
}
//! Returns the set of shader resource group layouts owned by a given supervariant.
AZStd::array_view<RHI::Ptr<RHI::ShaderResourceGroupLayout>> GetShaderResourceGroupLayouts( SupervariantIndex supervariantIndex) const;
AZStd::array_view<RHI::Ptr<RHI::ShaderResourceGroupLayout>> GetShaderResourceGroupLayouts() const
{
return GetShaderResourceGroupLayouts(DefaultSupervariantIndex);
}
//! Returns the pipeline layout descriptor shared by all variants in the asset.
const RHI::PipelineLayoutDescriptor* GetPipelineLayoutDescriptor(SupervariantIndex supervariantIndex) const;
const RHI::PipelineLayoutDescriptor* GetPipelineLayoutDescriptor() const
{
return GetPipelineLayoutDescriptor(DefaultSupervariantIndex);
}
//! Returns the shader resource group asset that has per-draw frequency, which is added to every draw packet.
const RHI::Ptr<RHI::ShaderResourceGroupLayout> GetDrawSrgLayout(SupervariantIndex supervariantIndex) const;
const RHI::Ptr<RHI::ShaderResourceGroupLayout> GetDrawSrgLayout() const
{
return GetDrawSrgLayout(DefaultSupervariantIndex);
}
//! Returns the ShaderInputContract which describes which inputs the shader requires
const ShaderInputContract& GetInputContract(SupervariantIndex supervariantIndex) const;
const ShaderInputContract& GetInputContract() const
{
return GetInputContract(DefaultSupervariantIndex);
}
//! Returns the ShaderOuputContract which describes which outputs the shader requires
const ShaderOutputContract& GetOutputContract(SupervariantIndex supervariantIndex) const;
const ShaderOutputContract& GetOutputContract() const
{
return GetOutputContract(DefaultSupervariantIndex);
}
//! Returns the render states for the draw pipeline. Only used for draw pipelines.
const RHI::RenderStates& GetRenderStates(SupervariantIndex supervariantIndex) const;
const RHI::RenderStates& GetRenderStates() const
{
return GetRenderStates(DefaultSupervariantIndex);
}
//! Returns a list of arguments for the specified attribute, or nullopt_t if the attribute is not found. The list can be empty which is still valid.
AZStd::optional<RHI::ShaderStageAttributeArguments> GetAttribute(
const RHI::ShaderStage& shaderStage, const Name& attributeName, SupervariantIndex supervariantIndex) const;
AZStd::optional<RHI::ShaderStageAttributeArguments> GetAttribute(
const RHI::ShaderStage& shaderStage, const Name& attributeName) const
{
return GetAttribute(shaderStage, attributeName, DefaultSupervariantIndex);
}
private:
///////////////////////////////////////////////////////////////////
/// AssetBus overrides
void OnAssetReloaded(Data::Asset<Data::AssetData> asset) override;
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
/// ShaderVariantFinderNotificationBus2 overrides
void OnShaderVariantTreeAssetReady(Data::Asset<ShaderVariantTreeAsset> shaderVariantTreeAsset, bool isError) override;
void OnShaderVariantAssetReady(Data::Asset<ShaderVariantAsset2> /*shaderVariantAsset*/, bool /*isError*/) override {};
///////////////////////////////////////////////////////////////////
//! A Supervariant represents a set of static shader compilation parameters.
//! Those parameters can be predefined c-preprocessor macros or specific arguments
//! for AZSLc.
//! For each Supervariant there's a unique Root ShaderVariantAsset, and possibly an N amount
//! of ShaderVariantAssets. The 'N' amount is the same across all Supervariants because all Supervariants
//! share the same ShaderVariantTreeAsset.
struct Supervariant
{
AZ_TYPE_INFO(Supervariant, "{850826EF-B267-4752-92F6-A85E4175CAB8}");
static void Reflect(AZ::ReflectContext* context);
AZ::Name m_name;
ShaderResourceGroupLayoutList m_srgLayoutList;
RHI::Ptr<RHI::PipelineLayoutDescriptor> m_pipelineLayoutDescriptor;
ShaderInputContract m_inputContract;
ShaderOutputContract m_outputContract;
RHI::RenderStates m_renderStates;
RHI::ShaderStageAttributeMapList m_attributeMaps;
Data::Asset<ShaderVariantAsset2> m_rootShaderVariantAsset;
};
//! Container of shader data that is specific to an RHI API.
//! A ShaderAsset2 can contain shader data for multiple RHI APIs if
//! the platform support multiple RHIs.
struct ShaderApiDataContainer
{
AZ_TYPE_INFO(ShaderApiDataContainer, "{C636722C-60B9-421C-ACAD-9750BF634A27}");
static void Reflect(AZ::ReflectContext* context);
//! RHI API Type for this shader data.
RHI::APIType m_APIType;
// Index 0, will always be the default Supervariant. (see DefaultSupervariantIndex)
AZStd::vector<Supervariant> m_supervariants;
};
bool FinalizeAfterLoad();
void SetReady();
ShaderApiDataContainer& GetCurrentShaderApiData();
const ShaderApiDataContainer& GetCurrentShaderApiData() const;
//! Returning pointers instead of references to allow for error checking
//! and not having to assert.
Supervariant* GetSupervariant(SupervariantIndex supervariantIndex);
const Supervariant* GetSupervariant(SupervariantIndex supervariantIndex) const;
//! The name is the stem of the source <name>.shader file.
Name m_name;
//! Dictates the type of pipeline state generated by this asset (Draw / Dispatch / etc.).
//! All shader variants, across all supervariants, in the asset adhere to this type.
RHI::PipelineStateType m_pipelineStateType = RHI::PipelineStateType::Count;
//! Defines the layout of the shader options in the asset.
Ptr<ShaderOptionGroupLayout> m_shaderOptionGroupLayout;
//! List with shader data per RHI backend.
AZStd::vector<ShaderApiDataContainer> m_perAPIShaderData;
Name m_drawListName;
//! Use to synchronize versions of the ShaderAsset2 and ShaderVariantTreeAsset, especially during hot-reload.
AZStd::sys_time_t m_shaderAssetBuildTimestamp = 0;
///////////////////////////////////////////////////////////////////
//! Do Not Serialize!
static constexpr size_t InvalidAPITypeIndex = std::numeric_limits<size_t>::max();
//! Index that indicates which ShaderDataContainer to use.
//! At runtime, the asset checks the current active RHI Backend
//! and based on the results this variable gets set on asset load.
//! The vector @m_perAPIShaderData will be indexed with this variable.
size_t m_currentAPITypeIndex = InvalidAPITypeIndex;
//! We can not know the ShaderVariantTreeAsset by the time this asset is being created.
//! This is a value that is discovered at run time. It becomes valid when FindVariantStableId is called at least once.
Data::Asset<ShaderVariantTreeAsset> m_shaderVariantTree;
//! Used for thread safety for FindVariantStableId().
mutable AZStd::shared_mutex m_variantTreeMutex;
bool m_shaderVariantTreeLoadWasRequested = false;
};
class ShaderAssetHandler2 final
: public AssetHandler<ShaderAsset2>
{
using Base = AssetHandler<ShaderAsset2>;
public:
ShaderAssetHandler2() = default;
private:
Data::AssetHandler::LoadResult LoadAssetData(
const Data::Asset<Data::AssetData>& asset,
AZStd::shared_ptr<Data::AssetDataStream> stream,
const Data::AssetFilterCB& assetLoadFilterCB) override;
Data::AssetHandler::LoadResult PostLoadInit(const Data::Asset<Data::AssetData>& asset);
};
//////////////////////////////////////////////////////////////////////////
} // namespace RPI
} // namespace AZ

@ -0,0 +1,97 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Atom/RPI.Reflect/AssetCreator.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <AzCore/std/containers/map.h>
namespace AZ
{
namespace RPI
{
class ShaderAssetCreator2
: public AssetCreator<ShaderAsset2>
{
public:
//! Begins creation of a shader asset.
void Begin(const Data::AssetId& assetId);
//! [Optional] Set the timestamp for when the ShaderAsset build process began.
//! This is needed to synchronize between the ShaderAsset and ShaderVariantTreeAsset when hot-reloading shaders.
void SetShaderAssetBuildTimestamp(AZStd::sys_time_t shaderAssetBuildTimestamp);
//! [Optional] Sets the name of the shader asset from content.
void SetName(const Name& name);
//! [Optional] Sets the DrawListTag name associated with this shader.
void SetDrawListName(const Name& name);
//! [Required] Assigns the layout used to construct and parse shader options packed into shader variant keys.
//! Requires that the keys assigned to shader variants were constructed using the same layout.
void SetShaderOptionGroupLayout(const Ptr<ShaderOptionGroupLayout>& shaderOptionGroupLayout);
//! Begins the shader creation for a specific RHI API.
//! Begin must be called before the BeginAPI function is called.
//! @param type The target RHI API type.
void BeginAPI(RHI::APIType type);
//! Begins the creation of a Supervariant for the current RHI::APIType.
//! If this is the first supervariant its name must be empty. The first
//! supervariant is always the default, nameless, supervariant.
void BeginSupervariant(const Name& name);
void SetSrgLayoutList(const ShaderResourceGroupLayoutList& srgLayoutList);
//! [Required] Assigns the pipeline layout descriptor shared by all variants in the shader. Shader variants
//! embedded in a single shader asset are required to use the same pipeline layout. It is not necessary to call
//! Finalize() on the pipeline layout prior to assignment, but still permitted.
void SetPipelineLayout(RHI::Ptr<RHI::PipelineLayoutDescriptor> m_pipelineLayoutDescriptor);
//! Assigns the contract for inputs required by the shader.
void SetInputContract(const ShaderInputContract& contract);
//! Assigns the contract for outputs required by the shader.
void SetOutputContract(const ShaderOutputContract& contract);
//! Assigns the render states for the draw pipeline. Ignored for non-draw pipelines.
void SetRenderStates(const RHI::RenderStates& renderStates);
//! [Optional] Not all shaders have attributes before functions. Some attributes do not exist for all RHI::APIType either.
void SetShaderStageAttributeMapList(const RHI::ShaderStageAttributeMapList& shaderStageAttributeMapList);
//! [Required] There's always a root variant for each supervariant.
void SetRootShaderVariantAsset(Data::Asset<ShaderVariantAsset2> shaderVariantAsset);
bool EndSupervariant();
bool EndAPI();
bool End(Data::Asset<ShaderAsset2>& shaderAsset);
//! Clones an existing ShaderAsset.
void Clone(const Data::AssetId& assetId,
const ShaderAsset2& sourceShaderAsset);
private:
// Shader variants will use this draw list when they don't specify one.
Name m_defaultDrawList;
// The current supervariant is cached here to facilitate asset
// construction. Additionally, prevents BeginSupervariant to be called more than once before calling EndSupervariant.
ShaderAsset2::Supervariant* m_currentSupervariant = nullptr;
};
} // namespace RPI
} // namespace AZ

@ -0,0 +1,56 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/RTTI/TypeInfo.h>
#include <Atom/RHI.Reflect/Handle.h>
namespace AZ
{
namespace RPI
{
// Common bit positions for ShaderAsset2 and ShaderVariantAsset2 product SubIds.
static constexpr uint32_t RhiIndexBitPosition = 30;
static constexpr uint32_t RhiIndexNumBits = 32 - RhiIndexBitPosition;
static constexpr uint32_t RhiIndexMaxValue = (1 << RhiIndexNumBits) - 1;
static constexpr uint32_t SupervariantIndexBitPosition = 22;
static constexpr uint32_t SupervariantIndexNumBits = RhiIndexBitPosition - SupervariantIndexBitPosition;
static constexpr uint32_t SupervariantIndexMaxValue = (1 << SupervariantIndexNumBits) - 1;
//! A wrapper around a supervariant index for type conformity.
//! A supervariant index is required to find shader data from
//! Shader2 and ShaderAsset2 related APIs.
using SupervariantIndex = RHI::Handle<uint32_t, class ShaderAsset2>;
static const SupervariantIndex DefaultSupervariantIndex(0);
static const SupervariantIndex InvalidSupervariantIndex;
enum class ShaderStageType : uint32_t
{
Vertex,
Geometry,
TessellationControl,
TessellationEvaluation,
Fragment,
Compute,
RayTracing
};
const char* ToString(ShaderStageType shaderStageType);
void ReflectShaderStageType(ReflectContext* context);
} // namespace RPI
AZ_TYPE_INFO_SPECIALIZE(RPI::ShaderStageType, "{A6408508-748B-4963-B618-E1E6ECA3629A}");
} // namespace AZ

@ -43,8 +43,13 @@ namespace AZ
static constexpr const char* DisplayName = "ShaderVariant";
static constexpr const char* Group = "Shader";
static constexpr uint32_t ShaderVariantAssetSubProductType = 0;
//! @rhiApiUniqueIndex comes from RHI::Factory::GetAPIUniqueIndex()
static uint32_t GetAssetSubId(uint32_t rhiApiUniqueIndex, ShaderVariantStableId variantStableId);
//! @subProductType is always 0 for a regular ShaderVariantAsset, for all other debug subProducts created
//! by ShaderVariantAssetBuilder this is 1+.
static uint32_t MakeAssetProductSubId(
uint32_t rhiApiUniqueIndex, ShaderVariantStableId variantStableId,
uint32_t subProductType = ShaderVariantAssetSubProductType);
ShaderVariantAsset() = default;
~ShaderVariantAsset() = default;

@ -0,0 +1,104 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/containers/array.h>
#include <Atom/RHI.Reflect/ShaderStageFunction.h>
#include <Atom/RPI.Reflect/Asset/AssetHandler.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantKey.h>
namespace AZ
{
namespace RPI
{
//! A ShaderVariantAsset2 contains the shader byte code for each shader stage (Vertex, Fragment, Tessellation, etc) for a given RHI::APIType (dx12, vulkan, metal, etc).
//! One independent file per RHI::APIType.
class ShaderVariantAsset2 final
: public Data::AssetData
{
friend class ShaderVariantAssetHandler2;
friend class ShaderVariantAssetCreator2;
public:
AZ_RTTI(ShaderVariantAsset2, "{51BED815-36D8-410E-90F0-1FA9FF765FBA}", Data::AssetData);
static void Reflect(ReflectContext* context);
static constexpr const char* Extension = "azshadervariant2";
static constexpr const char* DisplayName = "ShaderVariant";
static constexpr const char* Group = "Shader";
static constexpr uint32_t ShaderVariantAsset2SubProductType = 1;
//! @rhiApiUniqueIndex comes from RHI::Factory::GetAPIUniqueIndex()
//! @subProductType is always 0 for a regular ShaderVariantAsset2, for all other debug subProducts created
//! by ShaderVariantAssetBuilder2 this is 1+.
static uint32_t MakeAssetProductSubId(
uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, ShaderVariantStableId variantStableId,
uint32_t subProductType = ShaderVariantAsset2SubProductType);
ShaderVariantAsset2() = default;
~ShaderVariantAsset2() = default;
AZ_DISABLE_COPY_MOVE(ShaderVariantAsset2);
RPI::ShaderVariantStableId GetStableId() const { return m_stableId; }
const ShaderVariantId& GetShaderVariantId() const { return m_shaderVariantId; }
//! Returns the shader stage function associated with the provided stage enum value.
const RHI::ShaderStageFunction* GetShaderStageFunction(RHI::ShaderStage shaderStage) const;
//! Returns whether the variant is fully baked variant (all options are static branches), or false if the
//! variant uses dynamic branches for some shader options.
//! If the shader variant is not fully baked, the ShaderVariantKeyFallbackValue must be correctly set when drawing.
bool IsFullyBaked() const;
//! Return the timestamp when this asset was built, and it must be >= than the timestamp of the main ShaderAsset.
//! This is used to synchronize versions of the ShaderAsset and ShaderVariantAsset2, especially during hot-reload.
AZStd::sys_time_t GetBuildTimestamp() const;
bool IsRootVariant() const { return m_stableId == RPI::RootShaderVariantStableId; }
private:
//! Called by asset creators to assign the asset to a ready state.
void SetReady();
bool FinalizeAfterLoad();
//! See AZ::RPI::ShaderVariantListSourceData::VariantInfo::m_stableId for details.
RPI::ShaderVariantStableId m_stableId;
ShaderVariantId m_shaderVariantId;
bool m_isFullyBaked = false;
AZStd::array<RHI::Ptr<RHI::ShaderStageFunction>, RHI::ShaderStageCount> m_functionsByStage;
//! Used to synchronize versions of the ShaderAsset and ShaderVariantAsset2, especially during hot-reload.
AZStd::sys_time_t m_buildTimestamp = 0;
};
class ShaderVariantAssetHandler2 final
: public AssetHandler<ShaderVariantAsset2>
{
using Base = AssetHandler<ShaderVariantAsset2>;
public:
ShaderVariantAssetHandler2() = default;
private:
LoadResult LoadAssetData(const Data::Asset<Data::AssetData>& asset, AZStd::shared_ptr<Data::AssetDataStream> stream, const AZ::Data::AssetFilterCB& assetLoadFilterCB) override;
bool PostLoadInit(const Data::Asset<Data::AssetData>& asset);
};
} // namespace RPI
} // namespace AZ

@ -26,6 +26,7 @@
#include <Atom/RPI.Reflect/Asset/AssetHandler.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderResourceGroupAsset.h>
#include <Atom/RPI.Reflect/Image/StreamingImagePoolAsset.h>
#include <Atom/RPI.Reflect/Buffer/BufferAsset.h>
@ -35,6 +36,7 @@
#include <Atom/RPI.Reflect/Model/SkinMetaAsset.h>
#include <Atom/RPI.Reflect/Pass/PassAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantTreeAsset.h>
#include <BuilderComponent.h>
@ -88,6 +90,7 @@ namespace AZ
m_assetWorkers.emplace_back(MakeAssetBuilder<PassBuilder>());
m_assetHandlers.emplace_back(MakeAssetHandler<ShaderAssetHandler>());
m_assetHandlers.emplace_back(MakeAssetHandler<ShaderAssetHandler2>());
m_assetHandlers.emplace_back(MakeAssetHandler<ShaderResourceGroupAssetHandler>());
m_assetHandlers.emplace_back(MakeAssetHandler<MaterialTypeAssetHandler>());
m_assetHandlers.emplace_back(MakeAssetHandler<MaterialAssetHandler>());
@ -98,6 +101,7 @@ namespace AZ
m_assetHandlers.emplace_back(MakeAssetHandler<ModelAssetHandler>());
m_assetHandlers.emplace_back(MakeAssetHandler<PassAssetHandler>());
m_assetHandlers.emplace_back(MakeAssetHandler<ShaderVariantAssetHandler>());
m_assetHandlers.emplace_back(MakeAssetHandler<ShaderVariantAssetHandler2>());
m_assetHandlers.emplace_back(MakeAssetHandler<ShaderVariantTreeAssetHandler>());
m_assetHandlers.emplace_back(MakeAssetHandler<SkinMetaAssetHandler>());
m_assetHandlers.emplace_back(MakeAssetHandler<MorphTargetMetaAssetHandler>());

@ -11,19 +11,19 @@
*/
#include <Atom/RPI.Edit/Shader/ShaderSourceData.h>
#include <AzCore/std/string/regex.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace AZ
{
namespace RPI
{
const char* ShaderSourceData::Extension = "shader";
void ShaderSourceData::Reflect(ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<ShaderSourceData>()
->Version(3)
->Version(4)
->Field("Source", &ShaderSourceData::m_source)
->Field("DrawList", &ShaderSourceData::m_drawListName)
->Field("DepthStencilState", &ShaderSourceData::m_depthStencilState)
@ -31,8 +31,8 @@ namespace AZ
->Field("BlendState", &ShaderSourceData::m_blendState)
->Field("ProgramSettings", &ShaderSourceData::m_programSettings)
->Field("CompilerHints", &ShaderSourceData::m_compiler)
->Field("ShaderVariantHints", &ShaderSourceData::m_shaderOptionGroupHints)
->Field("DisabledRHIBackends", &ShaderSourceData::m_disabledRhiBackends)
->Field("Supervariants", &ShaderSourceData::m_supervariants)
;
serializeContext->Class<ShaderSourceData::ProgramSettings>()
@ -45,15 +45,145 @@ namespace AZ
->Field("Name", &EntryPoint::m_name)
->Field("Type", &EntryPoint::m_type)
;
serializeContext->Class<ShaderSourceData::SupervariantInfo>()
->Version(1)
->Field("Name", &SupervariantInfo::m_name)
->Field("PlusArguments", &SupervariantInfo::m_plusArguments)
->Field("MinusArguments", &SupervariantInfo::m_minusArguments);
}
}
bool ShaderSourceData::IsRhiBackendDisabled(const AZ::Name& rhiName)
bool ShaderSourceData::IsRhiBackendDisabled(const AZ::Name& rhiName) const
{
return AZStd::any_of(m_disabledRhiBackends.begin(), m_disabledRhiBackends.end(), [&](const AZStd::string& currentRhiName)
{
return currentRhiName == rhiName.GetStringView();
});
}
//! Helper function.
//! Parses a string of command line arguments looking for c-preprocessor macro definitions and appends the name of macro definition arguments.
//! Example:
//! Input string: "--switch1 -DMACRO1 -v -DMACRO2=23"
//! append the following items: ["MACRO1", "MACRO2"]
static void GetListOfMacroDefinitionNames(
const AZStd::string& stringWithArguments, AZStd::vector<AZStd::string>& macroDefinitionNames)
{
static const AZStd::regex macroRegex("-D\\s*(\\w+)", AZStd::regex::ECMAScript);
AZStd::cmatch match;
if (AZStd::regex_search(stringWithArguments.c_str(), match, macroRegex))
{
// First pattern is always the entire string
for (unsigned i = 1; i < match.size(); ++i)
{
if (match[i].matched)
{
macroDefinitionNames.push_back(match[i].str().c_str());
}
}
}
}
AZStd::vector<AZStd::string> ShaderSourceData::SupervariantInfo::GetCombinedListOfMacroDefinitionNamesToRemove() const
{
AZStd::vector<AZStd::string> macroDefinitionNames;
GetListOfMacroDefinitionNames(m_minusArguments, macroDefinitionNames);
GetListOfMacroDefinitionNames(m_plusArguments, macroDefinitionNames);
return macroDefinitionNames;
}
//! Helper function.
//! Parses a string of command line arguments looking for c-preprocessor macro definitions and appends macro definition
//! arguments. Example: Input string: "--switch1 -DMACRO1 -v -DMACRO2=23" append the following items: ["MACRO1", "MACRO2=23"]
static void GetListOfMacroDefinitions(
const AZStd::string& stringWithArguments, AZStd::vector<AZStd::string>& macroDefinitions)
{
static const AZStd::regex macroRegex("-D\\s*(\\w+(=\\w+)?)", AZStd::regex::ECMAScript);
AZStd::cmatch match;
if (AZStd::regex_search(stringWithArguments.c_str(), match, macroRegex))
{
// First pattern is always the entire string
for (unsigned i = 1; i < match.size(); ++i)
{
if (match[i].matched)
{
macroDefinitions.push_back(match[i].str().c_str());
}
}
}
}
AZStd::vector<AZStd::string> ShaderSourceData::SupervariantInfo::GetMacroDefinitionsToAdd() const
{
AZStd::vector<AZStd::string> parsedMacroDefinitions;
GetListOfMacroDefinitions(m_plusArguments, parsedMacroDefinitions);
return parsedMacroDefinitions;
}
// Helper.
// @arguments: A string with command line arguments for a console application of the form:
// "-<arg1> --<arg2> --<arg3>[=<value3>] ..."
// Example: "--use-spaces --namespace=vk"
// Returns: A list with just the [-|--]<argument name>:
// ["-<arg1>", "--<arg2>", "--arg3"]
// For the example shown above it will return this vector:
// ["--use-spaces", "--namespace"]
AZStd::vector<AZStd::string> GetListOfArgumentNames(const AZStd::string& arguments)
{
AZStd::vector<AZStd::string> listOfTokens;
AzFramework::StringFunc::Tokenize(arguments, listOfTokens);
AZStd::vector<AZStd::string> listOfArguments;
for (const AZStd::string& token : listOfTokens)
{
AZStd::vector<AZStd::string> splitArguments;
AzFramework::StringFunc::Tokenize(token, splitArguments, "=");
listOfArguments.push_back(splitArguments[0]);
}
return listOfArguments;
}
AZStd::string ShaderSourceData::SupervariantInfo::GetCustomizedArgumentsForAzslc(
const AZStd::string& initialAzslcCompilerArguments) const
{
static const AZStd::regex macroRegex("-D\\s*(\\w+(=\\S+)?)", AZStd::regex::ECMAScript);
// We are only concerned with AZSLc arguments. Let's remove the C-Preprocessor macro definitions
// from @minusArguments.
const AZStd::string minusArguments = AZStd::regex_replace(m_minusArguments, macroRegex, "");
const AZStd::string plusArguments = AZStd::regex_replace(m_plusArguments, macroRegex, "");
AZStd::string azslcArgumentsToRemove = minusArguments + " " + plusArguments;
AZStd::vector<AZStd::string> azslcArgumentNamesToRemove = GetListOfArgumentNames(azslcArgumentsToRemove);
// At this moment @azslcArgumentsToRemove contains arguments for AZSLc that can be of the form:
// -<arg>
// --<arg>[=<value>]
// We need to remove those from @initialAzslcCompilerArguments.
AZStd::string customizedArguments = initialAzslcCompilerArguments;
for (const AZStd::string& azslcArgumentName : azslcArgumentNamesToRemove)
{
AZStd::string regexStr = AZStd::string::format("%s(=\\S+)?", azslcArgumentName.c_str());
AZStd::regex replaceRegex(regexStr, AZStd::regex::ECMAScript);
customizedArguments = AZStd::regex_replace(customizedArguments, replaceRegex, "");
}
customizedArguments += " " + plusArguments;
// Will contain the results that will be joined by a space.
// This is used to get a clean string to return without excess spaces.
AZStd::vector<AZStd::string> argumentList;
AzFramework::StringFunc::Tokenize(customizedArguments, argumentList, " \t\n");
customizedArguments.clear(); // Need to clear because Join appends.
AzFramework::StringFunc::Join(customizedArguments, argumentList.begin(), argumentList.end(), " ");
return customizedArguments;
}
} // namespace RPI
} // namespace AZ

@ -0,0 +1,112 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Atom/RPI.Edit/Shader/ShaderVariantAssetCreator2.h>
#include <AzCore/Utils/TypeHash.h>
#include <Atom/RPI.Reflect/Shader/ShaderInputContract.h>
#include <Atom/RPI.Reflect/Shader/ShaderOutputContract.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
namespace AZ
{
namespace RPI
{
void ShaderVariantAssetCreator2::Begin(const AZ::Data::AssetId& assetId, const ShaderVariantId& shaderVariantId, RPI::ShaderVariantStableId stableId, bool isFullyBaked)
{
BeginCommon(assetId);
if (ValidateIsReady())
{
m_asset->m_stableId = stableId;
m_asset->m_shaderVariantId = shaderVariantId;
m_asset->m_isFullyBaked = isFullyBaked;
}
}
bool ShaderVariantAssetCreator2::End(Data::Asset<ShaderVariantAsset2>& result)
{
if (!ValidateIsReady())
{
return false;
}
if (!m_asset->FinalizeAfterLoad())
{
ReportError("Failed to finalize the ShaderResourceGroupAsset.");
return false;
}
bool foundDrawFunctions = false;
bool foundDispatchFunctions = false;
if (m_asset->GetShaderStageFunction(RHI::ShaderStage::Vertex) ||
m_asset->GetShaderStageFunction(RHI::ShaderStage::Tessellation) ||
m_asset->GetShaderStageFunction(RHI::ShaderStage::Fragment))
{
foundDrawFunctions = true;
}
if (m_asset->GetShaderStageFunction(RHI::ShaderStage::Compute))
{
foundDispatchFunctions = true;
}
if (foundDrawFunctions && foundDispatchFunctions)
{
ReportError("ShaderVariant contains both Draw functions and Dispatch functions.");
return false;
}
if (m_asset->GetShaderStageFunction(RHI::ShaderStage::Fragment) &&
!m_asset->GetShaderStageFunction(RHI::ShaderStage::Vertex))
{
ReportError("Shader Variant with StableId '%u' has a fragment function but no vertex function.", m_asset->m_stableId);
return false;
}
if (m_asset->GetShaderStageFunction(RHI::ShaderStage::Tessellation) &&
!m_asset->GetShaderStageFunction(RHI::ShaderStage::Vertex))
{
ReportError("Shader Variant with StableId '%u' has a tessellation function but no vertex function.", m_asset->m_stableId);
return false;
}
m_asset->SetReady();
return EndCommon(result);
}
/////////////////////////////////////////////////////////////////////
// Methods for all shader variant types
void ShaderVariantAssetCreator2::SetBuildTimestamp(AZStd::sys_time_t buildTimestamp)
{
if (ValidateIsReady())
{
m_asset->m_buildTimestamp = buildTimestamp;
}
}
void ShaderVariantAssetCreator2::SetShaderFunction(RHI::ShaderStage shaderStage, RHI::Ptr<RHI::ShaderStageFunction> shaderStageFunction)
{
if (ValidateIsReady())
{
m_asset->m_functionsByStage[static_cast<size_t>(shaderStage)] = shaderStageFunction;
}
}
} // namespace RPI
} // namespace AZ

@ -0,0 +1,413 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Atom/RPI.Public/Shader/Shader2.h>
#include <AzCore/IO/SystemFile.h>
#include <Atom/RHI/RHISystemInterface.h>
#include <Atom/RHI/PipelineStateCache.h>
#include <Atom/RHI/Factory.h>
#include <AtomCore/Instance/InstanceDatabase.h>
#include <AzCore/Interface/Interface.h>
#include <Atom/RPI.Public/Shader/ShaderReloadNotificationBus2.h>
#include <Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h>
namespace AZ
{
namespace RPI
{
Data::Instance<Shader2> Shader2::FindOrCreate(const Data::Asset<ShaderAsset2>& shaderAsset, const Name& supervariantName)
{
Data::Instance<Shader2> shaderInstance = Data::InstanceDatabase<Shader2>::Instance().FindOrCreate(
Data::InstanceId::CreateFromAssetId(shaderAsset.GetId()),
shaderAsset);
if (!shaderInstance)
{
return nullptr;
}
if (!shaderInstance->SelectSupervariant(supervariantName))
{
return nullptr;
}
const RHI::ResultCode resultCode = shaderInstance->Init(*shaderAsset.Get());
if (resultCode != RHI::ResultCode::Success)
{
return nullptr;
}
return shaderInstance;
}
Data::Instance<Shader2> Shader2::CreateInternal([[maybe_unused]] ShaderAsset2& shaderAsset)
{
Data::Instance<Shader2> shader = aznew Shader2();
return shader;
}
Shader2::~Shader2()
{
Shutdown();
}
bool Shader2::SelectSupervariant(const Name& supervariantName)
{
if (supervariantName.IsEmpty())
{
m_supervariantIndex = DefaultSupervariantIndex;
return true;
}
auto supervariantIndex = m_asset->GetSupervariantIndex(supervariantName);
if (supervariantIndex == InvalidSupervariantIndex)
{
return false;
}
m_supervariantIndex = supervariantIndex;
return true;
}
RHI::ResultCode Shader2::Init(ShaderAsset2& shaderAsset)
{
AZ_Assert(m_supervariantIndex != InvalidSupervariantIndex, "Invalid supervariant index");
ShaderVariantFinderNotificationBus2::Handler::BusDisconnect();
ShaderVariantFinderNotificationBus2::Handler::BusConnect(shaderAsset.GetId());
RHI::RHISystemInterface* rhiSystem = RHI::RHISystemInterface::Get();
RHI::DrawListTagRegistry* drawListTagRegistry = rhiSystem->GetDrawListTagRegistry();
m_asset = { &shaderAsset, AZ::Data::AssetLoadBehavior::PreLoad };
m_pipelineStateType = shaderAsset.GetPipelineStateType();
{
AZStd::unique_lock<decltype(m_variantCacheMutex)> lock(m_variantCacheMutex);
m_shaderVariants.clear();
}
m_rootVariant.Init(shaderAsset, shaderAsset.GetRootVariant(m_supervariantIndex), m_supervariantIndex);
if (m_pipelineLibraryHandle.IsNull())
{
// We set up a pipeline library only once for the lifetime of the Shader2 instance.
// This should allow the Shader2 to be reloaded at runtime many times, and cache and reuse PipelineState objects rather than rebuild them.
// It also fixes a particular TDR crash that occurred on some hardware when hot-reloading shaders and building pipeline states
// in a new pipeline library every time.
RHI::PipelineStateCache* pipelineStateCache = rhiSystem->GetPipelineStateCache();
ConstPtr<RHI::PipelineLibraryData> serializedData = LoadPipelineLibrary();
RHI::PipelineLibraryHandle pipelineLibraryHandle = pipelineStateCache->CreateLibrary(serializedData.get());
if (pipelineLibraryHandle.IsNull())
{
AZ_Error("Shader2", false, "Failed to create pipeline library from pipeline state cache.");
return RHI::ResultCode::Fail;
}
m_pipelineLibraryHandle = pipelineLibraryHandle;
m_pipelineStateCache = pipelineStateCache;
}
const Name& drawListName = shaderAsset.GetDrawListName();
if (!drawListName.IsEmpty())
{
m_drawListTag = drawListTagRegistry->AcquireTag(drawListName);
if (!m_drawListTag.IsValid())
{
AZ_Error("Shader2", false, "Failed to acquire a DrawListTag. Entries are full.");
}
}
Data::AssetBus::Handler::BusConnect(m_asset.GetId());
return RHI::ResultCode::Success;
}
void Shader2::Shutdown()
{
ShaderVariantFinderNotificationBus2::Handler::BusDisconnect();
Data::AssetBus::Handler::BusDisconnect();
if (m_pipelineLibraryHandle.IsValid())
{
SavePipelineLibrary();
m_pipelineStateCache->ReleaseLibrary(m_pipelineLibraryHandle);
m_pipelineStateCache = nullptr;
m_pipelineLibraryHandle = {};
}
if (m_drawListTag.IsValid())
{
RHI::DrawListTagRegistry* drawListTagRegistry = RHI::RHISystemInterface::Get()->GetDrawListTagRegistry();
drawListTagRegistry->ReleaseTag(m_drawListTag);
m_drawListTag.Reset();
}
}
///////////////////////////////////////////////////////////////////////
// AssetBus overrides
void Shader2::OnAssetReloaded(Data::Asset<Data::AssetData> asset)
{
ShaderReloadDebugTracker::ScopedSection reloadSection("Shader2::OnAssetReloaded %s", asset.GetHint().c_str());
if (asset->GetId() == m_asset->GetId())
{
Data::Asset<ShaderAsset2> newAsset = { asset.GetAs<ShaderAsset2>(), AZ::Data::AssetLoadBehavior::PreLoad };
AZ_Assert(newAsset, "Reloaded ShaderAsset2 is null");
Data::AssetBus::Handler::BusDisconnect();
Init(*newAsset.Get());
ShaderReloadNotificationBus2::Event(asset.GetId(), &ShaderReloadNotificationBus2::Events::OnShaderReinitialized, *this);
}
}
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
/// ShaderVariantFinderNotificationBus2 overrides
void Shader2::OnShaderVariantAssetReady(Data::Asset<ShaderVariantAsset2> shaderVariantAsset, bool isError)
{
AZ_Assert(shaderVariantAsset, "Reloaded ShaderVariantAsset is null");
const ShaderVariantStableId stableId = shaderVariantAsset->GetStableId();
const ShaderVariantId& shaderVariantId = shaderVariantAsset->GetShaderVariantId();
if (isError)
{
//Remark: We do not assert if the stableId == RootShaderVariantStableId, because we can not trust in the asset data
//on error. so it is possible that on error the stbleId == RootShaderVariantStableId;
if (stableId == RootShaderVariantStableId)
{
return;
}
AZStd::unique_lock<decltype(m_variantCacheMutex)> lock(m_variantCacheMutex);
m_shaderVariants.erase(stableId);
}
else
{
AZ_Assert(stableId != RootShaderVariantStableId,
"The root variant is expected to be updated by the ShaderAsset2.");
AZStd::unique_lock<decltype(m_variantCacheMutex)> lock(m_variantCacheMutex);
auto iter = m_shaderVariants.find(stableId);
if (iter != m_shaderVariants.end())
{
ShaderVariant2& shaderVariant = iter->second;
if (!shaderVariant.Init(*m_asset.Get(), shaderVariantAsset, m_supervariantIndex))
{
AZ_Error("Shader2", false, "Failed to init shaderVariant with StableId=%u", shaderVariantAsset->GetStableId());
m_shaderVariants.erase(stableId);
}
}
else
{
//This is the first time the shader variant asset comes to life.
ShaderVariant2 newVariant;
newVariant.Init(*m_asset, shaderVariantAsset, m_supervariantIndex);
m_shaderVariants.emplace(stableId, newVariant);
}
}
//Even if there was an error, the interested parties should be notified.
ShaderReloadNotificationBus2::Event(m_asset.GetId(), &ShaderReloadNotificationBus2::Events::OnShaderVariantReinitialized, *this, shaderVariantId, stableId);
}
///////////////////////////////////////////////////////////////////
ConstPtr<RHI::PipelineLibraryData> Shader2::LoadPipelineLibrary() const
{
if (IO::FileIOBase::GetInstance())
{
return Utils::LoadObjectFromFile<RHI::PipelineLibraryData>(GetPipelineLibraryPath());
}
return nullptr;
}
void Shader2::SavePipelineLibrary() const
{
if (auto* fileIOBase = IO::FileIOBase::GetInstance())
{
RHI::ConstPtr<RHI::PipelineLibraryData> serializedData = m_pipelineStateCache->GetLibrarySerializedData(m_pipelineLibraryHandle);
if (serializedData)
{
const AZStd::string pipelineLibraryPath = GetPipelineLibraryPath();
char pipelineLibraryPathResolved[AZ_MAX_PATH_LEN] = { 0 };
fileIOBase->ResolvePath(pipelineLibraryPath.c_str(), pipelineLibraryPathResolved, AZ_MAX_PATH_LEN);
Utils::SaveObjectToFile(pipelineLibraryPathResolved, DataStream::ST_BINARY, serializedData.get());
}
}
else
{
AZ_Error("Shader2", false, "FileIOBase is not initialized");
}
}
AZStd::string Shader2::GetPipelineLibraryPath() const
{
const Data::InstanceId& instanceId = GetId();
Name platformName = RHI::Factory::Get().GetName();
Name shaderName = m_asset->GetName();
AZStd::string uuidString;
instanceId.m_guid.ToString<AZStd::string>(uuidString, false, false);
return AZStd::string::format("@user@/Atom/PipelineStateCache/%s/%s_%s_%d.bin", platformName.GetCStr(), shaderName.GetCStr(), uuidString.data(), instanceId.m_subId);
}
ShaderOptionGroup Shader2::CreateShaderOptionGroup() const
{
return ShaderOptionGroup(m_asset->GetShaderOptionGroupLayout());
}
const ShaderVariant2& Shader2::GetVariant(const ShaderVariantId& shaderVariantId)
{
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
Data::Asset<ShaderVariantAsset2> shaderVariantAsset = m_asset->GetVariant(shaderVariantId, m_supervariantIndex);
if (!shaderVariantAsset || shaderVariantAsset->IsRootVariant())
{
return m_rootVariant;
}
return GetVariant(shaderVariantAsset->GetStableId());
}
const ShaderVariant2& Shader2::GetRootVariant()
{
return m_rootVariant;
}
ShaderVariantSearchResult Shader2::FindVariantStableId(const ShaderVariantId& shaderVariantId) const
{
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
ShaderVariantSearchResult variantSearchResult = m_asset->FindVariantStableId(shaderVariantId);
return variantSearchResult;
}
const ShaderVariant2& Shader2::GetVariant(ShaderVariantStableId shaderVariantStableId)
{
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
if (!shaderVariantStableId.IsValid() || shaderVariantStableId == ShaderAsset2::RootShaderVariantStableId)
{
return m_rootVariant;
}
{
AZStd::shared_lock<decltype(m_variantCacheMutex)> lock(m_variantCacheMutex);
auto findIt = m_shaderVariants.find(shaderVariantStableId);
if (findIt != m_shaderVariants.end())
{
// When rebuilding shaders we may be in a state where the ShaderAsset2 and root ShaderVariantAsset have been rebuilt and
// reloaded, but some (or all) shader variants haven't been built yet. Since we want to use the latest version of the
// shader code, ignore the old variants and fall back to the newer root variant instead. There's no need to report a
// warning here because m_asset->GetVariant below will report one.
if (findIt->second.GetBuildTimestamp() >= m_asset->GetShaderAssetBuildTimestamp())
{
return findIt->second;
}
}
}
// By calling GetVariant, an asynchronous asset load request is enqueued if the variant
// is not fully ready.
Data::Asset<ShaderVariantAsset2> shaderVariantAsset = m_asset->GetVariant(shaderVariantStableId, m_supervariantIndex);
if (!shaderVariantAsset || shaderVariantAsset == m_asset->GetRootVariant())
{
// Return the root variant when the requested variant is not ready.
return m_rootVariant;
}
AZStd::unique_lock<decltype(m_variantCacheMutex)> lock(m_variantCacheMutex);
// For performance reasons We are breaking this function into two locking steps.
// which means We must check again if the variant is already in the cache.
auto findIt = m_shaderVariants.find(shaderVariantStableId);
if (findIt != m_shaderVariants.end())
{
if (findIt->second.GetBuildTimestamp() >= m_asset->GetShaderAssetBuildTimestamp())
{
return findIt->second;
}
else
{
// This is probably very rare, but if the variant was loaded on another thread and it's out of date
// we just return the root variant. Otherwise we could end up replacing the variant in the map below while
// it's being used for rendering.
AZ_Warning(
"Shader2", false,
"Detected an uncommon state during shader reload. Returning the root variant instead of replacing the old one.");
return m_rootVariant;
}
}
ShaderVariant2 newVariant;
newVariant.Init(*m_asset, shaderVariantAsset, m_supervariantIndex);
m_shaderVariants.emplace(shaderVariantStableId, newVariant);
return m_shaderVariants.at(shaderVariantStableId);
}
RHI::PipelineStateType Shader2::GetPipelineStateType() const
{
return m_pipelineStateType;
}
const ShaderInputContract& Shader2::GetInputContract() const
{
return m_asset->GetInputContract(m_supervariantIndex);
}
const ShaderOutputContract& Shader2::GetOutputContract() const
{
return m_asset->GetOutputContract(m_supervariantIndex);
}
const RHI::PipelineState* Shader2::AcquirePipelineState(const RHI::PipelineStateDescriptor& descriptor) const
{
return m_pipelineStateCache->AcquirePipelineState(m_pipelineLibraryHandle, descriptor);
}
const RHI::Ptr<RHI::ShaderResourceGroupLayout> Shader2::FindShaderResourceGroupLayout(const Name& shaderResourceGroupName) const
{
return m_asset->FindShaderResourceGroupLayout(shaderResourceGroupName, m_supervariantIndex);
}
const RHI::Ptr<RHI::ShaderResourceGroupLayout> Shader2::FindShaderResourceGroupLayout(uint32_t bindingSlot) const
{
return m_asset->FindShaderResourceGroupLayout(bindingSlot, m_supervariantIndex);
}
const RHI::Ptr<RHI::ShaderResourceGroupLayout> Shader2::FindFallbackShaderResourceGroupLayout() const
{
return m_asset->FindFallbackShaderResourceGroupLayout(m_supervariantIndex);
}
AZStd::array_view<RHI::Ptr<RHI::ShaderResourceGroupLayout>> Shader2::GetShaderResourceGroupLayouts() const
{
return m_asset->GetShaderResourceGroupLayouts(m_supervariantIndex);
}
const Data::Asset<ShaderAsset2>& Shader2::GetAsset() const
{
return m_asset;
}
RHI::DrawListTag Shader2::GetDrawListTag() const
{
return m_drawListTag;
}
} // namespace RPI
} // namespace AZ

@ -88,6 +88,41 @@ namespace AZ
return RHI::ResultCode::Success;
}
bool ShaderResourceGroup::ReplaceSrgLayoutUsingShaderAsset(
Data::Asset<ShaderAsset2> shaderAsset, const Name& supervariantName, const Name& srgName)
{
AZ_TRACE_METHOD();
SupervariantIndex supervariantIndex = shaderAsset->GetSupervariantIndex(supervariantName);
if (supervariantIndex == InvalidSupervariantIndex)
{
AZ_Assert(
false, "Supervariant with name [%s] not found in shader asset [%s]", supervariantName.GetCStr(),
shaderAsset->GetName().GetCStr());
return false;
}
m_layout = shaderAsset->FindShaderResourceGroupLayout(srgName, supervariantIndex).get();
if (!m_layout)
{
AZ_Assert(false, "ShaderResourceGroup cannot be initialized due to invalid ShaderResourceGroupLayout");
return false;
}
m_shaderResourceGroup->SetName(m_layout->GetName());
m_data = RHI::ShaderResourceGroupData(m_layout);
m_shaderAsset = shaderAsset;
// The RPI groups match the same dimensions as the RHI group.
m_imageGroup.clear();
m_imageGroup.resize(m_layout->GetGroupSizeForImages());
m_bufferGroup.clear();
m_bufferGroup.resize(m_layout->GetGroupSizeForBuffers());
return true;
}
void ShaderResourceGroup::Compile()
{
m_shaderResourceGroup->Compile(m_data);

@ -12,15 +12,18 @@
#include <Atom/RPI.Public/Shader/ShaderSystem.h>
#include <Atom/RPI.Public/Shader/Shader.h>
#include <Atom/RPI.Public/Shader/Shader2.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroupPool.h>
#include <Atom/RPI.Reflect/Asset/AssetHandler.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
#include <Atom/RPI.Reflect/Shader/ShaderResourceGroupAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantTreeAsset.h>
#include <Atom/RPI.Reflect/Shader/PrecompiledShaderAssetSourceData.h>
@ -42,9 +45,11 @@ namespace AZ
ShaderVariantId::Reflect(context);
ShaderVariantStableId::Reflect(context);
ShaderAsset::Reflect(context);
ShaderAsset2::Reflect(context);
ShaderInputContract::Reflect(context);
ShaderOutputContract::Reflect(context);
ShaderVariantAsset::Reflect(context);
ShaderVariantAsset2::Reflect(context);
ShaderVariantTreeAsset::Reflect(context);
ReflectShaderStageType(context);
PrecompiledShaderAssetSourceData::Reflect(context);
@ -58,8 +63,10 @@ namespace AZ
void ShaderSystem::GetAssetHandlers(AssetHandlerPtrList& assetHandlers)
{
assetHandlers.emplace_back(MakeAssetHandler<ShaderAssetHandler>());
assetHandlers.emplace_back(MakeAssetHandler<ShaderAssetHandler2>());
assetHandlers.emplace_back(MakeAssetHandler<ShaderResourceGroupAssetHandler>());
assetHandlers.emplace_back(MakeAssetHandler<ShaderVariantAssetHandler>());
assetHandlers.emplace_back(MakeAssetHandler<ShaderVariantAssetHandler2>());
assetHandlers.emplace_back(MakeAssetHandler<ShaderVariantTreeAssetHandler>());
}
@ -78,6 +85,14 @@ namespace AZ
Data::InstanceDatabase<Shader>::Create(azrtti_typeid<ShaderAsset>(), handler);
}
{
Data::InstanceHandler<Shader2> handler;
handler.m_createFunction = [](Data::AssetData* shaderAsset) {
return Shader2::CreateInternal(*(azrtti_cast<ShaderAsset2*>(shaderAsset)));
};
Data::InstanceDatabase<Shader2>::Create(azrtti_typeid<ShaderAsset2>(), handler);
}
{
Data::InstanceHandler<ShaderResourceGroup> handler;
handler.m_createFunction = [](Data::AssetData* srgAsset)
@ -100,6 +115,7 @@ namespace AZ
void ShaderSystem::Shutdown()
{
Data::InstanceDatabase<Shader>::Destroy();
Data::InstanceDatabase<Shader2>::Destroy();
Data::InstanceDatabase<ShaderResourceGroup>::Destroy();
Data::InstanceDatabase<ShaderResourceGroupPool>::Destroy();
Interface<ShaderSystemInterface>::Unregister(this);

@ -0,0 +1,76 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Atom/RPI.Public/Shader/ShaderVariant2.h>
#include <Atom/RHI/DrawListTagRegistry.h>
#include <Atom/RHI/RHISystemInterface.h>
#include <Atom/RHI.Reflect/ShaderStageFunction.h>
namespace AZ
{
namespace RPI
{
bool ShaderVariant2::Init(
const ShaderAsset2& shaderAsset,
Data::Asset<ShaderVariantAsset2> shaderVariantAsset,
SupervariantIndex supervariantIndex)
{
m_pipelineStateType = shaderAsset.GetPipelineStateType();
m_pipelineLayoutDescriptor = shaderAsset.GetPipelineLayoutDescriptor(supervariantIndex);
m_shaderVariantAsset = shaderVariantAsset;
m_renderStates = &shaderAsset.GetRenderStates(supervariantIndex);
return true;
}
void ShaderVariant2::ConfigurePipelineState(RHI::PipelineStateDescriptor& descriptor) const
{
descriptor.m_pipelineLayoutDescriptor = m_pipelineLayoutDescriptor;
switch (descriptor.GetType())
{
case RHI::PipelineStateType::Draw:
{
AZ_Assert(m_pipelineStateType == RHI::PipelineStateType::Draw, "ShaderVariant2 is not intended for the raster pipeline.");
AZ_Assert(m_renderStates, "Invalid RenderStates");
RHI::PipelineStateDescriptorForDraw& descriptorForDraw = static_cast<RHI::PipelineStateDescriptorForDraw&>(descriptor);
descriptorForDraw.m_vertexFunction = m_shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Vertex);
descriptorForDraw.m_tessellationFunction = m_shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Tessellation);
descriptorForDraw.m_fragmentFunction = m_shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Fragment);
descriptorForDraw.m_renderStates = *m_renderStates;
break;
}
case RHI::PipelineStateType::Dispatch:
{
AZ_Assert(m_pipelineStateType == RHI::PipelineStateType::Dispatch, "ShaderVariant2 is not intended for the compute pipeline.");
RHI::PipelineStateDescriptorForDispatch& descriptorForDispatch = static_cast<RHI::PipelineStateDescriptorForDispatch&>(descriptor);
descriptorForDispatch.m_computeFunction = m_shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Compute);
break;
}
case RHI::PipelineStateType::RayTracing:
{
AZ_Assert(m_pipelineStateType == RHI::PipelineStateType::RayTracing, "ShaderVariant2 is not intended for the ray tracing pipeline.");
RHI::PipelineStateDescriptorForRayTracing& descriptorForRayTracing = static_cast<RHI::PipelineStateDescriptorForRayTracing&>(descriptor);
descriptorForRayTracing.m_rayTracingFunction = m_shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::RayTracing);
break;
}
default:
AZ_Assert(false, "Unexpected PipelineStateType");
break;
}
}
} // namespace RPI
} // namespace AZ

@ -111,7 +111,7 @@ namespace AZ
ShaderMetricsSystem::Get()->RequestShaderVariant(pairItor->m_shaderAsset.Get(), pairItor->m_shaderVariantId, searchResult);
uint32_t shaderVariantProductSubId =
ShaderVariantAsset::GetAssetSubId(RHI::Factory::Get().GetAPIUniqueIndex(), searchResult.GetStableId());
ShaderVariantAsset::MakeAssetProductSubId(RHI::Factory::Get().GetAPIUniqueIndex(), searchResult.GetStableId());
Data::AssetId shaderVariantAssetId(shaderVariantTreeAsset.GetId().m_guid, shaderVariantProductSubId);
shaderVariantPendingRequests.insert(shaderVariantAssetId);
pairItor = newShaderVariantPendingRequests.erase(pairItor);
@ -211,7 +211,7 @@ namespace AZ
AZ_Assert(variantStableId != RootShaderVariantStableId, "Root Variants Are Found inside ShaderAssets");
uint32_t shaderVariantProductSubId =
ShaderVariantAsset::GetAssetSubId(RHI::Factory::Get().GetAPIUniqueIndex(), variantStableId);
ShaderVariantAsset::MakeAssetProductSubId(RHI::Factory::Get().GetAPIUniqueIndex(), variantStableId);
Data::AssetId shaderVariantAssetId(shaderVariantTreeAssetId.m_guid, shaderVariantProductSubId);
{
AZStd::unique_lock<decltype(m_mutex)> lock(m_mutex);
@ -299,7 +299,7 @@ namespace AZ
{
AZ_Assert(variantStableId != RootShaderVariantStableId, "Root Variants Are Found inside ShaderAssets");
uint32_t shaderVariantProductSubId = ShaderVariantAsset::GetAssetSubId(RHI::Factory::Get().GetAPIUniqueIndex(), variantStableId);
uint32_t shaderVariantProductSubId = ShaderVariantAsset::MakeAssetProductSubId(RHI::Factory::Get().GetAPIUniqueIndex(), variantStableId);
Data::AssetId shaderVariantAssetId(shaderVariantTreeAssetId.m_guid, shaderVariantProductSubId);
AZStd::unique_lock<decltype(m_mutex)> lock(m_mutex);

@ -32,6 +32,25 @@ namespace AZ
const ShaderVariantStableId ShaderAsset::RootShaderVariantStableId{ 0 };
uint32_t ShaderAsset::MakeAssetProductSubId(uint32_t rhiApiUniqueIndex, uint32_t subProductType)
{
static constexpr uint32_t RhiIndexBitPosition = 30;
static constexpr uint32_t RhiIndexNumBits = 32 - RhiIndexBitPosition;
static constexpr uint32_t RhiIndexMaxValue = (1 << RhiIndexNumBits) - 1;
static constexpr uint32_t SubProductTypeBitPosition = 0;
static constexpr uint32_t SubProductTypeNumBits = RhiIndexBitPosition - SubProductTypeBitPosition;
static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1;
static_assert(RhiIndexMaxValue == RHI::Limits::APIType::PerPlatformApiUniqueIndexMax);
AZ_Assert(rhiApiUniqueIndex <= RhiIndexMaxValue, "Invalid rhiApiUniqueIndex [%u]", rhiApiUniqueIndex);
AZ_Assert(subProductType <= SubProductTypeMaxValue, "Invalid subProductType [%u]", subProductType);
const uint32_t assetProductSubId = (rhiApiUniqueIndex << RhiIndexBitPosition) |
(subProductType << SubProductTypeBitPosition);
return assetProductSubId;
}
void ShaderAsset::ShaderApiDataContainer::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
@ -430,115 +449,5 @@ namespace AZ
///////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Deprecated System
//////////////////////////////////////////////////////////////////////////
const char* ToString(ShaderStageType shaderStageType)
{
switch (shaderStageType)
{
case ShaderStageType::Vertex: return "Vertex";
case ShaderStageType::Geometry: return "Geometry";
case ShaderStageType::TessellationControl: return "TessellationControl";
case ShaderStageType::TessellationEvaluation: return "TessellationEvaluation";
case ShaderStageType::Fragment: return "Fragment";
case ShaderStageType::Compute: return "Compute";
case ShaderStageType::RayTracing: return "RayTracing";
default:
AZ_Assert(false, "Unhandled type");
return "<Unknown>";
}
}
void ReflectShaderStageType(ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Enum<ShaderStageType>()
->Value(ToString(ShaderStageType::Vertex), ShaderStageType::Vertex)
->Value(ToString(ShaderStageType::Geometry), ShaderStageType::Geometry)
->Value(ToString(ShaderStageType::TessellationControl), ShaderStageType::TessellationControl)
->Value(ToString(ShaderStageType::TessellationEvaluation), ShaderStageType::TessellationEvaluation)
->Value(ToString(ShaderStageType::Fragment), ShaderStageType::Fragment)
->Value(ToString(ShaderStageType::Compute), ShaderStageType::Compute)
->Value(ToString(ShaderStageType::RayTracing), ShaderStageType::RayTracing)
;
}
}
ShaderAssetSubId ShaderStageToSubId(ShaderStageType stageType)
{
switch (stageType)
{
case RPI::ShaderStageType::Vertex:
return ShaderAssetSubId::AzVertexShader;
case RPI::ShaderStageType::Geometry:
return ShaderAssetSubId::AzGeometryShader;
case RPI::ShaderStageType::TessellationControl:
return ShaderAssetSubId::AzTessellationControlShader;
case RPI::ShaderStageType::TessellationEvaluation:
return ShaderAssetSubId::AzTessellationEvaluationShader;
case RPI::ShaderStageType::Fragment:
return ShaderAssetSubId::AzFragmentShader;
case RPI::ShaderStageType::Compute:
return ShaderAssetSubId::AzComputeShader;
case RPI::ShaderStageType::RayTracing:
return ShaderAssetSubId::AzRayTracingShader;
default:
AZ_Assert(false, "Trying to get a ShaderAssetSubId from an unknown ShaderStageType. Defaulting to a vertex shader.");
break;
}
return ShaderAssetSubId::AzVertexShader;
}
void ShaderStageDescriptor::Reflect(ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<ShaderStageDescriptor>()
->Version(1)
->Field("m_stageType", &ShaderStageDescriptor::m_stageType)
->Field("m_byteCode", &ShaderStageDescriptor::m_byteCode)
;
}
}
///////////////////////////////////////////////////////////////////////
// ShaderStageAsset
void ShaderStageAsset::Reflect(ReflectContext* context)
{
ShaderStageDescriptor::Reflect(context);
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<ShaderStageAsset>()
->Version(1)
->Field("m_descriptor", &ShaderStageAsset::m_descriptor)
->Field("m_srgLayouts", &ShaderStageAsset::m_srgLayouts)
;
}
}
ShaderStageAsset::ShaderStageAsset(const ShaderStageAsset& rhs)
{
*this = rhs;
}
ShaderStageAsset::ShaderStageAsset(ShaderStageAsset&& rhs)
: m_descriptor(AZStd::move(rhs.m_descriptor))
, m_srgLayouts(AZStd::move(rhs.m_srgLayouts))
{}
ShaderStageAsset& ShaderStageAsset::operator= (const ShaderStageAsset& rhs)
{
m_descriptor = rhs.m_descriptor;
m_srgLayouts = rhs.m_srgLayouts;
return *this;
}
///////////////////////////////////////////////////////////////////////
} // namespace RPI
} // namespace AZ

@ -0,0 +1,589 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/algorithm.h>
#include <Atom/RHI/Factory.h>
#include <AzCore/Interface/Interface.h>
#include <Atom/RPI.Reflect/Shader/IShaderVariantFinder2.h>
#include <Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h>
#include <Atom/RPI.Public/Shader/ShaderReloadNotificationBus2.h>
namespace AZ
{
namespace RPI
{
const ShaderVariantStableId ShaderAsset2::RootShaderVariantStableId{0};
static constexpr uint32_t SubProductTypeBitPosition = 0;
static constexpr uint32_t SubProductTypeNumBits = SupervariantIndexBitPosition - SubProductTypeBitPosition;
static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1;
static_assert(RhiIndexMaxValue == RHI::Limits::APIType::PerPlatformApiUniqueIndexMax);
uint32_t ShaderAsset2::MakeProductAssetSubId(
uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, uint32_t subProductType)
{
AZ_Assert(rhiApiUniqueIndex <= RhiIndexMaxValue, "Invalid rhiApiUniqueIndex [%u]", rhiApiUniqueIndex);
AZ_Assert(supervariantIndex <= SupervariantIndexMaxValue, "Invalid supervariantIndex [%u]", supervariantIndex);
AZ_Assert(subProductType <= SubProductTypeMaxValue, "Invalid subProductType [%u]", subProductType);
const uint32_t assetProductSubId = (rhiApiUniqueIndex << RhiIndexBitPosition) |
(supervariantIndex << SupervariantIndexBitPosition) | (subProductType << SubProductTypeBitPosition);
return assetProductSubId;
}
SupervariantIndex ShaderAsset2::GetSupervariantIndexFromProductAssetSubId(uint32_t assetProducSubId)
{
const uint32_t supervariantIndex = assetProducSubId >> SupervariantIndexBitPosition;
return SupervariantIndex{supervariantIndex & SupervariantIndexMaxValue};
}
SupervariantIndex ShaderAsset2::GetSupervariantIndexFromAssetId(const Data::AssetId& assetId)
{
return GetSupervariantIndexFromProductAssetSubId(assetId.m_subId);
}
void ShaderAsset2::Supervariant::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<Supervariant>()
->Version(1)
->Field("Name", &Supervariant::m_name)
->Field("SrgLayoutList", &Supervariant::m_srgLayoutList)
->Field("PipelineLayout", &Supervariant::m_pipelineLayoutDescriptor)
->Field("InputContract", &Supervariant::m_inputContract)
->Field("OutputContract", &Supervariant::m_outputContract)
->Field("RenderStates", &Supervariant::m_renderStates)
->Field("AttributeMapList", &Supervariant::m_attributeMaps)
->Field("RootVariantAsset", &Supervariant::m_rootShaderVariantAsset)
;
}
}
void ShaderAsset2::ShaderApiDataContainer::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<ShaderApiDataContainer>()
->Version(1)
->Field("APIType", &ShaderApiDataContainer::m_APIType)
->Field("Supervariants", &ShaderApiDataContainer::m_supervariants)
;
}
}
void ShaderAsset2::Reflect(ReflectContext* context)
{
Supervariant::Reflect(context);
ShaderApiDataContainer::Reflect(context);
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<ShaderAsset2>()
->Version(1)
->Field("name", &ShaderAsset2::m_name)
->Field("pipelineStateType", &ShaderAsset2::m_pipelineStateType)
->Field("shaderOptionGroupLayout", &ShaderAsset2::m_shaderOptionGroupLayout)
->Field("drawListName", &ShaderAsset2::m_drawListName)
->Field("shaderAssetBuildTimestamp", &ShaderAsset2::m_shaderAssetBuildTimestamp)
->Field("perAPIShaderData", &ShaderAsset2::m_perAPIShaderData)
;
}
}
ShaderAsset2::~ShaderAsset2()
{
Data::AssetBus::Handler::BusDisconnect();
ShaderVariantFinderNotificationBus2::Handler::BusDisconnect();
}
const Name& ShaderAsset2::GetName() const
{
return m_name;
}
RHI::PipelineStateType ShaderAsset2::GetPipelineStateType() const
{
return m_pipelineStateType;
}
const ShaderOptionGroupLayout* ShaderAsset2::GetShaderOptionGroupLayout() const
{
AZ_Assert(m_shaderOptionGroupLayout, "m_shaderOptionGroupLayout is null");
return m_shaderOptionGroupLayout.get();
}
const Name& ShaderAsset2::GetDrawListName() const
{
return m_drawListName;
}
AZStd::sys_time_t ShaderAsset2::GetShaderAssetBuildTimestamp() const
{
return m_shaderAssetBuildTimestamp;
}
void ShaderAsset2::SetReady()
{
m_status = AssetStatus::Ready;
}
SupervariantIndex ShaderAsset2::GetSupervariantIndex(const AZ::Name& supervariantName) const
{
const auto& supervariants = GetCurrentShaderApiData().m_supervariants;
const uint32_t supervariantCount = supervariants.size();
for (uint32_t index = 0; index < supervariantCount; ++index)
{
if (supervariants[index].m_name == supervariantName)
{
return SupervariantIndex{index};
}
}
return InvalidSupervariantIndex;
}
Data::Asset<ShaderVariantAsset2> ShaderAsset2::GetVariant(
const ShaderVariantId& shaderVariantId, SupervariantIndex supervariantIndex)
{
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
auto variantFinder = AZ::Interface<IShaderVariantFinder2>::Get();
AZ_Assert(variantFinder, "The IShaderVariantFinder doesn't exist");
Data::Asset<ShaderAsset2> thisAsset(this, Data::AssetLoadBehavior::Default);
Data::Asset<ShaderVariantAsset2> shaderVariantAsset =
variantFinder->GetShaderVariantAssetByVariantId(thisAsset, shaderVariantId, supervariantIndex);
if (!shaderVariantAsset)
{
variantFinder->QueueLoadShaderVariantAssetByVariantId(thisAsset, shaderVariantId, supervariantIndex);
}
return shaderVariantAsset;
}
ShaderVariantSearchResult ShaderAsset2::FindVariantStableId(const ShaderVariantId& shaderVariantId)
{
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
uint32_t dynamicOptionCount = aznumeric_cast<uint32_t>(GetShaderOptionGroupLayout()->GetShaderOptions().size());
ShaderVariantSearchResult variantSearchResult{RootShaderVariantStableId, dynamicOptionCount };
if (!dynamicOptionCount)
{
// The shader has no options at all. There's nothing to search.
return variantSearchResult;
}
auto variantFinder = AZ::Interface<IShaderVariantFinder2>::Get();
AZ_Assert(variantFinder, "The IShaderVariantFinder doesn't exist");
{
AZStd::shared_lock<decltype(m_variantTreeMutex)> lock(m_variantTreeMutex);
if (m_shaderVariantTree)
{
return m_shaderVariantTree->FindVariantStableId(GetShaderOptionGroupLayout(), shaderVariantId);
}
}
AZStd::unique_lock<decltype(m_variantTreeMutex)> lock(m_variantTreeMutex);
if (!m_shaderVariantTree)
{
m_shaderVariantTree = variantFinder->GetShaderVariantTreeAsset(GetId());
if (!m_shaderVariantTree)
{
if (!m_shaderVariantTreeLoadWasRequested)
{
variantFinder->QueueLoadShaderVariantTreeAsset(GetId());
m_shaderVariantTreeLoadWasRequested = true;
}
// The variant tree could be under construction or simply doesn't exist at all.
return variantSearchResult;
}
}
return m_shaderVariantTree->FindVariantStableId(GetShaderOptionGroupLayout(), shaderVariantId);
}
Data::Asset<ShaderVariantAsset2> ShaderAsset2::GetVariant(
ShaderVariantStableId shaderVariantStableId, SupervariantIndex supervariantIndex) const
{
if (!shaderVariantStableId.IsValid() || shaderVariantStableId == RootShaderVariantStableId)
{
return GetRootVariant(supervariantIndex);
}
auto variantFinder = AZ::Interface<IShaderVariantFinder2>::Get();
AZ_Assert(variantFinder, "No Variant Finder For shaderAsset with name [%s] and stableId [%u]", GetName().GetCStr(), shaderVariantStableId.GetIndex());
Data::Asset<ShaderVariantAsset2> variant =
variantFinder->GetShaderVariantAsset(m_shaderVariantTree.GetId(), shaderVariantStableId, supervariantIndex);
if (!variant.IsReady())
{
// Enqueue a request to load the variant, next time around the caller will get the asset.
Data::AssetId variantTreeAssetId;
{
AZStd::shared_lock<decltype(m_variantTreeMutex)> lock(m_variantTreeMutex);
if (m_shaderVariantTree)
{
variantTreeAssetId = m_shaderVariantTree.GetId();
}
}
if (variantTreeAssetId.IsValid())
{
variantFinder->QueueLoadShaderVariantAsset(variantTreeAssetId, shaderVariantStableId, supervariantIndex);
}
return GetRootVariant(supervariantIndex);
}
else if (variant->GetBuildTimestamp() >= m_shaderAssetBuildTimestamp)
{
return variant;
}
else
{
// When rebuilding shaders we may be in a state where the ShaderAsset2 and root ShaderVariantAsset have been rebuilt and reloaded, but some (or all)
// shader variants haven't been built yet. Since we want to use the latest version of the shader code, ignore the old variants and fall back to the newer root variant instead.
AZ_Warning("ShaderAsset2", false, "ShaderAsset2 and ShaderVariantAsset are out of sync; defaulting to root shader variant. (This is common while reloading shaders).");
return GetRootVariant(supervariantIndex);
}
}
Data::Asset<ShaderVariantAsset2> ShaderAsset2::GetRootVariant(SupervariantIndex supervariantIndex) const
{
auto supervariant = GetSupervariant(supervariantIndex);
if (!supervariant)
{
return Data::Asset<ShaderVariantAsset2>();
}
return supervariant->m_rootShaderVariantAsset;
}
const RHI::Ptr<RHI::ShaderResourceGroupLayout> ShaderAsset2::FindShaderResourceGroupLayout(
const Name& shaderResourceGroupName, SupervariantIndex supervariantIndex) const
{
auto supervariant = GetSupervariant(supervariantIndex);
if (!supervariant)
{
return nullptr;
}
const auto& srgLayoutList = supervariant->m_srgLayoutList;
const auto findIt = AZStd::find_if(srgLayoutList.begin(), srgLayoutList.end(), [&](const RHI::Ptr<RHI::ShaderResourceGroupLayout>& layout)
{
return layout->GetName() == shaderResourceGroupName;
});
if (findIt != srgLayoutList.end())
{
return *findIt;
}
return nullptr;
}
const RHI::Ptr<RHI::ShaderResourceGroupLayout> ShaderAsset2::FindShaderResourceGroupLayout(
uint32_t bindingSlot, SupervariantIndex supervariantIndex) const
{
auto supervariant = GetSupervariant(supervariantIndex);
if (!supervariant)
{
return nullptr;
}
const auto& srgLayoutList = supervariant->m_srgLayoutList;
const auto findIt =
AZStd::find_if(srgLayoutList.begin(), srgLayoutList.end(), [&](const RHI::Ptr<RHI::ShaderResourceGroupLayout>& layout)
{
return layout && layout->GetBindingSlot() == bindingSlot;
});
if (findIt != srgLayoutList.end())
{
return *findIt;
}
return nullptr;
}
const RHI::Ptr<RHI::ShaderResourceGroupLayout> ShaderAsset2::FindFallbackShaderResourceGroupLayout(
SupervariantIndex supervariantIndex) const
{
auto supervariant = GetSupervariant(supervariantIndex);
if (!supervariant)
{
return nullptr;
}
const auto& srgLayoutList = supervariant->m_srgLayoutList;
const auto findIt =
AZStd::find_if(srgLayoutList.begin(), srgLayoutList.end(), [&](const RHI::Ptr<RHI::ShaderResourceGroupLayout>& layout)
{
return layout && layout->HasShaderVariantKeyFallbackEntry();
});
if (findIt != srgLayoutList.end())
{
return *findIt;
}
return nullptr;
}
AZStd::array_view<RHI::Ptr<RHI::ShaderResourceGroupLayout>> ShaderAsset2::GetShaderResourceGroupLayouts(
SupervariantIndex supervariantIndex) const
{
auto supervariant = GetSupervariant(supervariantIndex);
if (!supervariant)
{
return {};
}
return supervariant->m_srgLayoutList;
}
const RHI::Ptr<RHI::ShaderResourceGroupLayout> ShaderAsset2::GetDrawSrgLayout(SupervariantIndex supervariantIndex) const
{
return FindShaderResourceGroupLayout(SrgBindingSlot::Draw, supervariantIndex);
}
const ShaderInputContract& ShaderAsset2::GetInputContract(SupervariantIndex supervariantIndex) const
{
auto supervariant = GetSupervariant(supervariantIndex);
return supervariant->m_inputContract;
}
const ShaderOutputContract& ShaderAsset2::GetOutputContract(SupervariantIndex supervariantIndex) const
{
auto supervariant = GetSupervariant(supervariantIndex);
return supervariant->m_outputContract;
}
const RHI::RenderStates& ShaderAsset2::GetRenderStates(SupervariantIndex supervariantIndex) const
{
auto supervariant = GetSupervariant(supervariantIndex);
return supervariant->m_renderStates;
}
const RHI::PipelineLayoutDescriptor* ShaderAsset2::GetPipelineLayoutDescriptor(SupervariantIndex supervariantIndex) const
{
auto supervariant = GetSupervariant(supervariantIndex);
if (!supervariant)
{
return nullptr;
}
AZ_Assert(supervariant->m_pipelineLayoutDescriptor, "m_pipelineLayoutDescriptor is null");
return supervariant->m_pipelineLayoutDescriptor.get();
}
AZStd::optional<RHI::ShaderStageAttributeArguments> ShaderAsset2::GetAttribute(const RHI::ShaderStage& shaderStage, const Name& attributeName,
SupervariantIndex supervariantIndex) const
{
auto supervariant = GetSupervariant(supervariantIndex);
if (!supervariant)
{
return AZStd::nullopt;
}
const auto stageIndex = static_cast<uint32_t>(shaderStage);
AZ_Assert(stageIndex < RHI::ShaderStageCount, "Invalid shader stage specified!");
const auto& attributeMaps = supervariant->m_attributeMaps;
const auto& attrPair = attributeMaps[stageIndex].find(attributeName);
if (attrPair == attributeMaps[stageIndex].end())
{
return AZStd::nullopt;
}
return attrPair->second;
}
ShaderAsset2::ShaderApiDataContainer& ShaderAsset2::GetCurrentShaderApiData()
{
const size_t perApiShaderDataCount = m_perAPIShaderData.size();
AZ_Assert(perApiShaderDataCount > 0, "Invalid m_perAPIShaderData");
if (m_currentAPITypeIndex < perApiShaderDataCount)
{
return m_perAPIShaderData[m_currentAPITypeIndex];
}
// We may only endup here when running in a Builder context.
return m_perAPIShaderData[0];
}
const ShaderAsset2::ShaderApiDataContainer& ShaderAsset2::GetCurrentShaderApiData() const
{
const size_t perApiShaderDataCount = m_perAPIShaderData.size();
AZ_Assert(perApiShaderDataCount > 0, "Invalid m_perAPIShaderData");
if (m_currentAPITypeIndex < perApiShaderDataCount)
{
return m_perAPIShaderData[m_currentAPITypeIndex];
}
// We may only endup here when running in a Builder context.
return m_perAPIShaderData[0];
}
ShaderAsset2::Supervariant* ShaderAsset2::GetSupervariant(SupervariantIndex supervariantIndex)
{
auto& supervariants = GetCurrentShaderApiData().m_supervariants;
auto index = supervariantIndex.GetIndex();
if (index >= supervariants.size())
{
AZ_Error(
"ShaderAsset2", false, "Supervariant index = %u is invalid because there are only %zu supervariants", index,
supervariants.size());
return nullptr;
}
return &supervariants[index];
}
const ShaderAsset2::Supervariant* ShaderAsset2::GetSupervariant(SupervariantIndex supervariantIndex) const
{
const auto& supervariants = GetCurrentShaderApiData().m_supervariants;
auto index = supervariantIndex.GetIndex();
if (index >= supervariants.size())
{
AZ_Error(
"ShaderAsset2", false, "Supervariant index = %u is invalid because there are only %zu supervariants", index,
supervariants.size());
return nullptr;
}
return &supervariants[index];
}
bool ShaderAsset2::FinalizeAfterLoad()
{
// Use the current RHI that is active to select which shader data to use.
// We don't assert if the Factory is not available because this method could be called during build time,
// when no Factory is available. Some assets (like the material asset) need to load the ShaderAsset2
// in order to get some non API specific data (like a ShaderResourceGroup) during their build
// process. If they try to access any RHI API specific data, an assert will be trigger because the
// correct API index will not set.
if (RHI::Factory::IsReady())
{
auto rhiType = RHI::Factory::Get().GetType();
auto findIt = AZStd::find_if(m_perAPIShaderData.begin(), m_perAPIShaderData.end(), [&rhiType](const auto& shaderData)
{
return shaderData.m_APIType == rhiType;
});
if (findIt != m_perAPIShaderData.end())
{
m_currentAPITypeIndex = AZStd::distance(m_perAPIShaderData.begin(), findIt);
}
else
{
AZ_Error("ShaderAsset2", false, "Could not find shader for API %s in shader %s", RHI::Factory::Get().GetName().GetCStr(), GetName().GetCStr());
return false;
}
}
// Common finalize check
for (const auto& shaderApiData : m_perAPIShaderData)
{
const auto& supervariants = shaderApiData.m_supervariants;
for (const auto& supervariant : supervariants)
{
bool beTrue = supervariant.m_attributeMaps.size() == RHI::ShaderStageCount;
if (!beTrue)
{
AZ_Error("ShaderAsset2", false, "Unexpected number of shader stages at supervariant with name [%s]!", supervariant.m_name.GetCStr());
return false;
}
}
}
// Once the ShaderAsset2 is loaded, it is necessary to listen for changes in the Root Variant Asset.
Data::AssetBus::Handler::BusConnect(GetRootVariant().GetId());
ShaderVariantFinderNotificationBus2::Handler::BusConnect(GetId());
return true;
}
///////////////////////////////////////////////////////////////////////
// AssetBus overrides...
void ShaderAsset2::OnAssetReloaded(Data::Asset<Data::AssetData> asset)
{
ShaderReloadDebugTracker::ScopedSection reloadSection("ShaderAsset2::OnAssetReloaded %s", asset.GetHint().c_str());
Data::Asset<ShaderVariantAsset2> shaderVariantAsset = { asset.GetAs<ShaderVariantAsset2>(), AZ::Data::AssetLoadBehavior::PreLoad };
AZ_Assert(shaderVariantAsset->GetStableId() == RootShaderVariantStableId,
"Was expecting to update the root variant");
SupervariantIndex supervariantIndex = GetSupervariantIndexFromAssetId(asset.GetId());
GetCurrentShaderApiData().m_supervariants[supervariantIndex.GetIndex()].m_rootShaderVariantAsset = asset;
ShaderReloadNotificationBus2::Event(GetId(), &ShaderReloadNotificationBus2::Events::OnShaderAssetReinitialized, Data::Asset<ShaderAsset2>{ this, AZ::Data::AssetLoadBehavior::PreLoad } );
}
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
/// ShaderVariantFinderNotificationBus2 overrides
void ShaderAsset2::OnShaderVariantTreeAssetReady(Data::Asset<ShaderVariantTreeAsset> shaderVariantTreeAsset, bool isError)
{
ShaderReloadDebugTracker::ScopedSection reloadSection("ShaderAsset2::OnShaderVariantTreeAssetReady %s", shaderVariantTreeAsset.GetHint().c_str());
AZStd::unique_lock<decltype(m_variantTreeMutex)> lock(m_variantTreeMutex);
if (isError)
{
m_shaderVariantTree = {}; //This will force to attempt to reload later.
m_shaderVariantTreeLoadWasRequested = false;
}
else
{
m_shaderVariantTree = shaderVariantTreeAsset;
}
lock.unlock();
ShaderReloadNotificationBus2::Event(GetId(), &ShaderReloadNotificationBus2::Events::OnShaderAssetReinitialized, Data::Asset<ShaderAsset2>{ this, AZ::Data::AssetLoadBehavior::PreLoad });
}
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
// ShaderAssetHandler
Data::AssetHandler::LoadResult ShaderAssetHandler2::LoadAssetData(
const Data::Asset<Data::AssetData>& asset,
AZStd::shared_ptr<Data::AssetDataStream> stream,
const Data::AssetFilterCB& assetLoadFilterCB)
{
if (Base::LoadAssetData(asset, stream, assetLoadFilterCB) == Data::AssetHandler::LoadResult::LoadComplete)
{
return PostLoadInit(asset);
}
return Data::AssetHandler::LoadResult::Error;
}
Data::AssetHandler::LoadResult ShaderAssetHandler2::PostLoadInit(const Data::Asset<Data::AssetData>& asset)
{
if (ShaderAsset2* shaderAsset = asset.GetAs<ShaderAsset2>())
{
if (!shaderAsset->FinalizeAfterLoad())
{
AZ_Error("ShaderAssetHandler", false, "Shader asset failed to finalize.");
return Data::AssetHandler::LoadResult::Error;
}
return Data::AssetHandler::LoadResult::LoadComplete;
}
return Data::AssetHandler::LoadResult::Error;
}
///////////////////////////////////////////////////////////////////////
} // namespace RPI
} // namespace AZ

@ -0,0 +1,404 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Atom/RPI.Reflect/Shader/ShaderAssetCreator2.h>
namespace AZ
{
namespace RPI
{
void ShaderAssetCreator2::Begin(const Data::AssetId& assetId)
{
BeginCommon(assetId);
}
void ShaderAssetCreator2::SetShaderAssetBuildTimestamp(AZStd::sys_time_t shaderAssetBuildTimestamp)
{
if (ValidateIsReady())
{
m_asset->m_shaderAssetBuildTimestamp = shaderAssetBuildTimestamp;
}
}
void ShaderAssetCreator2::SetName(const Name& name)
{
if (ValidateIsReady())
{
m_asset->m_name = name;
}
}
void ShaderAssetCreator2::SetDrawListName(const Name& name)
{
if (ValidateIsReady())
{
m_asset->m_drawListName = name;
}
}
void ShaderAssetCreator2::SetShaderOptionGroupLayout(const Ptr<ShaderOptionGroupLayout>& shaderOptionGroupLayout)
{
if (ValidateIsReady())
{
m_asset->m_shaderOptionGroupLayout = shaderOptionGroupLayout;
}
}
void ShaderAssetCreator2::BeginAPI(RHI::APIType type)
{
if (ValidateIsReady())
{
ShaderAsset2::ShaderApiDataContainer shaderData;
shaderData.m_APIType = type;
m_asset->m_currentAPITypeIndex = m_asset->m_perAPIShaderData.size();
m_asset->m_perAPIShaderData.push_back(shaderData);
}
}
void ShaderAssetCreator2::BeginSupervariant(const Name& name)
{
if (!ValidateIsReady())
{
return;
}
if (m_currentSupervariant)
{
ReportError("Call EndSupervariant() before calling BeginSupervariant again.");
return;
}
if (m_asset->m_currentAPITypeIndex == ShaderAsset2::InvalidAPITypeIndex)
{
ReportError("Can not begin supervariant with name [%s] because this function must be called between BeginAPI()/EndAPI()", name.GetCStr());
return;
}
if (m_asset->m_perAPIShaderData.empty())
{
ReportError("Can not add supervariant with name [%s] because there's no per API shader data", name.GetCStr());
return;
}
ShaderAsset2::ShaderApiDataContainer& perAPIShaderData = m_asset->m_perAPIShaderData[m_asset->m_perAPIShaderData.size() - 1];
if (perAPIShaderData.m_supervariants.empty())
{
if (!name.IsEmpty())
{
ReportError("The first supervariant must be nameless. Name [%s] is invalid", name.GetCStr());
return;
}
}
else
{
if (name.IsEmpty())
{
ReportError(
"Only the first supervariant can be nameless. So far there are %zu supervariants",
perAPIShaderData.m_supervariants.size());
return;
}
}
perAPIShaderData.m_supervariants.push_back({});
m_currentSupervariant = &perAPIShaderData.m_supervariants[perAPIShaderData.m_supervariants.size() - 1];
m_currentSupervariant->m_name = name;
}
void ShaderAssetCreator2::SetSrgLayoutList(const ShaderResourceGroupLayoutList& srgLayoutList)
{
if (!ValidateIsReady())
{
return;
}
if (!m_currentSupervariant)
{
ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__);
return;
}
m_currentSupervariant->m_srgLayoutList = srgLayoutList;
for (auto srgLayout : m_currentSupervariant->m_srgLayoutList)
{
if (!srgLayout->Finalize())
{
ReportError(
"The current supervariant [%s], failed to finalize SRG Layout [%s]", m_currentSupervariant->m_name.GetCStr(),
srgLayout->GetName().GetCStr());
return;
}
}
}
//! [Required] Assigns the pipeline layout descriptor shared by all variants in the shader. Shader variants
//! embedded in a single shader asset are required to use the same pipeline layout. It is not necessary to call
//! Finalize() on the pipeline layout prior to assignment, but still permitted.
void ShaderAssetCreator2::SetPipelineLayout(RHI::Ptr<RHI::PipelineLayoutDescriptor> pipelineLayoutDescriptor)
{
if (!ValidateIsReady())
{
return;
}
if (!m_currentSupervariant)
{
ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__);
return;
}
if (m_currentSupervariant->m_srgLayoutList.empty())
{
ReportError(
"Before setting the pipeline layout, the supervariant [%s] needs the SRG layouts",
m_currentSupervariant->m_name.GetCStr());
return;
}
m_currentSupervariant->m_pipelineLayoutDescriptor = pipelineLayoutDescriptor;
}
//! Assigns the contract for inputs required by the shader.
void ShaderAssetCreator2::SetInputContract(const ShaderInputContract& contract)
{
if (!ValidateIsReady())
{
return;
}
if (!m_currentSupervariant)
{
ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__);
return;
}
m_currentSupervariant->m_inputContract = contract;
}
//! Assigns the contract for outputs required by the shader.
void ShaderAssetCreator2::SetOutputContract(const ShaderOutputContract& contract)
{
if (!ValidateIsReady())
{
return;
}
if (!m_currentSupervariant)
{
ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__);
return;
}
m_currentSupervariant->m_outputContract = contract;
}
//! Assigns the render states for the draw pipeline. Ignored for non-draw pipelines.
void ShaderAssetCreator2::SetRenderStates(const RHI::RenderStates& renderStates)
{
if (!ValidateIsReady())
{
return;
}
if (!m_currentSupervariant)
{
ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__);
return;
}
m_currentSupervariant->m_renderStates = renderStates;
}
//! [Optional] Not all shaders have attributes before functions. Some attributes do not exist for all RHI::APIType either.
void ShaderAssetCreator2::SetShaderStageAttributeMapList(const RHI::ShaderStageAttributeMapList& shaderStageAttributeMapList)
{
if (!ValidateIsReady())
{
return;
}
if (!m_currentSupervariant)
{
ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__);
return;
}
m_currentSupervariant->m_attributeMaps = shaderStageAttributeMapList;
}
//! [Required] There's always a root variant for each supervariant.
void ShaderAssetCreator2::SetRootShaderVariantAsset(Data::Asset<ShaderVariantAsset2> shaderVariantAsset)
{
if (!ValidateIsReady())
{
return;
}
if (!m_currentSupervariant)
{
ReportError("BeginSupervariant() should be called first before calling %s", __FUNCTION__);
return;
}
m_currentSupervariant->m_rootShaderVariantAsset = shaderVariantAsset;
}
static RHI::PipelineStateType GetPipelineStateType(const Data::Asset<ShaderVariantAsset2>& shaderVariantAsset)
{
if (shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Vertex) ||
shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Tessellation) ||
shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Fragment))
{
return RHI::PipelineStateType::Draw;
}
if (shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::Compute))
{
return RHI::PipelineStateType::Dispatch;
}
if (shaderVariantAsset->GetShaderStageFunction(RHI::ShaderStage::RayTracing))
{
return RHI::PipelineStateType::RayTracing;
}
return RHI::PipelineStateType::Count;
}
bool ShaderAssetCreator2::EndSupervariant()
{
if (!ValidateIsReady())
{
return false;
}
if (!m_currentSupervariant)
{
ReportError("Can not end a supervariant that has not started");
return false;
}
if (!m_currentSupervariant->m_rootShaderVariantAsset.IsReady())
{
ReportError(
"The current supervariant [%s], is missing the root ShaderVariantAsset", m_currentSupervariant->m_name.GetCStr());
return false;
}
// Supervariant specific resources
if (m_currentSupervariant->m_pipelineLayoutDescriptor)
{
if (!m_currentSupervariant->m_pipelineLayoutDescriptor->IsFinalized())
{
if (m_currentSupervariant->m_pipelineLayoutDescriptor->Finalize() != RHI::ResultCode::Success)
{
ReportError("Failed to finalize pipeline layout descriptor.");
return false;
}
}
}
else
{
ReportError("PipelineLayoutDescriptor not specified.");
return false;
}
const ShaderInputContract& shaderInputContract = m_currentSupervariant->m_inputContract;
// Validate that each stream ID appears only once.
for (const auto& channel : shaderInputContract.m_streamChannels)
{
int count = 0;
for (const auto& searchChannel : shaderInputContract.m_streamChannels)
{
if (channel.m_semantic == searchChannel.m_semantic)
{
++count;
}
}
if (count > 1)
{
ReportError(
"Input stream channel [%s] appears multiple times. For supervariant with name [%s]",
channel.m_semantic.ToString().c_str(), m_currentSupervariant->m_name.GetCStr());
return false;
}
}
auto pipelineStateType = GetPipelineStateType(m_currentSupervariant->m_rootShaderVariantAsset);
if (pipelineStateType == RHI::PipelineStateType::Count)
{
ReportError("Invalid pipelineStateType for supervariant [%s]", m_currentSupervariant->m_name.GetCStr());
return false;
}
if (m_currentSupervariant->m_name.IsEmpty())
{
m_asset->m_pipelineStateType = pipelineStateType;
}
else
{
if (m_asset->m_pipelineStateType != pipelineStateType)
{
ReportError("All supervariants must be of the same pipelineStateType. Current pipelineStateType is [%d], but for supervariant [%s] the pipelineStateType is [%d]",
m_asset->m_pipelineStateType, m_currentSupervariant->m_name.GetCStr(), pipelineStateType);
return false;
}
}
m_currentSupervariant = nullptr;
return true;
}
bool ShaderAssetCreator2::EndAPI()
{
if (!ValidateIsReady())
{
return false;
}
if (m_currentSupervariant)
{
ReportError("EndSupervariant() must be called before calling EndAPI()");
return false;
}
m_asset->m_currentAPITypeIndex = ShaderAsset2::InvalidAPITypeIndex;
return true;
}
bool ShaderAssetCreator2::End(Data::Asset<ShaderAsset2>& shaderAsset)
{
if (!ValidateIsReady())
{
return false;
}
if (m_asset->m_perAPIShaderData.empty())
{
ReportError("Empty shader data. Check that a valid RHI is enabled for this platform.");
return false;
}
if (!m_asset->FinalizeAfterLoad())
{
ReportError("Failed to finalize the ShaderAsset2.");
return false;
}
m_asset->SetReady();
return EndCommon(shaderAsset);
}
void ShaderAssetCreator2::Clone(const Data::AssetId& assetId, const ShaderAsset2& sourceShaderAsset)
{
BeginCommon(assetId);
m_asset->m_name = sourceShaderAsset.m_name;
m_asset->m_pipelineStateType = sourceShaderAsset.m_pipelineStateType;
m_asset->m_drawListName = sourceShaderAsset.m_drawListName;
m_asset->m_shaderOptionGroupLayout = sourceShaderAsset.m_shaderOptionGroupLayout;
m_asset->m_shaderAssetBuildTimestamp = sourceShaderAsset.m_shaderAssetBuildTimestamp;
m_asset->m_perAPIShaderData = sourceShaderAsset.m_perAPIShaderData;
}
} // namespace RPI
} // namespace AZ

@ -0,0 +1,54 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Serialization/SerializeContext.h>
#include <Atom/RPI.Reflect/Shader/ShaderCommonTypes.h>
namespace AZ
{
namespace RPI
{
const char* ToString(ShaderStageType shaderStageType)
{
switch (shaderStageType)
{
case ShaderStageType::Vertex: return "Vertex";
case ShaderStageType::Geometry: return "Geometry";
case ShaderStageType::TessellationControl: return "TessellationControl";
case ShaderStageType::TessellationEvaluation: return "TessellationEvaluation";
case ShaderStageType::Fragment: return "Fragment";
case ShaderStageType::Compute: return "Compute";
case ShaderStageType::RayTracing: return "RayTracing";
default:
AZ_Assert(false, "Unhandled type");
return "<Unknown>";
}
}
void ReflectShaderStageType(ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Enum<ShaderStageType>()
->Value(ToString(ShaderStageType::Vertex), ShaderStageType::Vertex)
->Value(ToString(ShaderStageType::Geometry), ShaderStageType::Geometry)
->Value(ToString(ShaderStageType::TessellationControl), ShaderStageType::TessellationControl)
->Value(ToString(ShaderStageType::TessellationEvaluation), ShaderStageType::TessellationEvaluation)
->Value(ToString(ShaderStageType::Fragment), ShaderStageType::Fragment)
->Value(ToString(ShaderStageType::Compute), ShaderStageType::Compute)
->Value(ToString(ShaderStageType::RayTracing), ShaderStageType::RayTracing)
;
}
}
} // namespace RPI
} // namespace AZ

@ -21,6 +21,34 @@ namespace AZ
{
namespace RPI
{
uint32_t ShaderVariantAsset::MakeAssetProductSubId(
uint32_t rhiApiUniqueIndex, ShaderVariantStableId variantStableId, uint32_t subProductType)
{
static constexpr uint32_t RhiIndexBitPosition = 30;
static constexpr uint32_t RhiIndexNumBits = 32 - RhiIndexBitPosition;
static constexpr uint32_t RhiIndexMaxValue = (1 << RhiIndexNumBits) - 1;
static constexpr uint32_t SubProductTypeBitPosition = 17;
static constexpr uint32_t SubProductTypeNumBits = RhiIndexBitPosition - SubProductTypeBitPosition;
static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1;
static constexpr uint32_t StableIdBitPosition = 0;
static constexpr uint32_t StableIdNumBits = SubProductTypeBitPosition - StableIdBitPosition;
static constexpr uint32_t StableIdMaxValue = (1 << StableIdNumBits) - 1;
static_assert(RhiIndexMaxValue == RHI::Limits::APIType::PerPlatformApiUniqueIndexMax);
// The 2 Most significant bits encode the the RHI::API unique index.
AZ_Assert(rhiApiUniqueIndex <= RhiIndexMaxValue, "Invalid rhiApiUniqueIndex [%u]", rhiApiUniqueIndex);
AZ_Assert(subProductType <= SubProductTypeMaxValue, "Invalid subProductType [%u]", subProductType);
AZ_Assert(variantStableId.GetIndex() <= StableIdMaxValue, "Invalid variantStableId [%u]", variantStableId.GetIndex());
const uint32_t assetProductSubId = (rhiApiUniqueIndex << RhiIndexBitPosition) |
(subProductType << SubProductTypeBitPosition) |
(variantStableId.GetIndex() << StableIdBitPosition);
return assetProductSubId;
}
void ShaderVariantAsset::Reflect(ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
@ -44,16 +72,6 @@ namespace AZ
return m_shaderAssetBuildTimestamp;
}
uint32_t ShaderVariantAsset::GetAssetSubId(uint32_t rhiApiUniqueIndex, ShaderVariantStableId variantStableId)
{
//The 2 Most significant bits encode the the RHI::API unique index.
AZ_Assert(rhiApiUniqueIndex <= RHI::Limits::APIType::PerPlatformApiUniqueIndexMax, "Invalid rhiApiUniqueIndex [%u]", rhiApiUniqueIndex);
AZ_Assert(variantStableId != RootShaderVariantStableId, "The product subId for the root variant is built differently.");
const uint32_t rhiApiSubId = rhiApiUniqueIndex << 30;
const uint32_t productSubId = rhiApiSubId | variantStableId.GetIndex();
return productSubId;
}
const RHI::ShaderStageFunction* ShaderVariantAsset::GetShaderStageFunction(RHI::ShaderStage shaderStage) const
{
return m_functionsByStage[static_cast<size_t>(shaderStage)].get();

@ -0,0 +1,114 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/algorithm.h>
#include <Atom/RPI.Reflect/Shader/ShaderCommonTypes.h>
#include <Atom/RHI.Reflect/ShaderStageFunction.h>
#include <Atom/RHI.Reflect/Limits.h>
namespace AZ
{
namespace RPI
{
uint32_t ShaderVariantAsset2::MakeAssetProductSubId(
uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, ShaderVariantStableId variantStableId, uint32_t subProductType)
{
static constexpr uint32_t SubProductTypeBitPosition = 17;
static constexpr uint32_t SubProductTypeNumBits = SupervariantIndexBitPosition - SubProductTypeBitPosition;
static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1;
static constexpr uint32_t StableIdBitPosition = 0;
static constexpr uint32_t StableIdNumBits = SubProductTypeBitPosition - StableIdBitPosition;
static constexpr uint32_t StableIdMaxValue = (1 << StableIdNumBits) - 1;
static_assert(RhiIndexMaxValue == RHI::Limits::APIType::PerPlatformApiUniqueIndexMax);
// The 2 Most significant bits encode the the RHI::API unique index.
AZ_Assert(rhiApiUniqueIndex <= RhiIndexMaxValue, "Invalid rhiApiUniqueIndex [%u]", rhiApiUniqueIndex);
AZ_Assert(supervariantIndex <= SupervariantIndexMaxValue, "Invalid supervariantIndex [%u]", supervariantIndex);
AZ_Assert(subProductType <= SubProductTypeMaxValue, "Invalid subProductType [%u]", subProductType);
AZ_Assert(variantStableId.GetIndex() <= StableIdMaxValue, "Invalid variantStableId [%u]", variantStableId.GetIndex());
const uint32_t assetProductSubId = (rhiApiUniqueIndex << RhiIndexBitPosition) |
(supervariantIndex << SupervariantIndexBitPosition) | (subProductType << SubProductTypeBitPosition) |
(variantStableId.GetIndex() << StableIdBitPosition);
return assetProductSubId;
}
void ShaderVariantAsset2::Reflect(ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<ShaderVariantAsset2, AZ::Data::AssetData>()
->Version(1)
->Field("StableId", &ShaderVariantAsset2::m_stableId)
->Field("ShaderVariantId", &ShaderVariantAsset2::m_shaderVariantId)
->Field("IsFullyBaked", &ShaderVariantAsset2::m_isFullyBaked)
->Field("FunctionsByStage", &ShaderVariantAsset2::m_functionsByStage)
->Field("BuildTimestamp", &ShaderVariantAsset2::m_buildTimestamp)
;
}
}
AZStd::sys_time_t ShaderVariantAsset2::GetBuildTimestamp() const
{
return m_buildTimestamp;
}
const RHI::ShaderStageFunction* ShaderVariantAsset2::GetShaderStageFunction(RHI::ShaderStage shaderStage) const
{
return m_functionsByStage[static_cast<size_t>(shaderStage)].get();
}
bool ShaderVariantAsset2::IsFullyBaked() const
{
return m_isFullyBaked;
}
void ShaderVariantAsset2::SetReady()
{
m_status = AssetStatus::Ready;
}
bool ShaderVariantAsset2::FinalizeAfterLoad()
{
return true;
}
ShaderVariantAssetHandler2::LoadResult ShaderVariantAssetHandler2::LoadAssetData(const Data::Asset<Data::AssetData>& asset, AZStd::shared_ptr<Data::AssetDataStream> stream, const AZ::Data::AssetFilterCB& assetLoadFilterCB)
{
if (Base::LoadAssetData(asset, stream, assetLoadFilterCB) == LoadResult::LoadComplete)
{
return PostLoadInit(asset) ? LoadResult::LoadComplete : LoadResult::Error;
}
return LoadResult::Error;
}
bool ShaderVariantAssetHandler2::PostLoadInit(const Data::Asset<Data::AssetData>& asset)
{
if (ShaderVariantAsset2* shaderVariantAsset = asset.GetAs<ShaderVariantAsset2>())
{
if (!shaderVariantAsset->FinalizeAfterLoad())
{
AZ_Error("ShaderVariantAssetHandler", false, "Shader asset failed to finalize.");
return false;
}
return true;
}
return false;
}
} // namespace RPI
} // namespace AZ

@ -35,6 +35,7 @@ set(FILES
Include/Atom/RPI.Edit/Shader/ShaderSourceData.h
Include/Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h
Include/Atom/RPI.Edit/Shader/ShaderVariantAssetCreator.h
Include/Atom/RPI.Edit/Shader/ShaderVariantAssetCreator2.h
Include/Atom/RPI.Edit/Shader/ShaderVariantTreeAssetCreator.h
Source/RPI.Edit/Material/LuaMaterialFunctorSourceData.cpp
Source/RPI.Edit/Material/MaterialTypeSourceData.cpp
@ -52,6 +53,7 @@ set(FILES
Source/RPI.Edit/Shader/ShaderSourceData.cpp
Source/RPI.Edit/Shader/ShaderVariantListSourceData.cpp
Source/RPI.Edit/Shader/ShaderVariantAssetCreator.cpp
Source/RPI.Edit/Shader/ShaderVariantAssetCreator2.cpp
Source/RPI.Edit/Shader/ShaderVariantTreeAssetCreator.cpp
Source/RPI.Edit/Common/AssetUtils.cpp
Source/RPI.Edit/Common/AssetAliasesSourceData.cpp

@ -81,8 +81,11 @@ set(FILES
Include/Atom/RPI.Public/Pass/Specific/SelectorPass.h
Include/Atom/RPI.Public/Pass/Specific/SwapChainPass.h
Include/Atom/RPI.Public/Shader/Shader.h
Include/Atom/RPI.Public/Shader/Shader2.h
Include/Atom/RPI.Public/Shader/ShaderReloadNotificationBus.h
Include/Atom/RPI.Public/Shader/ShaderReloadNotificationBus2.h
Include/Atom/RPI.Public/Shader/ShaderVariant.h
Include/Atom/RPI.Public/Shader/ShaderVariant2.h
Include/Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h
Include/Atom/RPI.Public/Shader/ShaderResourceGroup.h
Include/Atom/RPI.Public/Shader/ShaderResourceGroupPool.h
@ -155,7 +158,9 @@ set(FILES
Source/RPI.Public/Pass/Specific/SelectorPass.cpp
Source/RPI.Public/Pass/Specific/SwapChainPass.cpp
Source/RPI.Public/Shader/Shader.cpp
Source/RPI.Public/Shader/Shader2.cpp
Source/RPI.Public/Shader/ShaderVariant.cpp
Source/RPI.Public/Shader/ShaderVariant2.cpp
Source/RPI.Public/Shader/ShaderReloadDebugTracker.cpp
Source/RPI.Public/Shader/ShaderResourceGroup.cpp
Source/RPI.Public/Shader/ShaderResourceGroupPool.cpp

@ -75,8 +75,11 @@ set(FILES
Include/Atom/RPI.Reflect/Pass/PassTemplate.h
Include/Atom/RPI.Reflect/Pass/RasterPassData.h
Include/Atom/RPI.Reflect/Pass/RenderPassData.h
Include/Atom/RPI.Reflect/Shader/ShaderCommonTypes.h
Include/Atom/RPI.Reflect/Shader/ShaderAsset.h
Include/Atom/RPI.Reflect/Shader/ShaderAssetCreator.h
Include/Atom/RPI.Reflect/Shader/ShaderAsset2.h
Include/Atom/RPI.Reflect/Shader/ShaderAssetCreator2.h
Include/Atom/RPI.Reflect/Shader/ShaderInputContract.h
Include/Atom/RPI.Reflect/Shader/ShaderOptionGroup.h
Include/Atom/RPI.Reflect/Shader/ShaderOptionGroupLayout.h
@ -87,7 +90,9 @@ set(FILES
Include/Atom/RPI.Reflect/Shader/ShaderVariantKey.h
Include/Atom/RPI.Reflect/Shader/ShaderVariantTreeAsset.h
Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h
Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h
Include/Atom/RPI.Reflect/Shader/IShaderVariantFinder.h
Include/Atom/RPI.Reflect/Shader/IShaderVariantFinder2.h
Include/Atom/RPI.Reflect/Shader/PrecompiledShaderAssetSourceData.h
Include/Atom/RPI.Reflect/System/AnyAsset.h
Include/Atom/RPI.Reflect/System/AssetAliases.h
@ -145,8 +150,11 @@ set(FILES
Source/RPI.Reflect/Pass/PassAttachmentReflect.cpp
Source/RPI.Reflect/Pass/PassRequest.cpp
Source/RPI.Reflect/Pass/PassTemplate.cpp
Source/RPI.Reflect/Shader/ShaderStageType.cpp
Source/RPI.Reflect/Shader/ShaderAsset.cpp
Source/RPI.Reflect/Shader/ShaderAssetCreator.cpp
Source/RPI.Reflect/Shader/ShaderAsset2.cpp
Source/RPI.Reflect/Shader/ShaderAssetCreator2.cpp
Source/RPI.Reflect/Shader/ShaderInputContract.cpp
Source/RPI.Reflect/Shader/ShaderOptionGroup.cpp
Source/RPI.Reflect/Shader/ShaderOptionGroupLayout.cpp
@ -156,6 +164,7 @@ set(FILES
Source/RPI.Reflect/Shader/ShaderVariantKey.cpp
Source/RPI.Reflect/Shader/ShaderVariantTreeAsset.cpp
Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp
Source/RPI.Reflect/Shader/ShaderVariantAsset2.cpp
Source/RPI.Reflect/Shader/PrecompiledShaderAssetSourceData.cpp
Source/RPI.Reflect/System/AnyAsset.cpp
Source/RPI.Reflect/System/AssetAliases.cpp

@ -16,6 +16,7 @@
#include <AzCore/std/any.h>
#include <AzCore/std/string/string_view.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroupLayout.h>
#include <Atom/RPI.Edit/Shader/ShaderSourceData.h>
#include <Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h>

@ -14,6 +14,7 @@
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Asset/AssetCommon.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include <Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h>
#include <Atom/Document/ShaderManagementConsoleDocumentRequestBus.h>

@ -29,7 +29,7 @@ ly_associate_package(PACKAGE_NAME SQLite-3.32.2-rev3-multiplatform
ly_associate_package(PACKAGE_NAME SPIRVCross-2020.04.20-rev1-multiplatform TARGETS SPIRVCross PACKAGE_HASH 7c8c0eaa0166c26745c62d2238525af7e27ac058a5db3defdbaec1878e8798dd)
ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-2020.08.07-rev1-multiplatform TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 04a6850ce03d4c16e19ed206f7093d885276dfb74047e6aa99f0a834c8b7cc73)
ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxcAz-5.0.0_az-rev1-multiplatform TARGETS DirectXShaderCompilerDxcAz PACKAGE_HASH 94f24989a7a371d840b513aa5ffaff02747b3d19b119bc1f899427e29978f753)
ly_associate_package(PACKAGE_NAME azslc-1.7.20-rev1-multiplatform TARGETS azslc PACKAGE_HASH 45d55f28bea2ef823ed3204f60df52e5e329f42923923d4555fdbdf3bea0af60)
ly_associate_package(PACKAGE_NAME azslc-1.7.21-rev1-multiplatform TARGETS azslc PACKAGE_HASH 772b7a2d9cc68aa1da4f0ee7db57ee1b4e7a8f20b81961fc5849af779582f4df)
ly_associate_package(PACKAGE_NAME glad-2.0.0-beta-rev2-multiplatform TARGETS glad PACKAGE_HASH ff97ee9664e97d0854b52a3734c2289329d9f2b4cd69478df6d0ca1f1c9392ee)
ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux_core PACKAGE_HASH c8c13cf7bc351643e1abd294d0841b24dee60e51647dff13db7aec396ad1e0b5)
ly_associate_package(PACKAGE_NAME xxhash-0.7.4-rev1-multiplatform TARGETS xxhash PACKAGE_HASH e81f3e6c4065975833996dd1fcffe46c3cf0f9e3a4207ec5f4a1b564ba75861e)

@ -29,7 +29,7 @@ ly_associate_package(PACKAGE_NAME SQLite-3.32.2-rev3-multiplatform
ly_associate_package(PACKAGE_NAME SPIRVCross-2020.04.20-rev1-multiplatform TARGETS SPIRVCross PACKAGE_HASH 7c8c0eaa0166c26745c62d2238525af7e27ac058a5db3defdbaec1878e8798dd)
ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-2020.08.07-rev1-multiplatform TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 04a6850ce03d4c16e19ed206f7093d885276dfb74047e6aa99f0a834c8b7cc73)
ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxcAz-5.0.0_az-rev1-multiplatform TARGETS DirectXShaderCompilerDxcAz PACKAGE_HASH 94f24989a7a371d840b513aa5ffaff02747b3d19b119bc1f899427e29978f753)
ly_associate_package(PACKAGE_NAME azslc-1.7.20-rev1-multiplatform TARGETS azslc PACKAGE_HASH 45d55f28bea2ef823ed3204f60df52e5e329f42923923d4555fdbdf3bea0af60)
ly_associate_package(PACKAGE_NAME azslc-1.7.21-rev1-multiplatform TARGETS azslc PACKAGE_HASH 772b7a2d9cc68aa1da4f0ee7db57ee1b4e7a8f20b81961fc5849af779582f4df)
ly_associate_package(PACKAGE_NAME glad-2.0.0-beta-rev2-multiplatform TARGETS glad PACKAGE_HASH ff97ee9664e97d0854b52a3734c2289329d9f2b4cd69478df6d0ca1f1c9392ee)
ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux_core PACKAGE_HASH c8c13cf7bc351643e1abd294d0841b24dee60e51647dff13db7aec396ad1e0b5)
ly_associate_package(PACKAGE_NAME xxhash-0.7.4-rev1-multiplatform TARGETS xxhash PACKAGE_HASH e81f3e6c4065975833996dd1fcffe46c3cf0f9e3a4207ec5f4a1b564ba75861e)

Loading…
Cancel
Save