BistroBenchmarkComponent.cpp 29 KB

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