3
0

ShaderBuilderUtility.cpp 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935
  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 "ShaderBuilderUtility.h"
  9. #include <AzFramework/API/ApplicationAPI.h>
  10. #include <AzFramework/StringFunc/StringFunc.h>
  11. #include <AzFramework/Asset/AssetSystemBus.h>
  12. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  13. #include <AzToolsFramework/AssetCatalog/PlatformAddressedAssetCatalog.h>
  14. #include <AzCore/Asset/AssetManager.h>
  15. #include <AzCore/IO/IOUtils.h>
  16. #include <AzCore/IO/FileIO.h>
  17. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  18. #include <AzCore/std/string/regex.h>
  19. #include <AzCore/Serialization/Json/JsonUtils.h>
  20. #include <Atom/RPI.Edit/Common/JsonReportingHelper.h>
  21. #include <Atom/RPI.Edit/Common/AssetUtils.h>
  22. #include <Atom/RPI.Edit/Common/JsonUtils.h>
  23. #include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
  24. #include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
  25. #include <Atom/RHI.Edit/Utils.h>
  26. #include <Atom/RHI.Edit/ShaderPlatformInterface.h>
  27. #include <CommonFiles/Preprocessor.h>
  28. #include <AzslCompiler.h>
  29. #include "ShaderPlatformInterfaceRequest.h"
  30. #include "ShaderBuilder_Traits_Platform.h"
  31. #include "SrgLayoutUtility.h"
  32. namespace AZ
  33. {
  34. namespace ShaderBuilder
  35. {
  36. namespace ShaderBuilderUtility
  37. {
  38. [[maybe_unused]] static constexpr char ShaderBuilderUtilityName[] = "ShaderBuilderUtility";
  39. Outcome<RPI::ShaderSourceData, AZStd::string> LoadShaderDataJson(const AZStd::string& fullPathToJsonFile, bool warningsAsErrors)
  40. {
  41. RPI::ShaderSourceData shaderSourceData;
  42. AZ::Outcome<rapidjson::Document, AZStd::string> loadOutcome =
  43. JsonSerializationUtils::ReadJsonFile(fullPathToJsonFile, AZ::RPI::JsonUtils::DefaultMaxFileSize);
  44. if (!loadOutcome.IsSuccess())
  45. {
  46. return Failure(loadOutcome.GetError());
  47. }
  48. rapidjson::Document document = loadOutcome.TakeValue();
  49. JsonDeserializerSettings settings;
  50. RPI::JsonReportingHelper reportingHelper;
  51. reportingHelper.Attach(settings);
  52. JsonSerialization::Load(shaderSourceData, document, settings);
  53. if (reportingHelper.ErrorsReported())
  54. {
  55. return AZ::Failure(reportingHelper.GetErrorMessage());
  56. }
  57. else if (warningsAsErrors && reportingHelper.WarningsReported())
  58. {
  59. return AZ::Failure(AZStd::string("Warnings treated as error, see above."));
  60. }
  61. else
  62. {
  63. return AZ::Success(shaderSourceData);
  64. }
  65. }
  66. void GetAbsolutePathToAzslFile(const AZStd::string& shaderSourceFileFullPath, AZStd::string specifiedShaderPathAndName, AZStd::string& absoluteAzslPath)
  67. {
  68. AZStd::string sourcePath;
  69. AzFramework::StringFunc::Path::GetFullPath(shaderSourceFileFullPath.c_str(), sourcePath);
  70. AzFramework::StringFunc::Path::Normalize(specifiedShaderPathAndName);
  71. bool shaderNameHasPath = (specifiedShaderPathAndName.find(AZ_CORRECT_FILESYSTEM_SEPARATOR) != AZStd::string::npos);
  72. // Join will handle overlapping directory structures for us
  73. AzFramework::StringFunc::Path::Join(sourcePath.data(), specifiedShaderPathAndName.data(), absoluteAzslPath, shaderNameHasPath /* handle directory overlap? */, false /* be case insensitive? */);
  74. // The builders used to automatically set the ".azsl" extension, but no more, because that would make the .shader file confusing to read.
  75. // Here we just detect the issue and instruct the user what to change.
  76. // (There's no need to return a failure code, the builder will eventually fail anyway when it can't find the file).
  77. if (!IO::FileIOBase::GetInstance()->Exists(absoluteAzslPath.c_str()))
  78. {
  79. AZStd::string absoluteAzslPathWithForcedExtension = absoluteAzslPath;
  80. AzFramework::StringFunc::Path::ReplaceExtension(absoluteAzslPathWithForcedExtension, "azsl");
  81. if (IO::FileIOBase::GetInstance()->Exists(absoluteAzslPathWithForcedExtension.c_str()))
  82. {
  83. AZ_Error(ShaderBuilderUtilityName, false, "When the .shader file references a .azsl file, it must include the \".azsl\" extension.");
  84. }
  85. }
  86. }
  87. AZStd::shared_ptr<ShaderFiles> PrepareSourceInput(
  88. [[maybe_unused]] const char* builderName,
  89. const AZStd::string& shaderSourceFileFullPath,
  90. RPI::ShaderSourceData& sourceAsset)
  91. {
  92. auto shaderAssetSourceFileParseOutput = ShaderBuilderUtility::LoadShaderDataJson(shaderSourceFileFullPath);
  93. if (!shaderAssetSourceFileParseOutput.IsSuccess())
  94. {
  95. AZ_Error(builderName, false, "Failed to load/parse Shader Descriptor JSON: %s", shaderAssetSourceFileParseOutput.GetError().c_str());
  96. return nullptr;
  97. }
  98. sourceAsset = AZStd::move(shaderAssetSourceFileParseOutput.GetValue());
  99. AZStd::shared_ptr<ShaderFiles> files(new ShaderFiles);
  100. const AZStd::string& specifiedAzslName = sourceAsset.m_source;
  101. ShaderBuilderUtility::GetAbsolutePathToAzslFile(shaderSourceFileFullPath, specifiedAzslName, files->m_azslSourceFullPath);
  102. // specifiedAzslName may have a relative path on it so need to strip it
  103. AzFramework::StringFunc::Path::GetFileName(specifiedAzslName.c_str(), files->m_azslFileName);
  104. return files;
  105. }
  106. AssetBuilderSDK::ProcessJobResultCode PopulateAzslDataFromJsonFiles(
  107. const char* builderName,
  108. const AzslSubProducts::Paths& pathOfJsonFiles,
  109. AzslData& azslData,
  110. RPI::ShaderResourceGroupLayoutList& srgLayoutList,
  111. RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout,
  112. BindingDependencies& bindingDependencies,
  113. RootConstantData& rootConstantData,
  114. const AZStd::string& tempFolder,
  115. bool& useSpecializationConstants)
  116. {
  117. AzslCompiler azslc(azslData.m_preprocessedFullPath, // set the input file for eventual error messages, but the compiler won't be called on it.
  118. tempFolder);
  119. bool allReadSuccess = true;
  120. // read: input assembly reflection
  121. // shader resource group reflection
  122. // options reflection
  123. // binding dependencies reflection
  124. int indicesOfInterest[] = {
  125. AzslSubProducts::ia, AzslSubProducts::srg, AzslSubProducts::options, AzslSubProducts::bindingdep};
  126. AZStd::unordered_map<int, Outcome<rapidjson::Document, AZStd::string>> outcomes;
  127. for (int i : indicesOfInterest)
  128. {
  129. outcomes[i] = JsonSerializationUtils::ReadJsonFile(pathOfJsonFiles[i], AZ::RPI::JsonUtils::DefaultMaxFileSize);
  130. if (!outcomes[i].IsSuccess())
  131. {
  132. AZ_Error(builderName, false, "%s", outcomes[i].GetError().c_str());
  133. allReadSuccess = false;
  134. }
  135. }
  136. if (!allReadSuccess)
  137. {
  138. return AssetBuilderSDK::ProcessJobResult_Failed;
  139. }
  140. // Get full list of functions eligible for vertex shader entry points
  141. // along with metadata for constructing the InputAssembly for each of them
  142. if (!azslc.ParseIaPopulateFunctionData(outcomes[AzslSubProducts::ia].GetValue(), azslData.m_functions))
  143. {
  144. return AssetBuilderSDK::ProcessJobResult_Failed;
  145. }
  146. // Each SRG is built as a separate asset in the SrgLayoutBuilder, here we just
  147. // build the list and load the data from multiple dependency assets.
  148. if (!azslc.ParseSrgPopulateSrgData(outcomes[AzslSubProducts::srg].GetValue(), azslData.m_srgData))
  149. {
  150. return AssetBuilderSDK::ProcessJobResult_Failed;
  151. }
  152. // Add all Shader Resource Group Assets that were defined in the shader code to the shader asset
  153. if (!SrgLayoutUtility::LoadShaderResourceGroupLayouts(builderName, azslData.m_srgData, srgLayoutList))
  154. {
  155. AZ_Error(builderName, false, "Failed to obtain shader resource group assets");
  156. return AssetBuilderSDK::ProcessJobResult_Failed;
  157. }
  158. // The shader options define what options are available, what are the allowed values/range
  159. // for each option and what is its default value.
  160. if (!azslc.ParseOptionsPopulateOptionGroupLayout(
  161. outcomes[AzslSubProducts::options].GetValue(), shaderOptionGroupLayout, useSpecializationConstants))
  162. {
  163. AZ_Error(builderName, false, "Failed to find a valid list of shader options!");
  164. return AssetBuilderSDK::ProcessJobResult_Failed;
  165. }
  166. // It analyzes the shader external bindings (all SRG contents)
  167. // and informs us on register indexes and shader stages using these resources
  168. if (!azslc.ParseBindingdepPopulateBindingDependencies(
  169. outcomes[AzslSubProducts::bindingdep].GetValue(), bindingDependencies)) // consuming data from binding-dep
  170. {
  171. AZ_Error(builderName, false, "Failed to obtain shader resource binding reflection");
  172. return AssetBuilderSDK::ProcessJobResult_Failed;
  173. }
  174. // access the root constants reflection
  175. if (!azslc.ParseSrgPopulateRootConstantData(
  176. outcomes[AzslSubProducts::srg].GetValue(),
  177. rootConstantData)) // consuming data from --srg ("RootConstantBuffer" subjson section)
  178. {
  179. AZ_Error(builderName, false, "Failed to obtain root constant data reflection");
  180. return AssetBuilderSDK::ProcessJobResult_Failed;
  181. }
  182. return AssetBuilderSDK::ProcessJobResult_Success;
  183. }
  184. RHI::ShaderHardwareStage ToAssetBuilderShaderType(RPI::ShaderStageType stageType)
  185. {
  186. switch (stageType)
  187. {
  188. case RPI::ShaderStageType::Compute:
  189. return RHI::ShaderHardwareStage::Compute;
  190. case RPI::ShaderStageType::Fragment:
  191. return RHI::ShaderHardwareStage::Fragment;
  192. case RPI::ShaderStageType::Geometry:
  193. return RHI::ShaderHardwareStage::Geometry;
  194. case RPI::ShaderStageType::Vertex:
  195. return RHI::ShaderHardwareStage::Vertex;
  196. case RPI::ShaderStageType::RayTracing:
  197. return RHI::ShaderHardwareStage::RayTracing;
  198. }
  199. AZ_Assert(false, "Unable to find Shader stage given RPI ShaderStage %d", stageType);
  200. return RHI::ShaderHardwareStage::Invalid;
  201. }
  202. //! the binding dependency structure may store lots of high level function names which are not entry points
  203. static void PruneNonEntryFunctions(
  204. BindingDependencies& bindingDependencies /*inout*/, const MapOfStringToStageType& shaderEntryPoints)
  205. {
  206. auto cleaner = [&shaderEntryPoints](BindingDependencies::FunctionsNameVector& functionVector)
  207. {
  208. functionVector.erase(
  209. AZStd::remove_if(
  210. functionVector.begin(),
  211. functionVector.end(),
  212. [&shaderEntryPoints](const AZStd::string& functionName)
  213. {
  214. return shaderEntryPoints.find(functionName) == shaderEntryPoints.end();
  215. }),
  216. functionVector.end());
  217. };
  218. for (auto& srg : bindingDependencies.m_orderedSrgs)
  219. {
  220. cleaner(srg.m_srgConstantsDependencies.m_binding.m_dependentFunctions);
  221. AZStd::for_each(
  222. srg.m_resources.begin(),
  223. srg.m_resources.end(),
  224. [&cleaner](decltype(*srg.m_resources.begin())& nameResourcePair)
  225. {
  226. cleaner(nameResourcePair.second.m_dependentFunctions);
  227. });
  228. }
  229. }
  230. static AZStd::string DumpCode(
  231. [[maybe_unused]] const char* builderName,
  232. const AZStd::string& codeInString,
  233. const AZStd::string& dumpDirectory,
  234. const AZStd::string& stemName,
  235. const AZStd::string& apiTypeString,
  236. const AZStd::string& extension)
  237. {
  238. AZStd::string finalFilePath;
  239. AZStd::string formatted;
  240. if (apiTypeString.empty())
  241. {
  242. formatted = AZStd::string::format("%s.%s", stemName.c_str(), extension.c_str());
  243. }
  244. else
  245. {
  246. formatted = AZStd::string::format("%s_%s.%s", stemName.c_str(), apiTypeString.c_str(), extension.c_str());
  247. }
  248. AzFramework::StringFunc::Path::Join(dumpDirectory.c_str(), formatted.c_str(), finalFilePath, true, true);
  249. AZ::IO::FileIOStream outFileStream(finalFilePath.data(), AZ::IO::OpenMode::ModeWrite);
  250. if (!outFileStream.IsOpen())
  251. {
  252. AZ_Error(builderName, false, "Failed to open file to write (%s)\n", finalFilePath.data());
  253. return "";
  254. }
  255. outFileStream.Write(codeInString.size(), codeInString.data());
  256. // Prevent warning: "warning: End of input with no newline"
  257. static constexpr char newLine[] = "\n";
  258. outFileStream.Write(sizeof(newLine) - 1, newLine);
  259. outFileStream.Close();
  260. return finalFilePath;
  261. }
  262. AZStd::string DumpPreprocessedCode(const char* builderName, const AZStd::string& preprocessedCode, const AZStd::string& tempDirPath, const AZStd::string& stemName, const AZStd::string& apiTypeString)
  263. {
  264. return DumpCode(builderName, preprocessedCode, tempDirPath, stemName, apiTypeString, "azslin");
  265. }
  266. AZStd::string DumpAzslPrependedCode(const char* builderName, const AZStd::string& nonPreprocessedYetAzslSource, const AZStd::string& tempDirPath, const AZStd::string& stemName, const AZStd::string& apiTypeString)
  267. {
  268. return DumpCode(builderName, nonPreprocessedYetAzslSource, tempDirPath, stemName, apiTypeString, "azslprepend");
  269. }
  270. AZStd::string ExtractStemName(const char* path)
  271. {
  272. AZStd::string result;
  273. AzFramework::StringFunc::Path::GetFileName(path, result);
  274. return result;
  275. }
  276. AZStd::vector<RHI::ShaderPlatformInterface*> DiscoverValidShaderPlatformInterfaces(const AssetBuilderSDK::PlatformInfo& info)
  277. {
  278. AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces;
  279. ShaderPlatformInterfaceRequestBus::BroadcastResult(platformInterfaces, &ShaderPlatformInterfaceRequest::GetShaderPlatformInterface, info);
  280. // filter out nulls:
  281. platformInterfaces.erase(AZStd::remove_if(AZ_BEGIN_END(platformInterfaces), [](auto* element) { return element == nullptr; }), platformInterfaces.end());
  282. return platformInterfaces;
  283. }
  284. AZStd::vector<RHI::ShaderPlatformInterface*> DiscoverEnabledShaderPlatformInterfaces(const AssetBuilderSDK::PlatformInfo& info, const RPI::ShaderSourceData& shaderSourceData)
  285. {
  286. // Request the list of valid shader platform interfaces for the target platform.
  287. AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces;
  288. ShaderPlatformInterfaceRequestBus::BroadcastResult(
  289. platformInterfaces, &ShaderPlatformInterfaceRequest::GetShaderPlatformInterface, info);
  290. // Let's remove the unwanted RHI interfaces from the list.
  291. platformInterfaces.erase(
  292. AZStd::remove_if(AZ_BEGIN_END(platformInterfaces),
  293. [&](const RHI::ShaderPlatformInterface* shaderPlatformInterface) {
  294. return !shaderPlatformInterface ||
  295. shaderSourceData.IsRhiBackendDisabled(shaderPlatformInterface->GetAPIName());
  296. }),
  297. platformInterfaces.end());
  298. return platformInterfaces;
  299. }
  300. static bool IsValidSupervariantName(const AZStd::string& supervariantName)
  301. {
  302. return AZStd::all_of(AZ_BEGIN_END(supervariantName),
  303. [](AZStd::string::value_type ch)
  304. {
  305. return AZStd::is_alnum(ch); // allow alpha numeric only
  306. }
  307. );
  308. }
  309. AZStd::vector<RPI::ShaderSourceData::SupervariantInfo> GetSupervariantListFromShaderSourceData(
  310. const RPI::ShaderSourceData& shaderSourceData)
  311. {
  312. AZStd::vector<RPI::ShaderSourceData::SupervariantInfo> supervariants;
  313. supervariants.reserve(shaderSourceData.m_supervariants.size() + 1);
  314. // Add the supervariants, always making sure that:
  315. // 1- The default, nameless, supervariant goes to the front.
  316. // 2- Each supervariant has a unique name
  317. AZStd::unordered_set<AZ::Name> uniqueSuperVariants; // This set helps duplicate detection.
  318. // Although it is not common, it is possible to declare a nameless supervariant.
  319. bool addedNamelessSupervariant = false;
  320. for (const auto& supervariantInfo : shaderSourceData.m_supervariants)
  321. {
  322. if (!IsValidSupervariantName(supervariantInfo.m_name.GetStringView()))
  323. {
  324. AZ_Error(
  325. ShaderBuilderUtilityName, false, "The supervariant name: [%s] contains invalid characters. Only [a-zA-Z0-9] are supported",
  326. supervariantInfo.m_name.GetCStr());
  327. return {}; // Return an empty vector.
  328. }
  329. if (uniqueSuperVariants.count(supervariantInfo.m_name))
  330. {
  331. AZ_Error(
  332. ShaderBuilderUtilityName, false, "It is invalid to specify more than one supervariant with the same name: [%s]",
  333. supervariantInfo.m_name.GetCStr());
  334. return {}; // Return an empty vector.
  335. }
  336. uniqueSuperVariants.emplace(supervariantInfo.m_name);
  337. supervariants.push_back(supervariantInfo);
  338. if (supervariantInfo.m_name.IsEmpty())
  339. {
  340. addedNamelessSupervariant = true;
  341. // Always move the default, nameless, variant to the begining of the list.
  342. AZStd::swap(supervariants.front(), supervariants.back());
  343. }
  344. }
  345. if (!addedNamelessSupervariant)
  346. {
  347. supervariants.push_back({});
  348. // Always move the default, nameless, variant to the begining of the list.
  349. AZStd::swap(supervariants.front(), supervariants.back());
  350. }
  351. return supervariants;
  352. }
  353. static void ReadShaderCompilerProfiling([[maybe_unused]] const char* builderName, RHI::ShaderCompilerProfiling& shaderCompilerProfiling, AZStd::string_view shaderPath)
  354. {
  355. AZStd::string folderPath;
  356. AzFramework::StringFunc::Path::GetFullPath(shaderPath.data(), folderPath);
  357. AZStd::vector<AZStd::string> fileNames;
  358. IO::FileIOBase::GetInstance()->FindFiles(folderPath.c_str(), "*.profiling", [&](const char* filePath) -> bool
  359. {
  360. fileNames.push_back(AZStd::string(filePath));
  361. return true;
  362. });
  363. for (const AZStd::string& fileName : fileNames)
  364. {
  365. RHI::ShaderCompilerProfiling profiling;
  366. auto loadResult = AZ::JsonSerializationUtils::LoadObjectFromFile<RHI::ShaderCompilerProfiling>(profiling, fileName);
  367. if (!loadResult.IsSuccess())
  368. {
  369. AZ_Error(builderName, false, "Failed to load shader compiler profiling from file [%s]", fileName.data());
  370. AZ_Error(builderName, false, "Loading issues: %s", loadResult.GetError().data());
  371. continue;
  372. }
  373. shaderCompilerProfiling.m_entries.insert(
  374. shaderCompilerProfiling.m_entries.begin(),
  375. profiling.m_entries.begin(), profiling.m_entries.end());
  376. }
  377. }
  378. void LogProfilingData(const char* builderName, AZStd::string_view shaderPath)
  379. {
  380. RHI::ShaderCompilerProfiling shaderCompilerProfiling;
  381. ReadShaderCompilerProfiling(builderName, shaderCompilerProfiling, shaderPath);
  382. struct ProfilingPerCompiler
  383. {
  384. size_t m_calls;
  385. float m_totalElapsedTime;
  386. };
  387. // The key is the compiler executable path.
  388. AZStd::unordered_map<AZStd::string, ProfilingPerCompiler> profilingPerCompiler;
  389. for (const RHI::ShaderCompilerProfiling::Entry& shaderCompilerProfilingEntry : shaderCompilerProfiling.m_entries)
  390. {
  391. auto it = profilingPerCompiler.find(shaderCompilerProfilingEntry.m_executablePath);
  392. if (it == profilingPerCompiler.end())
  393. {
  394. profilingPerCompiler.emplace(
  395. shaderCompilerProfilingEntry.m_executablePath,
  396. ProfilingPerCompiler{ 1, shaderCompilerProfilingEntry.m_elapsedTimeSeconds });
  397. }
  398. else
  399. {
  400. (*it).second.m_calls++;
  401. (*it).second.m_totalElapsedTime += shaderCompilerProfilingEntry.m_elapsedTimeSeconds;
  402. }
  403. }
  404. #if defined(AZ_ENABLE_TRACING)
  405. for (const auto& profiling : profilingPerCompiler)
  406. {
  407. AZ_TracePrintf(builderName, "Compiler: %s\n>\tCalls: %d\n>\tTime: %.2f seconds\n",
  408. profiling.first.c_str(), profiling.second.m_calls, profiling.second.m_totalElapsedTime);
  409. }
  410. #endif
  411. }
  412. Outcome<AZStd::string, AZStd::string> ObtainBuildArtifactPathFromShaderAssetBuilder(
  413. const uint32_t rhiUniqueIndex, const AZStd::string& platformIdentifier, const AZStd::string& shaderJsonPath,
  414. const uint32_t supervariantIndex, RPI::ShaderAssetSubId shaderAssetSubId)
  415. {
  416. // Define a fallback platform ID based on the current host platform
  417. AzFramework::PlatformId platformId = AZ_TRAIT_ATOM_FALLBACK_ASSET_HOST_PLATFORM;
  418. if (platformIdentifier == "pc")
  419. {
  420. platformId = AzFramework::PlatformId::PC;
  421. }
  422. else if (platformIdentifier == "linux")
  423. {
  424. platformId = AzFramework::PlatformId::LINUX_ID;
  425. }
  426. else if (platformIdentifier == "mac")
  427. {
  428. platformId = AzFramework::PlatformId::MAC_ID;
  429. }
  430. else if (platformIdentifier == "android")
  431. {
  432. platformId = AzFramework::PlatformId::ANDROID_ID;
  433. }
  434. else if (platformIdentifier == "ios")
  435. {
  436. platformId = AzFramework::PlatformId::IOS;
  437. }
  438. else if (platformIdentifier == "salem")
  439. {
  440. platformId = AzFramework::PlatformId::SALEM;
  441. }
  442. else if (platformIdentifier == "jasper")
  443. {
  444. platformId = AzFramework::PlatformId::JASPER;
  445. }
  446. else if (platformIdentifier == "server")
  447. {
  448. platformId = AzFramework::PlatformId::SERVER;
  449. }
  450. uint32_t assetSubId = RPI::ShaderAsset::MakeProductAssetSubId(rhiUniqueIndex, supervariantIndex, aznumeric_cast<uint32_t>(shaderAssetSubId));
  451. auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(shaderJsonPath, assetSubId);
  452. if (!assetIdOutcome.IsSuccess())
  453. {
  454. return Failure(AZStd::string::format(
  455. "Missing ShaderAssetBuilder product %s, for sub %d", shaderJsonPath.c_str(), (uint32_t)shaderAssetSubId));
  456. }
  457. Data::AssetId assetId = assetIdOutcome.TakeValue();
  458. // get the relative path:
  459. AZStd::string assetPath;
  460. Data::AssetCatalogRequestBus::BroadcastResult(assetPath, &Data::AssetCatalogRequests::GetAssetPathById, assetId);
  461. // get the root:
  462. AZStd::string assetRoot = AzToolsFramework::PlatformAddressedAssetCatalog::GetAssetRootForPlatform(platformId);
  463. // join
  464. AZStd::string assetFullPath;
  465. AzFramework::StringFunc::Path::Join(assetRoot.c_str(), assetPath.c_str(), assetFullPath);
  466. bool fileExists = IO::FileIOBase::GetInstance()->Exists(assetFullPath.c_str()) &&
  467. !IO::FileIOBase::GetInstance()->IsDirectory(assetFullPath.c_str());
  468. if (!fileExists)
  469. {
  470. return Failure(AZStd::string::format(
  471. "asset [%s] from shader source %s and subId %d doesn't exist", assetFullPath.c_str(), shaderJsonPath.c_str(),
  472. (uint32_t)shaderAssetSubId));
  473. }
  474. return AZ::Success(assetFullPath);
  475. }
  476. RHI::Ptr<RHI::PipelineLayoutDescriptor> BuildPipelineLayoutDescriptorForApi(
  477. [[maybe_unused]] const char* builderName, const RPI::ShaderResourceGroupLayoutList& srgLayoutList, const MapOfStringToStageType& shaderEntryPoints,
  478. const RHI::ShaderBuildArguments& shaderBuildArguments, const RootConstantData& rootConstantData,
  479. RHI::ShaderPlatformInterface* shaderPlatformInterface, BindingDependencies& bindingDependencies /*inout*/)
  480. {
  481. PruneNonEntryFunctions(bindingDependencies, shaderEntryPoints);
  482. // Translates from a list of function names that use a resource to a shader stage mask.
  483. auto getRHIShaderStageMask = [&shaderEntryPoints](const BindingDependencies::FunctionsNameVector& functions) {
  484. RHI::ShaderStageMask mask = RHI::ShaderStageMask::None;
  485. // Iterate through all the functions that are using the resource.
  486. for (const auto& functionName : functions)
  487. {
  488. // Search the function name into the list of valid entry points into the shader.
  489. auto findId =
  490. AZStd::find_if(shaderEntryPoints.begin(), shaderEntryPoints.end(), [&functionName](const auto& item) {
  491. return item.first == functionName;
  492. });
  493. if (findId != shaderEntryPoints.end())
  494. {
  495. // Use the entry point shader stage type to calculate the mask.
  496. RHI::ShaderHardwareStage hardwareStage = ToAssetBuilderShaderType(findId->second);
  497. mask |= static_cast<RHI::ShaderStageMask>(AZ_BIT(static_cast<uint32_t>(RHI::ToRHIShaderStage(hardwareStage))));
  498. }
  499. }
  500. return mask;
  501. };
  502. // Build general PipelineLayoutDescriptor data that is provided for all platforms
  503. RHI::Ptr<RHI::PipelineLayoutDescriptor> pipelineLayoutDescriptor =
  504. shaderPlatformInterface->CreatePipelineLayoutDescriptor();
  505. RHI::ShaderPlatformInterface::ShaderResourceGroupInfoList srgInfos;
  506. for (const auto& srgLayout : srgLayoutList)
  507. {
  508. // Search the binding info for a Shader Resource Group.
  509. AZStd::string_view srgName = srgLayout->GetName().GetStringView();
  510. const BindingDependencies::SrgResources* srgResources = bindingDependencies.GetSrg(srgName);
  511. if (!srgResources)
  512. {
  513. AZ_Error(builderName, false, "SRG %s not found in the dependency dataset", srgName.data());
  514. return nullptr;
  515. }
  516. RHI::ShaderResourceGroupBindingInfo srgBindingInfo;
  517. const RHI::ShaderResourceGroupLayout* layout = srgLayout.get();
  518. // Calculate the binding in for the constant data. All constant data share the same binding info.
  519. srgBindingInfo.m_constantDataBindingInfo = {
  520. getRHIShaderStageMask(srgResources->m_srgConstantsDependencies.m_binding.m_dependentFunctions),
  521. srgResources->m_srgConstantsDependencies.m_binding.m_registerId,
  522. srgResources->m_srgConstantsDependencies.m_binding.m_registerSpace
  523. };
  524. // Calculate the binding info for each resource of the Shader Resource Group.
  525. for (auto const& resource : srgResources->m_resources)
  526. {
  527. auto const& resourceInfo = resource.second;
  528. srgBindingInfo.m_resourcesRegisterMap.insert(
  529. { AZ::Name(resourceInfo.m_selfName),
  530. RHI::ResourceBindingInfo(
  531. getRHIShaderStageMask(resourceInfo.m_dependentFunctions), resourceInfo.m_registerId,
  532. resourceInfo.m_registerSpace),
  533. });
  534. }
  535. pipelineLayoutDescriptor->AddShaderResourceGroupLayoutInfo(*layout, srgBindingInfo);
  536. srgInfos.push_back(RHI::ShaderPlatformInterface::ShaderResourceGroupInfo{layout, srgBindingInfo});
  537. }
  538. RHI::Ptr<RHI::ConstantsLayout> rootConstantsLayout = RHI::ConstantsLayout::Create();
  539. for (const auto& constantData : rootConstantData.m_constants)
  540. {
  541. RHI::ShaderInputConstantDescriptor rootConstantDesc(
  542. constantData.m_nameId, constantData.m_constantByteOffset, constantData.m_constantByteSize,
  543. rootConstantData.m_bindingInfo.m_registerId, rootConstantData.m_bindingInfo.m_space);
  544. rootConstantsLayout->AddShaderInput(rootConstantDesc);
  545. }
  546. if (!rootConstantsLayout->Finalize())
  547. {
  548. AZ_Error(builderName, false, "Failed to finalize root constants layout");
  549. return nullptr;
  550. }
  551. pipelineLayoutDescriptor->SetRootConstantsLayout(*rootConstantsLayout);
  552. RHI::ShaderPlatformInterface::RootConstantsInfo rootConstantInfo;
  553. rootConstantInfo.m_spaceId = rootConstantData.m_bindingInfo.m_space;
  554. rootConstantInfo.m_registerId = rootConstantData.m_bindingInfo.m_registerId;
  555. rootConstantInfo.m_totalSizeInBytes = rootConstantsLayout->GetDataSize();
  556. // Build platform-specific PipelineLayoutDescriptor data, and finalize
  557. if (!shaderPlatformInterface->BuildPipelineLayoutDescriptor(
  558. pipelineLayoutDescriptor, srgInfos, rootConstantInfo, shaderBuildArguments))
  559. {
  560. AZ_Error(builderName, false, "Failed to build pipeline layout descriptor");
  561. return nullptr;
  562. }
  563. return pipelineLayoutDescriptor;
  564. }
  565. static bool IsSystemValueSemantic(const AZStd::string_view semantic)
  566. {
  567. // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics#system-value-semantics
  568. return AzFramework::StringFunc::StartsWith(semantic, "sv_", false);
  569. }
  570. static bool CreateShaderInputContract(
  571. const AzslData& azslData,
  572. const AZStd::string& vertexShaderName,
  573. const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout,
  574. const AZStd::string& pathToIaJson,
  575. RPI::ShaderInputContract& contract,
  576. const AZStd::string& tempFolder)
  577. {
  578. StructData inputStruct;
  579. inputStruct.m_id = "";
  580. auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToIaJson, AZ::RPI::JsonUtils::DefaultMaxFileSize);
  581. if (!jsonOutcome.IsSuccess())
  582. {
  583. AZ_Error(ShaderBuilderUtilityName, false, "%s", jsonOutcome.GetError().c_str());
  584. return AssetBuilderSDK::ProcessJobResult_Failed;
  585. }
  586. AzslCompiler azslc(azslData.m_preprocessedFullPath, tempFolder);
  587. if (!azslc.ParseIaPopulateStructData(jsonOutcome.GetValue(), vertexShaderName, inputStruct))
  588. {
  589. AZ_Error(ShaderBuilderUtilityName, false, "Failed to parse input layout\n");
  590. return false;
  591. }
  592. if (inputStruct.m_id.empty())
  593. {
  594. AZ_Error(
  595. ShaderBuilderUtilityName, false, "Failed to find the input struct for vertex shader %s.",
  596. vertexShaderName.c_str());
  597. return false;
  598. }
  599. for (const auto& member : inputStruct.m_members)
  600. {
  601. RHI::ShaderSemantic streamChannelSemantic{Name{member.m_semanticText}, static_cast<uint32_t>(member.m_semanticIndex)};
  602. // Semantics that represent a system-generated value do not map to an input stream
  603. if (IsSystemValueSemantic(streamChannelSemantic.m_name.GetStringView()))
  604. {
  605. continue;
  606. }
  607. contract.m_streamChannels.emplace_back().m_semantic = streamChannelSemantic;
  608. if (member.m_variable.m_typeModifier == MatrixMajor::ColumnMajor)
  609. {
  610. contract.m_streamChannels.back().m_componentCount = member.m_variable.m_cols;
  611. }
  612. else
  613. {
  614. contract.m_streamChannels.back().m_componentCount = member.m_variable.m_rows;
  615. }
  616. // [GFX_TODO][ATOM-14475]: Come up with a more elegant way to mark optional channels and their corresponding shader
  617. // option
  618. static const char OptionalInputStreamPrefix[] = "m_optional_";
  619. if (AzFramework::StringFunc::StartsWith(member.m_variable.m_name, OptionalInputStreamPrefix, true))
  620. {
  621. AZStd::string expectedOptionName = AZStd::string::format(
  622. "o_%s_isBound", member.m_variable.m_name.substr(strlen(OptionalInputStreamPrefix)).c_str());
  623. RPI::ShaderOptionIndex shaderOptionIndex = shaderOptionGroupLayout.FindShaderOptionIndex(Name{expectedOptionName});
  624. if (!shaderOptionIndex.IsValid())
  625. {
  626. AZ_Error(
  627. ShaderBuilderUtilityName, false, "Shader option '%s' not found for optional input stream '%s'",
  628. expectedOptionName.c_str(), member.m_variable.m_name.c_str());
  629. return false;
  630. }
  631. const RPI::ShaderOptionDescriptor& option = shaderOptionGroupLayout.GetShaderOption(shaderOptionIndex);
  632. if (option.GetType() != RPI::ShaderOptionType::Boolean)
  633. {
  634. AZ_Error(ShaderBuilderUtilityName, false, "Shader option '%s' must be a bool.", expectedOptionName.c_str());
  635. return false;
  636. }
  637. if (option.GetDefaultValue().GetStringView() != "false")
  638. {
  639. AZ_Error(
  640. ShaderBuilderUtilityName, false, "Shader option '%s' must default to false.",
  641. expectedOptionName.c_str());
  642. return false;
  643. }
  644. contract.m_streamChannels.back().m_isOptional = true;
  645. contract.m_streamChannels.back().m_streamBoundIndicatorIndex = shaderOptionIndex;
  646. }
  647. }
  648. return true;
  649. }
  650. static bool CreateShaderOutputContract(
  651. const AzslData& azslData,
  652. const AZStd::string& fragmentShaderName,
  653. const AZStd::string& pathToOmJson,
  654. RPI::ShaderOutputContract& contract,
  655. const AZStd::string& tempFolder)
  656. {
  657. StructData outputStruct;
  658. outputStruct.m_id = "";
  659. auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(pathToOmJson, AZ::RPI::JsonUtils::DefaultMaxFileSize);
  660. if (!jsonOutcome.IsSuccess())
  661. {
  662. AZ_Error(ShaderBuilderUtilityName, false, "%s", jsonOutcome.GetError().c_str());
  663. return AssetBuilderSDK::ProcessJobResult_Failed;
  664. }
  665. AzslCompiler azslc(azslData.m_preprocessedFullPath, tempFolder);
  666. if (!azslc.ParseOmPopulateStructData(jsonOutcome.GetValue(), fragmentShaderName, outputStruct))
  667. {
  668. AZ_Error(ShaderBuilderUtilityName, false, "Failed to parse output layout\n");
  669. return false;
  670. }
  671. for (const auto& member : outputStruct.m_members)
  672. {
  673. RHI::ShaderSemantic semantic = RHI::ShaderSemantic::Parse(member.m_semanticText);
  674. bool depthFound = false;
  675. if (semantic.m_name.GetStringView() == "SV_Target")
  676. {
  677. // Render targets only support 1-D vector types and those are always column-major (per DXC)
  678. contract.m_requiredColorAttachments.emplace_back().m_componentCount = member.m_variable.m_cols;
  679. }
  680. else if (
  681. semantic.m_name.GetStringView() == "SV_Depth" || semantic.m_name.GetStringView() == "SV_DepthGreaterEqual" ||
  682. semantic.m_name.GetStringView() == "SV_DepthLessEqual")
  683. {
  684. if (depthFound)
  685. {
  686. AZ_Error(
  687. ShaderBuilderUtilityName, false,
  688. "SV_Depth specified more than once in the fragment shader output structure");
  689. return false;
  690. }
  691. depthFound = true;
  692. }
  693. else
  694. {
  695. AZ_Error(
  696. ShaderBuilderUtilityName, false, "Unsupported shader output semantic '%s'.", semantic.m_name.GetCStr());
  697. return false;
  698. }
  699. }
  700. return true;
  701. }
  702. bool CreateShaderInputAndOutputContracts(
  703. const AzslData& azslData,
  704. const MapOfStringToStageType& shaderEntryPoints,
  705. const RPI::ShaderOptionGroupLayout& shaderOptionGroupLayout,
  706. const AZStd::string& pathToOmJson,
  707. const AZStd::string& pathToIaJson,
  708. RPI::ShaderInputContract& shaderInputContract,
  709. RPI::ShaderOutputContract& shaderOutputContract,
  710. size_t& colorAttachmentCount,
  711. const AZStd::string& tempFolder)
  712. {
  713. bool success = true;
  714. for (const auto& shaderEntryPoint : shaderEntryPoints)
  715. {
  716. auto shaderEntryName = shaderEntryPoint.first;
  717. auto shaderStageType = shaderEntryPoint.second;
  718. if (shaderStageType == RPI::ShaderStageType::Vertex)
  719. {
  720. const bool layoutCreated = CreateShaderInputContract(azslData, shaderEntryName, shaderOptionGroupLayout, pathToIaJson, shaderInputContract, tempFolder);
  721. if (!layoutCreated)
  722. {
  723. success = false;
  724. AZ_Error(
  725. ShaderBuilderUtilityName, false, "Could not create the input contract for the vertex function %s",
  726. shaderEntryName.c_str());
  727. continue; // Using continue to report all the errors found
  728. }
  729. }
  730. if (shaderStageType == RPI::ShaderStageType::Fragment)
  731. {
  732. const bool layoutCreated =
  733. CreateShaderOutputContract(azslData, shaderEntryName, pathToOmJson, shaderOutputContract, tempFolder);
  734. if (!layoutCreated)
  735. {
  736. success = false;
  737. AZ_Error(
  738. ShaderBuilderUtilityName, false, "Could not create the output contract for the fragment function %s",
  739. shaderEntryName.c_str());
  740. continue; // Using continue to report all the errors found
  741. }
  742. colorAttachmentCount = shaderOutputContract.m_requiredColorAttachments.size();
  743. }
  744. }
  745. return success;
  746. }
  747. IncludedFilesParser::IncludedFilesParser()
  748. {
  749. #define FILE_PATH_REGEX R"([<|"]([\w|/|\\|\.|\-|\:]+)[>|"])"
  750. #define INCLUDE_REGEX R"(#\s*include\s+)"
  751. // TODO(MaterialPipeline): This is a very specialized hack to support material pipelines. The intermediate .azsli file looks like this:
  752. // #define MATERIAL_TYPE_AZSLI_FILE_PATH "D:\o3de\Gems\Atom\TestData\TestData\Materials\Types\MaterialPipelineTest_Animated.azsli"
  753. // #include "D:\o3de\Gems\Atom\Feature\Common\Assets\Materials\Pipelines\LowEndPipeline\ForwardPass_BaseLighting.azsli"
  754. // Then the ForwardPass_BaseLighting.azsli file has this line:
  755. // #include MATERIAL_TYPE_AZSLI_FILE_PATH
  756. // So we treat "#define MATERIAL_TYPE_AZSLI_FILE_PATH" the same as an include directive.
  757. // The "right" way to handle this would be to use an actual preprocessor which shouild not be done in CreateJobs. We could introduce
  758. // an intermediate builder that just preprocesses the file and outputs that as an intermediate asset, then do the normal processing
  759. // in a subsequent builder.
  760. #define SPECIAL_DEFINE_REGEX R"(#\s*define\s+MATERIAL_TYPE_AZSLI_FILE_PATH\s+)"
  761. m_includeRegex = AZStd::regex("(?:" INCLUDE_REGEX "|" SPECIAL_DEFINE_REGEX ")" FILE_PATH_REGEX, AZStd::regex::ECMAScript);
  762. #undef FILE_PATH_REGEX
  763. #undef INCLUDE_REGEX
  764. #undef SPECIAL_DEFINE_REGEX
  765. }
  766. AZStd::vector<AZStd::string> IncludedFilesParser::ParseStringAndGetIncludedFiles(AZStd::string_view haystack) const
  767. {
  768. AZStd::vector<AZStd::string> listOfFilePaths;
  769. AZStd::smatch match;
  770. AZStd::string::const_iterator searchStart(haystack.cbegin());
  771. while (AZStd::regex_search(searchStart, haystack.cend(), match, m_includeRegex))
  772. {
  773. if (match.size() > 1)
  774. {
  775. AZStd::string relativeFilePath(match[1].str().c_str());
  776. AzFramework::StringFunc::Path::Normalize(relativeFilePath);
  777. listOfFilePaths.push_back(relativeFilePath);
  778. }
  779. searchStart = match.suffix().first;
  780. }
  781. return listOfFilePaths;
  782. }
  783. AZ::Outcome<AZStd::vector<AZStd::string>, AZStd::string> IncludedFilesParser::ParseFileAndGetIncludedFiles(AZStd::string_view sourceFilePath) const
  784. {
  785. AZ::IO::FileIOStream stream(sourceFilePath.data(), AZ::IO::OpenMode::ModeRead);
  786. if (!stream.IsOpen())
  787. {
  788. return AZ::Failure(AZStd::string::format("\"%s\" source file could not be opened.", sourceFilePath.data()));
  789. }
  790. if (!stream.CanRead())
  791. {
  792. return AZ::Failure(AZStd::string::format("\"%s\" source file could not be read.", sourceFilePath.data()));
  793. }
  794. AZStd::string hayStack;
  795. hayStack.resize_no_construct(stream.GetLength());
  796. stream.Read(stream.GetLength(), hayStack.data());
  797. auto listOfFilePaths = ParseStringAndGetIncludedFiles(hayStack);
  798. return AZ::Success(AZStd::move(listOfFilePaths));
  799. }
  800. AZStd::string GetPlatformNameFromPlatformInfo(const AssetBuilderSDK::PlatformInfo& platformInfo)
  801. {
  802. auto platformId = AZ::PlatformDefaults::PlatformHelper::GetPlatformIdFromName(platformInfo.m_identifier);
  803. switch (platformId)
  804. {
  805. case AZ::PlatformDefaults::PlatformId::PC :
  806. case AZ::PlatformDefaults::PlatformId::SERVER : // Fallthrough. "pc" and "server" are both treated as "Windows".
  807. return { "Windows" };
  808. default:
  809. return AZ::PlatformDefaults::PlatformIdToPalFolder(platformId);
  810. }
  811. }
  812. } // namespace ShaderBuilderUtility
  813. } // namespace ShaderBuilder
  814. } // AZ