diff --git a/Source/engine/demomode.cpp b/Source/engine/demomode.cpp index 02ae7746c..e80e3345e 100644 --- a/Source/engine/demomode.cpp +++ b/Source/engine/demomode.cpp @@ -144,8 +144,6 @@ void InitRecording(int recordNumber) } void OverrideOptions() { - sgOptions.Graphics.nWidth = DemoGraphicsWidth; - sgOptions.Graphics.nHeight = DemoGraphicsHeight; #ifndef USE_SDL1 sgOptions.Graphics.fitToScreen.SetValue(false); #endif diff --git a/Source/options.cpp b/Source/options.cpp index 7631d8440..325bff592 100644 --- a/Source/options.cpp +++ b/Source/options.cpp @@ -299,8 +299,6 @@ void LoadOptions() sgOptions.Audio.nBufferSize = GetIniInt("Audio", "Buffer Size", DEFAULT_AUDIO_BUFFER_SIZE); sgOptions.Audio.nResamplingQuality = GetIniInt("Audio", "Resampling Quality", DEFAULT_AUDIO_RESAMPLING_QUALITY); - sgOptions.Graphics.nWidth = GetIniInt("Graphics", "Width", DEFAULT_WIDTH); - sgOptions.Graphics.nHeight = GetIniInt("Graphics", "Height", DEFAULT_HEIGHT); sgOptions.Graphics.nGammaCorrection = GetIniInt("Graphics", "Gamma Correction", 100); #if SDL_VERSION_ATLEAST(2, 0, 0) sgOptions.Graphics.bHardwareCursor = GetIniBool("Graphics", "Hardware Cursor", HardwareCursorDefault()); @@ -354,8 +352,6 @@ void SaveOptions() SetIniValue("Audio", "Channels", sgOptions.Audio.nChannels); SetIniValue("Audio", "Buffer Size", sgOptions.Audio.nBufferSize); SetIniValue("Audio", "Resampling Quality", sgOptions.Audio.nResamplingQuality); - SetIniValue("Graphics", "Width", sgOptions.Graphics.nWidth); - SetIniValue("Graphics", "Height", sgOptions.Graphics.nHeight); SetIniValue("Graphics", "Gamma Correction", sgOptions.Graphics.nGammaCorrection); #if SDL_VERSION_ATLEAST(2, 0, 0) SetIniValue("Graphics", "Hardware Cursor", sgOptions.Graphics.bHardwareCursor); @@ -572,6 +568,91 @@ std::vector AudioOptions::GetEntries() }; } +OptionEntryResolution::OptionEntryResolution() + : OptionEntryListBase("", OptionEntryFlags::CantChangeInGame | OptionEntryFlags::RecreateUI, N_("Resolution"), N_("Affect the game's internal resolution and determine your view area. Note: This can differ from screen resolution, when Upscaling, Integer Scaling or Fit to Screen is used.")) +{ +} +void OptionEntryResolution::LoadFromIni(string_view category) +{ + size = { GetIniInt(category.data(), "Width", DEFAULT_WIDTH), GetIniInt(category.data(), "Height", DEFAULT_HEIGHT) }; +} +void OptionEntryResolution::SaveToIni(string_view category) const +{ + SetIniValue(category.data(), "Width", size.width); + SetIniValue(category.data(), "Height", size.height); +} + +void OptionEntryResolution::CheckResolutionsAreInitialized() const +{ + if (!resolutions.empty()) + return; + + std::vector sizes; + + // Add resolutions +#ifdef USE_SDL1 + auto *modes = SDL_ListModes(nullptr, SDL_FULLSCREEN | SDL_HWPALETTE); + // SDL_ListModes returns -1 if any resolution is allowed (for example returned on 3DS) + if (modes != nullptr && modes != (SDL_Rect **)-1) { + for (size_t i = 0; modes[i] != nullptr; i++) { + sizes.emplace_back(Size { modes[i]->w, modes[i]->h }); + } + } +#else + int displayModeCount = SDL_GetNumDisplayModes(0); + for (int i = 0; i < displayModeCount; i++) { + SDL_DisplayMode mode; + if (SDL_GetDisplayMode(0, i, &mode) != 0) { + ErrSdl(); + } + sizes.emplace_back(Size { mode.w, mode.h }); + } +#endif + + // Ensures that the ini specified resolution is present in resolution list even if it doesn't match a monitor resolution (for example if played in window mode) + sizes.push_back(this->size); + // Ensures that the vanilla/default resolution is always present + sizes.emplace_back(Size { DEFAULT_WIDTH, DEFAULT_HEIGHT }); + + // Sort by width then by height + std::sort(sizes.begin(), sizes.end(), + [](const Size &x, const Size &y) -> bool { + if (x.width == y.width) + return x.height > y.height; + return x.width > y.width; + }); + // Remove duplicate entries + sizes.erase(std::unique(sizes.begin(), sizes.end()), sizes.end()); + + for (auto &size : sizes) { + resolutions.emplace_back(size, fmt::format("{}x{}", size.width, size.height)); + } +} + +size_t OptionEntryResolution::GetListSize() const +{ + CheckResolutionsAreInitialized(); + return resolutions.size(); +} +string_view OptionEntryResolution::GetListDescription(size_t index) const +{ + CheckResolutionsAreInitialized(); + return resolutions[index].second; +} +size_t OptionEntryResolution::GetActiveListIndex() const +{ + CheckResolutionsAreInitialized(); + auto found = std::find_if(resolutions.begin(), resolutions.end(), [this](const auto &x) { return x.first == this->size; }); + if (found == resolutions.end()) + return 0; + return std::distance(resolutions.begin(), found); +} +void OptionEntryResolution::SetActiveListIndex(size_t index) +{ + size = resolutions[index].first; + NotifyValueChanged(); +} + GraphicsOptions::GraphicsOptions() : OptionCategoryBase("Graphics", N_("Graphics"), N_("Graphics Settings")) , fullscreen("Fullscreen", OptionEntryFlags::CantChangeInGame | OptionEntryFlags::RecreateUI, N_("Fullscreen"), N_("Display the game in windowed or fullscreen mode."), true) @@ -594,6 +675,7 @@ GraphicsOptions::GraphicsOptions() , limitFPS("FPS Limiter", OptionEntryFlags::None, N_("FPS Limiter"), N_("FPS is limited to avoid high CPU load. Limit considers refresh rate."), true) , showFPS("Show FPS", OptionEntryFlags::None, N_("Show FPS"), N_("Displays the FPS in the upper left corner of the screen."), true) { + resolution.SetValueChangedCallback(ResizeWindow); fullscreen.SetValueChangedCallback(ResizeWindow); #if !defined(USE_SDL1) || defined(__3DS__) fitToScreen.SetValueChangedCallback(ResizeWindow); @@ -610,6 +692,7 @@ std::vector GraphicsOptions::GetEntries() { // clang-format off return { + &resolution, #ifndef __vita__ &fullscreen, #endif diff --git a/Source/options.h b/Source/options.h index c478670e4..354c81a56 100644 --- a/Source/options.h +++ b/Source/options.h @@ -216,6 +216,31 @@ private: void CheckLanguagesAreInitialized() const; }; +class OptionEntryResolution : public OptionEntryListBase { +public: + OptionEntryResolution(); + + void LoadFromIni(string_view category) override; + void SaveToIni(string_view category) const override; + + [[nodiscard]] size_t GetListSize() const override; + [[nodiscard]] string_view GetListDescription(size_t index) const override; + [[nodiscard]] size_t GetActiveListIndex() const override; + void SetActiveListIndex(size_t index) override; + + Size operator*() const + { + return size; + } + +private: + /** @brief View size. */ + Size size; + mutable std::vector> resolutions; + + void CheckResolutionsAreInitialized() const; +}; + struct OptionCategoryBase { OptionCategoryBase(string_view key, string_view name, string_view description); @@ -295,10 +320,7 @@ struct GraphicsOptions : OptionCategoryBase { GraphicsOptions(); std::vector GetEntries() override; - /** @brief Render width. */ - int nWidth; - /** @brief Render height. */ - int nHeight; + OptionEntryResolution resolution; /** @brief Run in fullscreen or windowed mode. */ OptionEntryBoolean fullscreen; #if !defined(USE_SDL1) || defined(__3DS__) diff --git a/Source/utils/display.cpp b/Source/utils/display.cpp index f247bd529..1fa514742 100644 --- a/Source/utils/display.cpp +++ b/Source/utils/display.cpp @@ -100,7 +100,7 @@ void AdjustToScreenGeometry(Size windowSize) Size GetPreferredWindowSize() { - Size windowSize = { sgOptions.Graphics.nWidth, sgOptions.Graphics.nHeight }; + Size windowSize = *sgOptions.Graphics.resolution; #ifndef USE_SDL1 if (*sgOptions.Graphics.upscale && *sgOptions.Graphics.fitToScreen) {