ShaderAssetBuilder.cpp 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "ShaderAssetBuilder.h"
  9. #include <CommonFiles/Preprocessor.h>
  10. #include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
  11. #include <Atom/RPI.Reflect/Shader/ShaderAssetCreator.h>
  12. #include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
  13. #include <Atom/RPI.Reflect/Shader/ShaderVariantKey.h>
  14. #include <Atom/RHI.Edit/Utils.h>
  15. #include <Atom/RHI.Edit/ShaderPlatformInterface.h>
  16. #include <Atom/RPI.Edit/Common/JsonReportingHelper.h>
  17. #include <Atom/RPI.Edit/Common/AssetUtils.h>
  18. #include <Atom/RPI.Edit/Common/JsonUtils.h>
  19. #include <Atom/RHI.Reflect/ConstantsLayout.h>
  20. #include <Atom/RHI.Reflect/PipelineLayoutDescriptor.h>
  21. #include <Atom/RHI.Reflect/ShaderStageFunction.h>
  22. #include <Atom/RHI/RHIUtils.h>
  23. #include <AzCore/Serialization/Json/JsonUtils.h>
  24. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  25. #include <AzToolsFramework/Debug/TraceContext.h>
  26. #include <AzFramework/API/ApplicationAPI.h>
  27. #include <AzFramework/StringFunc/StringFunc.h>
  28. #include <AzFramework/IO/LocalFileIO.h>
  29. #include <AzFramework/Platform/PlatformDefaults.h>
  30. #include <AzCore/Asset/AssetManager.h>
  31. #include <AzCore/JSON/document.h>
  32. #include <AzCore/IO/FileIO.h>
  33. #include <AzCore/IO/IOUtils.h>
  34. #include <AzCore/IO/SystemFile.h>
  35. #include <AzCore/std/algorithm.h>
  36. #include <AzCore/std/string/string.h>
  37. #include <AzCore/std/sort.h>
  38. #include <AzCore/Serialization/Json/JsonSerialization.h>
  39. #include <AzCore/Debug/Timer.h>
  40. #include "AzslCompiler.h"
  41. #include "ShaderVariantAssetBuilder.h"
  42. #include "ShaderBuilderUtility.h"
  43. #include "ShaderPlatformInterfaceRequest.h"
  44. #include "ShaderBuildArgumentsManager.h"
  45. #include <AssetBuilderSDK/AssetBuilderSDK.h>
  46. #include <AssetBuilderSDK/SerializationDependencies.h>
  47. namespace AZ
  48. {
  49. namespace ShaderBuilder
  50. {
  51. static constexpr char ShaderAssetBuilderName[] = "ShaderAssetBuilder";
  52. //! The search will start in @currentFolderPath.
  53. //! if the file is not found then it searches in order of appearence in @includeDirectories.
  54. //! If the search yields no existing file it returns an empty string.
  55. static AZStd::string DiscoverFullPath(AZStd::string_view normalizedRelativePath, AZStd::string_view currentFolderPath, const AZStd::vector<AZStd::string>& includeDirectories)
  56. {
  57. AZStd::string fullPath;
  58. AzFramework::StringFunc::Path::Join(currentFolderPath.data(), normalizedRelativePath.data(), fullPath);
  59. if (AZ::IO::SystemFile::Exists(fullPath.c_str()))
  60. {
  61. return fullPath;
  62. }
  63. for (const auto& includeDir : includeDirectories)
  64. {
  65. AzFramework::StringFunc::Path::Join(includeDir.c_str(), normalizedRelativePath.data(), fullPath);
  66. if (AZ::IO::SystemFile::Exists(fullPath.c_str()))
  67. {
  68. return fullPath;
  69. }
  70. }
  71. return "";
  72. }
  73. // Appends to @includedFiles normalized paths of possible future locations of the file @normalizedRelativePath.
  74. // The future locations are each directory listed in @includeDirectories joined with @normalizedRelativePath.
  75. // This function is called when an included file doesn't exist but We need to declare source dependency so a .shader
  76. // asset is rebuilt when the missing file appears in the future.
  77. static void AppendListOfPossibleFutureLocations(AZStd::unordered_set<AZStd::string>& includedFiles, AZStd::string_view normalizedRelativePath, AZStd::string_view currentFolderPath, const AZStd::vector<AZStd::string>& includeDirectories)
  78. {
  79. AZStd::string fullPath;
  80. AzFramework::StringFunc::Path::Join(currentFolderPath.data(), normalizedRelativePath.data(), fullPath);
  81. includedFiles.insert(fullPath);
  82. for (const auto& includeDir : includeDirectories)
  83. {
  84. AzFramework::StringFunc::Path::Join(includeDir.c_str(), normalizedRelativePath.data(), fullPath);
  85. includedFiles.insert(fullPath);
  86. }
  87. }
  88. //! Parses, using depth-first recursive approach, azsl files. Looks for '#include <foo/bar/blah.h>' or '#include "foo/bar/blah.h"' lines
  89. //! and in turn parses the included files.
  90. //! The included files are searched in the directories listed in @includeDirectories. Basically it's a similar approach
  91. //! as how most C-preprocessors would find included files.
  92. static void GetListOfIncludedFiles(AZStd::string_view sourceFilePath, const AZStd::vector<AZStd::string>& includeDirectories,
  93. const ShaderBuilderUtility::IncludedFilesParser& includedFilesParser, AZStd::unordered_set<AZStd::string>& includedFiles)
  94. {
  95. auto outcome = includedFilesParser.ParseFileAndGetIncludedFiles(sourceFilePath);
  96. if (!outcome.IsSuccess())
  97. {
  98. AZ_Warning(ShaderAssetBuilderName, false, outcome.GetError().c_str());
  99. return;
  100. }
  101. // Cache the path of the folder where @sourceFilePath is located.
  102. AZStd::string sourceFileFolderPath;
  103. {
  104. AZStd::string drive;
  105. AzFramework::StringFunc::Path::Split(sourceFilePath.data(), &drive, &sourceFileFolderPath);
  106. if (!drive.empty())
  107. {
  108. AzFramework::StringFunc::Path::Join(drive.c_str(), sourceFileFolderPath.c_str(), sourceFileFolderPath);
  109. }
  110. }
  111. auto listOfRelativePaths = outcome.TakeValue();
  112. for (const auto& relativePath : listOfRelativePaths)
  113. {
  114. auto fullPath = DiscoverFullPath(relativePath, sourceFileFolderPath, includeDirectories);
  115. if (fullPath.empty())
  116. {
  117. // The file doesn't exist in any of the includeDirectories. It doesn't exist in @sourceFileFolderPath either.
  118. // The file may appear in the future in one of those directories, We must build an exhaustive list
  119. // of full file paths where the file may appear in the future.
  120. AppendListOfPossibleFutureLocations(includedFiles, relativePath, sourceFileFolderPath, includeDirectories);
  121. continue;
  122. }
  123. // Add the file to the list and keep parsing recursively.
  124. if (includedFiles.contains(fullPath))
  125. {
  126. continue;
  127. }
  128. includedFiles.insert(fullPath);
  129. GetListOfIncludedFiles(fullPath, includeDirectories, includedFilesParser, includedFiles);
  130. }
  131. }
  132. void ShaderAssetBuilder::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const
  133. {
  134. // Used to measure the duration of CreateJobs
  135. AZ::u64 shaderAssetBuildTimestamp = AZStd::GetTimeUTCMilliSecond();
  136. AZStd::string shaderAssetSourceFileFullPath;
  137. AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), shaderAssetSourceFileFullPath, true);
  138. ShaderBuilderUtility::IncludedFilesParser includedFilesParser;
  139. AZ_TracePrintf(ShaderAssetBuilderName, "CreateJobs for Shader \"%s\"\n", shaderAssetSourceFileFullPath.data());
  140. // 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.
  141. // and the macro options to preprocess.
  142. auto descriptorParseOutcome = ShaderBuilderUtility::LoadShaderDataJson(shaderAssetSourceFileFullPath, false);
  143. if (!descriptorParseOutcome.IsSuccess())
  144. {
  145. AZ_Error(
  146. ShaderAssetBuilderName, false, "Failed to parse Shader Descriptor JSON: %s",
  147. descriptorParseOutcome.GetError().c_str());
  148. return;
  149. }
  150. RPI::ShaderSourceData shaderSourceData = descriptorParseOutcome.TakeValue();
  151. AZStd::string azslFullPath;
  152. ShaderBuilderUtility::GetAbsolutePathToAzslFile(shaderAssetSourceFileFullPath, shaderSourceData.m_source, azslFullPath);
  153. {
  154. // Add the AZSL as source dependency
  155. AssetBuilderSDK::SourceFileDependency azslFileDependency;
  156. azslFileDependency.m_sourceFileDependencyPath = azslFullPath;
  157. response.m_sourceFileDependencyList.emplace_back(AZStd::move(azslFileDependency));
  158. }
  159. if (!IO::FileIOBase::GetInstance()->Exists(azslFullPath.c_str()))
  160. {
  161. AZ_Error(
  162. ShaderAssetBuilderName, false, "Shader program listed as the source entry does not exist: %s.", azslFullPath.c_str());
  163. // Even though there was an error here, don't stop, because we need to report the SourceFileDependency so when the azsl
  164. // file shows up the AP will try to recompile. We will go ahead and create the job anyway, and then ProcessJob can
  165. // report the failure.
  166. }
  167. auto projectIncludePaths = BuildListOfIncludeDirectories(ShaderAssetBuilderName);
  168. AZStd::unordered_set<AZStd::string> includedFiles;
  169. GetListOfIncludedFiles(azslFullPath, projectIncludePaths, includedFilesParser, includedFiles);
  170. for (const auto& includePath : includedFiles)
  171. {
  172. AssetBuilderSDK::SourceFileDependency includeFileDependency;
  173. includeFileDependency.m_sourceFileDependencyPath = includePath;
  174. response.m_sourceFileDependencyList.emplace_back(AZStd::move(includeFileDependency));
  175. }
  176. // Add the shader_build_option files as source dependencies
  177. AZStd::unordered_map<AZStd::string, AZ::IO::FixedMaxPath> configFiles = ShaderBuildArgumentsManager::DiscoverConfigurationFiles();
  178. for (const auto& pair : configFiles)
  179. {
  180. AssetBuilderSDK::SourceFileDependency includeFileDependency;
  181. includeFileDependency.m_sourceFileDependencyPath = pair.second.c_str();
  182. response.m_sourceFileDependencyList.emplace_back(AZStd::move(includeFileDependency));
  183. }
  184. for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
  185. {
  186. AZ_TraceContext("For platform", platformInfo.m_identifier.data());
  187. // Get the platform interfaces to be able to access the prepend file
  188. AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces = ShaderBuilderUtility::DiscoverValidShaderPlatformInterfaces(platformInfo);
  189. if (platformInterfaces.empty())
  190. {
  191. continue;
  192. }
  193. AssetBuilderSDK::JobDescriptor jobDescriptor;
  194. jobDescriptor.m_priority = 2;
  195. jobDescriptor.m_critical = false;
  196. jobDescriptor.m_jobKey = ShaderAssetBuilderJobKey;
  197. jobDescriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
  198. response.m_createJobOutputs.emplace_back(AZStd::move(jobDescriptor));
  199. } // for all request.m_enabledPlatforms
  200. AZ_Printf(
  201. ShaderAssetBuilderName, "CreateJobs for %s took %llu milliseconds", shaderAssetSourceFileFullPath.c_str(),
  202. AZStd::GetTimeUTCMilliSecond() - shaderAssetBuildTimestamp);
  203. response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
  204. }
  205. static bool SerializeOutShaderAsset(Data::Asset<RPI::ShaderAsset> shaderAsset,
  206. const AZStd::string& tempDirPath,
  207. AssetBuilderSDK::ProcessJobResponse& response)
  208. {
  209. AZStd::string shaderAssetFileName = AZStd::string::format("%s.%s", shaderAsset->GetName().GetCStr(), RPI::ShaderAsset::Extension);
  210. AZStd::string shaderAssetOutputPath;
  211. AzFramework::StringFunc::Path::ConstructFull(tempDirPath.data(), shaderAssetFileName.data(), shaderAssetOutputPath, true);
  212. if (!Utils::SaveObjectToFile(shaderAssetOutputPath, DataStream::ST_BINARY, shaderAsset.Get()))
  213. {
  214. AZ_Error(ShaderAssetBuilderName, false, "Failed to output Shader Descriptor");
  215. return false;
  216. }
  217. // This step is very important, because it declares product dependency between ShaderAsset and the root ShaderVariantAssets (one for each supervariant).
  218. // This will guarantee that when the ShaderAsset is loaded at runtime, the ShaderAsset will report OnAssetReady only after the root ShaderVariantAssets
  219. // are already fully loaded and ready.
  220. AssetBuilderSDK::JobProduct shaderJobProduct;
  221. if (!AssetBuilderSDK::OutputObject(shaderAsset.Get(), shaderAssetOutputPath, azrtti_typeid<RPI::ShaderAsset>(),
  222. aznumeric_cast<uint32_t>(RPI::ShaderAssetSubId::ShaderAsset), shaderJobProduct))
  223. {
  224. AZ_Error(ShaderAssetBuilderName, false, "Failed to output product dependencies.");
  225. return false;
  226. }
  227. response.m_outputProducts.push_back(AZStd::move(shaderJobProduct));
  228. return true;
  229. }
  230. static AZ::Outcome<RHI::ShaderStageAttributeMapList, AZStd::string> BuildAttributesMap(
  231. const RHI::ShaderPlatformInterface* shaderPlatformInterface,
  232. const AzslData& azslData,
  233. const MapOfStringToStageType& shaderEntryPoints,
  234. bool& hasRasterProgram)
  235. {
  236. hasRasterProgram = false;
  237. bool hasComputeProgram = false;
  238. bool hasRayTracingProgram = false;
  239. RHI::ShaderStageAttributeMapList attributeMaps;
  240. attributeMaps.resize(RHI::ShaderStageCount);
  241. for (const auto& shaderEntryPoint : shaderEntryPoints)
  242. {
  243. auto shaderEntryName = shaderEntryPoint.first;
  244. auto shaderStageType = shaderEntryPoint.second;
  245. auto assetBuilderShaderType = ShaderBuilderUtility::ToAssetBuilderShaderType(shaderStageType);
  246. hasRasterProgram |= shaderPlatformInterface->IsShaderStageForRaster(assetBuilderShaderType);
  247. hasComputeProgram |= shaderPlatformInterface->IsShaderStageForCompute(assetBuilderShaderType);
  248. hasRayTracingProgram |= shaderPlatformInterface->IsShaderStageForRayTracing(assetBuilderShaderType);
  249. auto findId = AZStd::find_if(AZ_BEGIN_END(azslData.m_functions), [&shaderEntryPoint](const auto& func) {
  250. return func.m_name == shaderEntryPoint.first;
  251. });
  252. if (findId == azslData.m_functions.end())
  253. {
  254. // shaderData.m_functions only contains Vertex, Fragment and Compute entries for now
  255. // Tessellation shaders will need to be handled too
  256. continue;
  257. }
  258. const auto shaderStage = ToRHIShaderStage(assetBuilderShaderType);
  259. for (const auto& attr : findId->attributesList)
  260. {
  261. // Some stages like RHI::ShaderStage::Tessellation are compound and consist of two or more shader entries
  262. const Name& attributeName = attr.first;
  263. const RHI::ShaderStageAttributeArguments& args = attr.second;
  264. const auto stageIndex = static_cast<uint32_t>(shaderStage);
  265. AZ_Assert(stageIndex < RHI::ShaderStageCount, "Invalid shader stage specified!");
  266. attributeMaps[stageIndex][attributeName] = args;
  267. }
  268. }
  269. if (hasRasterProgram && hasComputeProgram)
  270. {
  271. return AZ::Failure(AZStd::string(" Shader asset descriptor defines both a raster entry point and a compute entry point."));
  272. }
  273. if (!hasRasterProgram && !hasComputeProgram && !hasRayTracingProgram)
  274. {
  275. return AZ::Failure(
  276. AZStd::string( "Shader asset descriptor has a program variant that does not define any entry points."
  277. " Please declare entry points in the .shader file."));
  278. }
  279. return AZ::Success(attributeMaps);
  280. }
  281. void ShaderAssetBuilder::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
  282. {
  283. AZ::Debug::Timer timer;
  284. timer.Stamp();
  285. AZStd::string shaderFullPath;
  286. AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), shaderFullPath, true);
  287. // Save .shader file name (no extension and no parent directory path)
  288. AZStd::string shaderFileName;
  289. AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.c_str(), shaderFileName);
  290. auto descriptorParseOutcome = ShaderBuilderUtility::LoadShaderDataJson(shaderFullPath);
  291. if (!descriptorParseOutcome.IsSuccess())
  292. {
  293. AZ_Error(
  294. ShaderAssetBuilderName, false, "Failed to parse Shader Descriptor JSON: %s",
  295. descriptorParseOutcome.GetError().c_str());
  296. return;
  297. }
  298. RPI::ShaderSourceData shaderSourceData = descriptorParseOutcome.TakeValue();
  299. AZStd::string azslFullPath;
  300. ShaderBuilderUtility::GetAbsolutePathToAzslFile(shaderFullPath, shaderSourceData.m_source, azslFullPath);
  301. AZ_TracePrintf(ShaderAssetBuilderName, "Original AZSL File: %s \n", azslFullPath.c_str());
  302. // The directory where the Azsl file was found must be added to the list of include paths
  303. AZStd::string azslFolderPath;
  304. AzFramework::StringFunc::Path::GetFolderPath(azslFullPath.c_str(), azslFolderPath);
  305. auto projectIncludePaths = BuildListOfIncludeDirectories(ShaderAssetBuilderName, azslFolderPath.c_str());
  306. ShaderBuildArgumentsManager buildArgsManager;
  307. buildArgsManager.Init();
  308. // A job always runs on behalf of an Asset Processing platform (aka PlatformInfo).
  309. // Let's merge the shader build arguments of the current PlatformInfo with the global
  310. // set of arguments.
  311. const auto platformName = ShaderBuilderUtility::GetPlatformNameFromPlatformInfo(request.m_platformInfo);
  312. buildArgsManager.PushArgumentScope(platformName);
  313. // Request the list of valid shader platform interfaces for the target platform.
  314. AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces = ShaderBuilderUtility::DiscoverEnabledShaderPlatformInterfaces(
  315. request.m_platformInfo, shaderSourceData);
  316. if (platformInterfaces.empty())
  317. {
  318. //No work to do. Exit gracefully.
  319. AZ_TracePrintf(ShaderAssetBuilderName,
  320. "No azshader is produced on behalf of %s because all valid RHI backends were disabled for this shader.\n",
  321. shaderFullPath.c_str());
  322. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  323. return;
  324. }
  325. auto supervariantList = ShaderBuilderUtility::GetSupervariantListFromShaderSourceData(shaderSourceData);
  326. RPI::ShaderAssetCreator shaderAssetCreator;
  327. shaderAssetCreator.Begin(Uuid::CreateRandom());
  328. shaderAssetCreator.SetName(AZ::Name{shaderFileName});
  329. shaderAssetCreator.SetDrawListName(shaderSourceData.m_drawListName);
  330. // The ShaderOptionGroupLayout must be the same across all supervariants because
  331. // there can be only a single ShaderVariantTreeAsset per ShaderAsset.
  332. // We will store here the one that results when the *.azslin file is
  333. // compiled for the default, nameless, supervariant.
  334. // For all other supervariants we just make sure the hashes are the same
  335. // as this one.
  336. RPI::Ptr<RPI::ShaderOptionGroupLayout> finalShaderOptionGroupLayout = nullptr;
  337. // Time to describe the big picture.
  338. // 1- Preprocess an AZSL file with MCPP (a C-Preprocessor), and generate a flat AZSL file without #include lines and any macros in it.
  339. // 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:
  340. // 1.1- From <GameProject>/Config/shader_global_build_options.json, which we have stored in the local variable @buildOptions.
  341. // 1.2- From the "Supervariant" definition key, which can be different for each supervariant.
  342. // 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
  343. // per supervariant.
  344. // 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.
  345. // DXC has a preprocessor embedded in it. DXC will be executed once for each entry function listed in the .shader file.
  346. // There will be one DXIL compiled binary for each entry function. All the DXIL compiled binaries for each supervariant will be combined
  347. // in the ROOT ShaderVariantAsset.
  348. // 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,
  349. // for each supervariant.
  350. for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces)
  351. {
  352. AZStd::string apiName(shaderPlatformInterface->GetAPIName().GetCStr());
  353. AZ_TraceContext("Platform API", apiName);
  354. buildArgsManager.PushArgumentScope(apiName);
  355. buildArgsManager.PushArgumentScope(shaderSourceData.m_removeBuildArguments, shaderSourceData.m_addBuildArguments, shaderSourceData.m_definitions);
  356. // Signal the begin of shader data for an RHI API.
  357. shaderAssetCreator.BeginAPI(shaderPlatformInterface->GetAPIType());
  358. // Each shaderPlatformInterface has its own azsli header that needs to be prepended to the AZSL file before
  359. // preprocessing. We will create a new temporary file that contains the combined data.
  360. RHI::PrependArguments args;
  361. args.m_sourceFile = azslFullPath.c_str();
  362. args.m_prependFile = shaderPlatformInterface->GetAzslHeader(request.m_platformInfo);
  363. args.m_addSuffixToFileName = apiName.c_str();
  364. args.m_destinationFolder = request.m_tempDirPath.c_str();
  365. AZStd::string prependedAzslFilePath = RHI::PrependFile(args);
  366. if (prependedAzslFilePath == azslFullPath)
  367. {
  368. // The specific error is already reported by RHI::PrependFile().
  369. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  370. return;
  371. }
  372. uint32_t supervariantIndex = 0;
  373. for (const auto& supervariantInfo : supervariantList)
  374. {
  375. AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
  376. if (jobCancelListener.IsCancelled())
  377. {
  378. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
  379. return;
  380. }
  381. buildArgsManager.PushArgumentScope(supervariantInfo.m_removeBuildArguments, supervariantInfo.m_addBuildArguments, supervariantInfo.m_definitions);
  382. shaderAssetCreator.BeginSupervariant(supervariantInfo.m_name);
  383. // Run the preprocessor.
  384. PreprocessorData output;
  385. auto preprocessorArguments = AppendIncludePathsToArgumentList(buildArgsManager.GetCurrentArguments().m_preprocessorArguments, projectIncludePaths);
  386. const bool preprocessorSuccess = PreprocessFile(prependedAzslFilePath, output, preprocessorArguments, true);
  387. RHI::ReportMessages(ShaderAssetBuilderName, output.diagnostics, !preprocessorSuccess);
  388. // Dump the preprocessed string as a flat AZSL file with extension .azslin, which will be given to AZSLc to generate the HLSL file.
  389. AZStd::string superVariantAzslinStemName = shaderFileName;
  390. if (!supervariantInfo.m_name.IsEmpty())
  391. {
  392. superVariantAzslinStemName += AZStd::string::format("-%s", supervariantInfo.m_name.GetCStr());
  393. }
  394. AZStd::string azslinFullPath = ShaderBuilderUtility::DumpPreprocessedCode(
  395. ShaderAssetBuilderName, output.code, request.m_tempDirPath, superVariantAzslinStemName,
  396. apiName);
  397. if (azslinFullPath.empty())
  398. {
  399. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  400. return;
  401. }
  402. AZ_TracePrintf(ShaderAssetBuilderName, "Preprocessed AZSL File: %s \n", prependedAzslFilePath.c_str());
  403. // Ready to transpile the azslin file into HLSL.
  404. ShaderBuilder::AzslCompiler azslc(azslinFullPath, request.m_tempDirPath);
  405. AZStd::string hlslFullPath = AZStd::string::format("%s_%s.hlsl", superVariantAzslinStemName.c_str(), apiName.c_str());
  406. AzFramework::StringFunc::Path::Join(request.m_tempDirPath.c_str(), hlslFullPath.c_str(), hlslFullPath, true);
  407. auto emitFullOutcome = azslc.EmitFullData(buildArgsManager.GetCurrentArguments().m_azslcArguments, hlslFullPath);
  408. if (!emitFullOutcome.IsSuccess())
  409. {
  410. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  411. return;
  412. }
  413. ShaderBuilderUtility::AzslSubProducts::Paths subProductsPaths = emitFullOutcome.TakeValue();
  414. // In addition to the hlsl file, there are other json files that were generated.
  415. // Each output file will become a product.
  416. static constexpr AZ::Uuid AzslOutcomeType{ "{6977AEB1-17AD-4992-957B-23BB2E85B18B}" };
  417. for (int i = 0; i < subProductsPaths.size(); ++i)
  418. {
  419. AssetBuilderSDK::JobProduct jobProduct;
  420. jobProduct.m_productFileName = subProductsPaths[i];
  421. jobProduct.m_productAssetType = AzslOutcomeType;
  422. // uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, uint32_t subProductType
  423. jobProduct.m_productSubID = RPI::ShaderAsset::MakeProductAssetSubId(
  424. shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex,
  425. aznumeric_cast<uint32_t>(ShaderBuilderUtility::AzslSubProducts::SubList[i]));
  426. jobProduct.m_dependenciesHandled = true;
  427. // Note that the output products are not traditional product assets that will be used by the game project.
  428. // They are artifacts that are produced once, cached, and used later by other AssetBuilders as a way to centralize
  429. // build organization.
  430. response.m_outputProducts.push_back(AZStd::move(jobProduct));
  431. }
  432. AZStd::shared_ptr<ShaderFiles> files(new ShaderFiles);
  433. AzslData azslData(files);
  434. azslData.m_preprocessedFullPath = azslinFullPath;
  435. RPI::ShaderResourceGroupLayoutList srgLayoutList;
  436. RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout = RPI::ShaderOptionGroupLayout::Create();
  437. BindingDependencies bindingDependencies;
  438. RootConstantData rootConstantData;
  439. bool usesSpecializationConstants = false;
  440. AssetBuilderSDK::ProcessJobResultCode azslJsonReadResult = ShaderBuilderUtility::PopulateAzslDataFromJsonFiles(
  441. ShaderAssetBuilderName, subProductsPaths, azslData, srgLayoutList,
  442. shaderOptionGroupLayout,
  443. bindingDependencies,
  444. rootConstantData,
  445. request.m_tempDirPath,
  446. usesSpecializationConstants);
  447. if (azslJsonReadResult != AssetBuilderSDK::ProcessJobResult_Success)
  448. {
  449. response.m_resultCode = azslJsonReadResult;
  450. return;
  451. }
  452. shaderAssetCreator.SetSrgLayoutList(srgLayoutList);
  453. shaderAssetCreator.SetUseSpecializationConstants(usesSpecializationConstants);
  454. if (!finalShaderOptionGroupLayout)
  455. {
  456. finalShaderOptionGroupLayout = shaderOptionGroupLayout;
  457. shaderAssetCreator.SetShaderOptionGroupLayout(finalShaderOptionGroupLayout);
  458. [[maybe_unused]] const uint32_t usedShaderOptionBits = shaderOptionGroupLayout->GetBitSize();
  459. AZ_TracePrintf(
  460. ShaderAssetBuilderName, "Note: This shader uses %u of %u available shader variant key bits. \n",
  461. usedShaderOptionBits, RPI::ShaderVariantKeyBitCount);
  462. }
  463. else
  464. {
  465. if (finalShaderOptionGroupLayout->GetHash() != shaderOptionGroupLayout->GetHash())
  466. {
  467. AZ_Error(
  468. ShaderAssetBuilderName, false, "Supervariant %s has a different ShaderOptionGroupLayout",
  469. supervariantInfo.m_name.GetCStr())
  470. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  471. return;
  472. }
  473. }
  474. if (shaderSourceData.m_programSettings.m_entryPoints.empty())
  475. {
  476. AZ_Error( ShaderAssetBuilderName, false, "ProgramSettings must specify entry points.");
  477. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  478. return;
  479. }
  480. // Discover entry points & type of programs.
  481. MapOfStringToStageType shaderEntryPoints;
  482. for (const auto& entryPoint : shaderSourceData.m_programSettings.m_entryPoints)
  483. {
  484. shaderEntryPoints[entryPoint.m_name] = entryPoint.m_type;
  485. }
  486. bool hasRasterProgram = false;
  487. auto attributeMapsOutcome = BuildAttributesMap(shaderPlatformInterface, azslData, shaderEntryPoints, hasRasterProgram);
  488. if (!attributeMapsOutcome.IsSuccess())
  489. {
  490. AZ_Error(ShaderAssetBuilderName, false, "%s\n", attributeMapsOutcome.GetError().c_str());
  491. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  492. return;
  493. }
  494. shaderAssetCreator.SetShaderStageAttributeMapList(attributeMapsOutcome.TakeValue());
  495. // Check if we were canceled before we do any heavy processing of
  496. // the shader data (compiling the shader kernels, processing SRG
  497. // and pipeline layout data, etc.).
  498. if (jobCancelListener.IsCancelled())
  499. {
  500. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
  501. return;
  502. }
  503. RHI::Ptr<RHI::PipelineLayoutDescriptor> pipelineLayoutDescriptor =
  504. ShaderBuilderUtility::BuildPipelineLayoutDescriptorForApi(
  505. ShaderAssetBuilderName, srgLayoutList, shaderEntryPoints, buildArgsManager.GetCurrentArguments(), rootConstantData,
  506. shaderPlatformInterface, bindingDependencies);
  507. if (!pipelineLayoutDescriptor)
  508. {
  509. AZ_Error(
  510. ShaderAssetBuilderName, false, "Failed to build pipeline layout descriptor for api=[%s]",
  511. shaderPlatformInterface->GetAPIName().GetCStr());
  512. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  513. return;
  514. }
  515. shaderAssetCreator.SetPipelineLayout(pipelineLayoutDescriptor);
  516. RPI::ShaderInputContract shaderInputContract;
  517. RPI::ShaderOutputContract shaderOutputContract;
  518. size_t colorAttachmentCount = 0;
  519. ShaderBuilderUtility::CreateShaderInputAndOutputContracts(
  520. azslData, shaderEntryPoints, *shaderOptionGroupLayout.get(),
  521. subProductsPaths[ShaderBuilderUtility::AzslSubProducts::om],
  522. subProductsPaths[ShaderBuilderUtility::AzslSubProducts::ia],
  523. shaderInputContract, shaderOutputContract, colorAttachmentCount, request.m_tempDirPath);
  524. shaderAssetCreator.SetInputContract(shaderInputContract);
  525. shaderAssetCreator.SetOutputContract(shaderOutputContract);
  526. if (hasRasterProgram)
  527. {
  528. RHI::RenderStates renderStates;
  529. renderStates.m_rasterState = shaderSourceData.m_rasterState;
  530. renderStates.m_depthStencilState = shaderSourceData.m_depthStencilState;
  531. renderStates.m_blendState = shaderSourceData.m_blendState;
  532. const RHI::TargetBlendState& globalTargetBlendState = shaderSourceData.m_globalTargetBlendState;
  533. const auto& targetBlendStates = shaderSourceData.m_targetBlendStates;
  534. // There are three ways to set blend state in the .shader file: "BlendState", "GlobalTargetBlendState", and "TargetBlendStates".
  535. // "BlendState" is a raw serialization of the BlendState struct, and is not very convenient to work with because it requires
  536. // every target to be specified in order for the data to load successfully. Normally users will want to use "GlobalTargetBlendState"
  537. // or "TargetBlendStates".
  538. for (size_t i = 0; i < colorAttachmentCount; ++i)
  539. {
  540. if (targetBlendStates.contains(static_cast<uint32_t>(i)))
  541. {
  542. renderStates.m_blendState.m_targets[i] = targetBlendStates.at(static_cast<uint32_t>(i));
  543. }
  544. // We have to ensure this actually has data before applying it, otherwise this would stomp any
  545. // data in the "BlendState" or "TargetBlendStates" with default values.
  546. else if(globalTargetBlendState.m_enable)
  547. {
  548. renderStates.m_blendState.m_targets[i] = globalTargetBlendState;
  549. }
  550. }
  551. #if defined(AZ_ENABLE_TRACING)
  552. // Enable unused target blend state tracking
  553. for (const auto& targetBlendState : targetBlendStates)
  554. {
  555. const bool invalidBlendStateIndex = targetBlendState.first >= colorAttachmentCount;
  556. AZ_Warning(
  557. ShaderAssetBuilderName, !invalidBlendStateIndex,
  558. "Invalid target blend state index detected, setting index %d out of %d possible color attachements. Ignoring this target blend state definition.",
  559. targetBlendState.first, colorAttachmentCount);
  560. }
  561. #endif // defined(AZ_ENABLE_TRACING)
  562. shaderAssetCreator.SetRenderStates(renderStates);
  563. }
  564. Outcome<AZStd::string, AZStd::string> hlslSourceCodeOutcome = Utils::ReadFile(hlslFullPath, AZ::RPI::JsonUtils::DefaultMaxFileSize);
  565. if (!hlslSourceCodeOutcome.IsSuccess())
  566. {
  567. AZ_Error(
  568. ShaderAssetBuilderName, false, "Failed to obtain shader source from %s. [%s]", hlslFullPath.c_str(),
  569. hlslSourceCodeOutcome.GetError().c_str());
  570. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  571. return;
  572. }
  573. AZStd::string hlslSourceCode = hlslSourceCodeOutcome.TakeValue();
  574. // The root ShaderVariantAsset needs to be created with the known uuid of the source .shader asset because
  575. // the ShaderAsset owns a Data::Asset<> reference that gets serialized. It must have the correct uuid
  576. // so the root ShaderVariantAsset is found when the ShaderAsset is deserialized.
  577. uint32_t rootVariantProductSubId = RPI::ShaderAsset::MakeProductAssetSubId(
  578. shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex,
  579. aznumeric_cast<uint32_t>(RPI::ShaderAssetSubId::RootShaderVariantAsset));
  580. auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(shaderFullPath, rootVariantProductSubId);
  581. AZ_Assert(assetIdOutcome.IsSuccess(), "Failed to get AssetId from shader %s", shaderFullPath.c_str());
  582. const Data::AssetId variantAssetId = assetIdOutcome.TakeValue();
  583. RPI::ShaderVariantListSourceData::VariantInfo rootVariantInfo;
  584. ShaderVariantCreationContext shaderVariantCreationContext = {
  585. *shaderPlatformInterface,
  586. request.m_platformInfo,
  587. buildArgsManager.GetCurrentArguments(),
  588. request.m_tempDirPath,
  589. shaderSourceData,
  590. *shaderOptionGroupLayout.get(),
  591. shaderEntryPoints,
  592. variantAssetId,
  593. superVariantAzslinStemName,
  594. hlslFullPath,
  595. hlslSourceCode,
  596. usesSpecializationConstants };
  597. // Preserve the Temp folder when shaders are compiled with debug symbols
  598. // or because the ShaderSourceData has m_keepTempFolder set to true.
  599. response.m_keepTempFolder |= shaderVariantCreationContext.m_shaderBuildArguments.m_generateDebugInfo
  600. || shaderSourceData.m_keepTempFolder || RHI::IsGraphicsDevModeEnabled();
  601. AZStd::optional<RHI::ShaderPlatformInterface::ByProducts> outputByproducts;
  602. auto rootShaderVariantAssetOutcome = ShaderVariantAssetBuilder::CreateShaderVariantAsset(rootVariantInfo, shaderVariantCreationContext, outputByproducts);
  603. if (!rootShaderVariantAssetOutcome.IsSuccess())
  604. {
  605. AZ_Error(ShaderAssetBuilderName, false, "%s\n", rootShaderVariantAssetOutcome.GetError().c_str())
  606. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  607. return;
  608. }
  609. Data::Asset<RPI::ShaderVariantAsset> rootShaderVariantAsset = rootShaderVariantAssetOutcome.TakeValue();
  610. shaderAssetCreator.SetRootShaderVariantAsset(rootShaderVariantAsset);
  611. if (!shaderAssetCreator.EndSupervariant())
  612. {
  613. AZ_Error(
  614. ShaderAssetBuilderName, false, "Failed to create shader asset for supervariant [%s]", supervariantInfo.m_name.GetCStr())
  615. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  616. return;
  617. }
  618. // Time to save the root variant related assets in the cache.
  619. AssetBuilderSDK::JobProduct assetProduct;
  620. if (!ShaderVariantAssetBuilder::SerializeOutShaderVariantAsset(
  621. rootShaderVariantAsset, superVariantAzslinStemName, request.m_tempDirPath, *shaderPlatformInterface,
  622. rootVariantProductSubId,
  623. assetProduct))
  624. {
  625. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  626. return;
  627. }
  628. response.m_outputProducts.push_back(assetProduct);
  629. if (outputByproducts)
  630. {
  631. // add byproducts as job output products:
  632. uint32_t subProductType = aznumeric_cast<uint32_t>(RPI::ShaderAssetSubId::FirstByProduct);
  633. for (const AZStd::string& byproduct : outputByproducts.value().m_intermediatePaths)
  634. {
  635. AssetBuilderSDK::JobProduct jobProduct;
  636. jobProduct.m_productFileName = byproduct;
  637. jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt");
  638. jobProduct.m_productSubID = RPI::ShaderAsset::MakeProductAssetSubId(
  639. shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex,
  640. subProductType++);
  641. response.m_outputProducts.push_back(AZStd::move(jobProduct));
  642. }
  643. }
  644. buildArgsManager.PopArgumentScope();
  645. supervariantIndex++;
  646. } // end for the supervariant
  647. for (const auto& [shaderOptionName, value] : shaderSourceData.m_shaderOptionValues)
  648. {
  649. shaderAssetCreator.SetShaderOptionDefaultValue(shaderOptionName, value);
  650. }
  651. buildArgsManager.PopArgumentScope(); // Pop .shader arguments
  652. buildArgsManager.PopArgumentScope(); // Pop rhi api arguments.
  653. shaderAssetCreator.EndAPI();
  654. } // end for all ShaderPlatformInterfaces
  655. Data::Asset<RPI::ShaderAsset> shaderAsset;
  656. if (!shaderAssetCreator.End(shaderAsset))
  657. {
  658. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  659. return;
  660. }
  661. if (!SerializeOutShaderAsset(shaderAsset, request.m_tempDirPath, response))
  662. {
  663. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  664. return;
  665. }
  666. AZ_TracePrintf(ShaderAssetBuilderName, "Finished processing %s in %.3f seconds\n", request.m_sourceFile.c_str(), timer.GetDeltaTimeInSeconds());
  667. ShaderBuilderUtility::LogProfilingData(ShaderAssetBuilderName, shaderFileName);
  668. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  669. }
  670. } // ShaderBuilder
  671. } // AZ