3
0

AtomViewportDisplayInfoSystemComponent.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  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 "AtomViewportDisplayInfoSystemComponent.h"
  9. #include <AzCore/Serialization/SerializeContext.h>
  10. #include <AzCore/Serialization/EditContext.h>
  11. #include <AzCore/Serialization/EditContextConstants.inl>
  12. #include <AzCore/Console/IConsole.h>
  13. #include <AzCore/Interface/Interface.h>
  14. #include <Atom/RPI.Public/Image/ImageSystemInterface.h>
  15. #include <Atom/RPI.Public/Image/StreamingImagePool.h>
  16. #include <Atom/RPI.Public/Pass/ParentPass.h>
  17. #include <Atom/RPI.Public/RenderPipeline.h>
  18. #include <Atom/RPI.Public/ViewportContextBus.h>
  19. #include <Atom/RPI.Public/ViewportContext.h>
  20. #include <Atom/RPI.Public/View.h>
  21. #include <Atom/RHI/Factory.h>
  22. #include <Atom/RHI/RHISystemInterface.h>
  23. #include <Atom/RHI/RHIMemoryStatisticsInterface.h>
  24. #include <Atom/RHI.Reflect/MemoryUsage.h>
  25. #include <CryCommon/ISystem.h>
  26. #include <CryCommon/IConsole.h>
  27. namespace AZ::Render
  28. {
  29. AZ_CVAR(int, r_displayInfo, 1, [](const int& newDisplayInfoVal)->void
  30. {
  31. // Forward this event to the system component so it can update accordingly.
  32. // This callback only gets triggered by console commands, so this will not recurse.
  33. AtomBridge::AtomViewportInfoDisplayRequestBus::Broadcast(
  34. &AtomBridge::AtomViewportInfoDisplayRequestBus::Events::SetDisplayState,
  35. aznumeric_cast<AtomBridge::ViewportInfoDisplayState>(newDisplayInfoVal)
  36. );
  37. }, AZ::ConsoleFunctorFlags::DontReplicate,
  38. "Toggles debugging information display.\n"
  39. "Usage: r_displayInfo [0=off/1=show/2=enhanced/3=compact]"
  40. );
  41. AZ_CVAR(float, r_fpsCalcInterval, 1.0f, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  42. "The time period over which to calculate the framerate for r_displayInfo."
  43. );
  44. AZ_CVAR(
  45. AZ::Vector2, r_topRightBorderPadding, AZ::Vector2(-40.0f, 22.0f), nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  46. "The top right border padding for the viewport debug display text");
  47. void AtomViewportDisplayInfoSystemComponent::Reflect(AZ::ReflectContext* context)
  48. {
  49. if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
  50. {
  51. serialize->Class<AtomViewportDisplayInfoSystemComponent, AZ::Component>()
  52. ->Version(0)
  53. ;
  54. if (AZ::EditContext* ec = serialize->GetEditContext())
  55. {
  56. ec->Class<AtomViewportDisplayInfoSystemComponent>("Viewport Display Info", "Manages debug viewport information through r_displayInfo")
  57. ->ClassElement(Edit::ClassElements::EditorData, "")
  58. ->Attribute(Edit::Attributes::AutoExpand, true)
  59. ;
  60. }
  61. }
  62. }
  63. void AtomViewportDisplayInfoSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  64. {
  65. provided.push_back(AZ_CRC("ViewportDisplayInfoService"));
  66. }
  67. void AtomViewportDisplayInfoSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  68. {
  69. incompatible.push_back(AZ_CRC("ViewportDisplayInfoService"));
  70. }
  71. void AtomViewportDisplayInfoSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  72. {
  73. required.push_back(AZ_CRC("RPISystem", 0xf2add773));
  74. }
  75. void AtomViewportDisplayInfoSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
  76. {
  77. }
  78. void AtomViewportDisplayInfoSystemComponent::Activate()
  79. {
  80. AZ::Name apiName = AZ::RHI::Factory::Get().GetName();
  81. if (!apiName.IsEmpty())
  82. {
  83. m_rendererDescription = AZStd::string::format("Atom using %s RHI", apiName.GetCStr());
  84. }
  85. AZ::RPI::ViewportContextNotificationBus::Handler::BusConnect(
  86. AZ::RPI::ViewportContextRequests::Get()->GetDefaultViewportContextName());
  87. AZ::AtomBridge::AtomViewportInfoDisplayRequestBus::Handler::BusConnect();
  88. }
  89. void AtomViewportDisplayInfoSystemComponent::Deactivate()
  90. {
  91. AZ::AtomBridge::AtomViewportInfoDisplayRequestBus::Handler::BusDisconnect();
  92. AZ::RPI::ViewportContextNotificationBus::Handler::BusDisconnect();
  93. }
  94. AZ::RPI::ViewportContextPtr AtomViewportDisplayInfoSystemComponent::GetViewportContext() const
  95. {
  96. return AZ::RPI::ViewportContextRequests::Get()->GetDefaultViewportContext();
  97. }
  98. void AtomViewportDisplayInfoSystemComponent::DrawLine(AZStd::string_view line, AZ::Color color)
  99. {
  100. m_drawParams.m_color = color;
  101. AZ::Vector2 textSize = m_fontDrawInterface->GetTextSize(m_drawParams, line);
  102. m_fontDrawInterface->DrawScreenAlignedText2d(m_drawParams, line);
  103. m_drawParams.m_position.SetY(m_drawParams.m_position.GetY() + textSize.GetY() + m_lineSpacing);
  104. }
  105. void AtomViewportDisplayInfoSystemComponent::OnRenderTick()
  106. {
  107. if (!m_fontDrawInterface)
  108. {
  109. auto fontQueryInterface = AZ::Interface<AzFramework::FontQueryInterface>::Get();
  110. if (!fontQueryInterface)
  111. {
  112. return;
  113. }
  114. m_fontDrawInterface =
  115. fontQueryInterface->GetDefaultFontDrawInterface();
  116. }
  117. AZ::RPI::ViewportContextPtr viewportContext = GetViewportContext();
  118. if (!m_fontDrawInterface || !viewportContext || !viewportContext->GetRenderScene() ||
  119. !AZ::Interface<AzFramework::FontQueryInterface>::Get())
  120. {
  121. return;
  122. }
  123. m_fpsInterval = AZStd::chrono::seconds(static_cast<AZStd::sys_time_t>(r_fpsCalcInterval));
  124. UpdateFramerate();
  125. const AtomBridge::ViewportInfoDisplayState displayLevel = GetDisplayState();
  126. if (displayLevel == AtomBridge::ViewportInfoDisplayState::NoInfo)
  127. {
  128. return;
  129. }
  130. if (m_updateRootPassQuery)
  131. {
  132. if (auto currentPipeline = viewportContext->GetCurrentPipeline())
  133. {
  134. if (auto rootPass = currentPipeline->GetRootPass())
  135. {
  136. rootPass->SetPipelineStatisticsQueryEnabled(displayLevel != AtomBridge::ViewportInfoDisplayState::CompactInfo);
  137. m_updateRootPassQuery = false;
  138. }
  139. }
  140. }
  141. m_drawParams.m_drawViewportId = viewportContext->GetId();
  142. AZ::IConsole* console = AZ::Interface<AZ::IConsole>::Get();
  143. uint32_t r_width, r_resolutionMode = 0;
  144. console->GetCvarValue("r_resolutionMode", r_resolutionMode);
  145. if (r_resolutionMode > 0u)
  146. {
  147. //This ensures the debug text is always attached to the top right when r_resolutionMode is enabled
  148. console->GetCvarValue("r_width", r_width);
  149. m_drawParams.m_position = AZ::Vector3(static_cast<float>(r_width), 0.0f, 1.0f) +
  150. AZ::Vector3(r_topRightBorderPadding) * viewportContext->GetDpiScalingFactor();
  151. }
  152. else
  153. {
  154. auto viewportSize = viewportContext->GetViewportSize();
  155. m_drawParams.m_position = AZ::Vector3(static_cast<float>(viewportSize.m_width), 0.0f, 1.0f) +
  156. AZ::Vector3(r_topRightBorderPadding) * viewportContext->GetDpiScalingFactor();
  157. }
  158. m_drawParams.m_color = AZ::Colors::White;
  159. m_drawParams.m_scale = AZ::Vector2(BaseFontSize);
  160. m_drawParams.m_hAlign = AzFramework::TextHorizontalAlignment::Right;
  161. m_drawParams.m_monospace = false;
  162. m_drawParams.m_depthTest = false;
  163. m_drawParams.m_virtual800x600ScreenSize = false;
  164. m_drawParams.m_scaleWithWindow = false;
  165. m_drawParams.m_multiline = true;
  166. m_drawParams.m_lineSpacing = 0.5f;
  167. // Calculate line spacing based on the font's actual line height
  168. const float lineHeight = m_fontDrawInterface->GetTextSize(m_drawParams, " ").GetY();
  169. m_lineSpacing = lineHeight * m_drawParams.m_lineSpacing;
  170. DrawRendererInfo();
  171. if (displayLevel == AtomBridge::ViewportInfoDisplayState::FullInfo)
  172. {
  173. DrawCameraInfo();
  174. }
  175. if (displayLevel != AtomBridge::ViewportInfoDisplayState::CompactInfo)
  176. {
  177. DrawPassInfo();
  178. }
  179. DrawMemoryInfo();
  180. DrawFramerate();
  181. }
  182. AtomBridge::ViewportInfoDisplayState AtomViewportDisplayInfoSystemComponent::GetDisplayState() const
  183. {
  184. return aznumeric_cast<AtomBridge::ViewportInfoDisplayState>(r_displayInfo.operator int());
  185. }
  186. void AtomViewportDisplayInfoSystemComponent::SetDisplayState(AtomBridge::ViewportInfoDisplayState state)
  187. {
  188. r_displayInfo = aznumeric_cast<int>(state);
  189. AtomBridge::AtomViewportInfoDisplayNotificationBus::Broadcast(
  190. &AtomBridge::AtomViewportInfoDisplayNotificationBus::Events::OnViewportInfoDisplayStateChanged,
  191. state);
  192. m_updateRootPassQuery = true;
  193. }
  194. void AtomViewportDisplayInfoSystemComponent::DrawRendererInfo()
  195. {
  196. DrawLine(m_rendererDescription, AZ::Colors::Yellow);
  197. // resolution and MSAA state
  198. AZ::RPI::ViewportContextPtr viewportContext = GetViewportContext();
  199. const RHI::MultisampleState& multisampleState = RPI::RPISystemInterface::Get()->GetApplicationMultisampleState();
  200. AZ::RPI::ScenePtr pScene = viewportContext->GetRenderScene();
  201. AZStd::string defaultAA = "MSAA";
  202. bool hasAAMethod = false;
  203. if (pScene != nullptr)
  204. {
  205. AZ::RPI::RenderPipelinePtr pPipeline = pScene->GetDefaultRenderPipeline();
  206. AZ::RPI::AntiAliasingMode defaultAAMethod = pPipeline->GetActiveAAMethod();
  207. defaultAA = AZ::RPI::RenderPipeline::GetAAMethodNameByIndex(defaultAAMethod);
  208. hasAAMethod = (defaultAAMethod != AZ::RPI::AntiAliasingMode::MSAA && defaultAAMethod != AZ::RPI::AntiAliasingMode::Default);
  209. }
  210. auto resolutionStr =
  211. AZStd::string::format(
  212. "Resolution: %dx%d", viewportContext->GetViewportSize().m_width, viewportContext->GetViewportSize().m_height);
  213. auto msaaStr =
  214. multisampleState.m_samples > 1 ? AZStd::string::format("MSAA %dx", multisampleState.m_samples) : AZStd::string("NoMSAA");
  215. if (hasAAMethod)
  216. {
  217. if (multisampleState.m_samples > 1)
  218. {
  219. DrawLine(AZStd::string::format("%s (%s + %s)", resolutionStr.c_str(), defaultAA.c_str(), msaaStr.c_str()));
  220. }
  221. else
  222. {
  223. DrawLine(AZStd::string::format("%s (%s)", resolutionStr.c_str(), defaultAA.c_str()));
  224. }
  225. }
  226. else
  227. {
  228. DrawLine(AZStd::string::format("%s (%s)", resolutionStr.c_str(), msaaStr.c_str()));
  229. }
  230. if(viewportContext->GetCurrentPipeline()) // avoid VR crash on nullptr
  231. {
  232. DrawLine(AZStd::string::format("Render pipeline: %s", viewportContext->GetCurrentPipeline()->GetId().GetCStr()));
  233. }
  234. }
  235. void AtomViewportDisplayInfoSystemComponent::DrawCameraInfo()
  236. {
  237. AZ::RPI::ViewportContextPtr viewportContext = GetViewportContext();
  238. AZ::RPI::ViewPtr currentView = viewportContext->GetDefaultView();
  239. if (currentView == nullptr)
  240. {
  241. return;
  242. }
  243. AzFramework::CameraState cameraState;
  244. AzFramework::SetCameraClippingVolumeFromPerspectiveFovMatrixRH(cameraState, currentView->GetViewToClipMatrix());
  245. const AZ::Transform transform = currentView->GetCameraTransform();
  246. const AZ::Vector3 translation = transform.GetTranslation();
  247. const AZ::Vector3 rotation = transform.GetEulerDegrees();
  248. DrawLine(AZStd::string::format(
  249. "CamPos=%.2f %.2f %.2f Angl=%3.0f %3.0f %4.0f ZN=%.2f ZF=%.0f",
  250. translation.GetX(), translation.GetY(), translation.GetZ(),
  251. rotation.GetX(), rotation.GetY(), rotation.GetZ(),
  252. cameraState.m_nearClip, cameraState.m_farClip
  253. ));
  254. }
  255. void AtomViewportDisplayInfoSystemComponent::DrawPassInfo()
  256. {
  257. AZ::RPI::ViewportContextPtr viewportContext = GetViewportContext();
  258. if (!viewportContext->GetCurrentPipeline())
  259. {
  260. return;
  261. }
  262. auto rootPass = viewportContext->GetCurrentPipeline()->GetRootPass();
  263. RPI::PassSystemFrameStatistics passSystemFrameStatistics = AZ::RPI::PassSystemInterface::Get()->GetFrameStatistics();
  264. DrawLine(AZStd::string::format(
  265. "RenderPasses: %d",
  266. passSystemFrameStatistics.m_numRenderPassesExecuted
  267. ));
  268. DrawLine(AZStd::string::format(
  269. "Total Draw Item Count: %d Max Draw Items in a Pass: %d",
  270. passSystemFrameStatistics.m_totalDrawItemsRendered,
  271. passSystemFrameStatistics.m_maxDrawItemsRenderedInAPass
  272. ));
  273. }
  274. void AtomViewportDisplayInfoSystemComponent::UpdateFramerate()
  275. {
  276. auto currentTime = AZStd::chrono::steady_clock::now();
  277. while (!m_fpsHistory.empty() && (currentTime - m_fpsHistory.front()) > m_fpsInterval)
  278. {
  279. m_fpsHistory.pop_front();
  280. }
  281. m_fpsHistory.push_back(currentTime);
  282. }
  283. void AtomViewportDisplayInfoSystemComponent::DrawMemoryInfo()
  284. {
  285. RHI::RHISystemInterface* rhi = RHI::RHISystemInterface::Get();
  286. if (!rhi)
  287. {
  288. return;
  289. }
  290. RHI::RHIMemoryStatisticsInterface* rhiMemStats = RHI::RHIMemoryStatisticsInterface::Get();
  291. if (!rhiMemStats)
  292. {
  293. return;
  294. }
  295. const RHI::MemoryStatistics* stats = rhiMemStats->GetMemoryStatistics();
  296. if (!stats)
  297. {
  298. return;
  299. }
  300. // Accumulate total device memory pressure (reserved, resident)
  301. size_t deviceResident = 0;
  302. size_t deviceReserved = 0;
  303. for (const auto& pool : stats->m_pools)
  304. {
  305. deviceReserved += pool.m_memoryUsage.GetHeapMemoryUsage(RHI::HeapMemoryLevel::Device).m_totalResidentInBytes;
  306. deviceResident += pool.m_memoryUsage.GetHeapMemoryUsage(RHI::HeapMemoryLevel::Device).m_usedResidentInBytes;
  307. }
  308. // Query for available device memory
  309. float availableDeviceMemoryMB = 0.f;
  310. static constexpr size_t MB = 1u << 20;
  311. if (RHI::Device* device = rhi->GetDevice(); device)
  312. {
  313. const RHI::PhysicalDeviceDescriptor& deviceDesc = device->GetPhysicalDevice().GetDescriptor();
  314. availableDeviceMemoryMB =
  315. static_cast<float>(deviceDesc.m_heapSizePerLevel[static_cast<size_t>(RHI::HeapMemoryLevel::Device)]) / MB;
  316. }
  317. float deviceResidentMB = static_cast<float>(deviceResident) / MB;
  318. float deviceReservedMB = static_cast<float>(deviceReserved) / MB;
  319. AZ::Color deviceMemoryColor = AZ::Colors::White;
  320. if (availableDeviceMemoryMB != 0.f)
  321. {
  322. // Highlight text based on device memory pressure
  323. if (deviceResidentMB > 0.6f * availableDeviceMemoryMB)
  324. {
  325. deviceMemoryColor = AZ::Colors::Yellow;
  326. }
  327. else if (deviceResidentMB > 0.8f * availableDeviceMemoryMB)
  328. {
  329. deviceMemoryColor = AZ::Colors::Red;
  330. }
  331. }
  332. DrawLine(
  333. AZStd::string::format(
  334. "VRAM (resident/reserved): %.2f / %.2f MiB | %.2f available", deviceResidentMB, deviceReservedMB, availableDeviceMemoryMB),
  335. deviceMemoryColor);
  336. // RPI default StreamingImagePool usage
  337. Data::Instance<RPI::StreamingImagePool> streamingImagePool = RPI::ImageSystemInterface::Get()->GetSystemStreamingPool();
  338. const RHI::HeapMemoryUsage& imagePoolMemoryUsage = streamingImagePool->GetRHIPool()->GetHeapMemoryUsage(RHI::HeapMemoryLevel::Device);
  339. float imagePoolUsedAllocatedMB = static_cast<float>(imagePoolMemoryUsage.m_usedResidentInBytes) / MB;
  340. float imagePoolTotalAllocatedMB = static_cast<float>(imagePoolMemoryUsage.m_totalResidentInBytes) / MB;
  341. float imagePoolBudgetMB = static_cast<float>(imagePoolMemoryUsage.m_budgetInBytes) / MB;
  342. bool supportTiledImage = streamingImagePool->GetRHIPool()->SupportTiledImage();
  343. AZ::Color fontColor = AZ::Colors::White;
  344. if (streamingImagePool->IsMemoryLow())
  345. {
  346. fontColor = AZ::Colors::Red;
  347. }
  348. DrawLine(
  349. AZStd::string::format("Texture %s (used/allocated/budget): %.2f / %.2f/%.2f MiB", supportTiledImage?"Tiled":"", imagePoolUsedAllocatedMB, imagePoolTotalAllocatedMB, imagePoolBudgetMB),
  350. fontColor
  351. );
  352. }
  353. void AtomViewportDisplayInfoSystemComponent::DrawFramerate()
  354. {
  355. AZStd::optional<AZStd::chrono::steady_clock::time_point> lastTime;
  356. double minFPS = DBL_MAX;
  357. double maxFPS = 0;
  358. AZStd::chrono::duration<double> deltaTime;
  359. for (const auto& time : m_fpsHistory)
  360. {
  361. if (lastTime.has_value())
  362. {
  363. deltaTime = time - lastTime.value();
  364. double fps = AZStd::chrono::seconds(1) / deltaTime;
  365. minFPS = AZStd::min(minFPS, fps);
  366. maxFPS = AZStd::max(maxFPS, fps);
  367. }
  368. lastTime = time;
  369. }
  370. double averageFPS = 0;
  371. double averageFrameMs = 0;
  372. if (m_fpsHistory.size() > 1)
  373. {
  374. deltaTime = m_fpsHistory.back() - m_fpsHistory.front();
  375. averageFPS = AZStd::chrono::seconds(m_fpsHistory.size()) / deltaTime;
  376. averageFrameMs = 1000.0f/averageFPS;
  377. }
  378. const double frameIntervalSeconds = m_fpsInterval.count();
  379. auto ClampedFloatDisplay = [](double value, const char* format) -> AZStd::string
  380. {
  381. constexpr float upperLimit = 10000.0f;
  382. return value > upperLimit ? "inf" : AZStd::string::format(format, value);
  383. };
  384. DrawLine(
  385. AZStd::string::format(
  386. "FPS %s [%s..%s], %sms/frame, avg over %.1fs",
  387. ClampedFloatDisplay(averageFPS, "%.1f").c_str(),
  388. ClampedFloatDisplay(minFPS, "%.0f").c_str(),
  389. ClampedFloatDisplay(maxFPS, "%.0f").c_str(),
  390. ClampedFloatDisplay(averageFrameMs, "%.1f").c_str(),
  391. frameIntervalSeconds),
  392. AZ::Colors::Yellow);
  393. }
  394. } // namespace AZ::Render