From a06db62288bc3fdbf3f25bcfa046c20510036665 Mon Sep 17 00:00:00 2001 From: Thorbjørn Lindeijer Date: Tue, 5 Mar 2024 10:21:43 +0100 Subject: Added support for scaling the output * Added "Scale" user option, which can either by "Auto" or an explicit scaling factor. Its maximum value depends on the current resolution. The "Auto" factor is based on keeping the logical resolution on at least 800x600, wheres the maximum scale is based on keeping the logical resolution on at least 640x480. * Enabled support for High DPI. This means the rendering target can now have a different resolution than the window size, which can happen on macOS, Windows and Wayland. The resulting scale is multiplied by the above user-controlled scale. Currently, this looks ugly for non-integer scales, which are not used on macOS and can only be configured on some Wayland compositors. Has not been tested on Windows. * Simplified OpenGL initialization (moved out of _beginDraw). * Made sure _beginDraw/_endDraw sets a clip area also for SDLGraphics. --- src/client.cpp | 25 ++++++---- src/client.h | 3 +- src/defaults.cpp | 1 + src/game.cpp | 3 +- src/graphics.cpp | 29 +++++------- src/graphics.h | 23 +++++++-- src/gui/equipmentwindow.cpp | 3 -- src/gui/gui.cpp | 21 +++++--- src/gui/sdlinput.cpp | 25 ++++++---- src/gui/setup_video.cpp | 106 ++++++++++++++++++++++++++++++++++------- src/gui/setup_video.h | 5 ++ src/gui/viewport.cpp | 19 ++++---- src/openglgraphics.cpp | 113 ++++++++++++++++++++++++++++---------------- src/openglgraphics.h | 18 +++++-- src/sdlgraphics.cpp | 51 ++++++++++++++++++-- src/sdlgraphics.h | 15 ++++-- src/video.cpp | 69 +++++++++++++++++---------- src/video.h | 11 +++++ 18 files changed, 386 insertions(+), 154 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index 1348112d..eac55c83 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -310,6 +310,7 @@ Client::Client(const Options &options): videoSettings.windowMode = static_cast(config.getIntValue("windowmode")); videoSettings.width = config.getIntValue("screenwidth"); videoSettings.height = config.getIntValue("screenheight"); + videoSettings.userScale = config.getIntValue("scale"); videoSettings.vsync = config.getBoolValue("vsync"); videoSettings.openGL = useOpenGL; @@ -348,9 +349,6 @@ Client::Client(const Options &options): } #endif - // Initialize for drawing - graphics->_beginDraw(); - Theme::prepareThemePath(); // Initialize the item and emote shortcuts. @@ -508,7 +506,8 @@ int Client::exec() case SDL_WINDOWEVENT: switch (event.window.event) { case SDL_WINDOWEVENT_SIZE_CHANGED: - videoResized(event.window.data1, event.window.data2); + handleWindowSizeChanged(event.window.data1, + event.window.data2); break; } break; @@ -1322,17 +1321,25 @@ void Client::accountLogin(LoginData *loginData) config.setValue("remember", loginData->remember); } -void Client::videoResized(int width, int height) +void Client::handleWindowSizeChanged(int width, int height) { // Store the new size in the configuration. config.setValue("screenwidth", width); config.setValue("screenheight", height); - graphics->videoResized(width, height); + mVideo.windowSizeChanged(width, height); + + checkGraphicsSize(); +} + +void Client::checkGraphicsSize() +{ + const int width = graphics->getWidth(); + const int height = graphics->getHeight(); - // Logical size might be different from physical - width = graphics->getWidth(); - height = graphics->getHeight(); + const auto guiTop = gui->getTop(); + if (guiTop->getWidth() == width && guiTop->getHeight() == height) + return; gui->videoResized(width, height); diff --git a/src/client.h b/src/client.h index be591979..49780a38 100644 --- a/src/client.h +++ b/src/client.h @@ -213,7 +213,8 @@ public: * Should be called after the window has been resized. Makes sure the GUI * and game adapt to the new size. */ - void videoResized(int width, int height); + void handleWindowSizeChanged(int width, int height); + void checkGraphicsSize(); static bool isActive(); static bool hasInputFocus(); diff --git a/src/defaults.cpp b/src/defaults.cpp index 1830d394..3c03355a 100644 --- a/src/defaults.cpp +++ b/src/defaults.cpp @@ -80,6 +80,7 @@ DefaultsData* getConfigDefaults() AddDEF(configData, "windowmode", static_cast(WindowMode::Windowed)); AddDEF(configData, "screenwidth", defaultScreenWidth); AddDEF(configData, "screenheight", defaultScreenHeight); + AddDEF(configData, "scale", 0); AddDEF(configData, "vsync", true); AddDEF(configData, "sound", false); AddDEF(configData, "sfxVolume", 100); diff --git a/src/game.cpp b/src/game.cpp index b7cc02fe..acf2d5a2 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -397,7 +397,8 @@ void Game::handleInput() if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { // Let the client deal with this one (it'll pass down from there) - Client::instance()->videoResized(event.window.data1, event.window.data2); + Client::instance()->handleWindowSizeChanged(event.window.data1, + event.window.data2); } // Keyboard events (for discontinuous keys) else if (event.type == SDL_KEYDOWN) diff --git a/src/graphics.cpp b/src/graphics.cpp index b5b30995..19f87e0c 100644 --- a/src/graphics.cpp +++ b/src/graphics.cpp @@ -33,20 +33,10 @@ void ImageRect::setAlpha(float alpha) } } -void Graphics::videoResized(int w, int h) +void Graphics::updateSize(int width, int height, float /*scale*/) { - mWidth = w; - mHeight = h; -} - -int Graphics::getWidth() const -{ - return mWidth; -} - -int Graphics::getHeight() const -{ - return mHeight; + mWidth = width; + mHeight = height; } bool Graphics::drawImage(Image *image, int x, int y) @@ -63,9 +53,6 @@ bool Graphics::drawImage(Image *image, int width, int height, bool useColor) { - if (!image) - return false; - return drawRescaledImage(image, srcX, srcY, dstX, dstY, @@ -133,3 +120,13 @@ void Graphics::drawImageRect(int x, int y, int w, int h, imgRect.grid[1], imgRect.grid[5], imgRect.grid[7], imgRect.grid[3], imgRect.grid[4]); } + +void Graphics::_beginDraw() +{ + pushClipArea(gcn::Rectangle(0, 0, mWidth, mHeight)); +} + +void Graphics::_endDraw() +{ + popClipArea(); +} diff --git a/src/graphics.h b/src/graphics.h index 956e729d..91bb01cf 100644 --- a/src/graphics.h +++ b/src/graphics.h @@ -80,7 +80,10 @@ class Graphics : public gcn::Graphics */ virtual void setVSync(bool sync) = 0; - virtual void videoResized(int w, int h); + /** + * Called when the window size or scale has changed. + */ + virtual void updateSize(int width, int height, float scale); using gcn::Graphics::drawImage; @@ -161,14 +164,24 @@ class Graphics : public gcn::Graphics virtual void updateScreen() = 0; /** - * Returns the width of the screen. + * Returns the logical width of the screen. + */ + int getWidth() const { return mWidth; } + + /** + * Returns the logical height of the screen. */ - int getWidth() const; + int getHeight() const { return mHeight; } /** - * Returns the height of the screen. + * Converts a window coordinate to a logical coordinate. Used for + * converting mouse coordinates. */ - int getHeight() const; + virtual void windowToLogical(int windowX, int windowY, + float &logicalX, float &logicalY) const = 0; + + void _beginDraw() override; + void _endDraw() override; /** * Takes a screenshot and returns it as SDL surface. diff --git a/src/gui/equipmentwindow.cpp b/src/gui/equipmentwindow.cpp index 45f3dcd1..e6230aed 100644 --- a/src/gui/equipmentwindow.cpp +++ b/src/gui/equipmentwindow.cpp @@ -246,9 +246,6 @@ void EquipmentWindow::mouseMoved(gcn::MouseEvent &event) const int x = event.getX(); const int y = event.getY(); - int mouseX, mouseY; - SDL_GetMouseState(&mouseX, &mouseY); - // Show ItemTooltip std::string slotName = getSlotName(x, y); if (!slotName.empty()) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2d470a3e..62350f99 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -193,11 +193,18 @@ void Gui::logic() void Gui::draw() { - mGraphics->pushClipArea(getTop()->getDimension()); - getTop()->draw(mGraphics); + mGraphics->_beginDraw(); - int mouseX, mouseY; + mGraphics->pushClipArea(mTop->getDimension()); + mTop->draw(mGraphics); + mGraphics->popClipArea(); + + int mouseX; + int mouseY; Uint8 button = SDL_GetMouseState(&mouseX, &mouseY); + float logicalX; + float logicalY; + graphics->windowToLogical(mouseX, mouseY, logicalX, logicalY); if ((Client::hasMouseFocus() || button & SDL_BUTTON(1)) && mCustomCursor @@ -208,11 +215,11 @@ void Gui::draw() static_cast(mGraphics)->drawImage( mouseCursor, - mouseX - 15, - mouseY - 17); + logicalX - 15, + logicalY - 17); } - mGraphics->popClipArea(); + mGraphics->_endDraw(); } void Gui::videoResized(int width, int height) @@ -221,6 +228,8 @@ void Gui::videoResized(int width, int height) int oldWidth = top->getWidth(); int oldHeight = top->getHeight(); + if (oldWidth == width && oldHeight == height) + return; top->setSize(width, height); top->adjustAfterResize(oldWidth, oldHeight); diff --git a/src/gui/sdlinput.cpp b/src/gui/sdlinput.cpp index 8396418c..3c8e3ecc 100644 --- a/src/gui/sdlinput.cpp +++ b/src/gui/sdlinput.cpp @@ -57,6 +57,7 @@ */ #include "sdlinput.h" +#include "graphics.h" #include @@ -119,6 +120,15 @@ TextInput SDLInput::dequeueTextInput() return textInput; } +static void setMouseCoordinates(gcn::MouseInput &mouseInput, int x, int y) +{ + float logicalX; + float logicalY; + graphics->windowToLogical(x, y, logicalX, logicalY); + mouseInput.setX(static_cast(logicalX)); + mouseInput.setY(static_cast(logicalY)); +} + void SDLInput::pushInput(SDL_Event event) { gcn::KeyInput keyInput; @@ -154,8 +164,7 @@ void SDLInput::pushInput(SDL_Event event) case SDL_MOUSEBUTTONDOWN: mMouseDown = true; - mouseInput.setX(event.button.x); - mouseInput.setY(event.button.y); + setMouseCoordinates(mouseInput, event.button.x, event.button.y); mouseInput.setButton(convertMouseButton(event.button.button)); mouseInput.setType(gcn::MouseInput::PRESSED); mouseInput.setTimeStamp(SDL_GetTicks()); @@ -164,8 +173,7 @@ void SDLInput::pushInput(SDL_Event event) case SDL_MOUSEBUTTONUP: mMouseDown = false; - mouseInput.setX(event.button.x); - mouseInput.setY(event.button.y); + setMouseCoordinates(mouseInput, event.button.x, event.button.y); mouseInput.setButton(convertMouseButton(event.button.button)); mouseInput.setType(gcn::MouseInput::RELEASED); mouseInput.setTimeStamp(SDL_GetTicks()); @@ -173,8 +181,7 @@ void SDLInput::pushInput(SDL_Event event) break; case SDL_MOUSEMOTION: - mouseInput.setX(event.button.x); - mouseInput.setY(event.button.y); + setMouseCoordinates(mouseInput, event.button.x, event.button.y); mouseInput.setButton(gcn::MouseInput::EMPTY); mouseInput.setType(gcn::MouseInput::MOVED); mouseInput.setTimeStamp(SDL_GetTicks()); @@ -184,13 +191,11 @@ void SDLInput::pushInput(SDL_Event event) case SDL_MOUSEWHEEL: if (event.wheel.y) { #if SDL_VERSION_ATLEAST(2, 26, 0) - mouseInput.setX(event.wheel.mouseX); - mouseInput.setY(event.wheel.mouseY); + setMouseCoordinates(mouseInput, event.wheel.mouseX, event.wheel.mouseY); #else int x, y; SDL_GetMouseState(&x, &y); - mouseInput.setX(x); - mouseInput.setY(y); + setMouseCoordinates(mouseInput, x, y); #endif mouseInput.setButton(gcn::MouseInput::EMPTY); mouseInput.setType(event.wheel.y > 0 ? gcn::MouseInput::WHEEL_MOVED_UP diff --git a/src/gui/setup_video.cpp b/src/gui/setup_video.cpp index 6f2f0d26..21f1935a 100644 --- a/src/gui/setup_video.cpp +++ b/src/gui/setup_video.cpp @@ -148,6 +148,40 @@ private: std::vector mDisplayModes; }; +/** + * The list model for choosing the scale. + * + * \ingroup Interface + */ +class ScaleListModel : public gcn::ListModel +{ +public: + ScaleListModel(const VideoSettings &videoSettings) + : mVideoSettings(videoSettings) + {} + + void setVideoSettings(const VideoSettings &videoSettings) + { + mVideoSettings = videoSettings; + } + + int getNumberOfElements() override + { + return mVideoSettings.maxScale() + 1; + } + + std::string getElementAt(int i) override + { + if (i == 0) + return strprintf(_("Auto (%dx)"), mVideoSettings.autoScale()); + + return strprintf(_("%dx"), i); + } + +private: + VideoSettings mVideoSettings; +}; + static const char *overlayDetailToString(int detail) { @@ -186,8 +220,10 @@ Setup_Video::Setup_Video(): mSDLTransparencyDisabled(config.getBoolValue("disableTransparency")), mWindowModeListModel(new StringListModel({ _("Windowed"), _("Windowed Fullscreen"), _("Fullscreen") })), mResolutionListModel(new ResolutionListModel), + mScaleListModel(new ScaleListModel(mVideoSettings)), mWindowModeDropDown(new DropDown(mWindowModeListModel.get())), mResolutionDropDown(new DropDown(mResolutionListModel.get())), + mScaleDropDown(new DropDown(mScaleListModel.get())), mVSyncCheckBox(new CheckBox(_("VSync"), mVideoSettings.vsync)), mOpenGLCheckBox(new CheckBox(_("OpenGL (Legacy)"), mVideoSettings.openGL)), mCustomCursorCheckBox(new CheckBox(_("Custom cursor"), mCustomCursorEnabled)), @@ -232,9 +268,11 @@ Setup_Video::Setup_Video(): mResolutionDropDown->setSelected(mResolutionListModel->getIndexOf(mVideoSettings.width, mVideoSettings.height)); mResolutionDropDown->setEnabled(mVideoSettings.windowMode != WindowMode::WindowedFullscreen); + mScaleDropDown->setSelected(mVideoSettings.userScale); // Set actions mWindowModeDropDown->setActionEventId("windowmode"); + mResolutionDropDown->setActionEventId("resolution"); mCustomCursorCheckBox->setActionEventId("customcursor"); mParticleEffectsCheckBox->setActionEventId("particleeffects"); mDisableSDLTransparencyCheckBox->setActionEventId("disableTransparency"); @@ -248,6 +286,7 @@ Setup_Video::Setup_Video(): // Set listeners mWindowModeDropDown->addActionListener(this); + mResolutionDropDown->addActionListener(this); mCustomCursorCheckBox->addActionListener(this); mOpenGLCheckBox->addActionListener(this); mParticleEffectsCheckBox->addActionListener(this); @@ -270,11 +309,13 @@ Setup_Video::Setup_Video(): place.getCell().setHAlign(LayoutCell::FILL); place(0, 0, new Label(_("Window mode:"))); - place(1, 0, mWindowModeDropDown, 2); + place(1, 0, mWindowModeDropDown, 2).setPadding(2); place(0, 1, new Label(_("Resolution:"))); - place(1, 1, mResolutionDropDown, 2); - place(0, 2, mVSyncCheckBox, 4); - place(0, 3, mOpenGLCheckBox, 4); + place(1, 1, mResolutionDropDown, 2).setPadding(2); + place(0, 2, new Label(_("Scale:"))); + place(1, 2, mScaleDropDown, 2).setPadding(2); + place(0, 3, mVSyncCheckBox, 4); + place(0, 4, mOpenGLCheckBox, 4); place = getPlacer(0, 1); place.getCell().setHAlign(LayoutCell::FILL); @@ -304,24 +345,29 @@ void Setup_Video::apply() { // Video mode changes auto &video = Client::getVideo(); - auto videoSettings = video.settings(); + mVideoSettings = video.settings(); + + mVideoSettings.windowMode = static_cast(mWindowModeDropDown->getSelected()); if (mResolutionDropDown->getSelected() > 0) { const auto &mode = mResolutionListModel->getModeAt(mResolutionDropDown->getSelected()); - videoSettings.width = mode.width; - videoSettings.height = mode.height; + mVideoSettings.width = mode.width; + mVideoSettings.height = mode.height; } - videoSettings.windowMode = static_cast(mWindowModeDropDown->getSelected()); - videoSettings.vsync = mVSyncCheckBox->isSelected(); + mVideoSettings.userScale = std::max(0, mScaleDropDown->getSelected()); + mVideoSettings.vsync = mVSyncCheckBox->isSelected(); - if (video.apply(videoSettings)) + if (video.apply(mVideoSettings)) { - config.setValue("windowmode", static_cast(videoSettings.windowMode)); - config.setValue("vsync", videoSettings.vsync); - config.setValue("screenwidth", videoSettings.width); - config.setValue("screenheight", videoSettings.height); + config.setValue("windowmode", static_cast(mVideoSettings.windowMode)); + config.setValue("scale", mVideoSettings.userScale); + config.setValue("vsync", mVideoSettings.vsync); + config.setValue("screenwidth", mVideoSettings.width); + config.setValue("screenheight", mVideoSettings.height); + + Client::instance()->checkGraphicsSize(); } else { @@ -387,9 +433,11 @@ void Setup_Video::apply() void Setup_Video::cancel() { // Set back to the current video mode. + mVideoSettings = Client::getVideo().settings(); + mWindowModeDropDown->setSelected(static_cast(mVideoSettings.windowMode)); mResolutionDropDown->setSelected(mResolutionListModel->getIndexOf(mVideoSettings.width, mVideoSettings.height)); - + mScaleDropDown->setSelected(mVideoSettings.userScale); mVSyncCheckBox->setSelected(mVideoSettings.vsync); mOpenGLCheckBox->setSelected(mVideoSettings.openGL); mCustomCursorCheckBox->setSelected(mCustomCursorEnabled); @@ -416,7 +464,7 @@ void Setup_Video::action(const gcn::ActionEvent &event) { const std::string &id = event.getId(); - if (id == "windowmode") + if (id == "windowmode" || id == "resolution") { auto windowMode = static_cast(mWindowModeDropDown->getSelected()); @@ -433,6 +481,12 @@ void Setup_Video::action(const gcn::ActionEvent &event) { mResolutionDropDown->setEnabled(true); } + + refreshScaleList(); + } + else if (id == "resolution") + { + refreshScaleList(); } else if (id == "customcursor") { @@ -488,3 +542,23 @@ void Setup_Video::action(const gcn::ActionEvent &event) } } } + +void Setup_Video::refreshScaleList() +{ + if (mResolutionDropDown->getSelected() > 0) + { + const auto &mode = mResolutionListModel->getModeAt(mResolutionDropDown->getSelected()); + mVideoSettings.width = mode.width; + mVideoSettings.height = mode.height; + } + else + { + auto &videoSettings = Client::getVideo().settings(); + mVideoSettings.width = videoSettings.width; + mVideoSettings.height = videoSettings.height; + } + + mScaleListModel->setVideoSettings(mVideoSettings); + mScaleDropDown->setListModel(mScaleListModel.get()); + mScaleDropDown->setSelected(mVideoSettings.userScale); +} diff --git a/src/gui/setup_video.h b/src/gui/setup_video.h index d16f6d73..0914f6b1 100644 --- a/src/gui/setup_video.h +++ b/src/gui/setup_video.h @@ -31,6 +31,7 @@ #include class ResolutionListModel; +class ScaleListModel; class Setup_Video : public SetupTab, public gcn::ActionListener, public gcn::KeyListener @@ -45,6 +46,8 @@ class Setup_Video : public SetupTab, public gcn::ActionListener, void action(const gcn::ActionEvent &event) override; private: + void refreshScaleList(); + VideoSettings mVideoSettings; bool mCustomCursorEnabled; bool mParticleEffectsEnabled; @@ -53,6 +56,7 @@ class Setup_Video : public SetupTab, public gcn::ActionListener, std::unique_ptr mWindowModeListModel; std::unique_ptr mResolutionListModel; + std::unique_ptr mScaleListModel; //gcn::Label *scrollRadiusLabel; //gcn::Label *scrollLazinessLabel; @@ -61,6 +65,7 @@ class Setup_Video : public SetupTab, public gcn::ActionListener, gcn::DropDown *mWindowModeDropDown; gcn::DropDown *mResolutionDropDown; + gcn::DropDown *mScaleDropDown; gcn::CheckBox *mVSyncCheckBox; gcn::CheckBox *mOpenGLCheckBox; gcn::CheckBox *mCustomCursorCheckBox; diff --git a/src/gui/viewport.cpp b/src/gui/viewport.cpp index a31a65f6..b71e6530 100644 --- a/src/gui/viewport.cpp +++ b/src/gui/viewport.cpp @@ -39,14 +39,10 @@ #include "net/net.h" #include "net/playerhandler.h" -#include "resources/resourcemanager.h" - #include "utils/stringutils.h" #include -extern volatile int tick_time; - Viewport::Viewport() { setOpaque(false); @@ -81,8 +77,6 @@ void Viewport::setMap(Map *map) mMap = map; } -extern MiniStatusWindow *miniStatusWindow; - void Viewport::draw(gcn::Graphics *gcnGraphics) { static int lastTick = tick_time; @@ -291,7 +285,13 @@ void Viewport::logic() void Viewport::_followMouse() { - Uint8 button = SDL_GetMouseState(&mMouseX, &mMouseY); + const Uint8 button = SDL_GetMouseState(&mMouseX, &mMouseY); + float logicalX; + float logicalY; + graphics->windowToLogical(mMouseX, mMouseY, logicalX, logicalY); + mMouseX = static_cast(logicalX); + mMouseY = static_cast(logicalY); + // If the left button is dragged if (mPlayerFollowMouse && button & SDL_BUTTON(1)) { @@ -459,7 +459,8 @@ void Viewport::mousePressed(gcn::MouseEvent &event) mPopupMenu->showPopup(event.getX(), event.getY(), mHoverBeing); return; } - else if (mHoverItem) + + if (mHoverItem) { mPopupMenu->showPopup(event.getX(), event.getY(), mHoverItem); return; @@ -541,7 +542,7 @@ void Viewport::mouseDragged(gcn::MouseEvent &event) { mLocalWalkTime = tick_time; local_player->setDestination(event.getX() + (int) mPixelViewX, - event.getY() + (int) mPixelViewY); + event.getY() + (int) mPixelViewY); local_player->pathSetByMouse(); } } diff --git a/src/openglgraphics.cpp b/src/openglgraphics.cpp index 3cc1b9e3..ea37bdaa 100644 --- a/src/openglgraphics.cpp +++ b/src/openglgraphics.cpp @@ -19,14 +19,15 @@ * along with this program. If not, see . */ +#ifdef USE_OPENGL + #include "openglgraphics.h" #include "log.h" +#include "video.h" #include "resources/image.h" -#ifdef USE_OPENGL - #ifdef __APPLE__ #include #endif @@ -42,6 +43,19 @@ const unsigned int vertexBufSize = 500; GLuint OpenGLGraphics::mLastImage = 0; +std::unique_ptr OpenGLGraphics::create(SDL_Window *window, + const VideoSettings &settings) +{ + SDL_GLContext glContext = SDL_GL_CreateContext(window); + if (!glContext) + return {}; + + if (settings.vsync) + SDL_GL_SetSwapInterval(1); + + return std::make_unique(window, glContext); +} + OpenGLGraphics::OpenGLGraphics(SDL_Window *window, SDL_GLContext glContext) : mWindow(window) , mContext(glContext) @@ -77,6 +91,23 @@ OpenGLGraphics::OpenGLGraphics(SDL_Window *window, SDL_GLContext glContext) Image::mTextureSize = texSize; logger->log("OpenGL texture size: %d pixels%s", Image::mTextureSize, rectTex ? " (rectangle textures)" : ""); + + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0, (double)mWidth, (double)mHeight, 0.0, -1.0, 1.0); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glEnable(GL_SCISSOR_TEST); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); } OpenGLGraphics::~OpenGLGraphics() @@ -98,15 +129,28 @@ void OpenGLGraphics::setReduceInputLag(bool reduceInputLag) mReduceInputLag = reduceInputLag; } -void OpenGLGraphics::videoResized(int width, int height) +void OpenGLGraphics::updateSize(int windowWidth, int windowHeight, float scale) { - _endDraw(); + mScale = scale; - SDL_GL_GetDrawableSize(mWindow, &mWidth, &mHeight); + int drawableWidth; + int drawableHeight; + SDL_GL_GetDrawableSize(mWindow, &drawableWidth, &drawableHeight); - glViewport(0, 0, mWidth, mHeight); + glViewport(0, 0, drawableWidth, drawableHeight); + + float displayScaleX = windowWidth > 0 ? static_cast(drawableWidth) / windowWidth : 1.0f; + float displayScaleY = windowHeight > 0 ? static_cast(drawableHeight) / windowHeight : 1.0f; + + mScaleX = mScale * displayScaleX; + mScaleY = mScale * displayScaleY; + + mWidth = std::ceil(drawableWidth / mScaleX); + mHeight = std::ceil(drawableHeight / mScaleY); - _beginDraw(); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0, (double)mWidth, (double)mHeight, 0.0, -1.0, 1.0); } static inline void drawQuad(Image *image, @@ -596,32 +640,11 @@ void OpenGLGraphics::updateScreen() glFinish(); } -void OpenGLGraphics::_beginDraw() -{ - glMatrixMode(GL_TEXTURE); - glLoadIdentity(); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - glOrtho(0.0, (double)mWidth, (double)mHeight, 0.0, -1.0, 1.0); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - glEnable(GL_SCISSOR_TEST); - - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - - pushClipArea(gcn::Rectangle(0, 0, mWidth, mHeight)); -} - -void OpenGLGraphics::_endDraw() +void OpenGLGraphics::windowToLogical(int windowX, int windowY, + float &logicalX, float &logicalY) const { - popClipArea(); + logicalX = windowX / mScale; + logicalY = windowY / mScale; } SDL_Surface *OpenGLGraphics::getScreenshot() @@ -685,10 +708,14 @@ bool OpenGLGraphics::pushClipArea(gcn::Rectangle area) glPushMatrix(); glTranslatef(transX, transY, 0); - glScissor(mClipStack.top().x, - mHeight - mClipStack.top().y - mClipStack.top().height, - mClipStack.top().width, - mClipStack.top().height); + + int x = (int) (mClipStack.top().x * mScaleX); + int y = (int) ((mHeight - mClipStack.top().y - + mClipStack.top().height) * mScaleY); + int width = (int) (mClipStack.top().width * mScaleX); + int height = (int) (mClipStack.top().height * mScaleY); + + glScissor(x, y, width, height); return result; } @@ -697,14 +724,18 @@ void OpenGLGraphics::popClipArea() { Graphics::popClipArea(); + glPopMatrix(); + if (mClipStack.empty()) return; - glPopMatrix(); - glScissor(mClipStack.top().x, - mHeight - mClipStack.top().y - mClipStack.top().height, - mClipStack.top().width, - mClipStack.top().height); + int x = (int) (mClipStack.top().x * mScaleX); + int y = (int) ((mHeight - mClipStack.top().y - + mClipStack.top().height) * mScaleY); + int width = (int) (mClipStack.top().width * mScaleX); + int height = (int) (mClipStack.top().height * mScaleY); + + glScissor(x, y, width, height); } void OpenGLGraphics::setColor(const gcn::Color& color) diff --git a/src/openglgraphics.h b/src/openglgraphics.h index 0bb07363..ab08d075 100644 --- a/src/openglgraphics.h +++ b/src/openglgraphics.h @@ -22,16 +22,23 @@ #ifndef OPENGLGRAPHICS_H #define OPENGLGRAPHICS_H +#ifdef USE_OPENGL #include "graphics.h" -#ifdef USE_OPENGL #define NO_SDL_GLEXT #include +#include + +class VideoSettings; + class OpenGLGraphics final : public Graphics { public: + static std::unique_ptr create(SDL_Window *window, + const VideoSettings &settings); + OpenGLGraphics(SDL_Window *window, SDL_GLContext glContext); ~OpenGLGraphics() override; @@ -48,7 +55,7 @@ class OpenGLGraphics final : public Graphics void setReduceInputLag(bool reduceInputLag); bool getReduceInputLag() const { return mReduceInputLag; } - void videoResized(int w, int h) override; + void updateSize(int windowWidth, int windowHeight, float scale) override; bool drawImage(Image *image, int srcX, int srcY, @@ -78,8 +85,8 @@ class OpenGLGraphics final : public Graphics void updateScreen() override; - void _beginDraw() override; - void _endDraw() override; + void windowToLogical(int windowX, int windowY, + float &logicalX, float &logicalY) const override; bool pushClipArea(gcn::Rectangle area) override; void popClipArea() override; @@ -118,6 +125,9 @@ class OpenGLGraphics final : public Graphics GLfloat *mFloatTexArray; GLint *mIntTexArray; GLint *mIntVertArray; + float mScale = 1.0f; + float mScaleX = 1.0f; + float mScaleY = 1.0f; bool mAlpha = false; bool mTexture = false; bool mColorAlpha = false; diff --git a/src/sdlgraphics.cpp b/src/sdlgraphics.cpp index 3f6e809b..8e77a64f 100644 --- a/src/sdlgraphics.cpp +++ b/src/sdlgraphics.cpp @@ -23,12 +23,30 @@ #include "log.h" #include "resources/image.h" +#include "utils/stringutils.h" +#include "video.h" #include -SDLGraphics::SDLGraphics(SDL_Window *window, SDL_Renderer *renderer) - : mWindow(window) - , mRenderer(renderer) +std::unique_ptr SDLGraphics::create(SDL_Window *window, const VideoSettings &settings) +{ + int rendererFlags = 0; + if (settings.vsync) + rendererFlags |= SDL_RENDERER_PRESENTVSYNC; + + SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, rendererFlags); + if (!renderer) + { + logger->error(strprintf("Failed to create renderer: %s", + SDL_GetError())); + return {}; + } + + return std::make_unique(renderer); +} + +SDLGraphics::SDLGraphics(SDL_Renderer *renderer) + : mRenderer(renderer) { Image::setRenderer(mRenderer); @@ -71,9 +89,20 @@ void SDLGraphics::setVSync(bool sync) #endif } -void SDLGraphics::videoResized(int w, int h) +void SDLGraphics::updateSize(int windowWidth, int windowHeight, float scale) { SDL_GetRendererOutputSize(mRenderer, &mWidth, &mHeight); + + float displayScaleX = windowWidth > 0 ? static_cast(mWidth) / windowWidth : 1.0f; + float displayScaleY = windowHeight > 0 ? static_cast(mHeight) / windowHeight : 1.0f; + + float scaleX = scale * displayScaleX; + float scaleY = scale * displayScaleY; + + mWidth = std::ceil(mWidth / scaleX); + mHeight = std::ceil(mHeight / scaleY); + + SDL_RenderSetScale(mRenderer, scaleX, scaleY); } bool SDLGraphics::drawRescaledImage(Image *image, @@ -148,6 +177,20 @@ void SDLGraphics::updateScreen() SDL_RenderPresent(mRenderer); } +void SDLGraphics::windowToLogical(int windowX, int windowY, + float &logicalX, float &logicalY) const +{ +#if SDL_VERSION_ATLEAST(2, 0, 18) + SDL_RenderWindowToLogical(mRenderer, windowX, windowY, &logicalX, &logicalY); +#else + float scaleX; + float scaleY; + SDL_RenderGetScale(mRenderer, &scaleX, &scaleY); + logicalX = windowX / scaleX; + logicalY = windowY / scaleY; +#endif +} + SDL_Surface *SDLGraphics::getScreenshot() { #if SDL_BYTEORDER == SDL_BIG_ENDIAN diff --git a/src/sdlgraphics.h b/src/sdlgraphics.h index 6685fc83..ab6407f0 100644 --- a/src/sdlgraphics.h +++ b/src/sdlgraphics.h @@ -24,15 +24,22 @@ #include "graphics.h" +#include + +class VideoSettings; + class SDLGraphics final : public Graphics { public: - SDLGraphics(SDL_Window *window, SDL_Renderer *renderer); + static std::unique_ptr create(SDL_Window *window, + const VideoSettings &settings); + + SDLGraphics(SDL_Renderer *renderer); ~SDLGraphics() override; void setVSync(bool sync) override; - void videoResized(int w, int h) override; + void updateSize(int windowWidth, int windowHeight, float scale) override; bool drawRescaledImage(Image *image, int srcX, int srcY, @@ -49,6 +56,9 @@ public: void updateScreen() override; + void windowToLogical(int windowX, int windowY, + float &logicalX, float &logicalY) const override; + SDL_Surface *getScreenshot() override; bool pushClipArea(gcn::Rectangle area) override; @@ -66,7 +76,6 @@ public: private: void updateSDLClipRect(); - SDL_Window *mWindow = nullptr; SDL_Renderer *mRenderer = nullptr; }; diff --git a/src/video.cpp b/src/video.cpp index 9e69ec27..7ab21d12 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -31,6 +31,26 @@ #include +int VideoSettings::scale() const +{ + if (userScale == 0) + return autoScale(); + + return std::clamp(userScale, 1, maxScale()); +} + +int VideoSettings::autoScale() const +{ + // Automatic scaling factor based on at least 800x600 logical resolution + return std::max(1, std::min(width / 800, height / 600)); +} + +int VideoSettings::maxScale() const +{ + // Logical resolution needs to stay at least 640x480 + return std::max(1, std::min(width / 640, height / 480)); +} + Video::~Video() { mGraphics.reset(); // reset graphics first @@ -66,7 +86,7 @@ Graphics *Video::initialize(const VideoSettings &settings) } } - int windowFlags = SDL_WINDOW_RESIZABLE; + int windowFlags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; const char *videoMode = "windowed"; switch (mSettings.windowMode) @@ -115,40 +135,27 @@ Graphics *Video::initialize(const VideoSettings &settings) } } + // Make sure the resolution is reflected in current settings + SDL_GetWindowSize(mWindow, &mSettings.width, &mSettings.height); + #ifdef USE_OPENGL if (mSettings.openGL) { - SDL_GLContext glContext = SDL_GL_CreateContext(mWindow); - if (!glContext) + mGraphics = OpenGLGraphics::create(mWindow, mSettings); + if (!mGraphics) { logger->log("Failed to create OpenGL context, falling back to SDL renderer: %s", SDL_GetError()); mSettings.openGL = false; } - else - { - if (mSettings.vsync) - SDL_GL_SetSwapInterval(1); - - mGraphics = std::make_unique(mWindow, glContext); - return mGraphics.get(); - } } #endif - int rendererFlags = 0; - if (settings.vsync) - rendererFlags |= SDL_RENDERER_PRESENTVSYNC; + if (!mGraphics) + mGraphics = SDLGraphics::create(mWindow, mSettings); - SDL_Renderer *renderer = SDL_CreateRenderer(mWindow, -1, rendererFlags); - if (!renderer) - { - logger->error(strprintf("Failed to create renderer: %s", - SDL_GetError())); - return nullptr; - } + mGraphics->updateSize(mSettings.width, mSettings.height, mSettings.scale()); - mGraphics = std::make_unique(mWindow, renderer); return mGraphics.get(); } @@ -206,7 +213,9 @@ bool Video::apply(const VideoSettings &settings) return false; } - if (settings.windowMode == WindowMode::Windowed) { + if (settings.windowMode == WindowMode::Windowed && + (settings.width != mSettings.width || + settings.height != mSettings.height)) { #ifdef __APPLE__ // Workaround SDL2 issue when setting the window size on a window // which the user has put in fullscreen. Unfortunately, this mode can't @@ -216,16 +225,24 @@ bool Video::apply(const VideoSettings &settings) SDL_SetWindowSize(mWindow, settings.width, settings.height); } - mGraphics->setVSync(settings.vsync); - mSettings = settings; - // Make sure the resolution is reflected in current settings SDL_GetWindowSize(mWindow, &mSettings.width, &mSettings.height); + mGraphics->setVSync(mSettings.vsync); + mGraphics->updateSize(mSettings.width, mSettings.height, mSettings.scale()); + return true; } +void Video::windowSizeChanged(int width, int height) +{ + mSettings.width = width; + mSettings.height = height; + + mGraphics->updateSize(width, height, mSettings.scale()); +} + bool Video::initDisplayModes() { const int displayIndex = mSettings.display; diff --git a/src/video.h b/src/video.h index df9cd518..c98434e6 100644 --- a/src/video.h +++ b/src/video.h @@ -49,15 +49,21 @@ struct VideoSettings int width = defaultScreenWidth; int height = defaultScreenHeight; int display = 0; + int userScale = 0; bool vsync = true; bool openGL = false; + int scale() const; + int autoScale() const; + int maxScale() const; + bool operator==(const VideoSettings &other) const { return width == other.width && height == other.height && windowMode == other.windowMode && display == other.display && + userScale == other.userScale && vsync == other.vsync && openGL == other.openGL; } @@ -83,6 +89,11 @@ public: */ bool apply(const VideoSettings &settings); + /** + * Handle a change in window size, possibly adjusting the scale. + */ + void windowSizeChanged(int width, int height); + const DisplayMode &desktopDisplayMode() const { return mDesktopDisplayMode; -- cgit v1.2.3-60-g2f50