3
0

PrecompiledShaderBuilder.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  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 <PrecompiledShaderBuilder.h>
  9. #include <AzCore/Asset/AssetManagerBus.h>
  10. #include <AzFramework/IO/LocalFileIO.h>
  11. #include <AzFramework/StringFunc/StringFunc.h>
  12. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  13. #include <AzCore/Serialization/Json/JsonUtils.h>
  14. #include <Atom/RHI.Edit/ShaderPlatformInterface.h>
  15. #include <Atom/RPI.Edit/Common/AssetUtils.h>
  16. #include <Atom/RPI.Reflect/Asset/AssetReference.h>
  17. #include <Atom/RPI.Reflect/Shader/PrecompiledShaderAssetSourceData.h>
  18. #include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
  19. #include <Atom/RPI.Reflect/Shader/ShaderVariantAsset.h>
  20. #include <Atom/RPI.Reflect/Shader/ShaderAssetCreator.h>
  21. #include <AssetBuilderSDK/AssetBuilderSDK.h>
  22. #include <ShaderPlatformInterfaceRequest.h>
  23. #include "ShaderBuilderUtility.h"
  24. namespace AZ
  25. {
  26. namespace
  27. {
  28. [[maybe_unused]] static const char* PrecompiledShaderBuilderName = "PrecompiledShaderBuilder";
  29. static const char* PrecompiledShaderBuilderJobKey = "PrecompiledShader Asset Builder";
  30. static const char* ShaderAssetExtension = "azshader";
  31. }
  32. void PrecompiledShaderBuilder::ShutDown()
  33. {
  34. m_isShuttingDown = true;
  35. }
  36. void PrecompiledShaderBuilder::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const
  37. {
  38. if (m_isShuttingDown)
  39. {
  40. response.m_result = AssetBuilderSDK::CreateJobsResultCode::ShuttingDown;
  41. return;
  42. }
  43. AZStd::string fullPath;
  44. AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), fullPath, true);
  45. // load precompiled shader information file
  46. RPI::PrecompiledShaderAssetSourceData precompiledShaderAsset;
  47. auto loadResult = AZ::JsonSerializationUtils::LoadObjectFromFile<RPI::PrecompiledShaderAssetSourceData>(precompiledShaderAsset, fullPath);
  48. if (!loadResult.IsSuccess())
  49. {
  50. AZ_Error(PrecompiledShaderBuilderName, false, "Failed to load precompiled shader assets file [%s] error [%s]", fullPath.c_str(), loadResult.GetError().data());
  51. response.m_result = AssetBuilderSDK::CreateJobsResultCode::Failed;
  52. return;
  53. }
  54. for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
  55. {
  56. AZStd::vector<AZStd::string>::iterator itPlatformIdentifier = AZStd::find(
  57. precompiledShaderAsset.m_platformIdentifiers.begin(),
  58. precompiledShaderAsset.m_platformIdentifiers.end(),
  59. platformInfo.m_identifier);
  60. if (itPlatformIdentifier != precompiledShaderAsset.m_platformIdentifiers.end())
  61. {
  62. // retrieve the shader APIs for this platform
  63. AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces = ShaderBuilder::ShaderBuilderUtility::DiscoverValidShaderPlatformInterfaces(platformInfo);
  64. if (platformInterfaces.empty())
  65. {
  66. continue;
  67. }
  68. AZStd::vector<AssetBuilderSDK::JobDependency> jobDependencyList;
  69. // setup dependencies on the root azshadervariant asset file names, for each supervariant
  70. for (const auto& supervariant : precompiledShaderAsset.m_supervariants)
  71. {
  72. for (const auto& rootShaderVariantAsset : supervariant->m_rootShaderVariantAssets)
  73. {
  74. // find the API in the list of supported APIs on this platform
  75. AZStd::vector<RHI::ShaderPlatformInterface*>::const_iterator itFoundAPI = AZStd::find_if(
  76. platformInterfaces.begin(),
  77. platformInterfaces.end(),
  78. [&rootShaderVariantAsset](const RHI::ShaderPlatformInterface* shaderPlatformInterface)
  79. {
  80. return rootShaderVariantAsset->m_apiName == shaderPlatformInterface->GetAPIName();
  81. });
  82. if (itFoundAPI == platformInterfaces.end())
  83. {
  84. // the API is not supported on this platform, skip this entry
  85. continue;
  86. }
  87. const AZStd::string rootShaderVariantAssetPath = RPI::AssetUtils::ResolvePathReference(
  88. request.m_sourceFile, rootShaderVariantAsset->m_rootShaderVariantAssetFileName);
  89. AssetBuilderSDK::JobDependency jobDependency;
  90. jobDependency.m_jobKey = "azshadervariant";
  91. jobDependency.m_platformIdentifier = platformInfo.m_identifier;
  92. jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order;
  93. jobDependency.m_sourceFile.m_sourceFileDependencyPath = rootShaderVariantAssetPath;
  94. jobDependencyList.push_back(jobDependency);
  95. }
  96. }
  97. AssetBuilderSDK::JobDescriptor job;
  98. job.m_jobKey = PrecompiledShaderBuilderJobKey;
  99. job.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
  100. job.m_jobDependencyList = jobDependencyList;
  101. job.m_critical = true;
  102. response.m_createJobOutputs.push_back(job);
  103. }
  104. }
  105. response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
  106. }
  107. void PrecompiledShaderBuilder::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
  108. {
  109. AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
  110. if (jobCancelListener.IsCancelled() || m_isShuttingDown)
  111. {
  112. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
  113. return;
  114. }
  115. SerializeContext* context = nullptr;
  116. ComponentApplicationBus::BroadcastResult(context, &ComponentApplicationBus::Events::GetSerializeContext);
  117. if (!context)
  118. {
  119. AZ_Assert(false, "No serialize context");
  120. return;
  121. }
  122. // load precompiled shader information file
  123. RPI::PrecompiledShaderAssetSourceData precompiledShaderAsset;
  124. auto loadResult = AZ::JsonSerializationUtils::LoadObjectFromFile<RPI::PrecompiledShaderAssetSourceData>(precompiledShaderAsset, request.m_fullPath);
  125. if (!loadResult.IsSuccess())
  126. {
  127. AZ_Error(PrecompiledShaderBuilderName, false, "Failed to load precompiled shader assets file [%s] error [%s]", request.m_fullPath.c_str(), loadResult.GetError().c_str());
  128. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  129. return;
  130. }
  131. // load shader source asset
  132. // this is the precompiled shader asset that we will be cloning and recreating from this builder
  133. AZStd::string fullShaderAssetPath = RPI::AssetUtils::ResolvePathReference(request.m_fullPath, precompiledShaderAsset.m_shaderAssetFileName);
  134. RPI::ShaderAsset* shaderAsset = LoadSourceAsset<RPI::ShaderAsset>(context, fullShaderAssetPath);
  135. if (!shaderAsset)
  136. {
  137. AZ_Error(PrecompiledShaderBuilderName, false, "Failed to retrieve shader asset for file [%s]", fullShaderAssetPath.c_str());
  138. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  139. return;
  140. }
  141. // retrieve the shader APIs for this platform
  142. AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces = ShaderBuilder::ShaderBuilderUtility::DiscoverValidShaderPlatformInterfaces(request.m_platformInfo);
  143. if (platformInterfaces.empty())
  144. {
  145. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  146. return;
  147. }
  148. AssetBuilderSDK::JobProduct jobProduct;
  149. // load the variant product assets, for each supervariant
  150. // these are the dependency root variant asset products that were processed prior to running this job
  151. RPI::ShaderAssetCreator::ShaderSupervariants supervariants;
  152. for (const auto& supervariant : precompiledShaderAsset.m_supervariants)
  153. {
  154. RPI::ShaderAssetCreator::ShaderRootVariantAssets rootVariantProductAssets;
  155. for (const auto& rootShaderVariantAsset : supervariant->m_rootShaderVariantAssets)
  156. {
  157. // find the API in the list of supported APIs on this platform
  158. AZStd::vector<RHI::ShaderPlatformInterface*>::const_iterator itFoundAPI = AZStd::find_if(
  159. platformInterfaces.begin(),
  160. platformInterfaces.end(),
  161. [&rootShaderVariantAsset](const RHI::ShaderPlatformInterface* shaderPlatformInterface)
  162. {
  163. return rootShaderVariantAsset->m_apiName == shaderPlatformInterface->GetAPIName();
  164. });
  165. if (itFoundAPI == platformInterfaces.end())
  166. {
  167. // the API is not supported on this platform, skip this entry
  168. continue;
  169. }
  170. // retrieve the variant asset
  171. auto assetOutcome = RPI::AssetUtils::LoadAsset<RPI::ShaderVariantAsset>(request.m_fullPath, rootShaderVariantAsset->m_rootShaderVariantAssetFileName, 0);
  172. if (!assetOutcome)
  173. {
  174. AZ_Error(PrecompiledShaderBuilderName, false, "Failed to retrieve Variant asset for file [%s]", rootShaderVariantAsset->m_rootShaderVariantAssetFileName.c_str());
  175. return;
  176. }
  177. rootVariantProductAssets.push_back(AZStd::make_pair(RHI::APIType{ rootShaderVariantAsset->m_apiName.GetCStr() }, assetOutcome.GetValue()));
  178. AssetBuilderSDK::ProductDependency productDependency;
  179. productDependency.m_dependencyId = assetOutcome.GetValue().GetId();
  180. productDependency.m_flags = AZ::Data::ProductDependencyInfo::CreateFlags(AZ::Data::AssetLoadBehavior::PreLoad);
  181. jobProduct.m_dependencies.push_back(productDependency);
  182. }
  183. if (!rootVariantProductAssets.empty())
  184. {
  185. supervariants.push_back({ supervariant->m_name, rootVariantProductAssets });
  186. }
  187. }
  188. if (supervariants.empty())
  189. {
  190. // no applicable shader variants for this platform
  191. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  192. return;
  193. }
  194. // use the ShaderAssetCreator to clone the shader asset, which will update the embedded Srg and Variant asset UUIDs
  195. // Note that the Srg and Variant assets do not have embedded asset references and are processed with the RC Copy functionality
  196. RPI::ShaderAssetCreator shaderAssetCreator;
  197. shaderAssetCreator.Clone(Uuid::CreateRandom(), *shaderAsset, supervariants, platformInterfaces);
  198. Data::Asset<RPI::ShaderAsset> outputShaderAsset;
  199. if (!shaderAssetCreator.End(outputShaderAsset))
  200. {
  201. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  202. return;
  203. }
  204. // build the output product path
  205. AZStd::string destFileName;
  206. AZStd::string destPath;
  207. AzFramework::StringFunc::Path::GetFullFileName(request.m_fullPath.c_str(), destFileName);
  208. AzFramework::StringFunc::Path::ConstructFull(request.m_tempDirPath.c_str(), destFileName.c_str(), ShaderAssetExtension, destPath, true);
  209. // save the cloned shader file
  210. if (!Utils::SaveObjectToFile(destPath, DataStream::ST_BINARY, outputShaderAsset.Get()))
  211. {
  212. AZ_Error(PrecompiledShaderBuilderName, false, "Failed to output Shader Asset");
  213. return;
  214. }
  215. // setup the job product
  216. jobProduct.m_productFileName = destPath;
  217. jobProduct.m_productSubID = static_cast<uint32_t>(RPI::ShaderAssetSubId::ShaderAsset);
  218. jobProduct.m_productAssetType = azrtti_typeid<RPI::ShaderAsset>();
  219. jobProduct.m_dependenciesHandled = true;
  220. response.m_outputProducts.push_back(AZStd::move(jobProduct));
  221. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  222. }
  223. template<typename T>
  224. T* PrecompiledShaderBuilder::LoadSourceAsset(SerializeContext* context, const AZStd::string& shaderAssetPath) const
  225. {
  226. AZStd::vector<char> buffer;
  227. AZ::IO::FileIOStream fileStream;
  228. if (!fileStream.Open(shaderAssetPath.c_str(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary))
  229. {
  230. return nullptr;
  231. }
  232. AZ::IO::SizeType length = fileStream.GetLength();
  233. if (length == 0)
  234. {
  235. return nullptr;
  236. }
  237. buffer.resize_no_construct(length + 1);
  238. fileStream.Read(length, buffer.data());
  239. buffer.back() = 0;
  240. AZ::ObjectStream::FilterDescriptor loadFilter(&AZ::Data::AssetFilterNoAssetLoading, AZ::ObjectStream::FILTERFLAG_IGNORE_UNKNOWN_CLASSES);
  241. return AZ::Utils::LoadObjectFromBuffer<T>(buffer.data(), length, context, loadFilter);
  242. }
  243. } // namespace AZ