diff options
-rw-r--r-- | src/CMakeLists.txt | 12 | ||||
-rw-r--r-- | src/client.cpp | 89 | ||||
-rw-r--r-- | src/client.h | 14 | ||||
-rw-r--r-- | src/configuration.cpp | 5 | ||||
-rw-r--r-- | src/defaults.cpp | 5 | ||||
-rw-r--r-- | src/game.cpp | 4 | ||||
-rw-r--r-- | src/graphics.cpp | 341 | ||||
-rw-r--r-- | src/graphics.h | 114 | ||||
-rw-r--r-- | src/gui/gui.cpp | 4 | ||||
-rw-r--r-- | src/gui/gui.h | 2 | ||||
-rw-r--r-- | src/gui/setup_video.cpp | 340 | ||||
-rw-r--r-- | src/gui/setup_video.h | 18 | ||||
-rw-r--r-- | src/openglgraphics.cpp | 133 | ||||
-rw-r--r-- | src/openglgraphics.h | 22 | ||||
-rw-r--r-- | src/resources/ambientlayer.cpp | 1 | ||||
-rw-r--r-- | src/resources/image.h | 2 | ||||
-rw-r--r-- | src/sdlgraphics.cpp | 304 | ||||
-rw-r--r-- | src/sdlgraphics.h | 73 | ||||
-rw-r--r-- | src/video.cpp | 263 | ||||
-rw-r--r-- | src/video.h | 106 |
20 files changed, 1068 insertions, 784 deletions
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<WindowMode>(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<int>(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 <guichan/exception.hpp> 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 <guichan/graphics.hpp> 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. @@ -205,57 +171,31 @@ 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<WindowContainer*>(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 <SDL.h> +#include <numeric> #include <string> #include <vector> -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<std::string> mVideoModes; +public: + StringListModel(std::vector<std::string> strings) + : mStrings(std::move(strings)) + {} + + int getNumberOfElements() override + { + return mStrings.size(); + } + + std::string getElementAt(int i) override + { + return mStrings[i]; + } + +private: + const std::vector<std::string> 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<DisplayMode> 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<int>(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<WindowMode>(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<int>(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<WindowMode>(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<int>(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<WindowMode>(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 <guichan/actionlistener.hpp> #include <guichan/keylistener.hpp> -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<gcn::ListModel> mWindowModeListModel; + std::unique_ptr<ResolutionListModel> 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<float>(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<float>(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<float>(srcX) / tw; float texY1 = static_cast<float>(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<float>(image->getTextureWidth()); const auto th = static_cast<float>(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 <SDL_opengl.h> -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 <http://www.gnu.org/licenses/>. + */ + +#include "sdlgraphics.h" + +#include "log.h" +#include "resources/image.h" + +#include <guichan/exception.hpp> + +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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. + */ + +#include "video.h" + +#include "log.h" +#include "sdlgraphics.h" +#include "utils/stringutils.h" + +#ifdef USE_OPENGL +#include "openglgraphics.h" +#endif + +#include <algorithm> + +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<OpenGLGraphics>(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<SDLGraphics>(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 <http://www.gnu.org/licenses/>. + */ + +#ifndef VIDEO_H +#define VIDEO_H + +#include "graphics.h" + +#include <memory> +#include <vector> + +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<DisplayMode> &displayModes() const + { + return mDisplayModes; + } + +private: + bool initDisplayModes(); + + VideoSettings mSettings; + DisplayMode mDesktopDisplayMode; + std::vector<DisplayMode> mDisplayModes; + std::unique_ptr<Graphics> mGraphics; + SDL_Window *mWindow = nullptr; +}; + +#endif // VIDEO_H |