/* * 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 "utils/stringutils.h" #include "video.h" #include std::unique_ptr SDLGraphics::create(SDL_Window *window, const VideoSettings &settings) { int rendererFlags = 0; if (settings.vsync) rendererFlags |= SDL_RENDERER_PRESENTVSYNC; SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, rendererFlags); if (!renderer) { logger->error(strprintf("Failed to create renderer: %s", SDL_GetError())); return {}; } return std::make_unique(renderer); } SDLGraphics::SDLGraphics(SDL_Renderer *renderer) : mRenderer(renderer) { Image::setRenderer(mRenderer); 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::updateSize(int windowWidth, int windowHeight, float scale) { SDL_GetRendererOutputSize(mRenderer, &mWidth, &mHeight); float displayScaleX = windowWidth > 0 ? static_cast(mWidth) / windowWidth : 1.0f; float displayScaleY = windowHeight > 0 ? static_cast(mHeight) / windowHeight : 1.0f; float scaleX = scale * displayScaleX; float scaleY = scale * displayScaleY; mWidth = std::ceil(mWidth / scaleX); mHeight = std::ceil(mHeight / scaleY); mScale = scaleX; SDL_RenderSetScale(mRenderer, scaleX, scaleY); } bool SDLGraphics::drawRescaledImage(const 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); } #if SDL_VERSION_ATLEAST(2, 0, 10) bool SDLGraphics::drawRescaledImageF(const Image *image, int srcX, int srcY, float dstX, float dstY, int width, int height, float desiredWidth, float 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_FRect 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_RenderCopyF(mRenderer, image->mTexture, &srcRect, &dstRect) < 0); } #endif void SDLGraphics::drawRescaledImagePattern(const 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; SDL_Rect srcRect; srcRect.x = image->mBounds.x; srcRect.y = image->mBounds.y; for (int py = 0; py < h; py += scaledHeight) // Y position on pattern plane { int dh = (py + scaledHeight >= h) ? h - py : scaledHeight; 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 dstX = x + px + mClipStack.top().xOffset; SDL_Rect dstRect; dstRect.x = dstX; dstRect.y = dstY; dstRect.w = dw; dstRect.h = dh; srcRect.w = dw; srcRect.h = dh; if (SDL_RenderCopy(mRenderer, image->mTexture, &srcRect, &dstRect)) return; } } } void SDLGraphics::updateScreen() { SDL_RenderPresent(mRenderer); } void SDLGraphics::windowToLogical(int windowX, int windowY, float &logicalX, float &logicalY) const { #if SDL_VERSION_ATLEAST(2, 0, 18) SDL_RenderWindowToLogical(mRenderer, windowX, windowY, &logicalX, &logicalY); #else float scaleX; float scaleY; SDL_RenderGetScale(mRenderer, &scaleX, &scaleY); logicalX = windowX / scaleX; logicalY = windowY / scaleY; #endif } SDL_Surface *SDLGraphics::getScreenshot() { #if SDL_BYTEORDER == SDL_BIG_ENDIAN int rmask = 0xff000000; int gmask = 0x00ff0000; int bmask = 0x0000ff00; #else int rmask = 0x000000ff; int gmask = 0x0000ff00; int bmask = 0x00ff0000; #endif int amask = 0x00000000; int width, height; if (SDL_GetRendererOutputSize(mRenderer, &width, &height) != 0) return nullptr; SDL_Surface *screenshot = SDL_CreateRGBSurface(0, width, height, 24, rmask, gmask, bmask, amask); if (SDL_RenderReadPixels(mRenderer, nullptr, screenshot->format->format, screenshot->pixels, screenshot->pitch) != 0) { SDL_FreeSurface(screenshot); screenshot = nullptr; } 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); }