3
0

ImageBuilderComponent.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  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 <ImageBuilderComponent.h>
  9. #include <AzCore/std/string/conversions.h>
  10. #include <AzCore/Serialization/SerializeContext.h>
  11. #include <AzFramework/StringFunc/StringFunc.h>
  12. #include <AzFramework/IO/LocalFileIO.h>
  13. #include <AzCore/Debug/Trace.h>
  14. #include <BuilderSettings/BuilderSettingManager.h>
  15. #include <BuilderSettings/CubemapSettings.h>
  16. #include <ImageLoader/ImageLoaders.h>
  17. #include <Processing/ImageAssetProducer.h>
  18. #include <Processing/ImageConvert.h>
  19. #include <Processing/ImageToProcess.h>
  20. #include <Processing/PixelFormatInfo.h>
  21. #include <AzFramework/API/ApplicationAPI.h>
  22. #include <AzCore/Serialization/EditContextConstants.inl>
  23. #include <QFile>
  24. #include <AzQtComponents/Utilities/QtPluginPaths.h>
  25. #include <Atom/RPI.Reflect/Asset/AssetHandler.h>
  26. #include <Atom/RPI.Reflect/Image/StreamingImageAssetHandler.h>
  27. #include <Atom/RPI.Reflect/Image/ImageMipChainAsset.h>
  28. namespace ImageProcessingAtom
  29. {
  30. BuilderPluginComponent::BuilderPluginComponent()
  31. {
  32. // AZ Components should only initialize their members to null and empty in constructor
  33. // after construction, they may be deserialized from file.
  34. }
  35. BuilderPluginComponent::~BuilderPluginComponent()
  36. {
  37. }
  38. void BuilderPluginComponent::Init()
  39. {
  40. }
  41. void BuilderPluginComponent::Activate()
  42. {
  43. //load qt plugins for some image file formats support
  44. AzQtComponents::PrepareQtPaths();
  45. // create and initialize BuilderSettingManager once since it's will be used for image conversion
  46. BuilderSettingManager::CreateInstance();
  47. auto outcome = BuilderSettingManager::Instance()->LoadConfig();
  48. AZ_Error("Image Processing", outcome.IsSuccess(), "Failed to load Atom image builder settings.");
  49. if (!outcome.IsSuccess())
  50. {
  51. return;
  52. }
  53. // Activate is where you'd perform registration with other objects and systems.
  54. // Since we want to register our builder, we do that here:
  55. AssetBuilderSDK::AssetBuilderDesc builderDescriptor;
  56. builderDescriptor.m_name = "Atom Image Builder";
  57. for (int i = 0; i < s_TotalSupportedImageExtensions; i++)
  58. {
  59. builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(s_SupportedImageExtensions[i], AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
  60. }
  61. builderDescriptor.m_busId = azrtti_typeid<ImageBuilderWorker>();
  62. builderDescriptor.m_createJobFunction = AZStd::bind(&ImageBuilderWorker::CreateJobs, &m_imageBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
  63. builderDescriptor.m_processJobFunction = AZStd::bind(&ImageBuilderWorker::ProcessJob, &m_imageBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
  64. builderDescriptor.m_version = 34; // change ImageMipchainAsset reference to NoLoad
  65. builderDescriptor.m_analysisFingerprint = ImageProcessingAtom::BuilderSettingManager::Instance()->GetAnalysisFingerprint();
  66. m_imageBuilder.BusConnect(builderDescriptor.m_busId);
  67. AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBusTraits::RegisterBuilderInformation, builderDescriptor);
  68. m_assetHandlers.emplace_back(AZ::RPI::MakeAssetHandler<AZ::RPI::ImageMipChainAssetHandler>());
  69. m_assetHandlers.emplace_back(AZ::RPI::MakeAssetHandler<AZ::RPI::StreamingImageAssetHandler>());
  70. ImageProcessingRequestBus::Handler::BusConnect();
  71. ImageBuilderRequestBus::Handler::BusConnect();
  72. }
  73. void BuilderPluginComponent::Deactivate()
  74. {
  75. ImageProcessingRequestBus::Handler::BusDisconnect();
  76. ImageBuilderRequestBus::Handler::BusDisconnect();
  77. m_imageBuilder.BusDisconnect();
  78. BuilderSettingManager::DestroyInstance();
  79. CPixelFormats::DestroyInstance();
  80. }
  81. void BuilderPluginComponent::Reflect(AZ::ReflectContext* context)
  82. {
  83. // components also get Reflect called automatically
  84. // this is your opportunity to perform static reflection or type registration of any types you want the serializer to know about
  85. if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
  86. {
  87. serialize->Class<BuilderPluginComponent, AZ::Component>()
  88. ->Version(0)
  89. ->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector<AZ::Crc32>({ AssetBuilderSDK::ComponentTags::AssetBuilder }))
  90. ;
  91. }
  92. BuilderSettingManager::Reflect(context);
  93. BuilderSettings::Reflect(context);
  94. MultiplatformPresetSettings::Reflect(context);
  95. PresetSettings::Reflect(context);
  96. CubemapSettings::Reflect(context);
  97. MipmapSettings::Reflect(context);
  98. TextureSettings::Reflect(context);
  99. }
  100. void BuilderPluginComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  101. {
  102. provided.push_back(AZ_CRC("ImagerBuilderPluginService", 0x6dc0db6e));
  103. }
  104. void BuilderPluginComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  105. {
  106. incompatible.push_back(AZ_CRC("ImagerBuilderPluginService", 0x6dc0db6e));
  107. }
  108. IImageObjectPtr BuilderPluginComponent::LoadImage(const AZStd::string& filePath)
  109. {
  110. return IImageObjectPtr(LoadImageFromFile(filePath));
  111. }
  112. IImageObjectPtr BuilderPluginComponent::LoadImagePreview(const AZStd::string& filePath)
  113. {
  114. IImageObjectPtr image(LoadImageFromFile(filePath));
  115. if (image)
  116. {
  117. ImageToProcess imageToProcess(image);
  118. imageToProcess.ConvertFormat(ePixelFormat_R8G8B8A8);
  119. return imageToProcess.Get();
  120. }
  121. return image;
  122. }
  123. IImageObjectPtr BuilderPluginComponent::CreateImage(
  124. AZ::u32 width,
  125. AZ::u32 height,
  126. AZ::u32 maxMipCount,
  127. EPixelFormat pixelFormat)
  128. {
  129. IImageObjectPtr image(IImageObject::CreateImage(width, height, maxMipCount, pixelFormat));
  130. return image;
  131. }
  132. AZStd::vector<AssetBuilderSDK::JobProduct> BuilderPluginComponent::ConvertImageObject(
  133. IImageObjectPtr imageObject,
  134. const AZStd::string& presetName,
  135. const AZStd::string& platformName,
  136. const AZStd::string& outputDir,
  137. const AZ::Data::AssetId& sourceAssetId,
  138. const AZStd::string& sourceAssetName)
  139. {
  140. AZStd::vector<AssetBuilderSDK::JobProduct> outProducts;
  141. AZStd::string_view presetFilePath;
  142. const PresetSettings* preset = BuilderSettingManager::Instance()->GetPreset(PresetName(presetName), platformName, &presetFilePath);
  143. if (preset == nullptr)
  144. {
  145. AZ_Assert(false, "Cannot find preset with name %s.", presetName.c_str());
  146. return outProducts;
  147. }
  148. AZStd::unique_ptr<ImageConvertProcessDescriptor> desc = AZStd::make_unique<ImageConvertProcessDescriptor>();
  149. TextureSettings& textureSettings = desc->m_textureSetting;
  150. textureSettings.m_preset = preset->m_name;
  151. desc->m_inputImage = imageObject;
  152. desc->m_presetSetting = *preset;
  153. desc->m_isPreview = false;
  154. desc->m_platform = platformName;
  155. desc->m_filePath = presetFilePath;
  156. desc->m_isStreaming = BuilderSettingManager::Instance()->GetBuilderSetting(platformName)->m_enableStreaming;
  157. desc->m_imageName = sourceAssetName;
  158. desc->m_outputFolder = outputDir;
  159. desc->m_sourceAssetId = sourceAssetId;
  160. // Create an image convert process
  161. ImageConvertProcess process(AZStd::move(desc));
  162. process.ProcessAll();
  163. bool result = process.IsSucceed();
  164. if (result)
  165. {
  166. process.GetAppendOutputProducts(outProducts);
  167. }
  168. return outProducts;
  169. }
  170. IImageObjectPtr BuilderPluginComponent::ConvertImageObjectInMemory(
  171. IImageObjectPtr imageObject,
  172. const AZStd::string& presetName,
  173. const AZStd::string& platformName,
  174. const AZ::Data::AssetId& sourceAssetId,
  175. const AZStd::string& sourceAssetName)
  176. {
  177. AZStd::vector<AssetBuilderSDK::JobProduct> outProducts;
  178. AZStd::string_view presetFilePath;
  179. const PresetSettings* preset = BuilderSettingManager::Instance()->GetPreset(PresetName(presetName), platformName, &presetFilePath);
  180. if (preset == nullptr)
  181. {
  182. AZ_Assert(false, "Cannot find preset with name %s.", presetName.c_str());
  183. return nullptr;
  184. }
  185. AZStd::unique_ptr<ImageConvertProcessDescriptor> desc = AZStd::make_unique<ImageConvertProcessDescriptor>();
  186. TextureSettings& textureSettings = desc->m_textureSetting;
  187. textureSettings.m_preset = preset->m_name;
  188. desc->m_inputImage = imageObject;
  189. desc->m_presetSetting = *preset;
  190. desc->m_isPreview = false;
  191. desc->m_platform = platformName;
  192. desc->m_filePath = presetFilePath;
  193. desc->m_isStreaming = BuilderSettingManager::Instance()->GetBuilderSetting(platformName)->m_enableStreaming;
  194. desc->m_imageName = sourceAssetName;
  195. desc->m_shouldSaveFile = false;
  196. desc->m_sourceAssetId = sourceAssetId;
  197. // Create an image convert process
  198. ImageConvertProcess process(AZStd::move(desc));
  199. process.ProcessAll();
  200. bool result = process.IsSucceed();
  201. if (result)
  202. {
  203. return process.GetOutputImage();
  204. }
  205. else
  206. {
  207. return nullptr;
  208. }
  209. }
  210. bool BuilderPluginComponent::DoesSupportPlatform(const AZStd::string& platformId)
  211. {
  212. return ImageProcessingAtom::BuilderSettingManager::Instance()->DoesSupportPlatform(platformId);
  213. }
  214. bool BuilderPluginComponent::IsPresetFormatSquarePow2(const AZStd::string& presetName, const AZStd::string& platformName)
  215. {
  216. AZStd::string_view filePath;
  217. const PresetSettings* preset = BuilderSettingManager::Instance()->GetPreset(PresetName(presetName), platformName, &filePath);
  218. if (preset == nullptr)
  219. {
  220. AZ_Assert(false, "Cannot find preset with name %s.", presetName.c_str());
  221. return false;
  222. }
  223. const PixelFormatInfo* info = CPixelFormats::GetInstance().GetPixelFormatInfo(preset->m_pixelFormat);
  224. return info->bSquarePow2;
  225. }
  226. FileMask BuilderPluginComponent::GetFileMask(AZStd::string_view imageFilePath)
  227. {
  228. return ImageProcessingAtom::BuilderSettingManager::Instance()->GetFileMask(imageFilePath);
  229. }
  230. AZStd::vector<AZStd::string> BuilderPluginComponent::GetFileMasksForPreset(const PresetName& presetName)
  231. {
  232. return ImageProcessingAtom::BuilderSettingManager::Instance()->GetFileMasksForPreset(presetName);
  233. }
  234. AZStd::vector<PresetName> BuilderPluginComponent::GetPresetsForFileMask(const FileMask& fileMask)
  235. {
  236. return ImageProcessingAtom::BuilderSettingManager::Instance()->GetPresetsForFileMask(fileMask);
  237. }
  238. PresetName BuilderPluginComponent::GetDefaultPreset()
  239. {
  240. return ImageProcessingAtom::BuilderSettingManager::Instance()->GetDefaultPreset();
  241. }
  242. PresetName BuilderPluginComponent::GetDefaultAlphaPreset()
  243. {
  244. return ImageProcessingAtom::BuilderSettingManager::Instance()->GetDefaultAlphaPreset();
  245. }
  246. bool BuilderPluginComponent::IsValidPreset(PresetName presetName)
  247. {
  248. return ImageProcessingAtom::BuilderSettingManager::Instance()->IsValidPreset(presetName);
  249. }
  250. void ImageBuilderWorker::ShutDown()
  251. {
  252. // it is important to note that this will be called on a different thread than your process job thread
  253. m_isShuttingDown = true;
  254. }
  255. PresetName GetImagePreset(const AZStd::string& imageFileFullPath)
  256. {
  257. // first let preset from asset info
  258. TextureSettings textureSettings;
  259. AZStd::string settingFilePath = imageFileFullPath + TextureSettings::ExtensionName;
  260. TextureSettings::LoadTextureSetting(settingFilePath, textureSettings);
  261. if (!textureSettings.m_preset.IsEmpty())
  262. {
  263. return textureSettings.m_preset;
  264. }
  265. return BuilderSettingManager::Instance()->GetSuggestedPreset(imageFileFullPath);
  266. }
  267. void HandlePresetDependency(PresetName presetName, AZStd::vector<AssetBuilderSDK::SourceFileDependency>& sourceDependencyList)
  268. {
  269. // Reload preset if it was changed
  270. ImageProcessingAtom::BuilderSettingManager::Instance()->ReloadPreset(presetName);
  271. auto presetSettings = BuilderSettingManager::Instance()->GetPreset(presetName, /*default platform*/"");
  272. AssetBuilderSDK::SourceFileDependency sourceFileDependency;
  273. sourceFileDependency.m_sourceDependencyType = AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Absolute;
  274. // Need to watch any possibe preset paths
  275. AZStd::vector<AZStd::string> possiblePresetPaths = BuilderSettingManager::Instance()->GetPossiblePresetPaths(presetName);
  276. for (const auto& path:possiblePresetPaths)
  277. {
  278. sourceFileDependency.m_sourceFileDependencyPath = path;
  279. sourceDependencyList.push_back(sourceFileDependency);
  280. }
  281. if (presetSettings)
  282. {
  283. // handle special case here
  284. // Cubemap setting may reference some other presets
  285. if (presetSettings->m_cubemapSetting)
  286. {
  287. if (presetSettings->m_cubemapSetting->m_generateIBLDiffuse && !presetSettings->m_cubemapSetting->m_iblDiffusePreset.IsEmpty())
  288. {
  289. HandlePresetDependency(presetSettings->m_cubemapSetting->m_iblDiffusePreset, sourceDependencyList);
  290. }
  291. if (presetSettings->m_cubemapSetting->m_generateIBLSpecular && !presetSettings->m_cubemapSetting->m_iblSpecularPreset.IsEmpty())
  292. {
  293. HandlePresetDependency(presetSettings->m_cubemapSetting->m_iblSpecularPreset, sourceDependencyList);
  294. }
  295. }
  296. }
  297. }
  298. void ReloadPresetIfNeeded(PresetName presetName)
  299. {
  300. // Reload preset if it was changed
  301. ImageProcessingAtom::BuilderSettingManager::Instance()->ReloadPreset(presetName);
  302. auto presetSettings = BuilderSettingManager::Instance()->GetPreset(presetName, /*default platform*/"");
  303. if (presetSettings)
  304. {
  305. // handle special case here
  306. // Cubemap setting may reference some other presets
  307. if (presetSettings->m_cubemapSetting)
  308. {
  309. if (presetSettings->m_cubemapSetting->m_generateIBLDiffuse && !presetSettings->m_cubemapSetting->m_iblDiffusePreset.IsEmpty())
  310. {
  311. ImageProcessingAtom::BuilderSettingManager::Instance()->ReloadPreset(presetSettings->m_cubemapSetting->m_iblDiffusePreset);
  312. }
  313. if (presetSettings->m_cubemapSetting->m_generateIBLSpecular && !presetSettings->m_cubemapSetting->m_iblSpecularPreset.IsEmpty())
  314. {
  315. ImageProcessingAtom::BuilderSettingManager::Instance()->ReloadPreset(presetSettings->m_cubemapSetting->m_iblSpecularPreset);
  316. }
  317. }
  318. }
  319. }
  320. // this happens early on in the file scanning pass
  321. // this function should consistently always create the same jobs, and should do no checking whether the job is up to date or not - just be consistent.
  322. void ImageBuilderWorker::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
  323. {
  324. if (m_isShuttingDown)
  325. {
  326. response.m_result = AssetBuilderSDK::CreateJobsResultCode::ShuttingDown;
  327. return;
  328. }
  329. // Full path of the image file
  330. AZStd::string fullPath;
  331. AzFramework::StringFunc::Path::Join(request.m_watchFolder.data(), request.m_sourceFile.data(), fullPath, true, true);
  332. // Get the extension of the file
  333. AZStd::string ext;
  334. AzFramework::StringFunc::Path::GetExtension(request.m_sourceFile.c_str(), ext, false);
  335. AZStd::to_upper(ext.begin(), ext.end());
  336. // We process the same file for all platforms
  337. for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
  338. {
  339. if (ImageProcessingAtom::BuilderSettingManager::Instance()->DoesSupportPlatform(platformInfo.m_identifier))
  340. {
  341. AssetBuilderSDK::JobDescriptor descriptor;
  342. descriptor.m_jobKey = "Image Compile: " + ext;
  343. descriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
  344. descriptor.m_critical = false;
  345. descriptor.m_additionalFingerprintInfo = "";
  346. response.m_createJobOutputs.push_back(descriptor);
  347. }
  348. }
  349. // add source dependency for .assetinfo file
  350. AssetBuilderSDK::SourceFileDependency sourceFileDependency;
  351. sourceFileDependency.m_sourceDependencyType = AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Absolute;
  352. sourceFileDependency.m_sourceFileDependencyPath = fullPath + TextureSettings::ExtensionName;
  353. response.m_sourceFileDependencyList.push_back(sourceFileDependency);
  354. // add source dependencies for .preset files
  355. // Get the preset for this file
  356. auto presetName = GetImagePreset(fullPath.c_str());
  357. HandlePresetDependency(presetName, response.m_sourceFileDependencyList);
  358. response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
  359. return;
  360. }
  361. // later on, this function will be called for jobs that actually need doing.
  362. // the request will contain the CreateJobResponse you constructed earlier, including any keys and values you placed into the hash table
  363. void ImageBuilderWorker::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
  364. {
  365. // Before we begin, let's make sure we are not meant to abort.
  366. AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
  367. AZStd::vector<AZStd::string> productFilepaths;
  368. bool imageProcessingSuccessful = false;
  369. bool needConversion = true;
  370. // Do conversion and get exported file's path
  371. if (needConversion)
  372. {
  373. // Handles preset changes
  374. auto presetName = GetImagePreset(request.m_fullPath);
  375. ReloadPresetIfNeeded(presetName);
  376. AZ_TracePrintf(AssetBuilderSDK::InfoWindow, "Performing image conversion: %s\n", request.m_fullPath.c_str());
  377. ImageConvertProcess* process = CreateImageConvertProcess(request.m_fullPath, request.m_tempDirPath,
  378. request.m_jobDescription.GetPlatformIdentifier(), response.m_outputProducts);
  379. if (process != nullptr)
  380. {
  381. //the process can be stopped if the job is cancelled or the worker is shutting down
  382. while (!process->IsFinished() && !m_isShuttingDown && !jobCancelListener.IsCancelled())
  383. {
  384. process->UpdateProcess();
  385. }
  386. //get process result
  387. imageProcessingSuccessful = process->IsSucceed();
  388. if (imageProcessingSuccessful)
  389. {
  390. process->GetAppendOutputProducts(response.m_outputProducts);
  391. }
  392. delete process;
  393. }
  394. else
  395. {
  396. imageProcessingSuccessful = false;
  397. }
  398. }
  399. if (imageProcessingSuccessful)
  400. {
  401. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  402. }
  403. else
  404. {
  405. if (m_isShuttingDown)
  406. {
  407. AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "Cancelled job %s because shutdown was requested.\n", request.m_fullPath.c_str());
  408. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
  409. }
  410. else if (jobCancelListener.IsCancelled())
  411. {
  412. AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "Cancelled was requested for job %s.\n", request.m_fullPath.c_str());
  413. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
  414. }
  415. else
  416. {
  417. AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "Unexpected error during processing job %s.\n", request.m_fullPath.c_str());
  418. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  419. }
  420. }
  421. }
  422. } // namespace ImageProcessingAtom