SponzaBenchmarkComponent.cpp 27 KB

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