MultiplayerSampleUserSettings.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  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 <Atom/RPI.Public/Image/ImageSystem.h>
  9. #include <Atom/RPI.Public/Image/StreamingImage.h>
  10. #include <Atom/RPI.Public/Image/StreamingImagePool.h>
  11. #include <Atom/RPI.Public/Pass/PassFilter.h>
  12. #include <Atom/RPI.Public/RenderPipeline.h>
  13. #include <Atom/RPI.Public/RPISystemInterface.h>
  14. #include <Atom/RPI.Public/Scene.h>
  15. #include <Atom/Bootstrap/DefaultWindowBus.h>
  16. #include <Atom/Feature/SpecularReflections/SpecularReflectionsFeatureProcessorInterface.h>
  17. #include <AzCore/Console/IConsole.h>
  18. #include <AzCore/IO/GenericStreams.h>
  19. #include <AzCore/Settings/SettingsRegistry.h>
  20. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  21. #include <AzCore/Utils/Utils.h>
  22. #include <AzFramework/Entity/GameEntityContextBus.h>
  23. #include <AzFramework/FileFunc/FileFunc.h>
  24. #include <AzFramework/Windowing/WindowBus.h>
  25. #include <IAudioSystem.h>
  26. #include <UserSettings/MultiplayerSampleUserSettings.h>
  27. namespace MultiplayerSample
  28. {
  29. static constexpr const char* DefaultGraphicsApi = ""; // default to the platform-specific default graphics API
  30. static constexpr AZ::u64 DefaultVolume[VolumeChannel::Max] =
  31. {
  32. 100, // MasterVolume: default to full volume (100)
  33. 100, // MusicVolume: default to full volume (100)
  34. 100, // SfxVolume: default to full volume (100)
  35. };
  36. static constexpr AZ::s64 DefaultTextureQuality = 1; // default to one mip level below highest.
  37. static constexpr bool DefaultFullscreenMode = false; // default to windowed
  38. static constexpr AZ::u64 DefaultResolutionWidth = 1920; // default to 1080p
  39. static constexpr AZ::u64 DefaultResolutionHeight = 1080; // default to 1080p
  40. static constexpr SpecularReflections DefaultReflectionType = SpecularReflections::None; // default to no reflections
  41. static constexpr Msaa DefaultMsaa = Msaa::X2; // default to 2x MSAA
  42. static constexpr bool DefaultTaa = true; // default to TAA enabled
  43. using FixedString = AZStd::fixed_string<256>;
  44. // The base registry key that all our user settings will live underneath.
  45. // We keep them separate from the rest of the registry hierarchy to ensure that users can't
  46. // edit their settings file by hand to overwrite any other registry keys that weren't intentionally exposed.
  47. static constexpr FixedString BaseRegistryKey = "/O3DE/MultiplayerSample/User/Settings";
  48. // These keep track of the specific registry keys used for each setting.
  49. static constexpr FixedString GraphicsApiKey(BaseRegistryKey + FixedString("/ApiName"));
  50. static constexpr FixedString TextureQualityKey(BaseRegistryKey + FixedString("/TextureQuality"));
  51. static constexpr FixedString VolumeKey[VolumeChannel::Max]
  52. {
  53. BaseRegistryKey + FixedString("/MasterVolume"),
  54. BaseRegistryKey + FixedString("/MusicVolume"),
  55. BaseRegistryKey + FixedString("/SfxVolume"),
  56. };
  57. static constexpr FixedString FullscreenKey(BaseRegistryKey + FixedString("/Fullscreen"));
  58. static constexpr FixedString ResolutionWidthKey(BaseRegistryKey + FixedString("/Resolution/Width"));
  59. static constexpr FixedString ResolutionHeightKey(BaseRegistryKey + FixedString("/Resolution/Height"));
  60. static constexpr FixedString ReflectionSettingKey(BaseRegistryKey + FixedString("/Reflections"));
  61. static constexpr FixedString MsaaKey(BaseRegistryKey + FixedString("/MSAA"));
  62. static constexpr FixedString TaaKey(BaseRegistryKey + FixedString("/TAA"));
  63. MultiplayerSampleUserSettings::MultiplayerSampleUserSettings()
  64. {
  65. m_registry = AZ::SettingsRegistry::Get();
  66. AZ_Assert(m_registry, "Initialization order incorrect, MultiplayerSampleUserSettings has somehow started before "
  67. "the Settings Registry. Initial settings won't get applied correctly.");
  68. MultiplayerSampleUserSettingsRequestBus::Handler::BusConnect();
  69. // Create a full path including filename for the user settings file.
  70. m_userSettingsPath = AZ::Utils::GetProjectUserPath();
  71. m_userSettingsPath /= "Registry";
  72. m_userSettingsPath /= "MultiplayerSampleUserSettings.setreg";
  73. // Load all of our settings keys, create default values if they don't exist and initialize the engine settings as appropriate.
  74. Load();
  75. }
  76. MultiplayerSampleUserSettings::~MultiplayerSampleUserSettings()
  77. {
  78. AZ::Render::Bootstrap::NotificationBus::Handler::BusDisconnect();
  79. MultiplayerSampleUserSettingsRequestBus::Handler::BusDisconnect();
  80. // Always auto-save the user settings on destruction.
  81. Save();
  82. }
  83. void MultiplayerSampleUserSettings::ApplyMsaaSetting()
  84. {
  85. // To apply the MSAA setting at the correct point in the boot process, we need to wait until the bootstrap scene is ready.
  86. // To listen for that, we need to connect to the Bootstrap::NotificationBus. However, we can't connect to that bus until
  87. // the scene system and main scene are created, which happens in the activation of the AzFrameworkConfigurationSystemComponent.
  88. // So, we'll have the MultiplayerSampleSystemComponent depend on the AzFrameworkConfigurationSystemComponent, then on
  89. // activation, call ApplyMsaaSetting(), which tells us to connect to the Bootstrap::NotificationBus, which then will tell
  90. // us when the bootstrap scene is ready so that we can apply the MSAA setting.
  91. // If we're ever able to change the MSAA setting at runtime, we can remove this entire flow and just call
  92. // SetMsaaInRenderer() from the SetMsaa() call.
  93. AZ::Render::Bootstrap::NotificationBus::Handler::BusConnect();
  94. }
  95. void MultiplayerSampleUserSettings::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapScene)
  96. {
  97. // Only set the MSAA setting at boot time. Changing it at runtime can lead to crashes.
  98. SetMsaaInRenderer(GetMsaa());
  99. }
  100. void MultiplayerSampleUserSettings::Load()
  101. {
  102. // Read the setreg file from a loose file into a string in memory. This isn't technically a "cfg" file,
  103. // but the method does the exact set of steps needed here to read a loose file into memory, so even though
  104. // it has a slightly misleading name, it keeps us from duplicating the code.
  105. AZ::Outcome<AZStd::string, AZStd::string> userSettings =
  106. AzFramework::FileFunc::GetCfgFileContents(AZStd::string(m_userSettingsPath.FixedMaxPathString()));
  107. if (userSettings.IsSuccess())
  108. {
  109. // Merge the user settings file under the base "/O3DE/MultiplayerSample/User/Settings" key.
  110. // This will ensure that it cannot overwrite any other engine settings.
  111. // MergeSettings() is used here instead of MergeSettingsFile() because MergeSettingsFile() uses
  112. // FileIOBase to read in the file, which will attempt to read it from a PAK file in PAK file builds.
  113. // Our settings file will always be a loose file, so we instead read it into a buffer beforehand and then
  114. // apply it here from the in-memory buffer.
  115. [[maybe_unused]] auto mergeSuccess = m_registry->MergeSettings(userSettings.GetValue(),
  116. AZ::SettingsRegistryInterface::Format::JsonMergePatch, BaseRegistryKey);
  117. AZ_Error("UserSettings", mergeSuccess, "Failed to merge user settings into the O3DE registry.");
  118. }
  119. // Get the current settings values (or the defaults if the keys don't exist) and pass the values back
  120. // in to set the settings values, which will notify the engine as well as write the keys back into the registry.
  121. SetGraphicsApi(GetGraphicsApi());
  122. SetVolume(VolumeChannel::MasterVolume, GetVolume(VolumeChannel::MasterVolume));
  123. SetVolume(VolumeChannel::MusicVolume, GetVolume(VolumeChannel::MusicVolume));
  124. SetVolume(VolumeChannel::SfxVolume, GetVolume(VolumeChannel::SfxVolume));
  125. SetTextureQuality(GetTextureQuality());
  126. SetFullscreen(GetFullscreen());
  127. SetResolution(GetResolution());
  128. SetReflectionSetting(GetReflectionSetting());
  129. SetMsaa(GetMsaa());
  130. SetTaa(GetTaa());
  131. }
  132. AZStd::string MultiplayerSampleUserSettings::GetGraphicsApi()
  133. {
  134. // Default to an empty string, which will just use the default API.
  135. AZStd::string apiName = DefaultGraphicsApi;
  136. m_registry->Get(apiName, GraphicsApiKey.c_str());
  137. return apiName;
  138. }
  139. void MultiplayerSampleUserSettings::SetGraphicsApi(const AZStd::string& apiName)
  140. {
  141. // Set the requested api name as the highest (and only) user priority in the registry.
  142. // Atom will select this api at startup as long as it exists and nothing was passed in via command-line.
  143. // If the passed-in apiName is empty, just let Atom use its standard default priorities for api selection.
  144. // If the passed-in apiName doesn't match one supported by Atom on this platform, Atom will ignore it and use
  145. // its standard default priorities as well.
  146. if (!apiName.empty())
  147. {
  148. AZStd::vector<AZStd::string> factoriesPriority;
  149. factoriesPriority.emplace_back(apiName);
  150. m_registry->SetObject("/O3DE/Atom/RHI/FactoryManager/factoriesPriority", factoriesPriority);
  151. }
  152. m_registry->Set(GraphicsApiKey.c_str(), apiName);
  153. }
  154. uint8_t MultiplayerSampleUserSettings::GetVolume(VolumeChannel volumeChannel)
  155. {
  156. // Default to full volume (100)
  157. AZ::u64 masterVolume = DefaultVolume[volumeChannel];
  158. m_registry->Get(masterVolume, VolumeKey[volumeChannel].c_str());
  159. // Make sure any hand-edited registry values stay within a valid range.
  160. return AZStd::clamp(aznumeric_cast<uint8_t>(masterVolume), aznumeric_cast<uint8_t>(0), aznumeric_cast<uint8_t>(100));
  161. }
  162. void MultiplayerSampleUserSettings::SetVolume(VolumeChannel volumeChannel, uint8_t masterVolume)
  163. {
  164. // Send a request to the audio system to change the volume.
  165. auto audioSystem = AZ::Interface<Audio::IAudioSystem>::Get();
  166. if (audioSystem)
  167. {
  168. static constexpr const char* volumeIds[] =
  169. {
  170. "Volume_Master",
  171. "Volume_Music",
  172. "Volume_SFX",
  173. };
  174. Audio::TAudioObjectID rtpcId = audioSystem->GetAudioRtpcID(volumeIds[volumeChannel]);
  175. if (rtpcId != INVALID_AUDIO_CONTROL_ID)
  176. {
  177. Audio::ObjectRequest::SetParameterValue setParameter;
  178. setParameter.m_audioObjectId = INVALID_AUDIO_OBJECT_ID;
  179. setParameter.m_parameterId = rtpcId;
  180. // Volume in the audio system is expected to be 0.0 (min) - 1.0 (max), but we're using 0 - 100 as integers,
  181. // so convert it from 0 - 100 to the 0 - 1 range.
  182. setParameter.m_value = masterVolume / 100.0f;
  183. AZ::Interface<Audio::IAudioSystem>::Get()->PushRequest(AZStd::move(setParameter));
  184. }
  185. }
  186. m_registry->Set(VolumeKey[volumeChannel].c_str(), aznumeric_cast<AZ::u64>(masterVolume));
  187. }
  188. int16_t MultiplayerSampleUserSettings::GetTextureQuality()
  189. {
  190. AZ::s64 textureQuality = DefaultTextureQuality;
  191. m_registry->Get(textureQuality, TextureQualityKey.c_str());
  192. return AZStd::clamp(aznumeric_cast<int16_t>(textureQuality), aznumeric_cast<int16_t>(0), aznumeric_cast<int16_t>(10));
  193. }
  194. void MultiplayerSampleUserSettings::SetTextureQuality(int16_t textureQuality)
  195. {
  196. if (auto* imageSystem = AZ::RPI::ImageSystemInterface::Get())
  197. {
  198. AZ::Data::Instance<AZ::RPI::StreamingImagePool> pool = imageSystem->GetSystemStreamingPool();
  199. pool->SetMipBias(textureQuality);
  200. }
  201. m_registry->Set(TextureQualityKey.c_str(), aznumeric_cast<AZ::s64>(textureQuality));
  202. }
  203. SpecularReflections MultiplayerSampleUserSettings::GetReflectionSetting()
  204. {
  205. AZ::u64 reflectionType = static_cast<AZ::u64>(DefaultReflectionType);
  206. m_registry->Get(reflectionType, ReflectionSettingKey.c_str());
  207. return static_cast<SpecularReflections>(reflectionType);
  208. }
  209. void MultiplayerSampleUserSettings::SetReflectionSetting(SpecularReflections reflectionType)
  210. {
  211. // Only try to set the settings if the scene system is active.
  212. // If we try to set it too early, it will assert / crash.
  213. if (AzFramework::SceneSystemInterface::Get())
  214. {
  215. AzFramework::EntityContextId entityContextId;
  216. AzFramework::GameEntityContextRequestBus::BroadcastResult(
  217. entityContextId, &AzFramework::GameEntityContextRequestBus::Events::GetGameEntityContextId);
  218. if (auto scene = AZ::RPI::Scene::GetSceneForEntityContextId(entityContextId); scene)
  219. {
  220. if (auto reflectionFeatureProcessor =
  221. scene->GetFeatureProcessor<AZ::Render::SpecularReflectionsFeatureProcessorInterface>(); reflectionFeatureProcessor)
  222. {
  223. auto ssrOptions = reflectionFeatureProcessor->GetSSROptions();
  224. ssrOptions.m_enable = (reflectionType != SpecularReflections::None);
  225. ssrOptions.m_rayTracing = (reflectionType == SpecularReflections::ScreenSpaceAndRaytracing);
  226. reflectionFeatureProcessor->SetSSROptions(ssrOptions);
  227. }
  228. }
  229. }
  230. m_registry->Set(ReflectionSettingKey.c_str(), aznumeric_cast<AZ::u64>(reflectionType));
  231. }
  232. Msaa MultiplayerSampleUserSettings::GetMsaa()
  233. {
  234. AZ::u64 msaa = static_cast<AZ::u64>(DefaultMsaa);
  235. m_registry->Get(msaa, MsaaKey.c_str());
  236. return static_cast<Msaa>(msaa);
  237. }
  238. void MultiplayerSampleUserSettings::SetMsaaInRenderer(Msaa msaa)
  239. {
  240. if (auto rpiSystem = AZ::RPI::RPISystemInterface::Get(); rpiSystem)
  241. {
  242. auto multisampleState = rpiSystem->GetApplicationMultisampleState();
  243. switch (msaa)
  244. {
  245. case Msaa::X1:
  246. multisampleState.m_samples = 1;
  247. break;
  248. case Msaa::X2:
  249. multisampleState.m_samples = 2;
  250. break;
  251. case Msaa::X4:
  252. multisampleState.m_samples = 4;
  253. break;
  254. }
  255. rpiSystem->SetApplicationMultisampleState(multisampleState);
  256. }
  257. }
  258. void MultiplayerSampleUserSettings::SetMsaa(Msaa msaa)
  259. {
  260. // Currently MSAA settings don't get changed at runtime because they have the potential
  261. // to cause a TDR graphics driver crash on at least Vulkan, maybe others.
  262. // This might be the result of mixing PSOs between the msaa variant and the non-msaa variant in the same frame.
  263. // Until the problem gets tracked down and resolved, we'll only set the MSAA setting in the renderer at boot time.
  264. // If this ever gets fixed, the call to SetMsaaInRenderer() can get moved to here, and the extra flow handling to call
  265. // it sooner can get removed.
  266. m_registry->Set(MsaaKey.c_str(), aznumeric_cast<AZ::u64>(msaa));
  267. }
  268. bool MultiplayerSampleUserSettings::GetTaa()
  269. {
  270. bool enabled = DefaultTaa;
  271. m_registry->Get(enabled, TaaKey.c_str());
  272. return enabled;
  273. }
  274. void MultiplayerSampleUserSettings::SetTaa(bool enabled)
  275. {
  276. // Only try to set the settings if the scene system is active.
  277. // If we try to set it too early, it will assert / crash.
  278. if (AzFramework::SceneSystemInterface::Get())
  279. {
  280. AzFramework::EntityContextId entityContextId;
  281. AzFramework::GameEntityContextRequestBus::BroadcastResult(
  282. entityContextId, &AzFramework::GameEntityContextRequestBus::Events::GetGameEntityContextId);
  283. if (auto scene = AZ::RPI::Scene::GetSceneForEntityContextId(entityContextId); scene)
  284. {
  285. AZ::RPI::PassFilter passFilter = AZ::RPI::PassFilter::CreateWithPassName(AZ::Name("TaaPass"), scene);
  286. AZ::RPI::PassSystemInterface::Get()->ForEachPass(
  287. passFilter, [enabled](AZ::RPI::Pass* pass) -> AZ::RPI::PassFilterExecutionFlow
  288. {
  289. pass->SetEnabled(enabled);
  290. return AZ::RPI::PassFilterExecutionFlow::ContinueVisitingPasses;
  291. });
  292. m_registry->Set(TaaKey.c_str(), enabled);
  293. }
  294. }
  295. }
  296. bool MultiplayerSampleUserSettings::GetFullscreen()
  297. {
  298. bool fullscreen = DefaultFullscreenMode;
  299. m_registry->Get(fullscreen, FullscreenKey.c_str());
  300. return fullscreen;
  301. }
  302. void MultiplayerSampleUserSettings::SetFullscreen(bool fullscreen)
  303. {
  304. // Because of the way some of our fullscreen/resolution refresh notifications work on the UI settings screen,
  305. // it's possible to get reentrancy with setting this value. We'll guard against reentrancy so that the top-level
  306. // setting is the one that sticks.
  307. if (m_changingResolution)
  308. {
  309. return;
  310. }
  311. if (AZ::IConsole* console = AZ::Interface<AZ::IConsole>::Get(); console)
  312. {
  313. AzFramework::NativeWindowHandle windowHandle = nullptr;
  314. AzFramework::WindowSystemRequestBus::BroadcastResult(
  315. windowHandle,
  316. &AzFramework::WindowSystemRequestBus::Events::GetDefaultWindowHandle);
  317. if (!windowHandle)
  318. {
  319. // Initialize the fullscreen state via CVARs if we haven't created the window yet.
  320. m_changingResolution = true;
  321. AZ::CVarFixedString commandString = AZ::CVarFixedString::format("r_fullscreen %u", fullscreen ? 1 : 0);
  322. console->PerformCommand(commandString.c_str());
  323. m_changingResolution = false;
  324. }
  325. else
  326. {
  327. // Change the existing fullscreen state if the window already exists.
  328. bool isFullscreen = false;
  329. AzFramework::WindowRequestBus::EventResult(
  330. isFullscreen, windowHandle,
  331. &AzFramework::WindowRequestBus::Events::GetFullScreenState);
  332. if (isFullscreen != fullscreen)
  333. {
  334. m_changingResolution = true;
  335. AzFramework::WindowRequestBus::Event(
  336. windowHandle,
  337. &AzFramework::WindowRequestBus::Events::SetFullScreenState, fullscreen);
  338. m_changingResolution = false;
  339. }
  340. }
  341. if (fullscreen)
  342. {
  343. // Once Atom supports setting a rendering resolution to something other than the current window size,
  344. // this is where we'd want to make the appropriate API calls to change it when entering fullscreen mode.
  345. // Right now, fullscreen mode uses the full monitor resolution as Atom's resolution.
  346. // Ideally, we would like Atom to use the resolution that's set in the Resolution user setting regardless
  347. // of windowed or fullscreen, and would instead scale to fill the rullscreen real estate.
  348. }
  349. else
  350. {
  351. // When leaving fullscreen, set the window resolution to the current requested resolution.
  352. // This is necessary because by default, leaving fullscreen will return the window back to its
  353. // pre-fullscreen state. But if we've changed the requested resolution between now and then, we
  354. // want to make sure we end up with a window that matches the currently-requested resolution instead.
  355. SetResolution(GetResolution());
  356. }
  357. }
  358. m_registry->Set(FullscreenKey.c_str(), fullscreen);
  359. }
  360. AZStd::pair<uint32_t, uint32_t> MultiplayerSampleUserSettings::GetResolution()
  361. {
  362. AZ::u64 width = DefaultResolutionWidth;
  363. AZ::u64 height = DefaultResolutionHeight;
  364. m_registry->Get(width, ResolutionWidthKey.c_str());
  365. m_registry->Get(height, ResolutionHeightKey.c_str());
  366. return { aznumeric_cast<uint32_t>(width), aznumeric_cast<uint32_t>(height) };
  367. }
  368. void MultiplayerSampleUserSettings::SetResolution(AZStd::pair<uint32_t, uint32_t> resolution)
  369. {
  370. // Because of the way some of our fullscreen/resolution refresh notifications work on the UI settings screen,
  371. // it's possible to get reentrancy with setting this value. We'll guard against reentrancy so that the top-level
  372. // setting is the one that sticks.
  373. if (m_changingResolution)
  374. {
  375. return;
  376. }
  377. if (AZ::IConsole* console = AZ::Interface<AZ::IConsole>::Get(); console)
  378. {
  379. AzFramework::NativeWindowHandle windowHandle = nullptr;
  380. AzFramework::WindowSystemRequestBus::BroadcastResult(
  381. windowHandle,
  382. &AzFramework::WindowSystemRequestBus::Events::GetDefaultWindowHandle);
  383. if (!windowHandle)
  384. {
  385. // Initialize the resolution via CVARs if the window doesn't exist yet.
  386. m_changingResolution = true;
  387. AZ::CVarFixedString commandString = AZ::CVarFixedString::format("r_width %u", resolution.first);
  388. console->PerformCommand(commandString.c_str());
  389. commandString = AZ::CVarFixedString::format("r_height %u", resolution.second);
  390. console->PerformCommand(commandString.c_str());
  391. m_changingResolution = false;
  392. }
  393. else
  394. {
  395. bool fullscreen = false;
  396. AzFramework::WindowRequestBus::EventResult(
  397. fullscreen, windowHandle,
  398. &AzFramework::WindowRequestBus::Events::GetFullScreenState);
  399. if (!fullscreen)
  400. {
  401. // If the window exists, and isn't in fullscreen mode, resize it to the requested resolution.
  402. // To prevent people from getting into a bad state, also clamp the resolution to the maximum
  403. // resolution that fits on the current monitor.
  404. auto maxResolution = GetMaxResolution();
  405. AzFramework::WindowSize desiredSize = {
  406. AZStd::min(resolution.first, maxResolution.first), AZStd::min(resolution.second, maxResolution.second) };
  407. AzFramework::WindowSize windowSize = desiredSize;
  408. AzFramework::WindowRequestBus::EventResult(
  409. windowSize, windowHandle,
  410. &AzFramework::WindowRequestBus::Events::GetClientAreaSize);
  411. if ((desiredSize.m_height != windowSize.m_height) || (desiredSize.m_width != windowSize.m_width))
  412. {
  413. m_changingResolution = true;
  414. AzFramework::WindowRequestBus::Event(
  415. windowHandle,
  416. &AzFramework::WindowRequestBus::Events::ResizeClientArea,
  417. AzFramework::WindowSize(
  418. AZStd::min(resolution.first, maxResolution.first), AZStd::min(resolution.second, maxResolution.second)),
  419. AzFramework::WindowPosOptions());
  420. m_changingResolution = false;
  421. }
  422. }
  423. else
  424. {
  425. m_changingResolution = true;
  426. // Once Atom supports setting a rendering resolution to something other than the current window size,
  427. // this is where we'd want to make the appropriate API calls to change it when changing resolutions while
  428. // in fullscreen mode.
  429. // Right now, fullscreen mode uses the full monitor resolution as Atom's resolution.
  430. // Ideally, we would like Atom to use the resolution that's set in the Resolution user setting regardless
  431. // of windowed or fullscreen, and would instead scale to fill the rullscreen real estate.
  432. m_changingResolution = false;
  433. }
  434. }
  435. }
  436. m_registry->Set(ResolutionWidthKey.c_str(), aznumeric_cast<AZ::u64>(resolution.first));
  437. m_registry->Set(ResolutionHeightKey.c_str(), aznumeric_cast<AZ::u64>(resolution.second));
  438. }
  439. AZStd::pair<uint32_t, uint32_t> MultiplayerSampleUserSettings::GetMaxResolution()
  440. {
  441. AzFramework::NativeWindowHandle windowHandle = nullptr;
  442. AzFramework::WindowSystemRequestBus::BroadcastResult(
  443. windowHandle,
  444. &AzFramework::WindowSystemRequestBus::Events::GetDefaultWindowHandle);
  445. AzFramework::WindowSize windowSize = { AZStd::numeric_limits<uint32_t>::max(), AZStd::numeric_limits<uint32_t>::max() };
  446. AzFramework::WindowRequestBus::EventResult(
  447. windowSize, windowHandle, &AzFramework::WindowRequestBus::Events::GetMaximumClientAreaSize);
  448. return { windowSize.m_width, windowSize.m_height };
  449. }
  450. void MultiplayerSampleUserSettings::Save()
  451. {
  452. AZ::IO::FixedMaxPath userSettingsSavePath = m_userSettingsPath;
  453. userSettingsSavePath.ReplaceExtension("setreg.tmp");
  454. constexpr AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath;
  455. // Write to a temporary file and then move the file to the final location
  456. if (AZ::IO::SystemFileStream userSettingsStream(userSettingsSavePath.c_str(), openMode); userSettingsStream.IsOpen())
  457. {
  458. auto settingsRegistry = AZ::SettingsRegistry::Get();
  459. // Remove the .tmp extension from the user settings path
  460. // This results in the final path where the settings will actually be saved
  461. userSettingsSavePath.ReplaceExtension();
  462. AZ::SettingsRegistryMergeUtils::DumperSettings dumperSettings;
  463. dumperSettings.m_prettifyOutput = true;
  464. if (AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream(
  465. *settingsRegistry, BaseRegistryKey, userSettingsStream, dumperSettings))
  466. {
  467. // Use SystemFile::Rename to move the file to the final destination
  468. userSettingsStream.Close();
  469. bool renameSuccess = AZ::IO::SystemFile::Rename(userSettingsStream.GetFilename(), userSettingsSavePath.c_str(), true);
  470. AZ_Error("UserSettings", renameSuccess,
  471. "Renaming '%s' to '%s' failed.", userSettingsStream.GetFilename(), userSettingsSavePath.c_str());
  472. }
  473. }
  474. }
  475. } // namespace MultiplayerSample