You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder2.cpp

979 lines
54 KiB
C++

/*
* 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