SponzaBenchmarkComponent.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  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 <SponzaBenchmarkComponent.h>
  9. #include <Atom/RHI/Device.h>
  10. #include <Atom/RHI/Factory.h>
  11. #include <Atom/Feature/ImGui/SystemBus.h>
  12. #include <Atom/Feature/Utils/FrameCaptureBus.h>
  13. #include <Atom/RPI.Reflect/Asset/AssetUtils.h>
  14. #include <Atom/RPI.Reflect/Model/ModelAsset.h>
  15. #include <AzCore/Serialization/SerializeContext.h>
  16. #include <AzCore/Serialization/Utils.h>
  17. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  18. #include <AzCore/std/sort.h>
  19. #include <AzFramework/IO/LocalFileIO.h>
  20. #include <AzFramework/Components/TransformComponent.h>
  21. #include <Utils/Utils.h>
  22. #include <SampleComponentManager.h>
  23. #include <SampleComponentConfig.h>
  24. #include <ctime>
  25. #include <RHI/BasicRHIComponent.h>
  26. namespace AtomSampleViewer
  27. {
  28. void SponzaBenchmarkComponent::Reflect(AZ::ReflectContext* context)
  29. {
  30. if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  31. {
  32. serializeContext->Class<SponzaBenchmarkComponent, AZ::Component>()
  33. ->Version(0)
  34. ;
  35. }
  36. SponzaBenchmarkComponent::LoadBenchmarkData::Reflect(context);
  37. SponzaBenchmarkComponent::LoadBenchmarkData::FileLoadedData::Reflect(context);
  38. SponzaBenchmarkComponent::RunBenchmarkData::Reflect(context);
  39. }
  40. void SponzaBenchmarkComponent::LoadBenchmarkData::Reflect(AZ::ReflectContext* context)
  41. {
  42. if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  43. {
  44. serializeContext->Class<SponzaBenchmarkComponent::LoadBenchmarkData>()
  45. ->Version(0)
  46. ->Field("Name", &SponzaBenchmarkComponent::LoadBenchmarkData::m_name)
  47. ->Field("TimeInSeconds", &SponzaBenchmarkComponent::LoadBenchmarkData::m_timeInSeconds)
  48. ->Field("TotalMBLoaded", &SponzaBenchmarkComponent::LoadBenchmarkData::m_totalMBLoaded)
  49. ->Field("MB/s", &SponzaBenchmarkComponent::LoadBenchmarkData::m_mbPerSec)
  50. ->Field("# of Files Loaded", &SponzaBenchmarkComponent::LoadBenchmarkData::m_numFilesLoaded)
  51. ->Field("FilesLoaded", &SponzaBenchmarkComponent::LoadBenchmarkData::m_filesLoaded)
  52. ;
  53. }
  54. }
  55. void SponzaBenchmarkComponent::LoadBenchmarkData::FileLoadedData::Reflect(AZ::ReflectContext* context)
  56. {
  57. if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  58. {
  59. serializeContext->Class<SponzaBenchmarkComponent::LoadBenchmarkData::FileLoadedData>()
  60. ->Version(0)
  61. ->Field("RelativePath", &SponzaBenchmarkComponent::LoadBenchmarkData::FileLoadedData::m_relativePath)
  62. ->Field("BytesLoaded", &SponzaBenchmarkComponent::LoadBenchmarkData::FileLoadedData::m_bytesLoaded)
  63. ;
  64. }
  65. }
  66. void SponzaBenchmarkComponent::RunBenchmarkData::Reflect(AZ::ReflectContext* context)
  67. {
  68. if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  69. {
  70. serializeContext->Class<SponzaBenchmarkComponent::RunBenchmarkData>()
  71. ->Version(0)
  72. ->Field("Name", &SponzaBenchmarkComponent::RunBenchmarkData::m_name)
  73. ->Field("FrameCount", &SponzaBenchmarkComponent::RunBenchmarkData::m_frameCount)
  74. ->Field("TimeToFirstFrame", &SponzaBenchmarkComponent::RunBenchmarkData::m_timeToFirstFrame)
  75. ->Field("TimeInSeconds", &SponzaBenchmarkComponent::RunBenchmarkData::m_timeInSeconds)
  76. ->Field("AverageFrameTime", &SponzaBenchmarkComponent::RunBenchmarkData::m_averageFrameTime)
  77. ->Field("50% of FrameTimes Under", &SponzaBenchmarkComponent::RunBenchmarkData::m_50pFramesUnder)
  78. ->Field("90% of FrameTimes Under", &SponzaBenchmarkComponent::RunBenchmarkData::m_90pFramesUnder)
  79. ->Field("MinFrameTime", &SponzaBenchmarkComponent::RunBenchmarkData::m_minFrameTime)
  80. ->Field("MaxFrameTime", &SponzaBenchmarkComponent::RunBenchmarkData::m_maxFrameTime)
  81. ->Field("AverageFrameRate", &SponzaBenchmarkComponent::RunBenchmarkData::m_averageFrameRate)
  82. ->Field("MinFrameRate", &SponzaBenchmarkComponent::RunBenchmarkData::m_minFrameRate)
  83. ->Field("MaxFrameRate", &SponzaBenchmarkComponent::RunBenchmarkData::m_maxFrameRate)
  84. ;
  85. }
  86. }
  87. void SponzaBenchmarkComponent::Activate()
  88. {
  89. auto traceLevel = AZ::RPI::AssetUtils::TraceLevel::Assert;
  90. m_sponzaInteriorAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>
  91. ("Objects/Sponza.azmodel", traceLevel);
  92. m_sponzaInteriorMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ m_sponzaInteriorAsset });
  93. // rotate the entities 180 degrees about Z (the vertical axis)
  94. // This makes it consistent with how it was positioned in the world when the world was Y-up.
  95. GetMeshFeatureProcessor()->SetTransform(m_sponzaInteriorMeshHandle, AZ::Transform::CreateRotationZ(AZ::Constants::Pi));
  96. BenchmarkLoadStart();
  97. // Capture screenshots on specific frames.
  98. const AzFramework::CommandLine* commandLine = nullptr;
  99. AzFramework::ApplicationRequests::Bus::BroadcastResult(commandLine, &AzFramework::ApplicationRequests::GetCommandLine);
  100. static const char* screenshotFlagName = "screenshot";
  101. if (commandLine && commandLine->HasSwitch(screenshotFlagName))
  102. {
  103. size_t capturesCount = commandLine->GetNumSwitchValues(screenshotFlagName);
  104. for (size_t i = 0; i < capturesCount; ++i)
  105. {
  106. AZStd::string frameNumberStr = commandLine->GetSwitchValue(screenshotFlagName, i);
  107. uint64_t frameNumber = strtoull(frameNumberStr.begin(), nullptr, 0);
  108. if (frameNumber > 0)
  109. {
  110. m_framesToCapture.push_back(frameNumber);
  111. }
  112. }
  113. AZStd::sort(m_framesToCapture.begin(), m_framesToCapture.end(), AZStd::greater<uint64_t>());
  114. }
  115. AZ::TickBus::Handler::BusConnect();
  116. auto settingsRegistry = AZ::SettingsRegistry::Get();
  117. AZ::IO::Path writableStoragePath;
  118. settingsRegistry->Get(writableStoragePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_DevWriteStorage);
  119. m_screenshotFolder = writableStoragePath / "Screenshots";
  120. m_directionalLightFeatureProcessor = m_scene->GetFeatureProcessor<AZ::Render::DirectionalLightFeatureProcessorInterface>();
  121. const auto handle = m_directionalLightFeatureProcessor->AcquireLight();
  122. AZ::Vector3 sunDirection = AZ::Vector3(1.0f, -1.0f, -3.0f);
  123. sunDirection.Normalize();
  124. m_directionalLightFeatureProcessor->SetDirection(handle, sunDirection);
  125. AZ::Render::PhotometricColor<AZ::Render::PhotometricUnit::Lux> sunColor(AZ::Color(1.0f, 1.0f, 0.97f, 1.0f) * 20.f);
  126. m_directionalLightFeatureProcessor->SetRgbIntensity(handle, sunColor);
  127. m_directionalLightFeatureProcessor->SetCascadeCount(handle, 4);
  128. m_directionalLightFeatureProcessor->SetShadowmapSize(handle, AZ::Render::ShadowmapSizeNamespace::ShadowmapSize::Size2048);
  129. m_directionalLightFeatureProcessor->SetViewFrustumCorrectionEnabled(handle, true);
  130. m_directionalLightFeatureProcessor->SetShadowFilterMethod(handle, AZ::Render::ShadowFilterMethod::EsmPcf);
  131. m_directionalLightFeatureProcessor->SetShadowFarClipDistance(handle, 100.0f);
  132. m_directionalLightFeatureProcessor->SetFilteringSampleCount(handle, 16);
  133. m_directionalLightFeatureProcessor->SetGroundHeight(handle, 0.f);
  134. m_directionalLightHandle = handle;
  135. // Enable physical sky
  136. m_skyboxFeatureProcessor = AZ::RPI::Scene::GetFeatureProcessorForEntityContextId<AZ::Render::SkyBoxFeatureProcessorInterface>(GetEntityContextId());
  137. AZ_Assert(m_skyboxFeatureProcessor, "SponzaBenchmarkComponent unable to find SkyBoxFeatureProcessorInterface.");
  138. m_skyboxFeatureProcessor->SetSkyboxMode(AZ::Render::SkyBoxMode::PhysicalSky);
  139. m_skyboxFeatureProcessor->Enable(true);
  140. float azimuth = atan2(-sunDirection.GetZ(), -sunDirection.GetX());
  141. float altitude = asin(-sunDirection.GetY() / sunDirection.GetLength());
  142. m_skyboxFeatureProcessor->SetSunPosition(azimuth, altitude);
  143. // Create IBL
  144. m_defaultIbl.Init(m_scene);
  145. m_defaultIbl.SetExposure(-3.0f);
  146. }
  147. void SponzaBenchmarkComponent::Deactivate()
  148. {
  149. AZ::TickBus::Handler::BusDisconnect();
  150. // If there are any assets that haven't finished loading yet, and thus haven't been disconnected, disconnect now.
  151. AZ::Data::AssetBus::MultiHandler::BusDisconnect();
  152. m_defaultIbl.Reset();
  153. m_skyboxFeatureProcessor->Enable(false);
  154. GetMeshFeatureProcessor()->ReleaseMesh(m_sponzaInteriorMeshHandle);
  155. m_directionalLightFeatureProcessor->ReleaseLight(m_directionalLightHandle);
  156. m_directionalLightFeatureProcessor = nullptr;
  157. }
  158. void SponzaBenchmarkComponent::OnTick(float deltaTime, AZ::ScriptTimePoint timePoint)
  159. {
  160. AZ_PROFILE_DATAPOINT(AzRender, deltaTime, "Frame Time");
  161. // Camera Configuration
  162. {
  163. Camera::Configuration config;
  164. Camera::CameraRequestBus::EventResult(
  165. config,
  166. GetCameraEntityId(),
  167. &Camera::CameraRequestBus::Events::GetCameraConfiguration);
  168. m_directionalLightFeatureProcessor->SetCameraConfiguration(
  169. m_directionalLightHandle,
  170. config);
  171. }
  172. // Camera Transform
  173. {
  174. AZ::Transform transform = AZ::Transform::CreateIdentity();
  175. AZ::TransformBus::EventResult(
  176. transform,
  177. GetCameraEntityId(),
  178. &AZ::TransformBus::Events::GetWorldTM);
  179. m_directionalLightFeatureProcessor->SetCameraTransform(
  180. m_directionalLightHandle, transform);
  181. }
  182. m_currentTimePointInSeconds = timePoint.GetSeconds();
  183. if (m_sponzaInteriorLoaded == false)
  184. {
  185. DisplayLoadingDialog();
  186. }
  187. else
  188. {
  189. if (m_frameCount >= m_exteriorPath.back().m_framePoint)
  190. {
  191. if (m_endBenchmarkCapture)
  192. {
  193. BenchmarkRunEnd();
  194. m_endBenchmarkCapture = false;
  195. }
  196. DisplayResults();
  197. }
  198. else
  199. {
  200. if (m_startBenchmarkCapture)
  201. {
  202. BenchmarkRunStart();
  203. m_startBenchmarkCapture = false;
  204. }
  205. bool screenshotRequested = false;
  206. // Check if a screenshot was requested for this frame. Loop in case there were multiple requests for the same frame.
  207. while (m_framesToCapture.size() > 0 && m_frameCount == m_framesToCapture.back())
  208. {
  209. screenshotRequested = true;
  210. m_framesToCapture.pop_back();
  211. }
  212. if (screenshotRequested)
  213. {
  214. AZ::IO::Path filePath = m_screenshotFolder / AZStd::string::format("screenshot_sponza_%llu.dds", m_frameCount);
  215. AZ::Render::FrameCaptureRequestBus::Broadcast(&AZ::Render::FrameCaptureRequestBus::Events::CaptureScreenshot, filePath.Native());
  216. }
  217. if (m_frameCount == 1)
  218. {
  219. m_timeToFirstFrame = m_currentTimePointInSeconds - m_benchmarkStartTimePoint;
  220. }
  221. CollectRunBenchmarkData(deltaTime, timePoint);
  222. // Find current working camera point
  223. size_t currentCameraPointIndex = 0;
  224. while (m_exteriorPath[currentCameraPointIndex + 1].m_framePoint < m_frameCount && currentCameraPointIndex < (m_exteriorPath.size() - 2))
  225. {
  226. currentCameraPointIndex++;
  227. }
  228. const CameraPathPoint& cameraPathPoint = m_exteriorPath[currentCameraPointIndex];
  229. const CameraPathPoint& nextCameraPathPoint = m_exteriorPath[currentCameraPointIndex + 1];
  230. // Lerp to get intermediate position
  231. {
  232. const float percentToNextPoint = static_cast<float>((m_frameCount - cameraPathPoint.m_framePoint)) / static_cast<float>(nextCameraPathPoint.m_framePoint - cameraPathPoint.m_framePoint);
  233. const AZ::Vector3 position = cameraPathPoint.m_position.Lerp(nextCameraPathPoint.m_position, percentToNextPoint);
  234. const AZ::Vector3 target = cameraPathPoint.m_target.Lerp(nextCameraPathPoint.m_target, percentToNextPoint);
  235. const AZ::Transform transform = AZ::Transform::CreateLookAt(position, target, AZ::Transform::Axis::YPositive);
  236. // Apply transform
  237. AZ::TransformBus::Event(GetCameraEntityId(), &AZ::TransformBus::Events::SetWorldTM, transform);
  238. }
  239. m_frameCount++;
  240. }
  241. }
  242. }
  243. void SponzaBenchmarkComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
  244. {
  245. if (asset.GetId() == m_sponzaInteriorAsset.GetId())
  246. {
  247. m_sponzaInteriorLoaded = true;
  248. }
  249. // Benchmark the count and total size of files loaded
  250. static double invBytesToMB = 1 / (1024.0 * 1024.0);
  251. AZ::Data::AssetInfo info;
  252. AZ::Data::AssetCatalogRequestBus::BroadcastResult(info, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, asset.GetId());
  253. LoadBenchmarkData::FileLoadedData fileLoadedData;
  254. fileLoadedData.m_relativePath = info.m_relativePath;
  255. fileLoadedData.m_bytesLoaded = info.m_sizeBytes;
  256. m_currentLoadBenchmarkData.m_totalMBLoaded += static_cast<double>(info.m_sizeBytes) * invBytesToMB;
  257. m_currentLoadBenchmarkData.m_filesLoaded.emplace_back(AZStd::move(fileLoadedData));
  258. if (m_sponzaInteriorLoaded)
  259. {
  260. BenchmarkLoadEnd();
  261. }
  262. AZ_PROFILE_DATAPOINT(AzRender, m_currentLoadBenchmarkData.m_totalMBLoaded, "MB Loaded Off Disk");
  263. }
  264. void SponzaBenchmarkComponent::BenchmarkLoadStart()
  265. {
  266. AZStd::vector<AZ::Data::AssetId> unloadedAssetsInCatalog;
  267. unloadedAssetsInCatalog.push_back(m_sponzaInteriorAsset.GetId());
  268. // Get a vector of all assets that haven't been loaded
  269. auto startCB = []() {};
  270. auto enumerateCB = [&unloadedAssetsInCatalog](const AZ::Data::AssetId id, [[maybe_unused]] const AZ::Data::AssetInfo& assetInfo)
  271. {
  272. // Don't "get" the asset and load it, we just want to query its status
  273. AZ::Data::Asset<AZ::Data::AssetData> asset = AZ::Data::AssetManager::Instance().FindAsset(id, AZ::Data::AssetLoadBehavior::PreLoad);
  274. if (asset.GetData() == nullptr || asset.GetStatus() == AZ::Data::AssetData::AssetStatus::NotLoaded)
  275. {
  276. unloadedAssetsInCatalog.push_back(id);
  277. }
  278. };
  279. auto endCB = []() {};
  280. AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::EnumerateAssets, startCB, enumerateCB, endCB);
  281. // Connect specifically to all assets in the catalog that haven't been loaded yet
  282. // Otherwise if we just connect to *every* asset the ones that have already been loaded
  283. // will still trigger OnAssetReady events
  284. for (const AZ::Data::AssetId& id : unloadedAssetsInCatalog)
  285. {
  286. // OnAssetReady will keep track of number and size of all the files that are loaded during this benchmark
  287. AZ::Data::AssetBus::MultiHandler::BusConnect(id);
  288. }
  289. m_currentLoadBenchmarkData = LoadBenchmarkData();
  290. m_currentLoadBenchmarkData.m_name = "Sponza Load";
  291. Utils::ToggleRadTMCapture();
  292. m_benchmarkStartTimePoint = static_cast<double>(AZStd::GetTimeUTCMilliSecond());
  293. }
  294. void SponzaBenchmarkComponent::FinalizeLoadBenchmarkData()
  295. {
  296. m_currentLoadBenchmarkData.m_timeInSeconds = (static_cast<double>(AZStd::GetTimeUTCMilliSecond()) - m_benchmarkStartTimePoint) / 1000.0f;
  297. m_currentLoadBenchmarkData.m_mbPerSec = m_currentLoadBenchmarkData.m_totalMBLoaded / m_currentLoadBenchmarkData.m_timeInSeconds;
  298. m_currentLoadBenchmarkData.m_numFilesLoaded = m_currentLoadBenchmarkData.m_filesLoaded.size();
  299. }
  300. void SponzaBenchmarkComponent::BenchmarkLoadEnd()
  301. {
  302. AZ::Data::AssetBus::MultiHandler::BusDisconnect();
  303. Utils::ToggleRadTMCapture();
  304. FinalizeLoadBenchmarkData();
  305. const AZStd::string unresolvedPath = AZStd::string::format("@user@/benchmarks/sponzaLoad_%ld.xml", time(0));
  306. char sponzaLoadBenchmarkDataFilePath[AZ_MAX_PATH_LEN] = { 0 };
  307. AZ::IO::FileIOBase::GetInstance()->ResolvePath(unresolvedPath.c_str(), sponzaLoadBenchmarkDataFilePath, AZ_MAX_PATH_LEN);
  308. if (!AZ::Utils::SaveObjectToFile(sponzaLoadBenchmarkDataFilePath, AZ::DataStream::ST_XML, &m_currentLoadBenchmarkData))
  309. {
  310. AZ_Error("SponzaBenchmarkComponent", false, "Failed to save sponza benchmark load data to file %s", sponzaLoadBenchmarkDataFilePath);
  311. }
  312. }
  313. void SponzaBenchmarkComponent::BenchmarkRunStart()
  314. {
  315. m_currentRunBenchmarkData = RunBenchmarkData();
  316. m_currentRunBenchmarkData.m_name = "Sponza Run";
  317. Utils::ToggleRadTMCapture();
  318. m_benchmarkStartTimePoint = m_currentTimePointInSeconds;
  319. }
  320. void SponzaBenchmarkComponent::CollectRunBenchmarkData(float deltaTime, AZ::ScriptTimePoint timePoint)
  321. {
  322. const float dtInMS = deltaTime * 1000.0f;
  323. m_currentRunBenchmarkData.m_frameTimes.push_back(dtInMS);
  324. m_currentRunBenchmarkData.m_frameCount++;
  325. m_currentRunBenchmarkData.m_timeInSeconds = timePoint.GetSeconds() - m_benchmarkStartTimePoint;
  326. if (dtInMS < m_currentRunBenchmarkData.m_minFrameTime)
  327. {
  328. m_currentRunBenchmarkData.m_minFrameTime = dtInMS;
  329. }
  330. if (dtInMS > m_currentRunBenchmarkData.m_maxFrameTime)
  331. {
  332. m_currentRunBenchmarkData.m_maxFrameTime = dtInMS;
  333. }
  334. if (m_frameCount == 1)
  335. {
  336. m_currentRunBenchmarkData.m_timeToFirstFrame = m_timeToFirstFrame;
  337. }
  338. }
  339. void SponzaBenchmarkComponent::FinalizeRunBenchmarkData()
  340. {
  341. m_currentRunBenchmarkData.m_averageFrameTime =
  342. (m_currentRunBenchmarkData.m_timeInSeconds / m_currentRunBenchmarkData.m_frameCount) * 1000.0f;
  343. // Need to sort the frame times so we can find the 50th and 90th percentile
  344. AZStd::vector<float> sortedFrameTimes = m_currentRunBenchmarkData.m_frameTimes;
  345. AZStd::sort(sortedFrameTimes.begin(), sortedFrameTimes.end());
  346. const size_t frameTimeCount = m_currentRunBenchmarkData.m_frameTimes.size();
  347. const bool evenNumberOfFrames = frameTimeCount & 1;
  348. if (!evenNumberOfFrames)
  349. {
  350. const size_t medianIndex = frameTimeCount / 2;
  351. m_currentRunBenchmarkData.m_50pFramesUnder = sortedFrameTimes[medianIndex];
  352. }
  353. else
  354. {
  355. const size_t medianIndex1 = frameTimeCount / 2;
  356. const size_t medianIndex2 = medianIndex1 + 1;
  357. const float median = (sortedFrameTimes[medianIndex1] + sortedFrameTimes[medianIndex2]) / 2.0f;
  358. m_currentRunBenchmarkData.m_50pFramesUnder = median;
  359. }
  360. const float p90Indexf = ceilf(static_cast<float>(frameTimeCount) * .9f);
  361. const size_t p90Index = static_cast<size_t>(p90Indexf);
  362. m_currentRunBenchmarkData.m_90pFramesUnder = sortedFrameTimes[p90Index];
  363. m_currentRunBenchmarkData.m_timeToFirstFrame = m_timeToFirstFrame;
  364. float averageFrameRate = 0.0f;
  365. for (auto frame : m_currentRunBenchmarkData.m_frameTimes)
  366. {
  367. float frameRate = 1.0f / (frame / 1000);
  368. if (frameRate > m_currentRunBenchmarkData.m_maxFrameRate)
  369. {
  370. m_currentRunBenchmarkData.m_maxFrameRate = frameRate;
  371. }
  372. if (frameRate < m_currentRunBenchmarkData.m_minFrameRate)
  373. {
  374. m_currentRunBenchmarkData.m_minFrameRate = frameRate;
  375. }
  376. m_currentRunBenchmarkData.m_frameRates.push_back(frameRate);
  377. averageFrameRate += frameRate;
  378. }
  379. m_currentRunBenchmarkData.m_averageFrameRate = averageFrameRate / m_currentRunBenchmarkData.m_frameRates.size();
  380. }
  381. void SponzaBenchmarkComponent::BenchmarkRunEnd()
  382. {
  383. Utils::ToggleRadTMCapture();
  384. FinalizeRunBenchmarkData();
  385. const AZStd::string unresolvedPath = AZStd::string::format("@user@/benchmarks/sponzaRun_%ld.xml", time(0));
  386. char sponzaRunBenchmarkDataFilePath[AZ_MAX_PATH_LEN] = { 0 };
  387. AZ::IO::FileIOBase::GetInstance()->ResolvePath(unresolvedPath.c_str(), sponzaRunBenchmarkDataFilePath, AZ_MAX_PATH_LEN);
  388. if (!AZ::Utils::SaveObjectToFile(sponzaRunBenchmarkDataFilePath, AZ::DataStream::ST_XML, &m_currentRunBenchmarkData))
  389. {
  390. AZ_Error("SponzaBenchmarkComponent", false, "Failed to save sponza benchmark run data to file %s", sponzaRunBenchmarkDataFilePath);
  391. }
  392. }
  393. void SponzaBenchmarkComponent::DisplayLoadingDialog()
  394. {
  395. const ImGuiWindowFlags windowFlags =
  396. ImGuiWindowFlags_NoCollapse |
  397. ImGuiWindowFlags_NoResize |
  398. ImGuiWindowFlags_NoMove;
  399. AzFramework::NativeWindowHandle windowHandle = nullptr;
  400. AzFramework::WindowSystemRequestBus::BroadcastResult(
  401. windowHandle,
  402. &AzFramework::WindowSystemRequestBus::Events::GetDefaultWindowHandle);
  403. AzFramework::WindowSize windowSize;
  404. AzFramework::WindowRequestBus::EventResult(
  405. windowSize,
  406. windowHandle,
  407. &AzFramework::WindowRequestBus::Events::GetClientAreaSize);
  408. const float loadingWindowWidth = 225.0f;
  409. const float loadingWindowHeight = 65.0f;
  410. const float halfLoadingWindowWidth = loadingWindowWidth * 0.5f;
  411. const float halfLoadingWindowHeight = loadingWindowHeight * 0.5f;
  412. const float halfWindowWidth = windowSize.m_width * 0.5f;
  413. const float halfWindowHeight = windowSize.m_height * 0.5f;
  414. ImGui::SetNextWindowPos(ImVec2(halfWindowWidth - halfLoadingWindowWidth, halfWindowHeight - halfLoadingWindowHeight));
  415. ImGui::SetNextWindowSize(ImVec2(loadingWindowWidth, loadingWindowHeight));
  416. if (ImGui::Begin("Loading", nullptr, windowFlags))
  417. {
  418. const size_t loadingIndicatorSize = static_cast<size_t>(fmod(m_currentTimePointInSeconds, 2.0) / 0.5);
  419. char* loadingIndicator = new char[loadingIndicatorSize + 1];
  420. memset(loadingIndicator, '.', loadingIndicatorSize);
  421. loadingIndicator[loadingIndicatorSize] = '\0';
  422. if (m_sponzaInteriorLoaded)
  423. {
  424. ImGui::Text("Sponza Interior: Loaded!");
  425. }
  426. else
  427. {
  428. ImGui::Text("Sponza Interior: Loading%s", loadingIndicator);
  429. }
  430. delete[] loadingIndicator;
  431. }
  432. ImGui::End();
  433. }
  434. void SponzaBenchmarkComponent::DisplayResults()
  435. {
  436. AzFramework::NativeWindowHandle windowHandle = nullptr;
  437. AzFramework::WindowSystemRequestBus::BroadcastResult(
  438. windowHandle,
  439. &AzFramework::WindowSystemRequestBus::Events::GetDefaultWindowHandle);
  440. AzFramework::WindowSize windowSize;
  441. AzFramework::WindowRequestBus::EventResult(
  442. windowSize,
  443. windowHandle,
  444. &AzFramework::WindowRequestBus::Events::GetClientAreaSize);
  445. const float halfWindowWidth = windowSize.m_width * 0.5f;
  446. const float halfWindowHeight = windowSize.m_height * 0.5f;
  447. const float frameTimeWindowWidth = static_cast<float>(windowSize.m_width);
  448. const float frameTimeWindowHeight = 200.0f;
  449. if (m_firstResultsDisplay)
  450. {
  451. ImGui::SetNextWindowPos(ImVec2(0.0f, windowSize.m_height - frameTimeWindowHeight));
  452. ImGui::SetNextWindowSize(ImVec2(frameTimeWindowWidth, frameTimeWindowHeight));
  453. }
  454. if (ImGui::Begin("Frame Times"))
  455. {
  456. ImGui::PlotHistogram("##FrameTimes",
  457. m_currentRunBenchmarkData.m_frameTimes.data(),
  458. static_cast<int32_t>(m_currentRunBenchmarkData.m_frameTimes.size()),
  459. 0, nullptr,
  460. 0,
  461. m_currentRunBenchmarkData.m_90pFramesUnder * 2.0f,
  462. ImGui::GetContentRegionAvail());
  463. }
  464. ImGui::End();
  465. const float resultWindowWidth = 500.0f;
  466. const float resultWindowHeight = 250.0f;
  467. const float halfResultWindowWidth = resultWindowWidth * 0.5f;
  468. const float halfResultWindowHeight = resultWindowHeight * 0.5f;
  469. if (m_firstResultsDisplay)
  470. {
  471. ImGui::SetNextWindowPos(ImVec2(halfWindowWidth - halfResultWindowWidth,
  472. halfWindowHeight - halfResultWindowHeight));
  473. ImGui::SetNextWindowSize(ImVec2(resultWindowWidth, resultWindowHeight));
  474. m_firstResultsDisplay = false;
  475. }
  476. if (ImGui::Begin("Results"))
  477. {
  478. ImGui::Columns(2);
  479. ImGui::Text("Load");
  480. ImGui::NextColumn();
  481. ImGui::Text("Run");
  482. ImGui::Separator();
  483. ImGui::NextColumn();
  484. ImGui::Text("File Count: %llu", m_currentLoadBenchmarkData.m_numFilesLoaded);
  485. ImGui::Text("Total Time: %f seconds", m_currentLoadBenchmarkData.m_timeInSeconds);
  486. ImGui::Text("Loaded: %f MB", m_currentLoadBenchmarkData.m_totalMBLoaded);
  487. ImGui::Text("Throughput: %f MB/s", m_currentLoadBenchmarkData.m_mbPerSec);
  488. ImGui::NextColumn();
  489. ImGui::Text("Frame Count: %llu", m_currentRunBenchmarkData.m_frameCount);
  490. ImGui::Text("Total Time: %f seconds", m_currentRunBenchmarkData.m_timeInSeconds);
  491. ImGui::Text("Time to First Frame: %f ms", m_currentRunBenchmarkData.m_timeToFirstFrame);
  492. ImGui::Text("Average Frame Time: %f ms", m_currentRunBenchmarkData.m_averageFrameTime);
  493. ImGui::Text("50%% Frames Under: %f ms", m_currentRunBenchmarkData.m_50pFramesUnder);
  494. ImGui::Text("90%% Frames Under: %f ms", m_currentRunBenchmarkData.m_90pFramesUnder);
  495. ImGui::Text("Min Frame Time: %f ms", m_currentRunBenchmarkData.m_minFrameTime);
  496. ImGui::Text("Max Frame Time: %f ms", m_currentRunBenchmarkData.m_maxFrameTime);
  497. ImGui::Text("Average Frame Rate: %f Hz", m_currentRunBenchmarkData.m_averageFrameRate);
  498. ImGui::Text("Min Frame Rate: %f Hz", m_currentRunBenchmarkData.m_minFrameRate);
  499. ImGui::Text("Max Frame Rate: %f Hz", m_currentRunBenchmarkData.m_maxFrameRate);
  500. }
  501. ImGui::Columns(1);
  502. ImGui::End();
  503. }
  504. } // namespace AtomSampleViewer