1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-26 03:52:01 +02:00

Merge pull request #2289 from IvanSavenko/resolution_fixes

Fix issues with changing game resolution
This commit is contained in:
Ivan Savenko 2023-07-11 14:35:07 +03:00 committed by GitHub
commit 78415c573b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 117 additions and 65 deletions

View File

@ -449,19 +449,6 @@ void playIntro()
static void mainLoop() static void mainLoop()
{ {
SettingsListener resChanged = settings.listen["video"]["resolution"];
SettingsListener fsChanged = settings.listen["video"]["fullscreen"];
auto functor = [](const JsonNode &newState){
GH.dispatchMainThread([](){
boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
GH.onScreenResize();
});
};
resChanged(functor);
fsChanged(functor);
inGuiThread.reset(new bool(true)); inGuiThread.reset(new bool(true));
while(1) //main SDL events loop while(1) //main SDL events loop

View File

@ -10,13 +10,10 @@
#pragma once #pragma once
struct SDL_Texture; struct SDL_Texture;
struct SDL_Window;
struct SDL_Renderer; struct SDL_Renderer;
struct SDL_Surface; struct SDL_Surface;
extern SDL_Texture * screenTexture; extern SDL_Texture * screenTexture;
extern SDL_Window * mainWindow;
extern SDL_Renderer * mainRenderer; extern SDL_Renderer * mainRenderer;
extern SDL_Surface *screen; // main screen surface extern SDL_Surface *screen; // main screen surface

View File

@ -121,6 +121,8 @@ void InputHandler::preprocessEvent(const SDL_Event & ev)
{ {
Settings full = settings.write["video"]["fullscreen"]; Settings full = settings.write["video"]["fullscreen"];
full->Bool() = !full->Bool(); full->Bool() = !full->Bool();
GH.onScreenResize();
return; return;
} }
else if(ev.type == SDL_USEREVENT) else if(ev.type == SDL_USEREVENT)

View File

@ -14,20 +14,17 @@
#include "../CMT.h" #include "../CMT.h"
#include "../gui/CGuiHandler.h" #include "../gui/CGuiHandler.h"
#include "../gui/EventDispatcher.h" #include "../gui/EventDispatcher.h"
#include "../render/IScreenHandler.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../../lib/Rect.h" #include "../../lib/Rect.h"
#include <SDL_events.h> #include <SDL_events.h>
#include <SDL_render.h>
#ifdef VCMI_APPLE #ifdef VCMI_APPLE
# include <dispatch/dispatch.h> # include <dispatch/dispatch.h>
#endif #endif
#ifdef VCMI_IOS
# include "ios/utils.h"
#endif
void InputSourceText::handleEventTextInput(const SDL_TextInputEvent & text) void InputSourceText::handleEventTextInput(const SDL_TextInputEvent & text)
{ {
GH.events().dispatchTextInput(text.text); GH.events().dispatchTextInput(text.text);
@ -40,30 +37,15 @@ void InputSourceText::handleEventTextEditing(const SDL_TextEditingEvent & text)
void InputSourceText::startTextInput(const Rect & whereInput) void InputSourceText::startTextInput(const Rect & whereInput)
{ {
#ifdef VCMI_APPLE #ifdef VCMI_APPLE
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
#endif #endif
// TODO ios: looks like SDL bug actually, try fixing there Rect rectInScreenCoordinates = GH.screenHandler().convertLogicalPointsToWindow(whereInput);
auto renderer = SDL_GetRenderer(mainWindow); SDL_Rect textInputRect = CSDL_Ext::toSDL(rectInScreenCoordinates);
float scaleX, scaleY;
SDL_Rect viewport;
SDL_RenderGetScale(renderer, &scaleX, &scaleY);
SDL_RenderGetViewport(renderer, &viewport);
#ifdef VCMI_IOS SDL_SetTextInputRect(&textInputRect);
const auto nativeScale = iOS_utils::screenScale();
scaleX /= nativeScale;
scaleY /= nativeScale;
#endif
SDL_Rect rectInScreenCoordinates;
rectInScreenCoordinates.x = (viewport.x + whereInput.x) * scaleX;
rectInScreenCoordinates.y = (viewport.y + whereInput.y) * scaleY;
rectInScreenCoordinates.w = whereInput.w * scaleX;
rectInScreenCoordinates.h = whereInput.h * scaleY;
SDL_SetTextInputRect(&rectInScreenCoordinates);
if (SDL_IsTextInputActive() == SDL_FALSE) if (SDL_IsTextInputActive() == SDL_FALSE)
{ {

View File

@ -12,6 +12,7 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
class Point; class Point;
class Rect;
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END
class IScreenHandler class IScreenHandler
@ -33,4 +34,7 @@ public:
/// Returns <min, max> range of possible values for screen scaling percentage /// Returns <min, max> range of possible values for screen scaling percentage
virtual std::tuple<int, int> getSupportedScalingRange() const = 0; virtual std::tuple<int, int> getSupportedScalingRange() const = 0;
/// Converts provided rect from logical coordinates into coordinates within window, accounting for scaling and viewport
virtual Rect convertLogicalPointsToWindow(const Rect & input) const = 0;
}; };

View File

@ -22,10 +22,14 @@
#include "../lib/CAndroidVMHelper.h" #include "../lib/CAndroidVMHelper.h"
#endif #endif
#ifdef VCMI_IOS
# include "ios/utils.h"
#endif
#include <SDL.h> #include <SDL.h>
// TODO: should be made into a private members of ScreenHandler // TODO: should be made into a private members of ScreenHandler
SDL_Window * mainWindow = nullptr; static SDL_Window * mainWindow = nullptr;
SDL_Renderer * mainRenderer = nullptr; SDL_Renderer * mainRenderer = nullptr;
SDL_Texture * screenTexture = nullptr; SDL_Texture * screenTexture = nullptr;
SDL_Surface * screen = nullptr; //main screen surface SDL_Surface * screen = nullptr; //main screen surface
@ -42,28 +46,70 @@ std::tuple<int, int> ScreenHandler::getSupportedScalingRange() const
// arbitrary limit on *downscaling*. Allow some downscaling, if requested by user. Should be generally limited to 100+ for all but few devices // arbitrary limit on *downscaling*. Allow some downscaling, if requested by user. Should be generally limited to 100+ for all but few devices
static const double minimalScaling = 50; static const double minimalScaling = 50;
Point renderResolution = getPreferredRenderingResolution(); Point renderResolution = getActualRenderResolution();
double maximalScalingWidth = 100.0 * renderResolution.x / minResolution.x; double reservedAreaWidth = settings["video"]["reservedWidth"].Float();
double maximalScalingHeight = 100.0 * renderResolution.y / minResolution.y; Point availableResolution = Point(renderResolution.x * (1 - reservedAreaWidth), renderResolution.y);
double maximalScalingWidth = 100.0 * availableResolution.x / minResolution.x;
double maximalScalingHeight = 100.0 * availableResolution.y / minResolution.y;
double maximalScaling = std::min(maximalScalingWidth, maximalScalingHeight); double maximalScaling = std::min(maximalScalingWidth, maximalScalingHeight);
return { minimalScaling, maximalScaling }; return { minimalScaling, maximalScaling };
} }
Rect ScreenHandler::convertLogicalPointsToWindow(const Rect & input) const
{
Rect result;
// FIXME: use SDL_RenderLogicalToWindow instead? Needs to be tested on ios
float scaleX, scaleY;
SDL_Rect viewport;
SDL_RenderGetScale(mainRenderer, &scaleX, &scaleY);
SDL_RenderGetViewport(mainRenderer, &viewport);
#ifdef VCMI_IOS
// TODO ios: looks like SDL bug actually, try fixing there
const auto nativeScale = iOS_utils::screenScale();
scaleX /= nativeScale;
scaleY /= nativeScale;
#endif
result.x = (viewport.x + input.x) * scaleX;
result.y = (viewport.y + input.y) * scaleY;
result.w = input.w * scaleX;
result.h = input.h * scaleY;
return result;
}
Point ScreenHandler::getPreferredLogicalResolution() const Point ScreenHandler::getPreferredLogicalResolution() const
{ {
Point renderResolution = getPreferredRenderingResolution(); Point renderResolution = getActualRenderResolution();
double reservedAreaWidth = settings["video"]["reservedWidth"].Float();
Point availableResolution = Point(renderResolution.x * (1 - reservedAreaWidth), renderResolution.y);
auto [minimalScaling, maximalScaling] = getSupportedScalingRange(); auto [minimalScaling, maximalScaling] = getSupportedScalingRange();
int userScaling = settings["video"]["resolution"]["scaling"].Integer(); int userScaling = settings["video"]["resolution"]["scaling"].Integer();
int scaling = std::clamp(userScaling, minimalScaling, maximalScaling); int scaling = std::clamp(userScaling, minimalScaling, maximalScaling);
Point logicalResolution = renderResolution * 100.0 / scaling; Point logicalResolution = availableResolution * 100.0 / scaling;
return logicalResolution; return logicalResolution;
} }
Point ScreenHandler::getPreferredRenderingResolution() const Point ScreenHandler::getActualRenderResolution() const
{
assert(mainRenderer != nullptr);
Point result;
SDL_GetRendererOutputSize(mainRenderer, &result.x, &result.y);
return result;
}
Point ScreenHandler::getPreferredWindowResolution() const
{ {
if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED) if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED)
{ {
@ -178,11 +224,14 @@ void ScreenHandler::updateWindowState()
{ {
case EWindowMode::FULLSCREEN_EXCLUSIVE: case EWindowMode::FULLSCREEN_EXCLUSIVE:
{ {
// for some reason, VCMI fails to switch from FULLSCREEN_BORDERLESS_WINDOWED to FULLSCREEN_EXCLUSIVE directly
// Switch to windowed mode first to avoid this bug
SDL_SetWindowFullscreen(mainWindow, 0);
SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN); SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN);
SDL_DisplayMode mode; SDL_DisplayMode mode;
SDL_GetDesktopDisplayMode(displayIndex, &mode); SDL_GetDesktopDisplayMode(displayIndex, &mode);
Point resolution = getPreferredRenderingResolution(); Point resolution = getPreferredWindowResolution();
mode.w = resolution.x; mode.w = resolution.x;
mode.h = resolution.y; mode.h = resolution.y;
@ -200,7 +249,7 @@ void ScreenHandler::updateWindowState()
} }
case EWindowMode::WINDOWED: case EWindowMode::WINDOWED:
{ {
Point resolution = getPreferredRenderingResolution(); Point resolution = getPreferredWindowResolution();
SDL_SetWindowFullscreen(mainWindow, 0); SDL_SetWindowFullscreen(mainWindow, 0);
SDL_SetWindowSize(mainWindow, resolution.x, resolution.y); SDL_SetWindowSize(mainWindow, resolution.x, resolution.y);
SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex)); SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex));
@ -290,7 +339,7 @@ SDL_Window * ScreenHandler::createWindowImpl(Point dimensions, int flags, bool c
SDL_Window * ScreenHandler::createWindow() SDL_Window * ScreenHandler::createWindow()
{ {
#ifndef VCMI_MOBILE #ifndef VCMI_MOBILE
Point dimensions = getPreferredRenderingResolution(); Point dimensions = getPreferredWindowResolution();
switch(getPreferredWindowMode()) switch(getPreferredWindowMode())
{ {
@ -350,7 +399,7 @@ void ScreenHandler::validateSettings()
{ {
//we only check that our desired window size fits on screen //we only check that our desired window size fits on screen
int displayIndex = getPreferredDisplayIndex(); int displayIndex = getPreferredDisplayIndex();
Point resolution = getPreferredRenderingResolution(); Point resolution = getPreferredWindowResolution();
SDL_DisplayMode mode; SDL_DisplayMode mode;
@ -368,7 +417,7 @@ void ScreenHandler::validateSettings()
if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_EXCLUSIVE) if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_EXCLUSIVE)
{ {
auto legalOptions = getSupportedResolutions(); auto legalOptions = getSupportedResolutions();
Point selectedResolution = getPreferredRenderingResolution(); Point selectedResolution = getPreferredWindowResolution();
if(!vstd::contains(legalOptions, selectedResolution)) if(!vstd::contains(legalOptions, selectedResolution))
{ {

View File

@ -30,14 +30,17 @@ enum class EWindowMode
}; };
/// This class is responsible for management of game window and its main rendering surface /// This class is responsible for management of game window and its main rendering surface
class ScreenHandler : public IScreenHandler class ScreenHandler final : public IScreenHandler
{ {
/// Dimensions of target surfaces/textures, this value is what game logic views as screen size /// Dimensions of target surfaces/textures, this value is what game logic views as screen size
Point getPreferredLogicalResolution() const; Point getPreferredLogicalResolution() const;
/// Dimensions of output window, if different from logicalResolution SDL will perform scaling /// Dimensions of output window, if different from logicalResolution SDL will perform scaling
/// This value is what player views as window size /// This value is what player views as window size
Point getPreferredRenderingResolution() const; Point getPreferredWindowResolution() const;
/// Dimensions of render output, usually same as window size except for high-DPI screens on macOS / iOS
Point getActualRenderResolution() const;
EWindowMode getPreferredWindowMode() const; EWindowMode getPreferredWindowMode() const;
@ -86,4 +89,5 @@ public:
std::vector<Point> getSupportedResolutions() const final; std::vector<Point> getSupportedResolutions() const final;
std::vector<Point> getSupportedResolutions(int displayIndex) const; std::vector<Point> getSupportedResolutions(int displayIndex) const;
std::tuple<int, int> getSupportedScalingRange() const final; std::tuple<int, int> getSupportedScalingRange() const final;
Rect convertLogicalPointsToWindow(const Rect & input) const final;
}; };

View File

@ -331,11 +331,16 @@ void CToggleBase::setEnabled(bool enabled)
// for overrides // for overrides
} }
void CToggleBase::setSelectedSilent(bool on)
{
selected = on;
doSelect(on);
}
void CToggleBase::setSelected(bool on) void CToggleBase::setSelected(bool on)
{ {
bool changed = (on != selected); bool changed = (on != selected);
selected = on; setSelectedSilent(on);
doSelect(on);
if (changed) if (changed)
callback(on); callback(on);
} }

View File

@ -136,6 +136,9 @@ public:
/// Changes selection to "on", and calls callback /// Changes selection to "on", and calls callback
void setSelected(bool on); void setSelected(bool on);
/// Changes selection to "on" without calling callback
void setSelectedSilent(bool on);
void addCallback(std::function<void(bool)> callback); void addCallback(std::function<void(bool)> callback);
/// Set whether the toggle is currently enabled for user to use, this is only inplemented in ToggleButton, not for other toggles yet. /// Set whether the toggle is currently enabled for user to use, this is only inplemented in ToggleButton, not for other toggles yet.

View File

@ -277,10 +277,18 @@ void GeneralOptionsTab::setGameResolution(int index)
gameRes["height"].Float() = resolution.y; gameRes["height"].Float() = resolution.y;
widget<CLabel>("resolutionLabel")->setText(resolutionToLabelString(resolution.x, resolution.y)); widget<CLabel>("resolutionLabel")->setText(resolutionToLabelString(resolution.x, resolution.y));
GH.dispatchMainThread([](){
boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
GH.onScreenResize();
});
} }
void GeneralOptionsTab::setFullscreenMode(bool on, bool exclusive) void GeneralOptionsTab::setFullscreenMode(bool on, bool exclusive)
{ {
if (on == settings["video"]["fullscreen"].Bool() && exclusive == settings["video"]["realFullscreen"].Bool())
return;
setBoolSetting("video", "realFullscreen", exclusive); setBoolSetting("video", "realFullscreen", exclusive);
setBoolSetting("video", "fullscreen", on); setBoolSetting("video", "fullscreen", on);
@ -288,12 +296,17 @@ void GeneralOptionsTab::setFullscreenMode(bool on, bool exclusive)
std::shared_ptr<CToggleButton> fullscreenBorderlessCheckbox = widget<CToggleButton>("fullscreenBorderlessCheckbox"); std::shared_ptr<CToggleButton> fullscreenBorderlessCheckbox = widget<CToggleButton>("fullscreenBorderlessCheckbox");
if (fullscreenBorderlessCheckbox) if (fullscreenBorderlessCheckbox)
fullscreenBorderlessCheckbox->setSelected(on && !exclusive); fullscreenBorderlessCheckbox->setSelectedSilent(on && !exclusive);
if (fullscreenExclusiveCheckbox) if (fullscreenExclusiveCheckbox)
fullscreenExclusiveCheckbox->setSelected(on && exclusive); fullscreenExclusiveCheckbox->setSelectedSilent(on && exclusive);
updateResolutionSelector(); updateResolutionSelector();
GH.dispatchMainThread([](){
boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
GH.onScreenResize();
});
} }
void GeneralOptionsTab::selectGameScaling() void GeneralOptionsTab::selectGameScaling()

View File

@ -109,13 +109,14 @@
"additionalProperties" : false, "additionalProperties" : false,
"default" : {}, "default" : {},
"required" : [ "required" : [
"resolution", "resolution",
"fullscreen", "reservedWidth",
"realFullscreen", "fullscreen",
"cursor", "realFullscreen",
"showIntro", "cursor",
"spellbookAnimation", "showIntro",
"driver", "spellbookAnimation",
"driver",
"displayIndex", "displayIndex",
"showfps", "showfps",
"targetfps" "targetfps"
@ -134,6 +135,11 @@
"defaultAndroid" : {"width" : 800, "height" : 600, "scaling" : 200 }, "defaultAndroid" : {"width" : 800, "height" : 600, "scaling" : 200 },
"default" : {"width" : 800, "height" : 600, "scaling" : 100 } "default" : {"width" : 800, "height" : 600, "scaling" : 100 }
}, },
"reservedWidth" : {
"type" : "number",
"defaultIOS" : 0.1, // iOS camera cutout / notch is excluded from available area by SDL
"default" : 0
},
"fullscreen" : { "fullscreen" : {
"type" : "boolean", "type" : "boolean",
"default" : false "default" : false