TextureAtlasSystemComponent.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  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 <AzCore/Serialization/SerializeContext.h>
  9. #include <AzCore/Serialization/EditContext.h>
  10. #include <AzCore/IO/FileIO.h>
  11. #include <AzFramework/API/ApplicationAPI.h>
  12. #include "TextureAtlasSystemComponent.h"
  13. #include "TextureAtlasImpl.h"
  14. #include <AzCore/Asset/AssetManagerBus.h>
  15. #include <AzFramework/StringFunc/StringFunc.h>
  16. #include <Atom/RPI.Public/Image/StreamingImage.h>
  17. namespace
  18. {
  19. AZ::Data::Instance<AZ::RPI::Image> LoadAtlasImage(const AZStd::string& imagePath)
  20. {
  21. // The file may not be in the AssetCatalog at this point if it is still processing or doesn't exist on disk.
  22. // Use GenerateAssetIdTEMP instead of GetAssetIdByPath so that it will return a valid AssetId anyways
  23. AZ::Data::AssetId streamingImageAssetId;
  24. AZ::Data::AssetCatalogRequestBus::BroadcastResult(
  25. streamingImageAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GenerateAssetIdTEMP,
  26. imagePath.c_str());
  27. streamingImageAssetId.m_subId = AZ::RPI::StreamingImageAsset::GetImageAssetSubId();
  28. auto streamingImageAsset = AZ::Data::AssetManager::Instance().FindOrCreateAsset<AZ::RPI::StreamingImageAsset>(streamingImageAssetId, AZ::Data::AssetLoadBehavior::PreLoad);
  29. AZ::Data::Instance<AZ::RPI::Image> image = AZ::RPI::StreamingImage::FindOrCreate(streamingImageAsset);
  30. return image;
  31. }
  32. }
  33. namespace TextureAtlasNamespace
  34. {
  35. void TextureAtlasSystemComponent::Reflect(AZ::ReflectContext* context)
  36. {
  37. if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
  38. {
  39. serialize->Class<TextureAtlasSystemComponent, AZ::Component>()
  40. ->Version(0)
  41. ->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector<AZ::Crc32>({ AZ_CRC("AssetBuilder", 0xc739c7d7) }));
  42. ;
  43. if (AZ::EditContext* ec = serialize->GetEditContext())
  44. {
  45. ec->Class<TextureAtlasSystemComponent>(
  46. "TextureAtlas", "This component loads and manages TextureAtlases")
  47. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  48. ->Attribute(AZ::Edit::Attributes::AutoExpand, true);
  49. }
  50. }
  51. TextureAtlasImpl::Reflect(context);
  52. }
  53. void TextureAtlasSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  54. {
  55. provided.push_back(AZ_CRC("TextureAtlasService"));
  56. }
  57. void
  58. TextureAtlasSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  59. {
  60. incompatible.push_back(AZ_CRC("TextureAtlasService"));
  61. }
  62. void TextureAtlasSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  63. {
  64. (void)required;
  65. }
  66. void TextureAtlasSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
  67. {
  68. (void)dependent;
  69. }
  70. void TextureAtlasSystemComponent::Init() { }
  71. void TextureAtlasSystemComponent::Activate()
  72. {
  73. TextureAtlasRequestBus::Handler::BusConnect();
  74. AzFramework::AssetCatalogEventBus::Handler::BusConnect();
  75. }
  76. void TextureAtlasSystemComponent::Deactivate()
  77. {
  78. TextureAtlasRequestBus::Handler::BusDisconnect();
  79. AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
  80. }
  81. void TextureAtlasSystemComponent::OnCatalogAssetChanged(const AZ::Data::AssetId& assetId)
  82. {
  83. for (auto iterator = m_atlases.begin(); iterator != m_atlases.end(); ++iterator)
  84. {
  85. if (iterator->second.m_atlasAssetId == assetId)
  86. {
  87. TextureAtlasImpl* input = AZ::Utils::LoadObjectFromFile<TextureAtlasImpl>(iterator->second.m_path);
  88. reinterpret_cast<TextureAtlasImpl*>(iterator->second.m_atlas)->OverwriteMappings(input);
  89. delete input;
  90. // We reload the image here to prevent stuttering in the editor
  91. if (iterator->second.m_atlas && iterator->second.m_atlas->GetTexture())
  92. {
  93. iterator->second.m_atlas->GetTexture().reset();
  94. }
  95. AZStd::string imagePath = iterator->second.m_path;
  96. AZ::Data::Instance<AZ::RPI::Image> texture = LoadAtlasImage(imagePath);
  97. if (!texture)
  98. {
  99. AZ_Error("TextureAtlasSystemComponent",
  100. false,
  101. "Failed to find or create an image instance for texture atlas '%s'"
  102. "NOTE: File must be in current project or a gem.",
  103. imagePath.c_str());
  104. TextureAtlas* temp = iterator->second.m_atlas;
  105. m_atlases.erase(iterator->first);
  106. TextureAtlasNotificationBus::Broadcast(&TextureAtlasNotifications::OnAtlasUnloaded, temp);
  107. return;
  108. }
  109. iterator->second.m_atlas->SetTexture(texture);
  110. TextureAtlasNotificationBus::Broadcast(&TextureAtlasNotifications::OnAtlasReloaded, iterator->second.m_atlas);
  111. break;
  112. }
  113. }
  114. }
  115. void TextureAtlasSystemComponent::SaveAtlasToFile(
  116. const AZStd::string& outputPath,
  117. AtlasCoordinateSets& handles,
  118. int width,
  119. int height)
  120. {
  121. TextureAtlasImpl atlas(handles);
  122. atlas.SetWidth(width);
  123. atlas.SetHeight(height);
  124. AZ::Utils::SaveObjectToFile(outputPath, AZ::DataStream::StreamType::ST_XML, &(atlas));
  125. }
  126. // Attempts to load an atlas from file
  127. TextureAtlas* TextureAtlasSystemComponent::LoadAtlas(const AZStd::string& filePath)
  128. {
  129. // Dont use an empty string as a path
  130. if (filePath.empty())
  131. {
  132. return nullptr;
  133. }
  134. // Normalize the file path
  135. AZStd::string path = filePath;
  136. AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePath, path);
  137. // Check if the atlas is already loaded
  138. AZStd::string assetPath = path;
  139. AzFramework::StringFunc::Path::ReplaceExtension(assetPath, "texatlasidx");
  140. auto iterator = m_atlases.find(assetPath);
  141. if (iterator != m_atlases.end())
  142. {
  143. iterator->second.m_refs++;
  144. return iterator->second.m_atlas;
  145. }
  146. // If it isn't loaded, load it
  147. // Open the file
  148. AZ::IO::FileIOBase* input = AZ::IO::FileIOBase::GetInstance();
  149. AZ::IO::HandleType handle;
  150. input->Open(assetPath.c_str(), AZ::IO::OpenMode::ModeRead, handle);
  151. // Read the file
  152. AZ::u64 size;
  153. input->Size(handle, size);
  154. char* buffer = new char[size + 1];
  155. input->Read(handle, buffer, size);
  156. buffer[size] = 0;
  157. // Close the file
  158. input->Close(handle);
  159. TextureAtlas* loadedAtlas = AZ::Utils::LoadObjectFromBuffer<TextureAtlasImpl>(buffer, size);
  160. delete[] buffer;
  161. if (loadedAtlas)
  162. {
  163. // Convert to image path based on the atlas path
  164. AZStd::string imagePath = path;
  165. AzFramework::StringFunc::Path::ReplaceExtension(imagePath, "texatlas");
  166. AZ::Data::Instance<AZ::RPI::Image> texture = LoadAtlasImage(imagePath);
  167. if (!texture)
  168. {
  169. AZ_Error("TextureAtlasSystemComponent",
  170. false,
  171. "Failed to find or create an image instance for texture atlas '%s'"
  172. "NOTE: File must be in current project or a gem.",
  173. path.c_str());
  174. delete loadedAtlas;
  175. return nullptr;
  176. }
  177. else
  178. {
  179. // Add the atlas to the list
  180. AtlasInfo info(loadedAtlas, assetPath);
  181. ++info.m_refs;
  182. loadedAtlas->SetTexture(texture);
  183. AZ::Data::AssetCatalogRequestBus::BroadcastResult(info.m_atlasAssetId, &AZ::Data::AssetCatalogRequests::GetAssetIdByPath, assetPath.c_str(),
  184. info.m_atlasAssetId.TYPEINFO_Uuid(), false);
  185. m_atlases[assetPath] = info;
  186. TextureAtlasNotificationBus::Broadcast(&TextureAtlasNotifications::OnAtlasLoaded, loadedAtlas);
  187. }
  188. }
  189. return loadedAtlas;
  190. }
  191. // Lowers the ref count on an atlas. If the ref count it less than one, deletes the atlas
  192. void TextureAtlasSystemComponent::UnloadAtlas(TextureAtlas* atlas)
  193. {
  194. // Check if the atlas is loaded
  195. for (auto iterator = m_atlases.begin(); iterator != m_atlases.end(); ++iterator)
  196. {
  197. if (iterator->second.m_atlas == atlas)
  198. {
  199. --iterator->second.m_refs;
  200. if (iterator->second.m_refs < 1 && iterator->second.m_atlas->GetTexture())
  201. {
  202. AtlasInfo temp = iterator->second;
  203. m_atlases.erase(iterator->first);
  204. TextureAtlasNotificationBus::Broadcast(&TextureAtlasNotifications::OnAtlasUnloaded, temp.m_atlas);
  205. // Tell the renderer to release the texture.
  206. if (temp.m_atlas && temp.m_atlas->GetTexture())
  207. {
  208. temp.m_atlas->GetTexture().reset();
  209. }
  210. // Delete the atlas
  211. if (temp.m_atlas)
  212. {
  213. delete temp.m_atlas;
  214. temp.m_atlas = NULL;
  215. }
  216. }
  217. return;
  218. }
  219. }
  220. }
  221. // Searches for an atlas that contains an image
  222. TextureAtlas* TextureAtlasSystemComponent::FindAtlasContainingImage(const AZStd::string& filePath)
  223. {
  224. // Check all atlases
  225. for (auto iterator = m_atlases.begin(); iterator != m_atlases.end(); ++iterator)
  226. {
  227. if (iterator->second.m_atlas->GetAtlasCoordinates(filePath).GetWidth() > 0)
  228. {
  229. // If we we found the image return the pointer to the atlas
  230. return iterator->second.m_atlas;
  231. }
  232. }
  233. return nullptr;
  234. }
  235. }