3
0

AtomViewportDisplayIconsSystemComponent.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  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 "AtomViewportDisplayIconsSystemComponent.h"
  9. #include <AzCore/Math/VectorConversions.h>
  10. #include <AzCore/Serialization/SerializeContext.h>
  11. #include <AzCore/Serialization/EditContext.h>
  12. #include <AzCore/Serialization/EditContextConstants.inl>
  13. #include <AzCore/std/containers/array.h>
  14. #include <AzFramework/Asset/AssetSystemBus.h>
  15. #include <AzFramework/Viewport/ViewportScreen.h>
  16. #include <AzToolsFramework/Viewport/ViewportMessages.h>
  17. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  18. #include <Atom/RPI.Public/View.h>
  19. #include <Atom/RPI.Public/Scene.h>
  20. #include <Atom/RPI.Public/ViewportContextBus.h>
  21. #include <Atom/RPI.Public/ViewportContext.h>
  22. #include <Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h>
  23. #include <Atom/RPI.Public/RPIUtils.h>
  24. #include <Atom/RPI.Public/Image/ImageSystemInterface.h>
  25. #include <Atom/RPI.Reflect/Image/StreamingImageAssetCreator.h>
  26. #include <Atom/RPI.Reflect/Image/ImageMipChainAssetCreator.h>
  27. #include <Atom/RPI.Reflect/Asset/AssetUtils.h>
  28. #include <Atom/RPI.Public/Image/StreamingImagePool.h>
  29. #include <AtomBridge/PerViewportDynamicDrawInterface.h>
  30. #include <QDir>
  31. #include <QFileInfo>
  32. #include <QImage>
  33. #include <QPainter>
  34. #include <QSvgRenderer>
  35. namespace AZ::Render
  36. {
  37. void AtomViewportDisplayIconsSystemComponent::Reflect(AZ::ReflectContext* context)
  38. {
  39. if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
  40. {
  41. serialize->Class<AtomViewportDisplayIconsSystemComponent, AZ::Component>()
  42. ->Version(0)
  43. ;
  44. if (AZ::EditContext* ec = serialize->GetEditContext())
  45. {
  46. ec->Class<AtomViewportDisplayIconsSystemComponent>("Viewport Display Icons", "Provides an interface for drawing simple icons to the Editor viewport")
  47. ->ClassElement(Edit::ClassElements::EditorData, "")
  48. ->Attribute(Edit::Attributes::AutoExpand, true)
  49. ;
  50. }
  51. }
  52. }
  53. void AtomViewportDisplayIconsSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  54. {
  55. provided.push_back(AZ_CRC("ViewportDisplayIconsService"));
  56. }
  57. void AtomViewportDisplayIconsSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  58. {
  59. incompatible.push_back(AZ_CRC("ViewportDisplayIconsService"));
  60. }
  61. void AtomViewportDisplayIconsSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  62. {
  63. required.push_back(AZ_CRC("RPISystem", 0xf2add773));
  64. required.push_back(AZ_CRC("AtomBridgeService", 0x92d990b5));
  65. }
  66. void AtomViewportDisplayIconsSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
  67. {
  68. }
  69. void AtomViewportDisplayIconsSystemComponent::Activate()
  70. {
  71. m_drawContextRegistered = false;
  72. AzToolsFramework::EditorViewportIconDisplay::Register(this);
  73. Bootstrap::NotificationBus::Handler::BusConnect();
  74. }
  75. void AtomViewportDisplayIconsSystemComponent::Deactivate()
  76. {
  77. Data::AssetBus::Handler::BusDisconnect();
  78. Bootstrap::NotificationBus::Handler::BusDisconnect();
  79. auto perViewportDynamicDrawInterface = AtomBridge::PerViewportDynamicDraw::Get();
  80. if (!perViewportDynamicDrawInterface)
  81. {
  82. return;
  83. }
  84. if (perViewportDynamicDrawInterface && m_drawContextRegistered)
  85. {
  86. perViewportDynamicDrawInterface->UnregisterDynamicDrawContext(m_drawContextName);
  87. m_drawContextRegistered = false;
  88. }
  89. AzToolsFramework::EditorViewportIconDisplay::Unregister(this);
  90. }
  91. void AtomViewportDisplayIconsSystemComponent::DrawIcon(const DrawParameters& drawParameters)
  92. {
  93. // Ensure we have a valid viewport context & dynamic draw interface
  94. auto viewportContext = RPI::ViewportContextRequests::Get()->GetViewportContextById(drawParameters.m_viewport);
  95. if (viewportContext == nullptr)
  96. {
  97. return;
  98. }
  99. auto perViewportDynamicDrawInterface = AtomBridge::PerViewportDynamicDraw::Get();
  100. if (!perViewportDynamicDrawInterface)
  101. {
  102. return;
  103. }
  104. RHI::Ptr<RPI::DynamicDrawContext> dynamicDraw =
  105. perViewportDynamicDrawInterface->GetDynamicDrawContextForViewport(m_drawContextName, drawParameters.m_viewport);
  106. if (dynamicDraw == nullptr)
  107. {
  108. return;
  109. }
  110. // Find our icon, falling back on a gray placeholder if its image is unavailable
  111. AZ::Data::Instance<AZ::RPI::Image> image = AZ::RPI::ImageSystemInterface::Get()->GetSystemImage(AZ::RPI::SystemImage::Grey);
  112. if (auto iconIt = m_iconData.find(drawParameters.m_icon); iconIt != m_iconData.end())
  113. {
  114. auto& iconData = iconIt->second;
  115. if (iconData.m_image)
  116. {
  117. image = iconData.m_image;
  118. }
  119. }
  120. else
  121. {
  122. return;
  123. }
  124. const auto [viewportWidth, viewportHeight] = viewportContext->GetViewportSize();
  125. const auto viewportSize = AzFramework::ScreenSize(viewportWidth, viewportHeight);
  126. // Initialize our shader
  127. AZ::Data::Instance<AZ::RPI::ShaderResourceGroup> drawSrg = dynamicDraw->NewDrawSrg();
  128. drawSrg->SetConstant(m_viewportSizeIndex, AzFramework::Vector2FromScreenSize(viewportSize));
  129. drawSrg->SetImageView(m_textureParameterIndex, image->GetImageView());
  130. drawSrg->Compile();
  131. // Scale icons by screen DPI
  132. float scalingFactor = 1.0f;
  133. {
  134. using ViewportRequestBus = AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus;
  135. ViewportRequestBus::EventResult(
  136. scalingFactor, drawParameters.m_viewport, &ViewportRequestBus::Events::DeviceScalingFactor);
  137. }
  138. AZ::Vector3 screenPosition;
  139. if (drawParameters.m_positionSpace == CoordinateSpace::ScreenSpace)
  140. {
  141. screenPosition = drawParameters.m_position;
  142. }
  143. else if (drawParameters.m_positionSpace == CoordinateSpace::WorldSpace)
  144. {
  145. // Calculate the ndc point (0.0-1.0 range) including depth
  146. const AZ::Vector3 ndcPoint = AzFramework::WorldToScreenNdc(
  147. drawParameters.m_position, viewportContext->GetCameraViewMatrixAsMatrix3x4(),
  148. viewportContext->GetCameraProjectionMatrix());
  149. // Calculate our screen space position using the viewport size
  150. // We want this instead of RenderViewportWidget::WorldToScreen which works in QWidget virtual coordinate space
  151. const AzFramework::ScreenPoint screenPoint = AzFramework::ScreenPointFromNdc(AZ::Vector3ToVector2(ndcPoint), viewportSize);
  152. screenPosition = AzFramework::Vector3FromScreenPoint(screenPoint, ndcPoint.GetZ());
  153. }
  154. struct Vertex
  155. {
  156. float m_position[3];
  157. AZ::u32 m_color;
  158. float m_uv[2];
  159. };
  160. using Indice = AZ::u16;
  161. // Create a vertex offset from the position to draw from based on the icon size
  162. // Vertex positions are in screen space coordinates
  163. auto createVertex = [&](float offsetX, float offsetY, float u, float v) -> Vertex
  164. {
  165. Vertex vertex;
  166. screenPosition.StoreToFloat3(vertex.m_position);
  167. vertex.m_position[0] += offsetX * drawParameters.m_size.GetX() * scalingFactor;
  168. vertex.m_position[1] += offsetY * drawParameters.m_size.GetY() * scalingFactor;
  169. vertex.m_color = drawParameters.m_color.ToU32();
  170. vertex.m_uv[0] = u;
  171. vertex.m_uv[1] = v;
  172. return vertex;
  173. };
  174. AZStd::array<Vertex, 4> vertices = {
  175. createVertex(-0.5f, -0.5f, 0.f, 0.f),
  176. createVertex(0.5f, -0.5f, 1.f, 0.f),
  177. createVertex(0.5f, 0.5f, 1.f, 1.f),
  178. createVertex(-0.5f, 0.5f, 0.f, 1.f)
  179. };
  180. AZStd::array<Indice, 6> indices = {0, 1, 2, 0, 2, 3};
  181. dynamicDraw->SetSortKey(
  182. aznumeric_cast<int64_t>(screenPosition.GetZ() * aznumeric_cast<float>(AZStd::numeric_limits<int64_t>::max())));
  183. dynamicDraw->DrawIndexed(
  184. &vertices, static_cast<uint32_t>(vertices.size()), &indices, static_cast<uint32_t>(indices.size()), RHI::IndexFormat::Uint16,
  185. drawSrg);
  186. }
  187. QString AtomViewportDisplayIconsSystemComponent::FindAssetPath(const QString& path) const
  188. {
  189. // If we get an absolute path, just use it.
  190. QFileInfo pathInfo(path);
  191. if (pathInfo.isAbsolute())
  192. {
  193. return path;
  194. }
  195. bool found = false;
  196. AZStd::vector<AZStd::string> scanFolders;
  197. AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
  198. found, &AzToolsFramework::AssetSystemRequestBus::Events::GetScanFolders, scanFolders);
  199. if (!found)
  200. {
  201. AZ_Error("AtomViewportDisplayIconSystemComponent", false, "Failed to load asset scan folders");
  202. return QString();
  203. }
  204. for (const auto& folder : scanFolders)
  205. {
  206. QDir dir(folder.data());
  207. if (dir.exists(path))
  208. {
  209. return dir.absoluteFilePath(path);
  210. }
  211. }
  212. return QString();
  213. }
  214. QImage AtomViewportDisplayIconsSystemComponent::RenderSvgToImage(const QString& svgPath) const
  215. {
  216. // Set up our SVG renderer
  217. QSvgRenderer renderer(svgPath);
  218. renderer.setAspectRatioMode(Qt::KeepAspectRatio);
  219. // Set up our target image
  220. QSize size = renderer.defaultSize().expandedTo(MinimumRenderedSvgSize);
  221. QImage image(size, QtImageFormat);
  222. image.fill(0x00000000);
  223. // Render the SVG
  224. QPainter painter(&image);
  225. renderer.render(&painter);
  226. return image;
  227. }
  228. AZ::Data::Instance<AZ::RPI::Image> AtomViewportDisplayIconsSystemComponent::ConvertToAtomImage(AZ::Uuid assetId, QImage image) const
  229. {
  230. // Ensure our image is in the correct pixel format so we can memcpy it to our renderer image
  231. image.convertTo(QtImageFormat);
  232. Data::Instance<RPI::StreamingImagePool> streamingImagePool = RPI::ImageSystemInterface::Get()->GetSystemStreamingPool();
  233. return RPI::StreamingImage::CreateFromCpuData(
  234. *streamingImagePool.get(),
  235. RHI::ImageDimension::Image2D,
  236. RHI::Size(image.width(), image.height(), 1),
  237. RHI::Format::R8G8B8A8_UNORM_SRGB,
  238. image.bits(),
  239. image.sizeInBytes(),
  240. assetId);
  241. }
  242. AzToolsFramework::EditorViewportIconDisplayInterface::IconId AtomViewportDisplayIconsSystemComponent::GetOrLoadIconForPath(
  243. AZStd::string_view path)
  244. {
  245. // Check our cache to see if the image is already loaded
  246. auto existingEntryIt = AZStd::find_if(m_iconData.begin(), m_iconData.end(), [&path](const auto& iconData)
  247. {
  248. return iconData.second.m_path == path;
  249. });
  250. if (existingEntryIt != m_iconData.end())
  251. {
  252. return existingEntryIt->first;
  253. }
  254. AZ::Uuid assetId = AZ::Uuid::CreateName(path.data());
  255. // Find the asset to load on disk
  256. QString assetPath = FindAssetPath(path.data());
  257. if (assetPath.isEmpty())
  258. {
  259. AZ_Error("AtomViewportDisplayIconSystemComponent", false, "Failed to locate icon on disk: \"%s\"", path.data());
  260. return InvalidIconId;
  261. }
  262. QImage loadedImage;
  263. AZStd::string extension;
  264. AzFramework::StringFunc::Path::GetExtension(path.data(), extension, false);
  265. // For SVGs, we need to actually rasterize to an image
  266. if (extension == "svg")
  267. {
  268. loadedImage = RenderSvgToImage(assetPath);
  269. }
  270. // For everything else, we can just load it through QImage via its image plugins
  271. else
  272. {
  273. const bool loaded = loadedImage.load(assetPath);
  274. if (!loaded)
  275. {
  276. AZ_Error("AtomViewportDisplayIconSystemComponent", false, "Failed to load icon: \"%s\"", assetPath.toUtf8().constData());
  277. return InvalidIconId;
  278. }
  279. }
  280. // Cache our loaded icon
  281. IconId id = m_currentId++;
  282. IconData& iconData = m_iconData[id];
  283. iconData.m_path = path;
  284. iconData.m_image = ConvertToAtomImage(assetId, loadedImage);
  285. return id;
  286. }
  287. AzToolsFramework::EditorViewportIconDisplayInterface::IconLoadStatus AtomViewportDisplayIconsSystemComponent::GetIconLoadStatus(
  288. IconId icon)
  289. {
  290. auto iconIt = m_iconData.find(icon);
  291. if (iconIt == m_iconData.end())
  292. {
  293. return IconLoadStatus::Unloaded;
  294. }
  295. if (iconIt->second.m_image)
  296. {
  297. return IconLoadStatus::Loaded;
  298. }
  299. return IconLoadStatus::Error;
  300. }
  301. void AtomViewportDisplayIconsSystemComponent::OnBootstrapSceneReady([[maybe_unused]]AZ::RPI::Scene* bootstrapScene)
  302. {
  303. // Queue a load for the draw context shader, and wait for it to load
  304. Data::Asset<RPI::ShaderAsset> shaderAsset = RPI::AssetUtils::GetAssetByProductPath<RPI::ShaderAsset>(DrawContextShaderPath, RPI::AssetUtils::TraceLevel::Assert);
  305. shaderAsset.QueueLoad();
  306. Data::AssetBus::Handler::BusConnect(shaderAsset.GetId());
  307. }
  308. void AtomViewportDisplayIconsSystemComponent::OnAssetReady(Data::Asset<Data::AssetData> asset)
  309. {
  310. // Once the shader is loaded, register it with the dynamic draw context
  311. Data::Asset<RPI::ShaderAsset> shaderAsset = asset;
  312. AtomBridge::PerViewportDynamicDraw::Get()->RegisterDynamicDrawContext(m_drawContextName, [shaderAsset](RPI::Ptr<RPI::DynamicDrawContext> dynamicDraw)
  313. {
  314. AZ_Assert(shaderAsset->IsReady(), "Attempting to register the AtomViewportDisplayIconsSystemComponent"
  315. " dynamic draw context before the shader asset is loaded. The shader should be loaded first"
  316. " to avoid a blocking asset load and potential deadlock, since the DynamicDrawContext lambda"
  317. " will be executed during scene processing and there may be multiple scenes executing in parallel.");
  318. Data::Instance<RPI::Shader> shader = RPI::Shader::FindOrCreate(shaderAsset);
  319. dynamicDraw->InitShader(shader);
  320. dynamicDraw->InitVertexFormat({ { "POSITION", RHI::Format::R32G32B32_FLOAT },
  321. { "COLOR", RHI::Format::R8G8B8A8_UNORM },
  322. { "TEXCOORD", RHI::Format::R32G32_FLOAT } });
  323. dynamicDraw->EndInit();
  324. });
  325. m_drawContextRegistered = true;
  326. Data::AssetBus::Handler::BusDisconnect();
  327. }
  328. } // namespace AZ::Render