From 254c3db4a39aa35274fd7cd4ec2cccde5b92d71b Mon Sep 17 00:00:00 2001 From: Thorbjørn Lindeijer Date: Tue, 27 Feb 2024 10:51:33 +0000 Subject: Added VSync and windowed fullscreen options The configuration and setup UI were adjusted to the new options. This also fixes issues in applying new video settings. Default resolution was changed from 800x600 to 1280x720. VSync is enabled by default while FPS limit was disabled. Display aspect ratio for the resolution options. I had to work around some macOS issues: * Don't change window size when it appears to be "maximized", since it just changes the rendering area while leaving the window maximized. * Unset fullscreen display mode temporarily to allow changing resolutions, otherwise the rendering area no longer matches the screen and mouse input is also off. * Removed SDL_WINDOW_ALLOW_HIGHDPI for now because it causes issues on macOS, since we're not actually handling the scaling factor. A Video class and an SDLGraphics subclass were split off from Graphics. This setup has Less duplication and leaves the OpenGLGraphics and SDLGraphics better separated. Fixes #57 Fixes #58 --- src/CMakeLists.txt | 12 +- src/client.cpp | 89 ++++------- src/client.h | 14 +- src/configuration.cpp | 5 +- src/defaults.cpp | 5 +- src/game.cpp | 4 +- src/graphics.cpp | 341 +---------------------------------------- src/graphics.h | 114 ++++---------- src/gui/gui.cpp | 4 +- src/gui/gui.h | 2 +- src/gui/setup_video.cpp | 340 ++++++++++++++++++++-------------------- src/gui/setup_video.h | 18 +-- src/openglgraphics.cpp | 133 +++++----------- src/openglgraphics.h | 22 +-- src/resources/ambientlayer.cpp | 1 + src/resources/image.h | 2 +- src/sdlgraphics.cpp | 304 ++++++++++++++++++++++++++++++++++++ src/sdlgraphics.h | 73 +++++++++ src/video.cpp | 263 +++++++++++++++++++++++++++++++ src/video.h | 106 +++++++++++++ 20 files changed, 1068 insertions(+), 784 deletions(-) create mode 100644 src/sdlgraphics.cpp create mode 100644 src/sdlgraphics.h create mode 100644 src/video.cpp create mode 100644 src/video.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 628fab82..a138578b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -419,14 +419,14 @@ SET(SRCS avatar.h being.cpp being.h - chatlogger.cpp - chatlogger.h - client.cpp - client.h channel.cpp channel.h channelmanager.cpp channelmanager.h + chatlogger.cpp + chatlogger.h + client.cpp + client.h commandhandler.cpp commandhandler.h compoundsprite.cpp @@ -495,6 +495,8 @@ SET(SRCS properties.h rotationalparticle.cpp rotationalparticle.h + sdlgraphics.cpp + sdlgraphics.h shopitem.cpp shopitem.h simpleanimation.cpp @@ -517,6 +519,8 @@ SET(SRCS variabledata.h vector.cpp vector.h + video.cpp + video.h ) SET(SRCS_TMWA diff --git a/src/client.cpp b/src/client.cpp index 506ad692..c337420d 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -294,33 +294,19 @@ Client::Client(const Options &options): if (!useOpenGL && (config.getValue("disableTransparency", 0) == 1)) Image::SDLdisableTransparency(); -#ifdef USE_OPENGL - // Setup image loading for the right image format - Image::setLoadAsOpenGL(useOpenGL); - - // Create the graphics context - graphics = useOpenGL ? new OpenGLGraphics : new Graphics; -#else - // Create the graphics context - graphics = new Graphics; -#endif + VideoSettings videoSettings; + videoSettings.windowMode = static_cast(config.getIntValue("windowmode")); + videoSettings.width = config.getIntValue("screenwidth"); + videoSettings.height = config.getIntValue("screenheight"); + videoSettings.vsync = config.getBoolValue("vsync"); + videoSettings.openGL = useOpenGL; - const int width = config.getIntValue("screenwidth"); - const int height = config.getIntValue("screenheight"); - const bool fullscreen = config.getBoolValue("screen"); - - // Try to set the desired video mode - if (!graphics->setVideoMode(width, height, fullscreen)) - { - logger->error(strprintf("Couldn't set %dx%d video mode: %s", - width, height, SDL_GetError())); - } + // Try to set the desired video mode and create the graphics context + graphics = mVideo.initialize(videoSettings); - SDL_SetWindowTitle(graphics->getTarget(), + SDL_SetWindowTitle(mVideo.window(), branding.getValue("appName", "Mana").c_str()); - Image::setRenderer(graphics->getRenderer()); - std::string iconFile = branding.getValue("appIcon", "icons/mana"); #ifdef _WIN32 iconFile += ".ico"; @@ -346,7 +332,7 @@ Client::Client(const Options &options): mIcon = IMG_Load(iconFile.c_str()); if (mIcon) { - SDL_SetWindowIcon(graphics->getTarget(), mIcon); + SDL_SetWindowIcon(mVideo.window(), mIcon); } #endif @@ -398,11 +384,11 @@ Client::Client(const Options &options): loginData.registerLogin = false; if (mCurrentServer.hostname.empty()) - mCurrentServer.hostname = branding.getValue("defaultServer","").c_str(); + mCurrentServer.hostname = branding.getValue("defaultServer", std::string()); if (mCurrentServer.port == 0) { - mCurrentServer.port = (short) branding.getValue("defaultPort", + mCurrentServer.port = (unsigned short) branding.getValue("defaultPort", DEFAULT_PORT); mCurrentServer.type = ServerInfo::parseType( branding.getValue("defaultServerType", "tmwathena")); @@ -451,7 +437,6 @@ Client::~Client() delete emoteShortcut; delete gui; - delete graphics; // Shutdown libxml xmlCleanupParser(); @@ -493,20 +478,20 @@ int Client::exec() { switch (event.type) { - case SDL_QUIT: - mState = STATE_EXIT; - break; + case SDL_QUIT: + mState = STATE_EXIT; + break; - case SDL_KEYDOWN: - break; + case SDL_KEYDOWN: + break; - case SDL_WINDOWEVENT: - switch (event.window.event) { - case SDL_WINDOWEVENT_RESIZED: - handleVideoResize(event.window.data1, event.window.data2); - break; - } + case SDL_WINDOWEVENT: + switch (event.window.event) { + case SDL_WINDOWEVENT_SIZE_CHANGED: + videoResized(event.window.data1, event.window.data2); break; + } + break; } guiInput->pushInput(event); @@ -1031,7 +1016,6 @@ void Client::event(Event::Channel channel, const Event &event) if (mLimitFps) SDL_setFramerate(&mFpsManager, fpsLimit); } - } void Client::action(const gcn::ActionEvent &event) @@ -1326,24 +1310,19 @@ void Client::accountLogin(LoginData *loginData) config.setValue("remember", loginData->remember); } -void Client::handleVideoResize(int width, int height) +void Client::videoResized(int width, int height) { - if (graphics->getWidth() == width && graphics->getHeight() == height) - return; + // Store the new size in the configuration. + config.setValue("screenwidth", width); + config.setValue("screenheight", height); graphics->videoResized(width, height); - videoResized(width, height); + // Logical size might be different from physical + width = graphics->getWidth(); + height = graphics->getHeight(); - // Since everything appears to have worked out, remember to store the - // new size in the configuration. - config.setValue("screenwidth", width); - config.setValue("screenheight", height); -} - -void Client::videoResized(int width, int height) -{ - gui->videoResized(); + gui->videoResized(width, height); if (mDesktop) mDesktop->setSize(width, height); @@ -1357,15 +1336,15 @@ void Client::videoResized(int width, int height) bool Client::isActive() { - return !(SDL_GetWindowFlags(graphics->getTarget()) & SDL_WINDOW_MINIMIZED); + return !(SDL_GetWindowFlags(getVideo().window()) & SDL_WINDOW_MINIMIZED); } bool Client::hasInputFocus() { - return SDL_GetWindowFlags(graphics->getTarget()) & SDL_WINDOW_INPUT_FOCUS; + return SDL_GetWindowFlags(getVideo().window()) & SDL_WINDOW_INPUT_FOCUS; } bool Client::hasMouseFocus() { - return SDL_GetWindowFlags(graphics->getTarget()) & SDL_WINDOW_MOUSE_FOCUS; + return SDL_GetWindowFlags(getVideo().window()) & SDL_WINDOW_MOUSE_FOCUS; } diff --git a/src/client.h b/src/client.h index 09233a5c..fe202ccc 100644 --- a/src/client.h +++ b/src/client.h @@ -23,6 +23,7 @@ #define CLIENT_H #include "eventlistener.h" +#include "video.h" #include "net/serverinfo.h" @@ -191,17 +192,15 @@ public: static const std::string &getScreenshotDirectory() { return instance()->mScreenshotDir; } + static Video &getVideo() + { return instance()->mVideo; } + void event(Event::Channel channel, const Event &event) override; void action(const gcn::ActionEvent &event) override; /** - * Should be called after the window has been resized by the user. - */ - void handleVideoResize(int width, int height); - - /** - * Should be called after a succesful resize or change of resolution, makes - * sure the GUI and game adapt to the new size. + * 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); @@ -231,6 +230,7 @@ private: std::string mRootDir; ServerInfo mCurrentServer; + Video mVideo; Game *mGame; Window *mCurrentDialog; diff --git a/src/configuration.cpp b/src/configuration.cpp index f0972052..f7718bdc 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -102,10 +102,9 @@ void Configuration::cleanDefaults() { if (mDefaultsData) { - for (DefaultsData::const_iterator iter = mDefaultsData->begin(); - iter != mDefaultsData->end(); iter++) + for (auto &[_, variableData] : *mDefaultsData) { - delete iter->second; + delete variableData; } delete mDefaultsData; mDefaultsData = nullptr; diff --git a/src/defaults.cpp b/src/defaults.cpp index 1c9cb7aa..8cf79590 100644 --- a/src/defaults.cpp +++ b/src/defaults.cpp @@ -79,9 +79,10 @@ DefaultsData* getConfigDefaults() AddDEF(configData, "particleeffects", true); AddDEF(configData, "logToStandardOut", false); AddDEF(configData, "opengl", false); + AddDEF(configData, "windowmode", static_cast(WindowMode::Windowed)); AddDEF(configData, "screenwidth", defaultScreenWidth); AddDEF(configData, "screenheight", defaultScreenHeight); - AddDEF(configData, "screen", false); + AddDEF(configData, "vsync", true); AddDEF(configData, "sound", false); AddDEF(configData, "sfxVolume", 100); AddDEF(configData, "notificationsVolume", 100); @@ -89,7 +90,7 @@ DefaultsData* getConfigDefaults() AddDEF(configData, "remember", false); AddDEF(configData, "username", ""); AddDEF(configData, "lastCharacter", ""); - AddDEF(configData, "fpslimit", 60); + AddDEF(configData, "fpslimit", 0); AddDEF(configData, "updatehost", ""); AddDEF(configData, "screenshotDirectory", ""); AddDEF(configData, "useScreenshotDirectorySuffix", true); diff --git a/src/game.cpp b/src/game.cpp index ea8cbae3..e27e6f5d 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -396,10 +396,10 @@ void Game::handleInput() { bool used = false; - if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) + 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()->handleVideoResize(event.window.data1, event.window.data2); + Client::instance()->videoResized(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 02144cab..b5b30995 100644 --- a/src/graphics.cpp +++ b/src/graphics.cpp @@ -20,12 +20,9 @@ */ #include "graphics.h" -#include "log.h" #include "resources/image.h" -#include "utils/gettext.h" - #include void ImageRect::setAlpha(float alpha) @@ -36,104 +33,6 @@ void ImageRect::setAlpha(float alpha) } } - -Graphics::~Graphics() -{ - _endDraw(); -} - -void Graphics::setTarget(SDL_Window *target) -{ - _endDraw(); - - mTarget = target; - - if (mTarget) - _beginDraw(); -} - -bool Graphics::setVideoMode(int w, int h, bool fs) -{ - logger->log("Setting video mode %dx%d %s", - w, h, fs ? "fullscreen" : "windowed"); - - int windowFlags = SDL_WINDOW_ALLOW_HIGHDPI; - - if (fs) - windowFlags |= SDL_WINDOW_FULLSCREEN; - else - windowFlags |= SDL_WINDOW_RESIZABLE; - - // TODO_SDL2: Support SDL_WINDOW_FULLSCREEN_DESKTOP - - SDL_Window *window = nullptr; - SDL_Renderer *renderer = nullptr; - SDL_CreateWindowAndRenderer(w, h, windowFlags, &window, &renderer); - - if (!window) - return false; - - SDL_SetWindowMinimumSize(window, 640, 480); - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); - - setTarget(window); - - mRenderer = renderer; - mWidth = w; - mHeight = h; - mFullscreen = fs; - - if (const char *driver = SDL_GetCurrentVideoDriver()) - logger->log("Using video driver: %s", driver); - else - logger->log("Using video driver: not initialized"); - - SDL_RendererInfo info; - - if (SDL_GetRendererInfo(mRenderer, &info) == 0) { - logger->log("Using renderer: %s", info.name); - - logger->log("The renderer is a software fallback: %s", - (info.flags & SDL_RENDERER_SOFTWARE) ? "yes" : "no"); - logger->log("The renderer is hardware accelerated: %s", - (info.flags & SDL_RENDERER_ACCELERATED) ? "yes" : "no"); - logger->log("Vsync: %s", - (info.flags & SDL_RENDERER_PRESENTVSYNC) ? "on" : "off"); - logger->log("Renderer supports rendering to texture: %s", - (info.flags & SDL_RENDERER_TARGETTEXTURE) ? "yes" : "no"); - logger->log("Max texture size: %dx%d", - info.max_texture_width, info.max_texture_height); - } - - return true; -} - -bool Graphics::changeVideoMode(int w, int h, bool fs) -{ - // Just return success if we're already in this mode - if (mWidth == w && - mHeight == h && - mFullscreen == fs) - return true; - - _endDraw(); - - bool success = setVideoMode(w, h, fs); - - // If it didn't work, try to restore the previous mode. If that doesn't - // work either, we're in big trouble and bail out. - if (!success) { - if (!setVideoMode(mWidth, mHeight, mFullscreen)) { - logger->error(_("Failed to change video mode and couldn't " - "switch back to the previous mode!")); - } - } - - _beginDraw(); - - return success; -} - void Graphics::videoResized(int w, int h) { mWidth = w; @@ -155,39 +54,14 @@ bool Graphics::drawImage(Image *image, int x, int y) if (!image) return false; - return drawImage(image, 0, 0, x, y, image->mBounds.w, image->mBounds.h); -} - -bool Graphics::drawRescaledImage(Image *image, int srcX, int srcY, - int dstX, int dstY, - int width, int height, - int desiredWidth, int desiredHeight, - bool useColor) -{ - // Check that preconditions for blitting are met. - if (!mTarget || !image || !image->mTexture) - return false; - - dstX += mClipStack.top().xOffset; - dstY += mClipStack.top().yOffset; - - srcX += image->mBounds.x; - srcY += image->mBounds.y; - - SDL_Rect dstRect; - SDL_Rect srcRect; - dstRect.x = dstX; dstRect.y = dstY; - srcRect.x = srcX; srcRect.y = srcY; - srcRect.w = width; - srcRect.h = height; - dstRect.w = desiredWidth; - dstRect.h = desiredHeight; - - return !(SDL_RenderCopy(mRenderer, image->mTexture, &srcRect, &dstRect) < 0); + return drawImage(image, 0, 0, x, y, image->getWidth(), image->getHeight()); } -bool Graphics::drawImage(Image *image, int srcX, int srcY, int dstX, int dstY, - int width, int height, bool useColor) +bool Graphics::drawImage(Image *image, + int srcX, int srcY, + int dstX, int dstY, + int width, int height, + bool useColor) { if (!image) return false; @@ -201,50 +75,11 @@ bool Graphics::drawImage(Image *image, int srcX, int srcY, int dstX, int dstY, void Graphics::drawImagePattern(Image *image, int x, int y, int w, int h) { - // Check that preconditions for blitting are met. if (!image) return; - const int iw = image->getWidth(); - const int ih = image->getHeight(); - - drawRescaledImagePattern(image, x, y, w, h, iw, ih); -} - -void Graphics::drawRescaledImagePattern(Image *image, - int x, int y, - int w, int h, - int scaledWidth, int scaledHeight) -{ - // Check that preconditions for blitting are met. - if (!mTarget || !image || !image->mTexture) - return; - - if (scaledHeight == 0 || scaledWidth == 0) - return; - - for (int py = 0; py < h; py += scaledHeight) // Y position on pattern plane - { - int dh = (py + scaledHeight >= h) ? h - py : scaledHeight; - int srcY = image->mBounds.y; - int dstY = y + py + mClipStack.top().yOffset; - - for (int px = 0; px < w; px += scaledWidth) // X position on pattern plane - { - int dw = (px + scaledWidth >= w) ? w - px : scaledWidth; - int srcX = image->mBounds.x; - int dstX = x + px + mClipStack.top().xOffset; - - SDL_Rect dstRect; - SDL_Rect srcRect; - dstRect.x = dstX; dstRect.y = dstY; - dstRect.w = dw; dstRect.h = dh; - srcRect.x = srcX; srcRect.y = srcY; - srcRect.w = dw; srcRect.h = dh; - - SDL_RenderCopy(mRenderer, image->mTexture, &srcRect, &dstRect); - } - } + drawRescaledImagePattern(image, x, y, w, h, + image->getWidth(), image->getHeight()); } void Graphics::drawImageRect(int x, int y, int w, int h, @@ -298,163 +133,3 @@ 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::updateScreen() -{ - SDL_RenderPresent(mRenderer); -} - -SDL_Surface *Graphics::getScreenshot() -{ -#if SDL_BYTEORDER == SDL_BIG_ENDIAN - int rmask = 0xff000000; - int gmask = 0x00ff0000; - int bmask = 0x0000ff00; -#else - int rmask = 0x000000ff; - int gmask = 0x0000ff00; - int bmask = 0x00ff0000; -#endif - int amask = 0x00000000; - - SDL_Surface *screenshot = SDL_CreateRGBSurface(0, mWidth, - mHeight, 24, rmask, gmask, bmask, amask); - - SDL_RenderReadPixels(mRenderer, nullptr, - screenshot->format->format, - screenshot->pixels, - screenshot->pitch); - - return screenshot; -} - -bool Graphics::pushClipArea(gcn::Rectangle area) -{ - bool result = gcn::Graphics::pushClipArea(area); - updateSDLClipRect(); - return result; -} - -void Graphics::popClipArea() -{ - gcn::Graphics::popClipArea(); - updateSDLClipRect(); -} - -void Graphics::updateSDLClipRect() -{ - if (mClipStack.empty()) - { - SDL_RenderSetClipRect(mRenderer, nullptr); - return; - } - - const gcn::ClipRectangle &carea = mClipStack.top(); - SDL_Rect rect; - rect.x = carea.x; - rect.y = carea.y; - rect.w = carea.width; - rect.h = carea.height; - - SDL_RenderSetClipRect(mRenderer, &rect); -} - -void Graphics::drawPoint(int x, int y) -{ - if (mClipStack.empty()) - { - throw GCN_EXCEPTION("Clip stack is empty, perhaps you called a draw funtion outside of _beginDraw() and _endDraw()?"); - } - - const gcn::ClipRectangle &top = mClipStack.top(); - - x += top.xOffset; - y += top.yOffset; - - if (!top.isPointInRect(x, y)) - return; - - SDL_SetRenderDrawColor(mRenderer, - (Uint8)(mColor.r), - (Uint8)(mColor.g), - (Uint8)(mColor.b), - (Uint8)(mColor.a)); - SDL_RenderDrawPoint(mRenderer, x, y); -} - -void Graphics::drawLine(int x1, int y1, int x2, int y2) -{ - if (mClipStack.empty()) - { - throw GCN_EXCEPTION("Clip stack is empty, perhaps you called a draw funtion outside of _beginDraw() and _endDraw()?"); - } - - const gcn::ClipRectangle &top = mClipStack.top(); - - x1 += top.xOffset; - y1 += top.yOffset; - x2 += top.xOffset; - y2 += top.yOffset; - - SDL_SetRenderDrawColor(mRenderer, - (Uint8)(mColor.r), - (Uint8)(mColor.g), - (Uint8)(mColor.b), - (Uint8)(mColor.a)); - SDL_RenderDrawLine(mRenderer, x1, y1, x2, y2); -} - -void Graphics::drawRectangle(const gcn::Rectangle &rectangle) -{ - if (mClipStack.empty()) - { - throw GCN_EXCEPTION("Clip stack is empty, perhaps you called a draw funtion outside of _beginDraw() and _endDraw()?"); - } - - const gcn::ClipRectangle &top = mClipStack.top(); - - SDL_Rect rect; - rect.x = rectangle.x + top.xOffset; - rect.y = rectangle.y + top.yOffset; - rect.w = rectangle.width; - rect.h = rectangle.height; - - SDL_SetRenderDrawColor(mRenderer, - (Uint8)(mColor.r), - (Uint8)(mColor.g), - (Uint8)(mColor.b), - (Uint8)(mColor.a)); - SDL_RenderDrawRect(mRenderer, &rect); -} - -void Graphics::fillRectangle(const gcn::Rectangle &rectangle) -{ - if (mClipStack.empty()) - { - throw GCN_EXCEPTION("Clip stack is empty, perhaps you called a draw funtion outside of _beginDraw() and _endDraw()?"); - } - - const gcn::ClipRectangle &top = mClipStack.top(); - - gcn::Rectangle area = rectangle; - area.x += top.xOffset; - area.y += top.yOffset; - - if(!area.isIntersecting(top)) - { - return; - } - - SDL_Rect rect; - rect.x = area.x; - rect.y = area.y; - rect.w = area.width; - rect.h = area.height; - - SDL_SetRenderDrawColor(mRenderer, - (Uint8)(mColor.r), - (Uint8)(mColor.g), - (Uint8)(mColor.b), - (Uint8)(mColor.a)); - SDL_RenderFillRect(mRenderer, &rect); -} diff --git a/src/graphics.h b/src/graphics.h index 24483078..956e729d 100644 --- a/src/graphics.h +++ b/src/graphics.h @@ -28,10 +28,6 @@ #include class Image; -class ImageRect; - -static const int defaultScreenWidth = 800; -static const int defaultScreenHeight = 600; /** * 9 images defining a rectangle. 4 corners, 4 sides and a middle area. The @@ -79,32 +75,15 @@ class Graphics : public gcn::Graphics public: Graphics() = default; - ~Graphics() override; - - /** - * Sets the target SDL_Window to draw to. This funtion also pushes a - * clip areas corresponding to the dimension of the target. - * - * @param target the target to draw to. - */ - virtual void setTarget(SDL_Window *target); - - SDL_Window *getTarget() const { return mTarget; } - SDL_Renderer *getRenderer() const { return mRenderer; } - /** - * Try to create a window with the given settings. + * Sets whether vertical refresh syncing is enabled. */ - virtual bool setVideoMode(int w, int h, bool fs); - - /** - * Change the video mode. Can be used for switching to full screen, - * changing resolution or adapting after window resize. - */ - bool changeVideoMode(int w, int h, bool fs); + virtual void setVSync(bool sync) = 0; virtual void videoResized(int w, int h); + using gcn::Graphics::drawImage; + /** * Blits an image onto the screen. * @@ -114,26 +93,13 @@ class Graphics : public gcn::Graphics bool drawImage(Image *image, int x, int y); /** - * Draws a resclaled version of the image - */ - bool drawRescaledImage(Image *image, int srcX, int srcY, - int dstX, int dstY, - int width, int height, - int desiredWidth, int desiredHeight) - { return drawRescaledImage(image, srcX, srcY, - dstX, dstY, - width, height, - desiredWidth, desiredHeight, - false); } - - /** - * Draws a resclaled version of the image + * Draws a rescaled version of the image. */ virtual bool drawRescaledImage(Image *image, int srcX, int srcY, - int dstX, int dstY, - int width, int height, - int desiredWidth, int desiredHeight, - bool useColor = false); + int dstX, int dstY, + int width, int height, + int desiredWidth, int desiredHeight, + bool useColor = false) = 0; /** * Blits an image onto the screen. @@ -155,28 +121,28 @@ class Graphics : public gcn::Graphics * Draw a pattern based on a rescaled version of the given image... */ virtual void drawRescaledImagePattern(Image *image, - int x, int y, int w, int h, - int scaledWidth, int scaledHeight); + int x, int y, + int w, int h, + int scaledWidth, + int scaledHeight) = 0; /** * Draws a rectangle using images. 4 corner images, 4 side images and 1 * image for the inside. */ - void drawImageRect( - int x, int y, int w, int h, - Image *topLeft, Image *topRight, - Image *bottomLeft, Image *bottomRight, - Image *top, Image *right, - Image *bottom, Image *left, - Image *center); + void drawImageRect(int x, int y, int w, int h, + Image *topLeft, Image *topRight, + Image *bottomLeft, Image *bottomRight, + Image *top, Image *right, + Image *bottom, Image *left, + Image *center); /** * Draws a rectangle using images. 4 corner images, 4 side images and 1 * image for the inside. */ - void drawImageRect( - int x, int y, int w, int h, - const ImageRect &imgRect); + void drawImageRect(int x, int y, int w, int h, + const ImageRect &imgRect); /** * Draws a rectangle using images. 4 corner images, 4 side images and 1 @@ -192,7 +158,7 @@ class Graphics : public gcn::Graphics * Updates the screen. This is done by either copying the buffer to the * screen or swapping pages. */ - virtual void updateScreen(); + virtual void updateScreen() = 0; /** * Returns the width of the screen. @@ -204,58 +170,32 @@ class Graphics : public gcn::Graphics */ int getHeight() const; - /** - * Returns whether we're in a full screen mode. - */ - bool getFullscreen() const { return mFullscreen; } - /** * Takes a screenshot and returns it as SDL surface. */ - virtual SDL_Surface *getScreenshot(); + virtual SDL_Surface *getScreenshot() = 0; gcn::Font *getFont() const { return mFont; } - bool pushClipArea(gcn::Rectangle area) override; - - void popClipArea() override; - void drawImage(const gcn::Image *image, - int srcX, - int srcY, - int dstX, - int dstY, - int width, - int height) override {} // not used - - void drawPoint(int x, int y) override; - - void drawLine(int x1, int y1, int x2, int y2) override; - - void drawRectangle(const gcn::Rectangle &rectangle) override; - - void fillRectangle(const gcn::Rectangle &rectangle) override; + int srcX, int srcY, + int dstX, int dstY, + int width, int height) override {} // not used void setColor(const gcn::Color &color) override { mColor = color; } - const gcn::Color &getColor() const override + const gcn::Color &getColor() const final { return mColor; } protected: - void updateSDLClipRect(); - int mWidth = 0; int mHeight = 0; - bool mFullscreen = false; gcn::Color mColor; - - SDL_Window *mTarget = nullptr; - SDL_Renderer *mRenderer = nullptr; }; extern Graphics *graphics; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index fd8f38ea..17f4897d 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -220,14 +220,14 @@ void Gui::draw() mGraphics->popClipArea(); } -void Gui::videoResized() +void Gui::videoResized(int width, int height) { auto *top = static_cast(getTop()); int oldWidth = top->getWidth(); int oldHeight = top->getHeight(); - top->setSize(graphics->getWidth(), graphics->getHeight()); + top->setSize(width, height); top->adjustAfterResize(oldWidth, oldHeight); } diff --git a/src/gui/gui.h b/src/gui/gui.h index c70ed9ad..eea3f23d 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -67,7 +67,7 @@ class Gui : public gcn::Gui /** * Called when the application window has been resized. */ - void videoResized(); + void videoResized(int width, int height); gcn::FocusHandler *getFocusHandler() const { return mFocusHandler; } diff --git a/src/gui/setup_video.cpp b/src/gui/setup_video.cpp index 2d4835ec..de32fcb1 100644 --- a/src/gui/setup_video.cpp +++ b/src/gui/setup_video.cpp @@ -25,6 +25,7 @@ #include "configuration.h" #include "game.h" #include "graphics.h" +#include "gui/widgets/dropdown.h" #include "localplayer.h" #include "particle.h" @@ -33,8 +34,6 @@ #include "gui/widgets/checkbox.h" #include "gui/widgets/label.h" #include "gui/widgets/layout.h" -#include "gui/widgets/listbox.h" -#include "gui/widgets/scrollarea.h" #include "gui/widgets/slider.h" #include "gui/widgets/spacer.h" @@ -46,75 +45,111 @@ #include +#include #include #include -extern Graphics *graphics; - /** - * The list model for mode list. + * A list model for a given list of strings. * * \ingroup Interface */ -class ModeListModel : public gcn::ListModel +class StringListModel : public gcn::ListModel { - public: - ModeListModel(); - - ~ModeListModel() override { } - - /** - * Returns the number of elements in container. - */ - int getNumberOfElements() override { return mVideoModes.size(); } - - /** - * Returns element from container. - */ - std::string getElementAt(int i) override { return mVideoModes[i]; } - - /** - * Returns the index corresponding to the given video mode. - * E.g.: "800x600". - * or -1 if not found. - */ - int getIndexOf(const std::string &widthXHeightMode); - - private: - std::vector mVideoModes; +public: + StringListModel(std::vector strings) + : mStrings(std::move(strings)) + {} + + int getNumberOfElements() override + { + return mStrings.size(); + } + + std::string getElementAt(int i) override + { + return mStrings[i]; + } + +private: + const std::vector mStrings; }; -ModeListModel::ModeListModel() +/** + * The list model for mode list. + * + * \ingroup Interface + */ +class ResolutionListModel : public gcn::ListModel { - /* Get available fullscreen/hardware modes */ - const int numModes = SDL_GetNumDisplayModes(0); - for (int i = 0; i < numModes; i++) +public: + ResolutionListModel() { - SDL_DisplayMode mode; - if (SDL_GetDisplayMode(0, i, &mode) != 0) - continue; + mDisplayModes = Client::getVideo().displayModes(); - // Skip the unreasonably small modes - if (mode.w < 640 || mode.h < 480) - continue; + // Add a dummy mode for "current window size" + mDisplayModes.insert(mDisplayModes.begin(), DisplayMode()); + } - // TODO_SDL2: Modes now dinstinguish between pixel format and refresh rate as well - // TODO_SDL2: Fullscreen mode needs display selection + int getNumberOfElements() override + { + return mDisplayModes.size(); + } - mVideoModes.push_back(toString(mode.w) + "x" + toString(mode.h)); + std::string getElementAt(int i) override + { + if (i == 0) + return _("Custom"); + + const auto &mode = getModeAt(i); + auto result = toString(mode.width) + "x" + toString(mode.height); + + // Append the aspect ratio + const int gcd = std::gcd(mode.width, mode.height); + int aspectWidth = mode.width / gcd; + int aspectHeight = mode.height / gcd; + if (aspectWidth == 8 && aspectHeight == 5) + { + aspectWidth = 16; + aspectHeight = 10; + } + if (aspectWidth == 7 && aspectHeight == 3) + { + aspectWidth = 21; + aspectHeight = 9; + } + if (aspectWidth <= 32) + result += " (" + toString(aspectWidth) + ":" + toString(aspectHeight) + ")"; + + return result; } -} -int ModeListModel::getIndexOf(const std::string &widthXHeightMode) -{ - for (unsigned i = 0; i < mVideoModes.size(); i++) - if (mVideoModes.at(i) == widthXHeightMode) - return i; + const DisplayMode &getModeAt(int i) const + { + return mDisplayModes.at(i); + } - return -1; -} + /** + * Returns the index corresponding to the given video resolution + * or -1 if not found. + */ + int getIndexOf(int width, int height) const + { + for (unsigned i = 1; i < mDisplayModes.size(); i++) { + const auto &mode = mDisplayModes[i]; + if (mode.width == width && mode.height == height) + return i; + } + + return 0; + } + +private: + std::vector mDisplayModes; +}; -const char *Setup_Video::overlayDetailToString(int detail) + +static const char *overlayDetailToString(int detail) { if (detail == -1) detail = config.getIntValue("OverlayDetail"); @@ -128,7 +163,7 @@ const char *Setup_Video::overlayDetailToString(int detail) return ""; } -const char *Setup_Video::particleDetailToString(int detail) +static const char *particleDetailToString(int detail) { if (detail == -1) detail = 3 - config.getIntValue("particleEmitterSkip"); @@ -144,20 +179,19 @@ const char *Setup_Video::particleDetailToString(int detail) } Setup_Video::Setup_Video(): - mFullScreenEnabled(config.getBoolValue("screen")), - mOpenGLEnabled(config.getBoolValue("opengl")), + mVideoSettings(Client::getVideo().settings()), mCustomCursorEnabled(config.getBoolValue("customcursor")), mParticleEffectsEnabled(config.getBoolValue("particleeffects")), mFps(config.getIntValue("fpslimit")), mSDLTransparencyDisabled(config.getBoolValue("disableTransparency")), - mModeListModel(new ModeListModel), - mModeList(new ListBox(mModeListModel)), - mFsCheckBox(new CheckBox(_("Full screen"), mFullScreenEnabled)), - mOpenGLCheckBox(new CheckBox(_("OpenGL"), mOpenGLEnabled)), - mCustomCursorCheckBox(new CheckBox(_("Custom cursor"), - mCustomCursorEnabled)), - mParticleEffectsCheckBox(new CheckBox(_("Particle effects"), - mParticleEffectsEnabled)), + mWindowModeListModel(new StringListModel({ _("Windowed"), _("Windowed Fullscreen"), _("Fullscreen") })), + mResolutionListModel(new ResolutionListModel), + mWindowModeDropDown(new DropDown(mWindowModeListModel.get())), + mResolutionDropDown(new DropDown(mResolutionListModel.get())), + mVSyncCheckBox(new CheckBox(_("VSync"), mVideoSettings.vsync)), + mOpenGLCheckBox(new CheckBox(_("OpenGL (Legacy)"), mVideoSettings.openGL)), + mCustomCursorCheckBox(new CheckBox(_("Custom cursor"), mCustomCursorEnabled)), + mParticleEffectsCheckBox(new CheckBox(_("Particle effects"), mParticleEffectsEnabled)), mFpsCheckBox(new CheckBox(_("FPS limit:"))), mFpsSlider(new Slider(10, 120)), mFpsLabel(new Label), @@ -173,17 +207,9 @@ Setup_Video::Setup_Video(): { setName(_("Video")); - auto *space = new Spacer(0,10); - - auto *scrollArea = new ScrollArea(mModeList); - scrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER); - scrollArea->setSize(100, 200); - overlayDetailLabel = new Label(_("Ambient FX:")); particleDetailLabel = new Label(_("Particle detail:")); - mModeList->setEnabled(true); - #ifndef USE_OPENGL mOpenGLCheckBox->setEnabled(false); #endif @@ -199,15 +225,16 @@ Setup_Video::Setup_Video(): // If the openGL Mode is enabled, disabling the transaprency // is irrelevant. - mDisableSDLTransparencyCheckBox->setEnabled(!mOpenGLEnabled); + mDisableSDLTransparencyCheckBox->setEnabled(!mVideoSettings.openGL); // Pre-select the current video mode. - std::string videoMode = toString(graphics->getWidth()) + "x" - + toString(graphics->getHeight()); - mModeList->setSelected(mModeListModel->getIndexOf(videoMode)); + mWindowModeDropDown->setSelected(static_cast(mVideoSettings.windowMode)); + mResolutionDropDown->setSelected(mResolutionListModel->getIndexOf(mVideoSettings.width, + mVideoSettings.height)); + mResolutionDropDown->setEnabled(mVideoSettings.windowMode != WindowMode::WindowedFullscreen); // Set actions - mModeList->setActionEventId("videomode"); + mWindowModeDropDown->setActionEventId("windowmode"); mCustomCursorCheckBox->setActionEventId("customcursor"); mParticleEffectsCheckBox->setActionEventId("particleeffects"); mDisableSDLTransparencyCheckBox->setActionEventId("disableTransparency"); @@ -220,7 +247,7 @@ Setup_Video::Setup_Video(): mParticleDetailField->setActionEventId("particledetailfield"); // Set listeners - mModeList->addActionListener(this); + mWindowModeDropDown->addActionListener(this); mCustomCursorCheckBox->addActionListener(this); mOpenGLCheckBox->addActionListener(this); mParticleEffectsCheckBox->addActionListener(this); @@ -242,104 +269,67 @@ Setup_Video::Setup_Video(): ContainerPlacer place = getPlacer(0, 0); place.getCell().setHAlign(LayoutCell::FILL); - place(0, 0, scrollArea, 1, 4).setPadding(2).setHAlign(LayoutCell::FILL); - place(1, 0, space, 1, 4); - place(2, 0, mFsCheckBox); - place(2, 1, mOpenGLCheckBox); - place(2, 2, mCustomCursorCheckBox); + place(0, 0, new Label(_("Window mode:"))); + place(1, 0, mWindowModeDropDown, 2); + place(0, 1, new Label(_("Resolution:"))); + place(1, 1, mResolutionDropDown, 2); + place(0, 2, mVSyncCheckBox, 4); + place(0, 3, mOpenGLCheckBox, 4); place = getPlacer(0, 1); place.getCell().setHAlign(LayoutCell::FILL); - place(0, 0, space, 3); - place(0, 1, mDisableSDLTransparencyCheckBox, 4); + place(0, 0, new Spacer(), 4); + place(0, 1, mCustomCursorCheckBox, 4); + place(0, 2, mDisableSDLTransparencyCheckBox, 4); - place(0, 2, mFpsCheckBox); - place(1, 2, mFpsSlider, 2); - place(3, 2, mFpsLabel); + place(0, 3, mFpsCheckBox); + place(1, 3, mFpsSlider, 2); + place(3, 3, mFpsLabel); - place(0, 3, mParticleEffectsCheckBox, 4); + place(0, 4, mParticleEffectsCheckBox, 4); - place(0, 4, particleDetailLabel); - place(1, 4, mParticleDetailSlider, 2); - place(3, 4, mParticleDetailField); + place(0, 5, particleDetailLabel); + place(1, 5, mParticleDetailSlider, 2); + place(3, 5, mParticleDetailField); - place(0, 5, overlayDetailLabel); - place(1, 5, mOverlayDetailSlider, 2); - place(3, 5, mOverlayDetailField); + place(0, 6, overlayDetailLabel); + place(1, 6, mOverlayDetailSlider, 2); + place(3, 6, mOverlayDetailField); } -Setup_Video::~Setup_Video() -{ - delete mModeListModel; - delete mModeList; -} +Setup_Video::~Setup_Video() = default; void Setup_Video::apply() { // Video mode changes - int screenWidth = graphics->getWidth(); - int screenHeight = graphics->getHeight(); + auto &video = Client::getVideo(); + auto videoSettings = video.settings(); - if (mModeList->getSelected() > -1) + if (mResolutionDropDown->getSelected() > 0) { - std::string mode = mModeListModel->getElementAt(mModeList->getSelected()); - screenWidth = atoi(mode.substr(0, mode.find("x")).c_str()); - screenHeight = atoi(mode.substr(mode.find("x") + 1).c_str()); + const auto &mode = mResolutionListModel->getModeAt(mResolutionDropDown->getSelected()); + videoSettings.width = mode.width; + videoSettings.height = mode.height; } - bool fullscreen = mFsCheckBox->isSelected(); + videoSettings.windowMode = static_cast(mWindowModeDropDown->getSelected()); + videoSettings.vsync = mVSyncCheckBox->isSelected(); - if (fullscreen != graphics->getFullscreen() || - screenWidth != graphics->getWidth() || - screenHeight != graphics->getHeight()) + if (video.apply(videoSettings)) { - /* The OpenGL test is only necessary on Windows, since switching - * to/from full screen works fine on Linux. On Windows we'd have to - * reinitialize the OpenGL state and reload all textures. - * - * See http://libsdl.org/cgi/docwiki.cgi/SDL_SetVideoMode - */ - -#if defined(_WIN32) || defined(__APPLE__) - // checks for opengl usage - if (config.getBoolValue("opengl")) - { - new OkDialog(_("Changing Video Mode"), - _("Restart needed for changes to take effect.")); - - config.setValue("screen", fullscreen); - config.setValue("screenwidth", screenWidth); - config.setValue("screenheight", screenHeight); - } - else -#endif - { - if (!graphics->changeVideoMode(screenWidth, - screenHeight, - fullscreen)) - { - std::stringstream errorMessage; - if (fullscreen) - errorMessage << _("Failed to switch to fullscreen mode."); - else - errorMessage << _("Failed to switch to windowed mode."); - - new OkDialog(_("Error"), errorMessage.str()); - } - else - { - Client::instance()->videoResized(screenWidth, screenHeight); - - config.setValue("screen", fullscreen); - config.setValue("screenwidth", screenWidth); - config.setValue("screenheight", screenHeight); - } - } + config.setValue("windowmode", static_cast(videoSettings.windowMode)); + config.setValue("vsync", videoSettings.vsync); + config.setValue("screenwidth", videoSettings.width); + config.setValue("screenheight", videoSettings.height); + } + else + { + new OkDialog(_("Error"), _("Failed to change video mode.")); } // OpenGL change - if (mOpenGLCheckBox->isSelected() != mOpenGLEnabled) + if (mOpenGLCheckBox->isSelected() != mVideoSettings.openGL) { config.setValue("opengl", mOpenGLCheckBox->isSelected()); @@ -385,21 +375,26 @@ void Setup_Video::apply() config.setValue("fpslimit", mFps); // We sync old and new values at apply time - mFullScreenEnabled = config.getBoolValue("screen"); + mVideoSettings.windowMode = static_cast(config.getIntValue("windowmode")); + mVideoSettings.vsync = config.getBoolValue("vsync"); + mVideoSettings.openGL = config.getBoolValue("opengl"); mCustomCursorEnabled = config.getBoolValue("customcursor"); mParticleEffectsEnabled = config.getBoolValue("particleeffects"); mOverlayDetail = config.getIntValue("OverlayDetail"); - mOpenGLEnabled = config.getBoolValue("opengl"); mSDLTransparencyDisabled = config.getBoolValue("disableTransparency"); } void Setup_Video::cancel() { - mFpsCheckBox->setSelected(mFps > 0); - mFsCheckBox->setSelected(mFullScreenEnabled); - mOpenGLCheckBox->setSelected(mOpenGLEnabled); + // Set back to the current video mode. + mResolutionDropDown->setSelected(mResolutionListModel->getIndexOf(mVideoSettings.width, + mVideoSettings.height)); + + mVSyncCheckBox->setSelected(mVideoSettings.vsync); + mOpenGLCheckBox->setSelected(mVideoSettings.openGL); mCustomCursorCheckBox->setSelected(mCustomCursorEnabled); mParticleEffectsCheckBox->setSelected(mParticleEffectsEnabled); + mFpsCheckBox->setSelected(mFps > 0); mFpsSlider->setValue(mFps); mFpsSlider->setEnabled(mFps > 0); mOverlayDetailSlider->setValue(mOverlayDetail); @@ -407,18 +402,13 @@ void Setup_Video::cancel() std::string text = mFpsCheckBox->isSelected() ? toString(mFps) : _("None"); mFpsLabel->setCaption(text); mDisableSDLTransparencyCheckBox->setSelected(mSDLTransparencyDisabled); - mDisableSDLTransparencyCheckBox->setEnabled(!mOpenGLEnabled); + mDisableSDLTransparencyCheckBox->setEnabled(!mVideoSettings.openGL); - config.setValue("screen", mFullScreenEnabled); - - // Set back to the current video mode. - std::string videoMode = toString(graphics->getWidth()) + "x" - + toString(graphics->getHeight()); - mModeList->setSelected(mModeListModel->getIndexOf(videoMode)); + config.setValue("windowmode", static_cast(mVideoSettings.windowMode)); config.setValue("customcursor", mCustomCursorEnabled); config.setValue("particleeffects", mParticleEffectsEnabled); - config.setValue("opengl", mOpenGLEnabled); + config.setValue("opengl", mVideoSettings.openGL); config.setValue("disableTransparency", mSDLTransparencyDisabled); } @@ -426,7 +416,25 @@ void Setup_Video::action(const gcn::ActionEvent &event) { const std::string &id = event.getId(); - if (id == "customcursor") + if (id == "windowmode") + { + auto windowMode = static_cast(mWindowModeDropDown->getSelected()); + + // When the window mode is "windowed fullscreen" we should select the + // desktop resolution and disable the option to change it + if (windowMode == WindowMode::WindowedFullscreen) + { + const auto &desktop = Client::getVideo().desktopDisplayMode(); + mResolutionDropDown->setSelected( + mResolutionListModel->getIndexOf(desktop.width, desktop.height)); + mResolutionDropDown->setEnabled(false); + } + else + { + mResolutionDropDown->setEnabled(true); + } + } + else if (id == "customcursor") { config.setValue("customcursor", mCustomCursorCheckBox->isSelected()); } diff --git a/src/gui/setup_video.h b/src/gui/setup_video.h index 6c902bb7..d16f6d73 100644 --- a/src/gui/setup_video.h +++ b/src/gui/setup_video.h @@ -25,11 +25,12 @@ #include "guichanfwd.h" #include "gui/widgets/setuptab.h" +#include "video.h" #include #include -class ModeListModel; +class ResolutionListModel; class Setup_Video : public SetupTab, public gcn::ActionListener, public gcn::KeyListener @@ -43,27 +44,24 @@ class Setup_Video : public SetupTab, public gcn::ActionListener, void action(const gcn::ActionEvent &event) override; - static const char *overlayDetailToString(int detail = -1); - - static const char *particleDetailToString(int detail = -1); - private: - bool mFullScreenEnabled; - bool mOpenGLEnabled; + VideoSettings mVideoSettings; bool mCustomCursorEnabled; bool mParticleEffectsEnabled; int mFps; bool mSDLTransparencyDisabled; - ModeListModel *mModeListModel; + std::unique_ptr mWindowModeListModel; + std::unique_ptr mResolutionListModel; //gcn::Label *scrollRadiusLabel; //gcn::Label *scrollLazinessLabel; gcn::Label *overlayDetailLabel; gcn::Label *particleDetailLabel; - gcn::ListBox *mModeList; - gcn::CheckBox *mFsCheckBox; + gcn::DropDown *mWindowModeDropDown; + gcn::DropDown *mResolutionDropDown; + gcn::CheckBox *mVSyncCheckBox; gcn::CheckBox *mOpenGLCheckBox; gcn::CheckBox *mCustomCursorCheckBox; gcn::CheckBox *mParticleEffectsCheckBox; diff --git a/src/openglgraphics.cpp b/src/openglgraphics.cpp index 008f1daa..2f83eea7 100644 --- a/src/openglgraphics.cpp +++ b/src/openglgraphics.cpp @@ -42,98 +42,21 @@ const unsigned int vertexBufSize = 500; GLuint OpenGLGraphics::mLastImage = 0; -OpenGLGraphics::OpenGLGraphics(): - mAlpha(false), mTexture(false), mColorAlpha(false), - mSync(false), - mReduceInputLag(true) +OpenGLGraphics::OpenGLGraphics(SDL_Window *window, SDL_GLContext glContext) + : mWindow(window) + , mContext(glContext) { + Image::setLoadAsOpenGL(true); + mFloatTexArray = new GLfloat[vertexBufSize * 4]; mIntTexArray = new GLint[vertexBufSize * 4]; mIntVertArray = new GLint[vertexBufSize * 4]; -} - -OpenGLGraphics::~OpenGLGraphics() -{ - delete[] mFloatTexArray; - delete[] mIntTexArray; - delete[] mIntVertArray; -} - -void OpenGLGraphics::setSync(bool sync) -{ - if (mSync == sync) - return; - - mSync = sync; - - if (mContext) - SDL_GL_SetSwapInterval(sync ? 1 : 0); -} - -void OpenGLGraphics::setReduceInputLag(bool reduceInputLag) -{ - mReduceInputLag = reduceInputLag; -} - -bool OpenGLGraphics::setVideoMode(int w, int h, bool fs) -{ - logger->log("Setting video mode %dx%d %s", - w, h, fs ? "fullscreen" : "windowed"); - - // TODO_SDL2: Support SDL_WINDOW_ALLOW_HIGHDPI, but check handling of clip area - - int windowFlags = SDL_WINDOW_OPENGL; - - if (fs) - { - windowFlags |= SDL_WINDOW_FULLSCREEN; - } - else - { - // Resizing currently not supported on Windows, where it would require - // reuploading all textures. -#if !defined(_WIN32) - windowFlags |= SDL_WINDOW_RESIZABLE; -#endif - } - - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - - SDL_Window *window = SDL_CreateWindow("Mana", - SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, - w, h, windowFlags); - if (!window) { - logger->log("Failed to create window: %s", SDL_GetError()); - return false; - } - SDL_SetWindowMinimumSize(window, 640, 480); - - SDL_GLContext glContext = SDL_GL_CreateContext(window); - if (!glContext) { - logger->log("Failed to create OpenGL context: %s", SDL_GetError()); - return false; - } - - mTarget = window; - mContext = glContext; - mWidth = w; - mHeight = h; - mFullscreen = fs; - - if (mSync) - { - SDL_GL_SetSwapInterval(1); - } + SDL_GL_GetDrawableSize(mWindow, &mWidth, &mHeight); // Setup OpenGL - glViewport(0, 0, w, h); + glViewport(0, 0, mWidth, mHeight); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); - int gotDoubleBuffer; - SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &gotDoubleBuffer); - logger->log("Using OpenGL %s double buffering.", - (gotDoubleBuffer ? "with" : "without")); char const *glExtensions = (char const *)glGetString(GL_EXTENSIONS); GLint texSize; @@ -154,18 +77,34 @@ bool OpenGLGraphics::setVideoMode(int w, int h, bool fs) Image::mTextureSize = texSize; logger->log("OpenGL texture size: %d pixels%s", Image::mTextureSize, rectTex ? " (rectangle textures)" : ""); +} - return true; +OpenGLGraphics::~OpenGLGraphics() +{ + SDL_GL_DeleteContext(mContext); + + delete[] mFloatTexArray; + delete[] mIntTexArray; + delete[] mIntVertArray; +} + +void OpenGLGraphics::setVSync(bool sync) +{ + SDL_GL_SetSwapInterval(sync ? 1 : 0); +} + +void OpenGLGraphics::setReduceInputLag(bool reduceInputLag) +{ + mReduceInputLag = reduceInputLag; } -void OpenGLGraphics::videoResized(int w, int h) +void OpenGLGraphics::videoResized(int width, int height) { _endDraw(); - mWidth = w; - mHeight = h; + SDL_GL_GetDrawableSize(mWindow, &mWidth, &mHeight); - glViewport(0, 0, w, h); + glViewport(0, 0, mWidth, mHeight); _beginDraw(); } @@ -174,7 +113,7 @@ static inline void drawQuad(Image *image, int srcX, int srcY, int dstX, int dstY, int width, int height) { - if (image->getTextureType() == GL_TEXTURE_2D) + if (Image::getTextureType() == GL_TEXTURE_2D) { // Find OpenGL normalized texture coordinates. const float texX1 = static_cast(srcX) / @@ -236,7 +175,7 @@ static inline void drawRescaledQuad(Image *image, int width, int height, int desiredWidth, int desiredHeight) { - if (image->getTextureType() == GL_TEXTURE_2D) + if (Image::getTextureType() == GL_TEXTURE_2D) { // Find OpenGL normalized texture coordinates. const float texX1 = static_cast(srcX) / @@ -391,7 +330,7 @@ void OpenGLGraphics::drawImagePattern(Image *image, int x, int y, int w, int h) unsigned int vp = 0; const unsigned int vLimit = vertexBufSize * 4; // Draw a set of textured rectangles - if (image->getTextureType() == GL_TEXTURE_2D) + if (Image::getTextureType() == GL_TEXTURE_2D) { float texX1 = static_cast(srcX) / tw; float texY1 = static_cast(srcY) / th; @@ -526,7 +465,7 @@ void OpenGLGraphics::drawRescaledImagePattern(Image *image, const unsigned int vLimit = vertexBufSize * 4; // Draw a set of textured rectangles - if (image->getTextureType() == GL_TEXTURE_2D) + if (Image::getTextureType() == GL_TEXTURE_2D) { const auto tw = static_cast(image->getTextureWidth()); const auto th = static_cast(image->getTextureHeight()); @@ -641,7 +580,7 @@ void OpenGLGraphics::drawRescaledImagePattern(Image *image, void OpenGLGraphics::updateScreen() { - SDL_GL_SwapWindow(mTarget); + SDL_GL_SwapWindow(mWindow); /* * glFinish flushes all OpenGL commands and makes sure they have been @@ -685,10 +624,10 @@ void OpenGLGraphics::_endDraw() popClipArea(); } -SDL_Surface* OpenGLGraphics::getScreenshot() +SDL_Surface *OpenGLGraphics::getScreenshot() { int w, h; - SDL_GL_GetDrawableSize(mTarget, &w, &h); + SDL_GL_GetDrawableSize(mWindow, &w, &h); GLint pack = 1; SDL_Surface *screenshot = SDL_CreateRGBSurface( @@ -772,7 +711,7 @@ void OpenGLGraphics::popClipArea() void OpenGLGraphics::setColor(const gcn::Color& color) { - mColor = color; + Graphics::setColor(color); glColor4ub(color.r, color.g, color.b, color.a); mColorAlpha = (color.a != 255); diff --git a/src/openglgraphics.h b/src/openglgraphics.h index d300dfd7..0bb07363 100644 --- a/src/openglgraphics.h +++ b/src/openglgraphics.h @@ -29,19 +29,14 @@ #include -class OpenGLGraphics : public Graphics +class OpenGLGraphics final : public Graphics { public: - OpenGLGraphics(); + OpenGLGraphics(SDL_Window *window, SDL_GLContext glContext); ~OpenGLGraphics() override; - /** - * Sets whether vertical refresh syncing is enabled. Takes effect - * immediately. - */ - void setSync(bool sync); - bool getSync() const { return mSync; } + void setVSync(bool sync) override; /** * Sets whether input lag should be reduced. @@ -53,8 +48,6 @@ class OpenGLGraphics : public Graphics void setReduceInputLag(bool reduceInputLag); bool getReduceInputLag() const { return mReduceInputLag; } - bool setVideoMode(int w, int h, bool fs) override; - void videoResized(int w, int h) override; bool drawImage(Image *image, @@ -120,14 +113,15 @@ class OpenGLGraphics : public Graphics void drawQuadArrayii(int size); + SDL_Window *mWindow = nullptr; SDL_GLContext mContext = nullptr; GLfloat *mFloatTexArray; GLint *mIntTexArray; GLint *mIntVertArray; - bool mAlpha, mTexture; - bool mColorAlpha; - bool mSync; - bool mReduceInputLag; + bool mAlpha = false; + bool mTexture = false; + bool mColorAlpha = false; + bool mReduceInputLag = true; }; #endif //USE_OPENGL diff --git a/src/resources/ambientlayer.cpp b/src/resources/ambientlayer.cpp index b71378cb..2fa522e5 100644 --- a/src/resources/ambientlayer.cpp +++ b/src/resources/ambientlayer.cpp @@ -21,6 +21,7 @@ #include "resources/ambientlayer.h" #include "graphics.h" +#include "video.h" #include "resources/image.h" #include "resources/resourcemanager.h" diff --git a/src/resources/image.h b/src/resources/image.h index 662c0393..20a85433 100644 --- a/src/resources/image.h +++ b/src/resources/image.h @@ -44,7 +44,7 @@ class Dye; */ class Image : public Resource { - friend class Graphics; + friend class SDLGraphics; #ifdef USE_OPENGL friend class OpenGLGraphics; #endif diff --git a/src/sdlgraphics.cpp b/src/sdlgraphics.cpp new file mode 100644 index 00000000..b7f80fd2 --- /dev/null +++ b/src/sdlgraphics.cpp @@ -0,0 +1,304 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2012 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "sdlgraphics.h" + +#include "log.h" +#include "resources/image.h" + +#include + +SDLGraphics::SDLGraphics(SDL_Window *window, SDL_Renderer *renderer) + : mWindow(window) + , mRenderer(renderer) +{ + Image::setRenderer(mRenderer); + + SDL_GetRendererOutputSize(mRenderer, &mWidth, &mHeight); + + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + + if (const char *driver = SDL_GetCurrentVideoDriver()) + logger->log("Using video driver: %s", driver); + else + logger->log("Using video driver: not initialized"); + + SDL_RendererInfo info; + + if (SDL_GetRendererInfo(renderer, &info) == 0) { + logger->log("Using renderer: %s", info.name); + + logger->log("The renderer is a software fallback: %s", + (info.flags & SDL_RENDERER_SOFTWARE) ? "yes" : "no"); + logger->log("The renderer is hardware accelerated: %s", + (info.flags & SDL_RENDERER_ACCELERATED) ? "yes" : "no"); + logger->log("Vsync: %s", + (info.flags & SDL_RENDERER_PRESENTVSYNC) ? "on" : "off"); + logger->log("Renderer supports rendering to texture: %s", + (info.flags & SDL_RENDERER_TARGETTEXTURE) ? "yes" : "no"); + logger->log("Max texture size: %dx%d", + info.max_texture_width, info.max_texture_height); + } +} + +SDLGraphics::~SDLGraphics() +{ + SDL_DestroyRenderer(mRenderer); +} + +void SDLGraphics::setVSync(bool sync) +{ +#if SDL_VERSION_ATLEAST(2, 0, 18) + SDL_RenderSetVSync(mRenderer, sync ? SDL_TRUE : SDL_FALSE); +#endif +} + +void SDLGraphics::videoResized(int w, int h) +{ + SDL_GetRendererOutputSize(mRenderer, &mWidth, &mHeight); +} + +bool SDLGraphics::drawRescaledImage(Image *image, + int srcX, int srcY, + int dstX, int dstY, + int width, int height, + int desiredWidth, int desiredHeight, + bool useColor) +{ + // Check that preconditions for blitting are met. + if (!image || !image->mTexture) + return false; + + dstX += mClipStack.top().xOffset; + dstY += mClipStack.top().yOffset; + + srcX += image->mBounds.x; + srcY += image->mBounds.y; + + SDL_Rect dstRect; + SDL_Rect srcRect; + dstRect.x = dstX; dstRect.y = dstY; + srcRect.x = srcX; srcRect.y = srcY; + srcRect.w = width; + srcRect.h = height; + dstRect.w = desiredWidth; + dstRect.h = desiredHeight; + + return !(SDL_RenderCopy(mRenderer, image->mTexture, &srcRect, &dstRect) < 0); +} + +void SDLGraphics::drawRescaledImagePattern(Image *image, + int x, int y, + int w, int h, + int scaledWidth, + int scaledHeight) +{ + // Check that preconditions for blitting are met. + if (!image || !image->mTexture) + return; + + if (scaledHeight <= 0 || scaledWidth <= 0) + return; + + for (int py = 0; py < h; py += scaledHeight) // Y position on pattern plane + { + int dh = (py + scaledHeight >= h) ? h - py : scaledHeight; + int srcY = image->mBounds.y; + int dstY = y + py + mClipStack.top().yOffset; + + for (int px = 0; px < w; px += scaledWidth) // X position on pattern plane + { + int dw = (px + scaledWidth >= w) ? w - px : scaledWidth; + int srcX = image->mBounds.x; + int dstX = x + px + mClipStack.top().xOffset; + + SDL_Rect dstRect; + SDL_Rect srcRect; + dstRect.x = dstX; dstRect.y = dstY; + dstRect.w = dw; dstRect.h = dh; + srcRect.x = srcX; srcRect.y = srcY; + srcRect.w = dw; srcRect.h = dh; + + if (SDL_RenderCopy(mRenderer, image->mTexture, &srcRect, &dstRect)) + return; + } + } +} + +void SDLGraphics::updateScreen() +{ + SDL_RenderPresent(mRenderer); +} + +SDL_Surface *SDLGraphics::getScreenshot() +{ +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + int rmask = 0xff000000; + int gmask = 0x00ff0000; + int bmask = 0x0000ff00; +#else + int rmask = 0x000000ff; + int gmask = 0x0000ff00; + int bmask = 0x00ff0000; +#endif + int amask = 0x00000000; + + SDL_Surface *screenshot = SDL_CreateRGBSurface(0, mWidth, + mHeight, 24, rmask, gmask, bmask, amask); + + SDL_RenderReadPixels(mRenderer, nullptr, + screenshot->format->format, + screenshot->pixels, + screenshot->pitch); + + return screenshot; +} + +bool SDLGraphics::pushClipArea(gcn::Rectangle area) +{ + bool result = Graphics::pushClipArea(area); + updateSDLClipRect(); + return result; +} + +void SDLGraphics::popClipArea() +{ + Graphics::popClipArea(); + updateSDLClipRect(); +} + +void SDLGraphics::updateSDLClipRect() +{ + if (mClipStack.empty()) + { + SDL_RenderSetClipRect(mRenderer, nullptr); + return; + } + + const gcn::ClipRectangle &carea = mClipStack.top(); + SDL_Rect rect; + rect.x = carea.x; + rect.y = carea.y; + rect.w = carea.width; + rect.h = carea.height; + + SDL_RenderSetClipRect(mRenderer, &rect); +} + +void SDLGraphics::drawPoint(int x, int y) +{ + if (mClipStack.empty()) + { + throw GCN_EXCEPTION("Clip stack is empty, perhaps you called a draw funtion outside of _beginDraw() and _endDraw()?"); + } + + const gcn::ClipRectangle &top = mClipStack.top(); + + x += top.xOffset; + y += top.yOffset; + + if (!top.isPointInRect(x, y)) + return; + + SDL_SetRenderDrawColor(mRenderer, + (Uint8)(mColor.r), + (Uint8)(mColor.g), + (Uint8)(mColor.b), + (Uint8)(mColor.a)); + SDL_RenderDrawPoint(mRenderer, x, y); +} + +void SDLGraphics::drawLine(int x1, int y1, int x2, int y2) +{ + if (mClipStack.empty()) + { + throw GCN_EXCEPTION("Clip stack is empty, perhaps you called a draw funtion outside of _beginDraw() and _endDraw()?"); + } + + const gcn::ClipRectangle &top = mClipStack.top(); + + x1 += top.xOffset; + y1 += top.yOffset; + x2 += top.xOffset; + y2 += top.yOffset; + + SDL_SetRenderDrawColor(mRenderer, + (Uint8)(mColor.r), + (Uint8)(mColor.g), + (Uint8)(mColor.b), + (Uint8)(mColor.a)); + SDL_RenderDrawLine(mRenderer, x1, y1, x2, y2); +} + +void SDLGraphics::drawRectangle(const gcn::Rectangle &rectangle) +{ + if (mClipStack.empty()) + { + throw GCN_EXCEPTION("Clip stack is empty, perhaps you called a draw funtion outside of _beginDraw() and _endDraw()?"); + } + + const gcn::ClipRectangle &top = mClipStack.top(); + + SDL_Rect rect; + rect.x = rectangle.x + top.xOffset; + rect.y = rectangle.y + top.yOffset; + rect.w = rectangle.width; + rect.h = rectangle.height; + + SDL_SetRenderDrawColor(mRenderer, + (Uint8)(mColor.r), + (Uint8)(mColor.g), + (Uint8)(mColor.b), + (Uint8)(mColor.a)); + SDL_RenderDrawRect(mRenderer, &rect); +} + +void SDLGraphics::fillRectangle(const gcn::Rectangle &rectangle) +{ + if (mClipStack.empty()) + { + throw GCN_EXCEPTION("Clip stack is empty, perhaps you called a draw funtion outside of _beginDraw() and _endDraw()?"); + } + + const gcn::ClipRectangle &top = mClipStack.top(); + + gcn::Rectangle area = rectangle; + area.x += top.xOffset; + area.y += top.yOffset; + + if(!area.isIntersecting(top)) + { + return; + } + + SDL_Rect rect; + rect.x = area.x; + rect.y = area.y; + rect.w = area.width; + rect.h = area.height; + + SDL_SetRenderDrawColor(mRenderer, + (Uint8)(mColor.r), + (Uint8)(mColor.g), + (Uint8)(mColor.b), + (Uint8)(mColor.a)); + SDL_RenderFillRect(mRenderer, &rect); +} diff --git a/src/sdlgraphics.h b/src/sdlgraphics.h new file mode 100644 index 00000000..6685fc83 --- /dev/null +++ b/src/sdlgraphics.h @@ -0,0 +1,73 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2012 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SDLGRAPHICS_H +#define SDLGRAPHICS_H + +#include "graphics.h" + +class SDLGraphics final : public Graphics +{ +public: + SDLGraphics(SDL_Window *window, SDL_Renderer *renderer); + ~SDLGraphics() override; + + void setVSync(bool sync) override; + + void videoResized(int w, int h) override; + + bool drawRescaledImage(Image *image, + int srcX, int srcY, + int dstX, int dstY, + int width, int height, + int desiredWidth, int desiredHeight, + bool useColor) override; + + void drawRescaledImagePattern(Image *image, + int x, int y, + int w, int h, + int scaledWidth, + int scaledHeight) override; + + void updateScreen() override; + + SDL_Surface *getScreenshot() override; + + bool pushClipArea(gcn::Rectangle area) override; + + void popClipArea() override; + + void drawPoint(int x, int y) override; + + void drawLine(int x1, int y1, int x2, int y2) override; + + void drawRectangle(const gcn::Rectangle &rectangle) override; + + void fillRectangle(const gcn::Rectangle &rectangle) override; + +private: + void updateSDLClipRect(); + + SDL_Window *mWindow = nullptr; + SDL_Renderer *mRenderer = nullptr; +}; + +#endif // SDLGRAPHICS_H diff --git a/src/video.cpp b/src/video.cpp new file mode 100644 index 00000000..9e69ec27 --- /dev/null +++ b/src/video.cpp @@ -0,0 +1,263 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2012 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "video.h" + +#include "log.h" +#include "sdlgraphics.h" +#include "utils/stringutils.h" + +#ifdef USE_OPENGL +#include "openglgraphics.h" +#endif + +#include + +Video::~Video() +{ + mGraphics.reset(); // reset graphics first + + if (mWindow) + SDL_DestroyWindow(mWindow); +} + +Graphics *Video::initialize(const VideoSettings &settings) +{ + mSettings = settings; + + if (!initDisplayModes()) + { + logger->log("Failed to initialize display modes: %s", SDL_GetError()); + } + + SDL_DisplayMode displayMode; + + if (mSettings.windowMode == WindowMode::Fullscreen) + { + SDL_DisplayMode requestedMode; + requestedMode.format = 0; + requestedMode.w = mSettings.width; + requestedMode.h = mSettings.height; + requestedMode.refresh_rate = 0; + requestedMode.driverdata = nullptr; + + if (SDL_GetClosestDisplayMode(mSettings.display, &requestedMode, &displayMode) == nullptr) + { + logger->log("SDL_GetClosestDisplayMode failed: %s, falling back to borderless mode", SDL_GetError()); + mSettings.windowMode = WindowMode::WindowedFullscreen; + } + } + + int windowFlags = SDL_WINDOW_RESIZABLE; + const char *videoMode = "windowed"; + + switch (mSettings.windowMode) + { + case WindowMode::Windowed: + break; + case WindowMode::Fullscreen: + windowFlags |= SDL_WINDOW_FULLSCREEN; + videoMode = "fullscreen"; + break; + case WindowMode::WindowedFullscreen: + windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + videoMode = "windowed fullscreen"; + break; + } + + if (mSettings.openGL) + windowFlags |= SDL_WINDOW_OPENGL; + + logger->log("Setting video mode %dx%d %s", + mSettings.width, + mSettings.height, + videoMode); + + mWindow = SDL_CreateWindow("Mana", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + mSettings.width, + mSettings.height, + windowFlags); + + if (!mWindow) + { + logger->error(strprintf("Failed to create window: %s", + SDL_GetError())); + return nullptr; + } + + SDL_SetWindowMinimumSize(mWindow, 640, 480); + + if (mSettings.windowMode == WindowMode::Fullscreen) + { + if (SDL_SetWindowDisplayMode(mWindow, &displayMode) != 0) + { + logger->log("SDL_SetWindowDisplayMode failed: %s", SDL_GetError()); + } + } + +#ifdef USE_OPENGL + if (mSettings.openGL) + { + SDL_GLContext glContext = SDL_GL_CreateContext(mWindow); + if (!glContext) + { + 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; + + SDL_Renderer *renderer = SDL_CreateRenderer(mWindow, -1, rendererFlags); + if (!renderer) + { + logger->error(strprintf("Failed to create renderer: %s", + SDL_GetError())); + return nullptr; + } + + mGraphics = std::make_unique(mWindow, renderer); + return mGraphics.get(); +} + +bool Video::apply(const VideoSettings &settings) +{ + if (mSettings == settings) + return true; + + // When changing to fullscreen mode, we set the display mode first + if (settings.windowMode == WindowMode::Fullscreen) + { + SDL_DisplayMode displayMode; + if (SDL_GetWindowDisplayMode(mWindow, &displayMode) != 0) + { + logger->error(strprintf("SDL_GetCurrentDisplayMode failed: %s", SDL_GetError())); + return false; + } + + if (displayMode.w != settings.width || displayMode.h != settings.height) + { +#ifdef __APPLE__ + // Workaround SDL2 issue when switching display modes while already + // fullscreen on macOS (tested as of SDL 2.30.0). + if (SDL_GetWindowFlags(mWindow) & SDL_WINDOW_FULLSCREEN) + SDL_SetWindowFullscreen(mWindow, 0); +#endif + + displayMode.w = settings.width; + displayMode.h = settings.height; + + if (SDL_SetWindowDisplayMode(mWindow, &displayMode) != 0) + { + logger->error(strprintf("SDL_SetWindowDisplayMode failed: %s", SDL_GetError())); + return false; + } + } + } + + int windowFlags = 0; + switch (settings.windowMode) + { + case WindowMode::Windowed: + break; + case WindowMode::WindowedFullscreen: + windowFlags = SDL_WINDOW_FULLSCREEN_DESKTOP; + break; + case WindowMode::Fullscreen: + windowFlags = SDL_WINDOW_FULLSCREEN; + break; + } + + if (SDL_SetWindowFullscreen(mWindow, windowFlags) != 0) + { + logger->error(strprintf("SDL_SetWindowFullscreen failed: %s", SDL_GetError())); + return false; + } + + if (settings.windowMode == WindowMode::Windowed) { +#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 + // be distinguished from a maximized window (tested as of SDL 2.30.0). + if (!(SDL_GetWindowFlags(mWindow) & SDL_WINDOW_MAXIMIZED)) +#endif + 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); + + return true; +} + +bool Video::initDisplayModes() +{ + const int displayIndex = mSettings.display; + SDL_DisplayMode mode; + if (SDL_GetDesktopDisplayMode(displayIndex, &mode) != 0) + return false; + + mDesktopDisplayMode.width = mode.w; + mDesktopDisplayMode.height = mode.h; + + // Get available fullscreen/hardware modes + const int numModes = SDL_GetNumDisplayModes(displayIndex); + for (int i = 0; i < numModes; i++) + { + if (SDL_GetDisplayMode(displayIndex, i, &mode) != 0) + return false; + + // Skip the unreasonably small modes + if (mode.w < 640 || mode.h < 480) + continue; + + // Only list each resolution once + // (we currently don't support selecting the refresh rate) + if (std::find_if(mDisplayModes.cbegin(), + mDisplayModes.cend(), + [&mode](const DisplayMode &other) { + return mode.w == other.width && mode.h == other.height; + }) != mDisplayModes.cend()) + continue; + + mDisplayModes.push_back(DisplayMode { mode.w, mode.h }); + } + + return true; +} diff --git a/src/video.h b/src/video.h new file mode 100644 index 00000000..df9cd518 --- /dev/null +++ b/src/video.h @@ -0,0 +1,106 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2012 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef VIDEO_H +#define VIDEO_H + +#include "graphics.h" + +#include +#include + +static constexpr int defaultScreenWidth = 1280; +static constexpr int defaultScreenHeight = 720; + +enum class WindowMode +{ + Windowed = 0, + WindowedFullscreen = 1, + Fullscreen = 2, +}; + +struct DisplayMode +{ + int width = 0; + int height = 0; +}; + +struct VideoSettings +{ + WindowMode windowMode = WindowMode::Windowed; + int width = defaultScreenWidth; + int height = defaultScreenHeight; + int display = 0; + bool vsync = true; + bool openGL = false; + + bool operator==(const VideoSettings &other) const + { + return width == other.width && + height == other.height && + windowMode == other.windowMode && + display == other.display && + vsync == other.vsync && + openGL == other.openGL; + } +}; + +class Video +{ +public: + Video() = default; + ~Video(); + + const VideoSettings &settings() const { return mSettings; } + SDL_Window *window() const { return mWindow; } + Graphics *graphics() const { return mGraphics.get(); } + + /** + * Try to create a window with the given settings. + */ + Graphics *initialize(const VideoSettings &settings); + + /** + * Try to apply the given video settings. + */ + bool apply(const VideoSettings &settings); + + const DisplayMode &desktopDisplayMode() const + { + return mDesktopDisplayMode; + } + + const std::vector &displayModes() const + { + return mDisplayModes; + } + +private: + bool initDisplayModes(); + + VideoSettings mSettings; + DisplayMode mDesktopDisplayMode; + std::vector mDisplayModes; + std::unique_ptr mGraphics; + SDL_Window *mWindow = nullptr; +}; + +#endif // VIDEO_H -- cgit v1.2.3-70-g09d2