PassBuilder.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  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 <Atom/RPI.Edit/Common/AssetUtils.h>
  9. #include <Atom/RPI.Reflect/Pass/PassTemplate.h>
  10. #include <Atom/RPI.Reflect/Pass/RenderPassData.h>
  11. #include <Pass/PassBuilder.h>
  12. #include <AzCore/Asset/AssetManagerBus.h>
  13. #include <AzCore/Serialization/Json/JsonUtils.h>
  14. #include <AzCore/StringFunc/StringFunc.h>
  15. #include <Atom/RPI.Reflect/Asset/AssetReference.h>
  16. #include <Atom/RPI.Reflect/Pass/PassAsset.h>
  17. #include <AzFramework/IO/LocalFileIO.h>
  18. #include <AzFramework/StringFunc/StringFunc.h>
  19. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  20. #include <AssetBuilderSDK/AssetBuilderSDK.h>
  21. namespace AZ
  22. {
  23. namespace RPI
  24. {
  25. namespace
  26. {
  27. [[maybe_unused]] static const char* PassBuilderName = "PassBuilder";
  28. static const char* PassBuilderJobKey = "Pass Asset Builder";
  29. static const char* PassAssetExtension = "pass";
  30. }
  31. namespace PassBuilderNamespace
  32. {
  33. enum PassDependencies
  34. {
  35. Shader,
  36. AttachmentImage,
  37. Count
  38. };
  39. static const AZStd::tuple<const char*, const char*> DependencyExtensionJobKeyTable[PassDependencies::Count] =
  40. {
  41. {".shader", "Shader Asset"},
  42. {".attimage", "Any Asset Builder"}
  43. };
  44. }
  45. void PassBuilder::RegisterBuilder()
  46. {
  47. AssetBuilderSDK::AssetBuilderDesc builder;
  48. builder.m_name = PassBuilderJobKey;
  49. builder.m_version = 18; // Add Allocator to ShaderStageFunction
  50. builder.m_busId = azrtti_typeid<PassBuilder>();
  51. builder.m_createJobFunction = AZStd::bind(&PassBuilder::CreateJobs, this, AZStd::placeholders::_1, AZStd::placeholders::_2);
  52. builder.m_processJobFunction = AZStd::bind(&PassBuilder::ProcessJob, this, AZStd::placeholders::_1, AZStd::placeholders::_2);
  53. // Match *.pass extension
  54. builder.m_patterns.emplace_back(
  55. AssetBuilderSDK::AssetBuilderPattern(
  56. AZStd::string("*.") + PassAssetExtension,
  57. AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard
  58. )
  59. );
  60. BusConnect(builder.m_busId);
  61. AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, builder);
  62. }
  63. PassBuilder::~PassBuilder()
  64. {
  65. BusDisconnect();
  66. }
  67. void PassBuilder::ShutDown()
  68. {
  69. m_isShuttingDown = true;
  70. }
  71. // --- Code related to dependency shader asset handling ---
  72. // Helper class to pass parameters to the AddDependency and FindReferencedAssets functions below
  73. struct FindPassReferenceAssetParams
  74. {
  75. void* passAssetObject = nullptr;
  76. Uuid passAssetUuid;
  77. SerializeContext* serializeContext = nullptr;
  78. AZStd::string_view passAssetSourceFile; // File path of the pass asset
  79. AZStd::string_view dependencySourceFile; // File pass of the asset the pass asset depends on
  80. const char* jobKey = nullptr; // Job key for adding job dependency
  81. };
  82. // Helper function to get a file reference and create a corresponding job dependency
  83. bool AddDependency(FindPassReferenceAssetParams& params, AssetBuilderSDK::JobDescriptor* job)
  84. {
  85. // We use an OrderOnce job dependency to ensure that the Asset Processor knows about the
  86. // referenced asset, so we can make an AssetId for it later in ProcessJob. OrderOnce is
  87. // enough because we don't need to read any data from the asset, we just needs its ID.
  88. AssetBuilderSDK::JobDependency jobDependency;
  89. jobDependency.m_jobKey = params.jobKey;
  90. jobDependency.m_type = AssetBuilderSDK::JobDependencyType::OrderOnce;
  91. jobDependency.m_sourceFile.m_sourceFileDependencyPath = params.dependencySourceFile;
  92. job->m_jobDependencyList.push_back(jobDependency);
  93. AZ_TracePrintf(PassBuilderName, "Creating job dependency on file [%.*s] \n", AZ_STRING_ARG(params.dependencySourceFile));
  94. return true;
  95. }
  96. bool SetJobKeyForExtension(const AZStd::string& filePath, FindPassReferenceAssetParams& params)
  97. {
  98. AZStd::string extension;
  99. StringFunc::Path::GetExtension(filePath.c_str(), extension);
  100. for (const auto& [dependencyExtension, jobKey] : PassBuilderNamespace::DependencyExtensionJobKeyTable)
  101. {
  102. if (extension == dependencyExtension)
  103. {
  104. params.jobKey = jobKey;
  105. return true;
  106. }
  107. }
  108. AZ_Error(PassBuilderName, false, "PassBuilder found a dependency with extension '%s', but does not know the corresponding job key. Add the job key for that extension to SetJobKeyForExtension in PassBuilder.cpp", extension.c_str());
  109. params.jobKey = "Unknown";
  110. return false;
  111. }
  112. // Helper function to find all assetId's and object references
  113. bool FindReferencedAssets(
  114. FindPassReferenceAssetParams& params, AssetBuilderSDK::JobDescriptor* job, AZStd::vector<AssetBuilderSDK::ProductDependency>* productDependencies)
  115. {
  116. SerializeContext::ErrorHandler errorLogger;
  117. errorLogger.Reset();
  118. bool success = true;
  119. // This callback will check whether the given element is an asset reference. If so, it will add it to the list of asset references
  120. auto beginCallback = [&](void* ptr, const SerializeContext::ClassData* classData, [[maybe_unused]] const SerializeContext::ClassElement* classElement)
  121. {
  122. // If the enumerated element is an asset reference
  123. if (classData->m_typeId == azrtti_typeid<AssetReference>())
  124. {
  125. AssetReference* assetReference = reinterpret_cast<AssetReference*>(ptr);
  126. // If the asset id isn't already provided, get it using the source file path
  127. if (!assetReference->m_assetId.IsValid() && !assetReference->m_filePath.empty())
  128. {
  129. const AZStd::string& path = assetReference->m_filePath;
  130. uint32_t subId = 0;
  131. if (job != nullptr) // Create Job Phase
  132. {
  133. params.dependencySourceFile = path;
  134. success &= SetJobKeyForExtension(path, params);
  135. success &= AddDependency(params, job);
  136. }
  137. else // Process Job Phase
  138. {
  139. auto assetIdOutcome = AssetUtils::MakeAssetId(path, subId);
  140. if (assetIdOutcome)
  141. {
  142. assetReference->m_assetId = assetIdOutcome.GetValue();
  143. productDependencies->push_back(
  144. AssetBuilderSDK::ProductDependency{assetReference->m_assetId, AZ::Data::ProductDependencyInfo::CreateFlags(Data::AssetLoadBehavior::NoLoad)}
  145. );
  146. }
  147. else
  148. {
  149. AZ_Error(PassBuilderName, false, "Could not get AssetId for [%s]", assetReference->m_filePath.c_str());
  150. success = false;
  151. }
  152. }
  153. }
  154. }
  155. return true;
  156. };
  157. // Setup enumeration context
  158. SerializeContext::EnumerateInstanceCallContext callContext(
  159. AZStd::move(beginCallback),
  160. nullptr,
  161. params.serializeContext,
  162. SerializeContext::ENUM_ACCESS_FOR_READ,
  163. &errorLogger
  164. );
  165. // Recursively iterate over all elements in the object to find asset references with the above callback
  166. params.serializeContext->EnumerateInstance(
  167. &callContext
  168. , params.passAssetObject
  169. , params.passAssetUuid
  170. , nullptr
  171. , nullptr
  172. );
  173. return success;
  174. }
  175. // --- Code related to dependency shader asset handling ---
  176. void PassBuilder::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const
  177. {
  178. // --- Handle shutdown case ---
  179. if (m_isShuttingDown)
  180. {
  181. response.m_result = AssetBuilderSDK::CreateJobsResultCode::ShuttingDown;
  182. return;
  183. }
  184. // --- Get serialization context ---
  185. SerializeContext* serializeContext = nullptr;
  186. ComponentApplicationBus::BroadcastResult(serializeContext, &ComponentApplicationBus::Events::GetSerializeContext);
  187. if (!serializeContext)
  188. {
  189. AZ_Assert(false, "No serialize context");
  190. return;
  191. }
  192. // --- Load PassAsset ---
  193. AZStd::string fullPath;
  194. AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), fullPath, true);
  195. PassAsset passAsset;
  196. AZ::Outcome<void, AZStd::string> loadResult = JsonSerializationUtils::LoadObjectFromFile(passAsset, fullPath);
  197. if (!loadResult.IsSuccess())
  198. {
  199. AZ_Error(PassBuilderName, false, "Failed to load pass asset [%s]", request.m_sourceFile.c_str());
  200. AZ_Error(PassBuilderName, false, "Loading issues: %s", loadResult.GetError().data());
  201. return;
  202. }
  203. AssetBuilderSDK::JobDescriptor job;
  204. job.m_jobKey = PassBuilderJobKey;
  205. job.m_critical = true; // Passes are a critical part of the rendering system
  206. // --- Find all dependencies ---
  207. AZStd::unordered_set<Data::AssetId> dependentList;
  208. Uuid passAssetUuid = AzTypeInfo<PassAsset>::Uuid();
  209. FindPassReferenceAssetParams params;
  210. params.passAssetObject = &passAsset;
  211. params.passAssetSourceFile = request.m_sourceFile;
  212. params.passAssetUuid = passAssetUuid;
  213. params.serializeContext = serializeContext;
  214. params.jobKey = "Unknown";
  215. if (!FindReferencedAssets(params, &job, nullptr))
  216. {
  217. return;
  218. }
  219. // --- Create a job per platform ---
  220. for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
  221. {
  222. for (auto& jobDependency : job.m_jobDependencyList)
  223. {
  224. jobDependency.m_platformIdentifier = platformInfo.m_identifier.c_str();
  225. }
  226. job.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
  227. response.m_createJobOutputs.push_back(job);
  228. }
  229. response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
  230. }
  231. void PassBuilder::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
  232. {
  233. // --- Handle job cancellation and shutdown cases ---
  234. AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
  235. if (jobCancelListener.IsCancelled() || m_isShuttingDown)
  236. {
  237. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
  238. return;
  239. }
  240. // --- Get serialization context ---
  241. SerializeContext* serializeContext = nullptr;
  242. ComponentApplicationBus::BroadcastResult(serializeContext, &ComponentApplicationBus::Events::GetSerializeContext);
  243. if (!serializeContext)
  244. {
  245. AZ_Assert(false, "No serialize context");
  246. return;
  247. }
  248. // --- Load PassAsset ---
  249. PassAsset passAsset;
  250. AZ::Outcome<void, AZStd::string> loadResult = JsonSerializationUtils::LoadObjectFromFile(passAsset, request.m_fullPath);
  251. if (!loadResult.IsSuccess())
  252. {
  253. AZ_Error(PassBuilderName, false, "Failed to load pass asset [%s]", request.m_fullPath.c_str());
  254. AZ_Error(PassBuilderName, false, "Loading issues: %s", loadResult.GetError().data());
  255. return;
  256. }
  257. // --- Find all dependencies ---
  258. Uuid passAssetUuid = AzTypeInfo<PassAsset>::Uuid();
  259. FindPassReferenceAssetParams params;
  260. params.passAssetObject = &passAsset;
  261. params.passAssetSourceFile = request.m_sourceFile;
  262. params.passAssetUuid = passAssetUuid;
  263. params.serializeContext = serializeContext;
  264. params.jobKey = "Unknown";
  265. AZStd::vector<AssetBuilderSDK::ProductDependency> productDependencies;
  266. if (!FindReferencedAssets(params, nullptr, &productDependencies))
  267. {
  268. return;
  269. }
  270. // --- Get destination file name and path ---
  271. AZStd::string destFileName;
  272. AZStd::string destPath;
  273. AzFramework::StringFunc::Path::GetFullFileName(request.m_fullPath.c_str(), destFileName);
  274. AzFramework::StringFunc::Path::ConstructFull(request.m_tempDirPath.c_str(), destFileName.c_str(), destPath, true);
  275. // --- Ensure the BindPassSrg - flag is set if the pass-data has a PipelineViewTag set
  276. auto& passTemplate{ passAsset.GetPassTemplate() };
  277. if (passTemplate && passTemplate->m_passData)
  278. {
  279. auto* passData = azrtti_cast<RenderPassData*>(passTemplate->m_passData.get());
  280. if (passData && !passData->m_pipelineViewTag.IsEmpty())
  281. {
  282. // "PipelineViewTag": "MainCamera" is deprecated, discard the view-tag and set BindViewSrg to true
  283. if (passData->m_pipelineViewTag == AZ::Name("MainCamera"))
  284. {
  285. AZ_Warning(
  286. PassBuilderName,
  287. false,
  288. "Asset %s: '\"PipelineViewTag\": \"MainCamera\"' is deprecated, use '\"BindViewSrg\": true' instead",
  289. request.m_fullPath.c_str());
  290. passData->m_pipelineViewTag = AZ::Name();
  291. passData->m_bindViewSrg = true;
  292. }
  293. else if (!passData->m_bindViewSrg)
  294. {
  295. // Explicitly set "PipelineViewTag": implicitly set BindViewSrg to true as well, if it isn't yet.
  296. AZ_Info(
  297. PassBuilderName,
  298. "Asset %s: Pass has explicit PipelineViewTag '%s' -> setting \"BindViewSrg\" to true as well.",
  299. request.m_fullPath.c_str(),
  300. passData->m_pipelineViewTag.GetCStr());
  301. passData->m_bindViewSrg = true;
  302. }
  303. }
  304. }
  305. // --- Save the asset to binary format for production ---
  306. bool result = Utils::SaveObjectToFile(destPath, DataStream::ST_BINARY, &passAsset, passAssetUuid, serializeContext);
  307. if (result == false)
  308. {
  309. AZ_Error(PassBuilderName, false, "Failed to save asset to %s", destPath.c_str());
  310. return;
  311. }
  312. // --- Save output product(s) to response ---
  313. AssetBuilderSDK::JobProduct jobProduct(destPath, PassAsset::RTTI_Type(), 0);
  314. jobProduct.m_dependencies = productDependencies;
  315. jobProduct.m_dependenciesHandled = true;
  316. response.m_outputProducts.push_back(jobProduct);
  317. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  318. }
  319. } // namespace RPI
  320. } // namespace AZ