AtomSampleViewerApplication.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  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 <AtomSampleViewerApplication.h>
  9. #include <SampleComponentManagerBus.h>
  10. #include <AzCore/Component/ComponentApplicationLifecycle.h>
  11. #include <AzCore/PlatformIncl.h>
  12. #include <AzCore/Component/ComponentApplication.h>
  13. #include <AzCore/Debug/Trace.h>
  14. #include <AzCore/Time/ITime.h>
  15. #include <AzFramework/Asset/AssetSystemBus.h>
  16. #include <AzFramework/Asset/AssetProcessorMessages.h>
  17. #include <AzFramework/IO/LocalFileIO.h>
  18. #include <AzFramework/Network/AssetProcessorConnection.h>
  19. #include <AzFramework/StringFunc/StringFunc.h>
  20. #include <Atom/RPI.Public/RPISystemInterface.h>
  21. #include <AzCore/Math/Random.h>
  22. #include <AzCore/Script/ScriptAsset.h>
  23. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  24. #include <AzCore/Utils/Utils.h>
  25. #include <AzGameFramework/Application/GameApplication.h>
  26. namespace AtomSampleViewer
  27. {
  28. //! This function returns the build system target name of "AtomSampleViewerStandalone"
  29. AZStd::string_view GetBuildTargetName()
  30. {
  31. #if !defined (LY_CMAKE_TARGET)
  32. #error "LY_CMAKE_TARGET must be defined in order to add this source file to a CMake executable target"
  33. #endif
  34. return AZStd::string_view{ LY_CMAKE_TARGET };
  35. }
  36. AtomSampleViewerApplication::AtomSampleViewerApplication()
  37. {
  38. AZ::Debug::Trace::Instance().Init();
  39. #if defined(AZ_DEBUG_BUILD) || defined(AZ_PROFILE_BUILD)
  40. AZ::Debug::Trace::SetAssertVerbosityLevel(2); //enable break on AZ_Assert()
  41. #endif
  42. m_startupLogSink = AZStd::make_unique<AZStd::vector<LogMessage>>();
  43. AZ::Debug::TraceMessageBus::Handler::BusConnect();
  44. AtomSampleViewerRequestsBus::Handler::BusConnect();
  45. SampleComponentManagerNotificationBus::Handler::BusConnect();
  46. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization(
  47. *AZ::SettingsRegistry::Get(), GetBuildTargetName());
  48. }
  49. AtomSampleViewerApplication::AtomSampleViewerApplication(int* argc, char*** argv)
  50. : Application(argc, argv)
  51. {
  52. AZ::Debug::Trace::Instance().Init();
  53. #if defined(AZ_DEBUG_BUILD) || defined(AZ_PROFILE_BUILD)
  54. AZ::Debug::Trace::SetAssertVerbosityLevel(2); //enable break on AZ_Assert()
  55. #endif
  56. m_startupLogSink = AZStd::make_unique<AZStd::vector<LogMessage>>();
  57. AZ::Debug::TraceMessageBus::Handler::BusConnect();
  58. AtomSampleViewerRequestsBus::Handler::BusConnect();
  59. SampleComponentManagerNotificationBus::Handler::BusConnect();
  60. SetupConsoleHandlerRoutine();
  61. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization(
  62. *AZ::SettingsRegistry::Get(), GetBuildTargetName());
  63. }
  64. AtomSampleViewerApplication::~AtomSampleViewerApplication()
  65. {
  66. AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
  67. AtomSampleViewerRequestsBus::Handler::BusDisconnect();
  68. SampleComponentManagerNotificationBus::Handler::BusDisconnect();
  69. if (s_connectToAssetProcessor)
  70. {
  71. AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::StartDisconnectingAssetProcessor);
  72. }
  73. }
  74. void AtomSampleViewerApplication::StartCommon(AZ::Entity* systemEntity)
  75. {
  76. AzFramework::AssetSystemStatusBus::Handler::BusConnect();
  77. AzFramework::Application::StartCommon(systemEntity);
  78. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  79. {
  80. AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "CriticalAssetsCompiled", R"({})");
  81. }
  82. ReadTimeoutShutdown();
  83. WriteStartupLog();
  84. }
  85. void AtomSampleViewerApplication::OnSampleManagerActivated()
  86. {
  87. ReadAutomatedTestOptions();
  88. // enable native UI for some error messages if it's not test mode
  89. if (!m_isTestMode)
  90. {
  91. if (auto nativeUI = AZ::Interface<AZ::NativeUI::NativeUIRequests>::Get(); nativeUI != nullptr)
  92. {
  93. nativeUI->SetMode(AZ::NativeUI::Mode::ENABLED);
  94. }
  95. }
  96. }
  97. void AtomSampleViewerApplication::WriteStartupLog()
  98. {
  99. AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
  100. AZ_Assert(fileIO != nullptr, "FileIO should be running at this point");
  101. // There is no log system online so we have to create your own log file.
  102. char resolveBuffer[AZ_MAX_PATH_LEN] = { 0 };
  103. fileIO->ResolvePath("@user@", resolveBuffer, AZ_MAX_PATH_LEN);
  104. // Note: @log@ hasn't been set at this point in the standalone AtomSampleViewer
  105. AZStd::string logDirectory;
  106. AzFramework::StringFunc::Path::Join(resolveBuffer, "log", logDirectory);
  107. fileIO->SetAlias("@log@", logDirectory.c_str());
  108. fileIO->CreatePath("@products@");
  109. fileIO->CreatePath("@user@");
  110. fileIO->CreatePath("@log@");
  111. AZStd::string logPath;
  112. AzFramework::StringFunc::Path::Join(logDirectory.c_str(), s_logFileBaseName, logPath);
  113. using namespace AzFramework;
  114. m_logFile.reset(aznew LogFile(logPath.c_str()));
  115. if (m_logFile)
  116. {
  117. m_logFile->SetMachineReadable(false);
  118. for (const LogMessage& message : *m_startupLogSink)
  119. {
  120. m_logFile->AppendLog(AzFramework::LogFile::SEV_NORMAL, message.window.c_str(), message.message.c_str());
  121. }
  122. m_startupLogSink->clear();
  123. m_logFile->FlushLog();
  124. }
  125. }
  126. void AtomSampleViewerApplication::ReadAutomatedTestOptions()
  127. {
  128. if (GetArgC() != nullptr && GetArgV() != nullptr)
  129. {
  130. AzFramework::CommandLine commandLine;
  131. commandLine.Parse(*GetArgC(), *GetArgV());
  132. constexpr const char* testSuiteSwitch = "runtestsuite";
  133. constexpr const char* testExitSwitch = "exitontestend";
  134. constexpr const char* testRandomSeed = "randomtestseed";
  135. bool exitOnTestEnd = commandLine.HasSwitch(testExitSwitch);
  136. if (commandLine.HasSwitch(testSuiteSwitch))
  137. {
  138. const AZStd::string& testSuitePath = commandLine.GetSwitchValue(testSuiteSwitch, 0);
  139. int randomSeed = 0;
  140. if (commandLine.HasSwitch(testRandomSeed))
  141. {
  142. randomSeed = atoi(commandLine.GetSwitchValue(testRandomSeed, 0).c_str());
  143. }
  144. SampleComponentManagerRequestBus::Broadcast(&SampleComponentManagerRequestBus::Events::RunMainTestSuite, testSuitePath, exitOnTestEnd, randomSeed);
  145. m_isTestMode = true;
  146. }
  147. }
  148. }
  149. bool AtomSampleViewerApplication::OnOutput(const char* window, const char* message)
  150. {
  151. if (m_logFile)
  152. {
  153. m_logFile->AppendLog(AzFramework::LogFile::SEV_NORMAL, window, message);
  154. }
  155. else if (m_startupLogSink)
  156. {
  157. m_startupLogSink->push_back({ window, message });
  158. }
  159. return false;
  160. }
  161. void AtomSampleViewerApplication::Destroy()
  162. {
  163. m_logFile.reset();
  164. m_startupLogSink.reset();
  165. if (s_connectToAssetProcessor)
  166. {
  167. AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::StartDisconnectingAssetProcessor);
  168. }
  169. Application::Destroy();
  170. }
  171. void AtomSampleViewerApplication::Tick()
  172. {
  173. TickSystem();
  174. Application::Tick();
  175. TickTimeoutShutdown();
  176. }
  177. void AtomSampleViewerApplication::ReadTimeoutShutdown()
  178. {
  179. if (m_commandLine.HasSwitch("timeout"))
  180. {
  181. const AZStd::string& timeoutValue = m_commandLine.GetSwitchValue("timeout", 0);
  182. const float timeoutInSeconds = static_cast<float>(atoi(timeoutValue.c_str()));
  183. AZ_Printf("AtomSampleViewer", "starting up with timeout shutdown of %f seconds", timeoutInSeconds);
  184. m_secondsBeforeShutdown = timeoutInSeconds;
  185. }
  186. }
  187. void AtomSampleViewerApplication::TickTimeoutShutdown()
  188. {
  189. if (m_secondsBeforeShutdown > 0.f)
  190. {
  191. const AZ::TimeUs deltaTimeUs = AZ::GetRealTickDeltaTimeUs();
  192. m_secondsBeforeShutdown -= AZ::TimeUsToSeconds(deltaTimeUs);
  193. if (m_secondsBeforeShutdown <= 0.f)
  194. {
  195. AZ_Printf("AtomSampleViewer", "Timeout reached, shutting down");
  196. AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::ExitMainLoop); // or ::TerminateOnError for a more forceful option
  197. }
  198. }
  199. }
  200. void AtomSampleViewerApplication::AssetSystemAvailable()
  201. {
  202. if (s_connectToAssetProcessor)
  203. {
  204. bool connectedToAssetProcessor = false;
  205. // When the AssetProcessor is already launched it should take less than a second to perform a connection
  206. // but when the AssetProcessor needs to be launch it could take up to 15 seconds to have the AssetProcessor initialize
  207. // and able to negotiate a connection when running a debug build
  208. // and to negotiate a connection
  209. AzFramework::AssetSystem::ConnectionSettings connectionSettings;
  210. AzFramework::AssetSystem::ReadConnectionSettingsFromSettingsRegistry(connectionSettings);
  211. connectionSettings.m_connectionDirection = AzFramework::AssetSystem::ConnectionSettings::ConnectionDirection::ConnectToAssetProcessor;
  212. connectionSettings.m_connectionIdentifier = "AtomSampleViewer";
  213. connectionSettings.m_loggingCallback = []([[maybe_unused]] AZStd::string_view logData)
  214. {
  215. AZ_TracePrintf("AtomSampleViewer", "%.*s", aznumeric_cast<int>(logData.size()), logData.data());
  216. };
  217. AzFramework::AssetSystemRequestBus::BroadcastResult(connectedToAssetProcessor,
  218. &AzFramework::AssetSystemRequestBus::Events::EstablishAssetProcessorConnection, connectionSettings);
  219. if (connectedToAssetProcessor)
  220. {
  221. CompileCriticalAssets();
  222. }
  223. }
  224. AzFramework::AssetSystemStatusBus::Handler::BusDisconnect();
  225. }
  226. void AtomSampleViewerApplication::CompileCriticalAssets()
  227. {
  228. // Critical assets for AtomSampleViewer application. They only matter if the application run with AP connection.
  229. const char AssetPaths[][128] =
  230. {
  231. "Shaders/imgui/imgui.azshader",
  232. "Shaders/auxgeom/auxgeomworld.azshader",
  233. "Shaders/auxgeom/auxgeomobject.azshader",
  234. "Shaders/auxgeom/auxgeomobjectlit.azshader"
  235. };
  236. const uint32_t AssetCount = sizeof(AssetPaths) / sizeof(AssetPaths[0]);
  237. // Wait for AP initial scan before we can request compile and sync asset
  238. // The following implementation is similar to CSystem::WaitForAssetProcessorToBeReady()
  239. bool isAssetProcessorReady = false;
  240. AzFramework::AssetSystem::RequestAssetProcessorStatus request;
  241. request.m_platform = "pc";
  242. while (!isAssetProcessorReady)
  243. {
  244. AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::PumpSystemEventLoopUntilEmpty);
  245. AzFramework::AssetSystem::ResponseAssetProcessorStatus response;
  246. if (!AzFramework::AssetSystem::SendRequest(request, response))
  247. {
  248. AZ_Warning("AtomSampleViewerApplication", false, "Failed to send Asset Processor Status request for platform %s.", request.m_platform.c_str());
  249. return;
  250. }
  251. else
  252. {
  253. if (response.m_isAssetProcessorReady)
  254. {
  255. isAssetProcessorReady = true;
  256. }
  257. }
  258. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(100));
  259. }
  260. // Force AP to compile assets and wait for them
  261. // Note: with AssetManager's current implementation, a compiled asset won't be added in asset registry until next system tick.
  262. // So the asset id won't be found right after CompileAssetSync call.
  263. for (uint32_t assetIdx = 0; assetIdx < AssetCount; assetIdx++)
  264. {
  265. // Wait for the shader asset be compiled
  266. AzFramework::AssetSystem::AssetStatus status = AzFramework::AssetSystem::AssetStatus_Unknown;
  267. AzFramework::AssetSystemRequestBus::BroadcastResult(
  268. status, &AzFramework::AssetSystemRequestBus::Events::CompileAssetSync, AssetPaths[assetIdx]);
  269. if (status != AzFramework::AssetSystem::AssetStatus_Compiled)
  270. {
  271. AZ_Error("AtomSampleViewerApplication", false, "Shader asset [%s] error %d", AssetPaths[assetIdx], status);
  272. }
  273. }
  274. }
  275. void AtomSampleViewerApplication::QueryApplicationType(AZ::ApplicationTypeQuery& appType) const
  276. {
  277. appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Game;
  278. };
  279. int RunGameCommon(int argc, char** argv, AZStd::function<void()> customRunCode)
  280. {
  281. AtomSampleViewer::AtomSampleViewerApplication app(&argc, &argv);
  282. const AzGameFramework::GameApplication::StartupParameters gameAppParams;
  283. app.Start({}, gameAppParams);
  284. if (customRunCode)
  285. {
  286. customRunCode();
  287. }
  288. app.RunMainLoop();
  289. app.Stop();
  290. return app.GetExitCode();
  291. }
  292. } // namespace AtomSampleViewer