/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * 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 3 of the License, or * (at your option) 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 "common/scummsys.h" #if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS) #include "backends/graphics3d/openglsdl/openglsdl-graphics3d.h" #include "backends/graphics3d/opengl/surfacerenderer.h" #include "backends/graphics3d/opengl/tiledsurface.h" #include "backends/graphics3d/opengl/texture.h" #include "backends/graphics3d/opengl/framebuffer.h" #include "backends/events/sdl/sdl-events.h" #include "common/config-manager.h" #include "common/file.h" #include "common/translation.h" #include "engines/engine.h" #include "graphics/conversion.h" #include "graphics/opengl/context.h" #include "graphics/opengl/system_headers.h" #ifdef USE_PNG #include "image/png.h" #else #include "image/bmp.h" #endif OpenGLSdlGraphics3dManager::OpenGLSdlGraphics3dManager(SdlEventSource *eventSource, SdlWindow *window, bool supportsFrameBuffer) : SdlGraphicsManager(eventSource, window), #if SDL_VERSION_ATLEAST(2, 0, 0) _glContext(nullptr), #endif _supportsFrameBuffer(supportsFrameBuffer), _overlayScreen(nullptr), _overlayBackground(nullptr), _fullscreen(false), _lockAspectRatio(true), _stretchMode(STRETCH_FIT), _frameBuffer(nullptr), _surfaceRenderer(nullptr), _engineRequestedWidth(0), _engineRequestedHeight(0), _transactionMode(kTransactionNone) { ConfMan.registerDefault("antialiasing", 0); ConfMan.registerDefault("aspect_ratio", true); // Don't start at zero so that the value is never the same as the surface graphics manager _screenChangeCount = 1 << (sizeof(int) * 5 - 2); // Set up proper SDL OpenGL context creation. #if SDL_VERSION_ATLEAST(2, 0, 0) enum { #ifdef USE_OPENGL_SHADERS DEFAULT_GL_MAJOR = 2, DEFAULT_GL_MINOR = 1, #else DEFAULT_GL_MAJOR = 1, DEFAULT_GL_MINOR = 3, #endif DEFAULT_GLES2_MAJOR = 2, DEFAULT_GLES2_MINOR = 0 }; #if USE_FORCED_GLES2 _glContextType = OpenGL::kContextGLES2; _glContextProfileMask = SDL_GL_CONTEXT_PROFILE_ES; _glContextMajor = DEFAULT_GLES2_MAJOR; _glContextMinor = DEFAULT_GLES2_MINOR; #else bool noDefaults = false; // Obtain the default GL(ES) context SDL2 tries to setup. // // Please note this might not actually be SDL2's defaults when multiple // instances of this object have been created. But that is no issue // because then we already set up what we want to use. // // In case no defaults are given we prefer OpenGL over OpenGL ES. if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &_glContextProfileMask) != 0) { _glContextProfileMask = 0; noDefaults = true; } if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &_glContextMajor) != 0) { noDefaults = true; } if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &_glContextMinor) != 0) { noDefaults = true; } if (noDefaults) { if (_glContextProfileMask == SDL_GL_CONTEXT_PROFILE_ES) { _glContextMajor = DEFAULT_GLES2_MAJOR; _glContextMinor = DEFAULT_GLES2_MINOR; } else { _glContextProfileMask = 0; _glContextMajor = DEFAULT_GL_MAJOR; _glContextMinor = DEFAULT_GL_MINOR; } } if (_glContextProfileMask == SDL_GL_CONTEXT_PROFILE_ES) { // TODO: Support GLES1 for games _glContextType = OpenGL::kContextGLES2; } else if (_glContextProfileMask == SDL_GL_CONTEXT_PROFILE_CORE) { _glContextType = OpenGL::kContextGL; // Core profile does not allow legacy functionality, which we use. // Thus we request a standard OpenGL context. _glContextProfileMask = 0; _glContextMajor = DEFAULT_GL_MAJOR; _glContextMinor = DEFAULT_GL_MINOR; } else { _glContextType = OpenGL::kContextGL; } #endif #else _glContextType = OpenGL::kContextGL; #endif } OpenGLSdlGraphics3dManager::~OpenGLSdlGraphics3dManager() { closeOverlay(); #if SDL_VERSION_ATLEAST(2, 0, 0) deinitializeRenderer(); #endif } bool OpenGLSdlGraphics3dManager::hasFeature(OSystem::Feature f) const { return (f == OSystem::kFeatureFullscreenMode) || (f == OSystem::kFeatureOpenGLForGame) || #if SDL_VERSION_ATLEAST(2, 0, 0) (f == OSystem::kFeatureFullscreenToggleKeepsContext) || #endif (f == OSystem::kFeatureVSync) || (f == OSystem::kFeatureAspectRatioCorrection) || (f == OSystem::kFeatureStretchMode) || (f == OSystem::kFeatureOverlaySupportsAlpha && _overlayFormat.aBits() > 3); } bool OpenGLSdlGraphics3dManager::getFeatureState(OSystem::Feature f) const { switch (f) { case OSystem::kFeatureVSync: return isVSyncEnabled(); case OSystem::kFeatureFullscreenMode: return _fullscreen; case OSystem::kFeatureAspectRatioCorrection: return _lockAspectRatio; default: return false; } } void OpenGLSdlGraphics3dManager::setFeatureState(OSystem::Feature f, bool enable) { switch (f) { case OSystem::kFeatureFullscreenMode: if (_fullscreen != enable) { _fullscreen = enable; if (_transactionMode == kTransactionNone) createOrUpdateScreen(); } break; case OSystem::kFeatureAspectRatioCorrection: _lockAspectRatio = enable; break; default: break; } } void OpenGLSdlGraphics3dManager::beginGFXTransaction() { assert(_transactionMode == kTransactionNone); _transactionMode = kTransactionActive; } OSystem::TransactionError OpenGLSdlGraphics3dManager::endGFXTransaction() { assert(_transactionMode != kTransactionNone); setupScreen(); _transactionMode = kTransactionNone; return OSystem::kTransactionSuccess; } const OSystem::GraphicsMode glGraphicsModes[] = { { "opengl3d", "OpenGL 3D", 0 }, { nullptr, nullptr, 0 } }; const OSystem::GraphicsMode *OpenGLSdlGraphics3dManager::getSupportedGraphicsModes() const { return glGraphicsModes; } int OpenGLSdlGraphics3dManager::getDefaultGraphicsMode() const { return 0; } bool OpenGLSdlGraphics3dManager::setGraphicsMode(int mode, uint flags) { assert(_transactionMode != kTransactionNone); assert(flags & OSystem::kGfxModeRender3d); return true; } int OpenGLSdlGraphics3dManager::getGraphicsMode() const { return 0; } const OSystem::GraphicsMode glStretchModes[] = { {"center", _s("Center"), STRETCH_CENTER}, {"pixel-perfect", _s("Pixel-perfect scaling"), STRETCH_INTEGRAL}, {"even-pixels", _s("Even pixels scaling"), STRETCH_INTEGRAL_AR}, {"fit", _s("Fit to window"), STRETCH_FIT}, {"stretch", _s("Stretch to window"), STRETCH_STRETCH}, {"fit_force_aspect", _s("Fit to window (4:3)"), STRETCH_FIT_FORCE_ASPECT}, {nullptr, nullptr, 0} }; const OSystem::GraphicsMode *OpenGLSdlGraphics3dManager::getSupportedStretchModes() const { return glStretchModes; } int OpenGLSdlGraphics3dManager::getDefaultStretchMode() const { return STRETCH_FIT; } bool OpenGLSdlGraphics3dManager::setStretchMode(int mode) { assert(_transactionMode != kTransactionNone); if (mode == _stretchMode) return true; // Check this is a valid mode const OSystem::GraphicsMode *sm = getSupportedStretchModes(); bool found = false; while (sm->name) { if (sm->id == mode) { found = true; break; } sm++; } if (!found) { warning("unknown stretch mode %d", mode); return false; } _stretchMode = mode; return true; } int OpenGLSdlGraphics3dManager::getStretchMode() const { return _stretchMode; } void OpenGLSdlGraphics3dManager::initSize(uint w, uint h, const Graphics::PixelFormat *format) { _engineRequestedWidth = w; _engineRequestedHeight = h; if (_transactionMode == kTransactionNone) setupScreen(); } void OpenGLSdlGraphics3dManager::setupScreen() { assert(_transactionMode == kTransactionActive); closeOverlay(); _antialiasing = ConfMan.getInt("antialiasing"); _vsync = ConfMan.getBool("vsync"); #if SDL_VERSION_ATLEAST(2, 0, 0) bool needsWindowReset = false; if (_window->getSDLWindow() && SDL_GL_GetCurrentContext()) { // The anti-aliasing setting cannot be changed without recreating the window. // So check if the window needs to be recreated. int currentSamples = 0; #if defined(__EMSCRIPTEN__) // SDL_GL_MULTISAMPLESAMPLES isn't available on a WebGL 1.0 context // (or not bridged in Emscripten?). This forces a windows reset. currentSamples = -1; #else SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, ¤tSamples); #endif // When rendering to a framebuffer, MSAA is enabled on that framebuffer, not on the screen int targetSamples = shouldRenderToFramebuffer() ? 0 : _antialiasing; if (currentSamples != targetSamples) { needsWindowReset = true; } } // Clear the GL context when going from / to the launcher SDL_GL_DeleteContext(_glContext); _glContext = nullptr; if (needsWindowReset) { _window->destroyWindow(); } #endif createOrUpdateScreen(); int glflag; const GLubyte *str; str = glGetString(GL_VENDOR); debug(2, "INFO: OpenGL Vendor: %s", str); str = glGetString(GL_RENDERER); debug(2, "INFO: OpenGL Renderer: %s", str); str = glGetString(GL_VERSION); debug(2, "INFO: OpenGL Version: %s", str); SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &glflag); debug(2, "INFO: OpenGL Red bits: %d", glflag); SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &glflag); debug(2, "INFO: OpenGL Green bits: %d", glflag); SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &glflag); debug(2, "INFO: OpenGL Blue bits: %d", glflag); SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &glflag); debug(2, "INFO: OpenGL Alpha bits: %d", glflag); SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &glflag); debug(2, "INFO: OpenGL Z buffer depth bits: %d", glflag); SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &glflag); debug(2, "INFO: OpenGL Double Buffer: %d", glflag); SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &glflag); debug(2, "INFO: OpenGL Stencil buffer bits: %d", glflag); #ifdef USE_OPENGL_SHADERS debug(2, "INFO: GLSL version: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); #endif } void OpenGLSdlGraphics3dManager::createOrUpdateScreen() { closeOverlay(); // If the game can't adapt to any resolution, render it to a framebuffer // so it can be scaled to fill the available space. bool engineSupportsArbitraryResolutions = !g_engine || g_engine->hasFeature(Engine::kSupportsArbitraryResolutions); bool renderToFrameBuffer = shouldRenderToFramebuffer(); // Choose the effective window size or fullscreen mode uint effectiveWidth; uint effectiveHeight; if (_fullscreen && (engineSupportsArbitraryResolutions || renderToFrameBuffer)) { Common::Rect fullscreenResolution = getPreferredFullscreenResolution(); effectiveWidth = fullscreenResolution.width(); effectiveHeight = fullscreenResolution.height(); } else { effectiveWidth = _engineRequestedWidth; effectiveHeight = _engineRequestedHeight; } if (!createOrUpdateGLContext(_engineRequestedWidth, _engineRequestedHeight, effectiveWidth, effectiveHeight, renderToFrameBuffer, engineSupportsArbitraryResolutions)) { warning("SDL Error: %s", SDL_GetError()); g_system->quit(); } #if SDL_VERSION_ATLEAST(2, 0, 1) int obtainedWidth = 0, obtainedHeight = 0; SDL_GL_GetDrawableSize(_window->getSDLWindow(), &obtainedWidth, &obtainedHeight); #else int obtainedWidth = effectiveWidth; int obtainedHeight = effectiveHeight; #endif _surfaceRenderer = OpenGL::createBestSurfaceRenderer(); _overlayFormat = OpenGL::TextureGL::getRGBAPixelFormat(); if (renderToFrameBuffer) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); _frameBuffer = createFramebuffer(_engineRequestedWidth, _engineRequestedHeight); _frameBuffer->attach(); handleResize(_engineRequestedWidth, _engineRequestedHeight); } else { handleResize(obtainedWidth, obtainedHeight); } } void OpenGLSdlGraphics3dManager::notifyResize(const int width, const int height) { #if SDL_VERSION_ATLEAST(2, 0, 0) // Get the updated size directly from SDL, in case there are multiple // resize events in the message queue. int newWidth = 0, newHeight = 0; SDL_GL_GetDrawableSize(_window->getSDLWindow(), &newWidth, &newHeight); if (newWidth == _overlayScreen->getWidth() && newHeight == _overlayScreen->getHeight()) { return; // nothing to do } handleResize(newWidth, newHeight); #else handleResize(width, height); #endif } void OpenGLSdlGraphics3dManager::handleResizeImpl(const int width, const int height) { // Update the overlay delete _overlayScreen; _overlayScreen = new OpenGL::TiledSurface(width, height, _overlayFormat); // Clear the overlay background so it is not displayed distorted while resizing delete _overlayBackground; _overlayBackground = nullptr; // Re-setup the scaling for the screen recalculateDisplayAreas(); // Something changed, so update the screen change ID. _screenChangeCount++; } bool OpenGLSdlGraphics3dManager::gameNeedsAspectRatioCorrection() const { if (_lockAspectRatio) { const uint width = getWidth(); const uint height = getHeight(); // In case we enable aspect ratio correction we force a 4/3 ratio. // But just for 320x200 and 640x400 games, since other games do not need // this. return (width == 320 && height == 200) || (width == 640 && height == 400); } return false; } void OpenGLSdlGraphics3dManager::initializeOpenGLContext() const { OpenGLContext.initialize(_glContextType); #if SDL_VERSION_ATLEAST(2, 0, 0) if (SDL_GL_SetSwapInterval(_vsync ? 1 : 0)) { warning("Unable to %s VSync: %s", _vsync ? "enable" : "disable", SDL_GetError()); } #endif } OpenGLSdlGraphics3dManager::OpenGLPixelFormat::OpenGLPixelFormat(uint screenBytesPerPixel, uint red, uint blue, uint green, uint alpha, int samples) : bytesPerPixel(screenBytesPerPixel), redSize(red), blueSize(blue), greenSize(green), alphaSize(alpha), multisampleSamples(samples) { } bool OpenGLSdlGraphics3dManager::createOrUpdateGLContext(uint gameWidth, uint gameHeight, uint effectiveWidth, uint effectiveHeight, bool renderToFramebuffer, bool engineSupportsArbitraryResolutions) { // Build a list of OpenGL pixel formats usable by ScummVM Common::Array pixelFormats; if (_antialiasing > 0 && !renderToFramebuffer) { // Don't enable screen level multisampling when rendering to a framebuffer pixelFormats.push_back(OpenGLPixelFormat(32, 8, 8, 8, 8, _antialiasing)); pixelFormats.push_back(OpenGLPixelFormat(16, 5, 5, 5, 1, _antialiasing)); pixelFormats.push_back(OpenGLPixelFormat(16, 5, 6, 5, 0, _antialiasing)); } pixelFormats.push_back(OpenGLPixelFormat(32, 8, 8, 8, 8, 0)); pixelFormats.push_back(OpenGLPixelFormat(16, 5, 5, 5, 1, 0)); pixelFormats.push_back(OpenGLPixelFormat(16, 5, 6, 5, 0, 0)); bool clear = false; // Unfortunately, SDL does not provide a list of valid pixel formats // for the current OpenGL implementation and hardware. // SDL may not be able to create a screen with the preferred pixel format. // Try all the pixel formats in the list until SDL returns a valid screen. Common::Array::const_iterator it = pixelFormats.begin(); for (; it != pixelFormats.end(); it++) { SDL_GL_SetAttribute(SDL_GL_RED_SIZE, it->redSize); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, it->greenSize); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, it->blueSize); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, it->alphaSize); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, it->multisampleSamples > 0); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, it->multisampleSamples); #if !SDL_VERSION_ATLEAST(2, 0, 0) SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, _vsync ? 1 : 0); #endif #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, _glContextProfileMask); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, _glContextMajor); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, _glContextMinor); #endif #if SDL_VERSION_ATLEAST(2, 0, 0) uint32 sdlflags = SDL_WINDOW_OPENGL; #ifdef NINTENDO_SWITCH // Switch quirk: Switch seems to need this flag, otherwise the screen // is zoomed when switching from Normal graphics mode to OpenGL sdlflags |= SDL_WINDOW_FULLSCREEN_DESKTOP; #endif if (renderToFramebuffer || engineSupportsArbitraryResolutions) { sdlflags |= SDL_WINDOW_RESIZABLE; } if (_fullscreen) { // On Linux/X11, when toggling to fullscreen, the window manager saves // the window size to be able to restore it when going back to windowed mode. // If the user configured ScummVM to start in fullscreen mode, we first // create a window and then toggle it to fullscreen to give the window manager // a chance to save the window size. That way if the user switches back // to windowed mode, the window manager has a window size to apply instead // of leaving the window at the fullscreen resolution size. if (!_window->getSDLWindow()) { _window->createOrUpdateWindow(gameWidth, gameHeight, sdlflags); } sdlflags |= SDL_WINDOW_FULLSCREEN; } if (_window->createOrUpdateWindow(effectiveWidth, effectiveHeight, sdlflags)) { // Get the current GL context from SDL in case the previous one // was destroyed because the window was recreated. _glContext = SDL_GL_GetCurrentContext(); if (!_glContext) { _glContext = SDL_GL_CreateContext(_window->getSDLWindow()); if (_glContext) { clear = true; } } if (_glContext) { assert(SDL_GL_GetCurrentWindow() == _window->getSDLWindow()); break; } } _window->destroyWindow(); #else uint32 sdlflags = SDL_OPENGL; if (_fullscreen) sdlflags |= SDL_FULLSCREEN; SDL_Surface *screen = SDL_SetVideoMode(effectiveWidth, effectiveHeight, it->bytesPerPixel, sdlflags); if (screen) { break; } #endif } // Display a warning if the effective pixel format is not the preferred one if (it != pixelFormats.begin() && it != pixelFormats.end()) { bool wantsAA = pixelFormats.front().multisampleSamples > 0; bool gotAA = it->multisampleSamples > 0; warning("Couldn't create a %d-bit visual%s, using to %d-bit%s instead", pixelFormats.front().bytesPerPixel, wantsAA && !gotAA ? " with AA" : "", it->bytesPerPixel, wantsAA && !gotAA ? " without AA" : ""); } if (it == pixelFormats.end()) return false; initializeOpenGLContext(); if (clear) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); return true; } bool OpenGLSdlGraphics3dManager::shouldRenderToFramebuffer() const { bool engineSupportsArbitraryResolutions = !g_engine || g_engine->hasFeature(Engine::kSupportsArbitraryResolutions); return !engineSupportsArbitraryResolutions && _supportsFrameBuffer; } bool OpenGLSdlGraphics3dManager::isVSyncEnabled() const { #if SDL_VERSION_ATLEAST(2, 0, 0) return SDL_GL_GetSwapInterval() != 0; #else int swapControl = 0; SDL_GL_GetAttribute(SDL_GL_SWAP_CONTROL, &swapControl); return swapControl != 0; #endif } void OpenGLSdlGraphics3dManager::drawOverlay() { _surfaceRenderer->prepareState(); glViewport(_overlayDrawRect.left, _windowHeight - _overlayDrawRect.top - _overlayDrawRect.height(), _overlayDrawRect.width(), _overlayDrawRect.height()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); if (_overlayBackground) { _overlayBackground->draw(_surfaceRenderer); } _surfaceRenderer->enableAlphaBlending(true); _surfaceRenderer->setFlipY(true); _overlayScreen->draw(_surfaceRenderer); _surfaceRenderer->restorePreviousState(); } OpenGL::FrameBuffer *OpenGLSdlGraphics3dManager::createFramebuffer(uint width, uint height) { #if !USE_FORCED_GLES2 if (_antialiasing && OpenGLContext.framebufferObjectMultisampleSupported) { return new OpenGL::MultiSampleFrameBuffer(width, height, _antialiasing); } else #endif { return new OpenGL::FrameBuffer(width, height); } } void OpenGLSdlGraphics3dManager::updateScreen() { if (_frameBuffer) { _frameBuffer->detach(); _surfaceRenderer->prepareState(); glViewport(_gameDrawRect.left, _windowHeight - _gameDrawRect.top - _gameDrawRect.height(), _gameDrawRect.width(), _gameDrawRect.height()); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); _surfaceRenderer->render(_frameBuffer, Math::Rect2d(Math::Vector2d(0, 0), Math::Vector2d(1, 1))); _surfaceRenderer->restorePreviousState(); } if (_overlayVisible) { _overlayScreen->update(); // If the overlay is in game we expect the game to continue calling OpenGL if (_overlayBackground && _overlayInGUI) { _overlayBackground->update(); } drawOverlay(); } #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_GL_SwapWindow(_window->getSDLWindow()); #else SDL_GL_SwapBuffers(); #endif if (_frameBuffer) { _frameBuffer->attach(); } } int16 OpenGLSdlGraphics3dManager::getHeight() const { if (_frameBuffer) return _frameBuffer->getHeight(); else return _overlayScreen->getHeight(); } int16 OpenGLSdlGraphics3dManager::getWidth() const { if (_frameBuffer) return _frameBuffer->getWidth(); else return _overlayScreen->getWidth(); } #pragma mark - #pragma mark --- Overlays --- #pragma mark - void OpenGLSdlGraphics3dManager::showOverlay(bool inGUI) { if (_overlayVisible && _overlayInGUI == inGUI) { return; } WindowedGraphicsManager::showOverlay(inGUI); delete _overlayBackground; _overlayBackground = nullptr; if (g_engine) { if (_frameBuffer) _frameBuffer->detach(); // If there is a game running capture the screen, so that it can be shown "below" the overlay. _overlayBackground = new OpenGL::TiledSurface(_overlayScreen->getWidth(), _overlayScreen->getHeight(), _overlayFormat); Graphics::Surface *background = _overlayBackground->getBackingSurface(); glReadPixels(0, 0, background->w, background->h, GL_RGBA, GL_UNSIGNED_BYTE, background->getPixels()); if (_frameBuffer) _frameBuffer->attach(); } } void OpenGLSdlGraphics3dManager::hideOverlay() { if (!_overlayVisible) { return; } WindowedGraphicsManager::hideOverlay(); delete _overlayBackground; _overlayBackground = nullptr; } void OpenGLSdlGraphics3dManager::copyRectToOverlay(const void *buf, int pitch, int x, int y, int w, int h) { _overlayScreen->copyRectToSurface(buf, pitch, x, y, w, h); } void OpenGLSdlGraphics3dManager::clearOverlay() { _overlayScreen->fill(0); } void OpenGLSdlGraphics3dManager::grabOverlay(Graphics::Surface &surface) const { const Graphics::Surface *overlayData = _overlayScreen->getBackingSurface(); assert(surface.w >= overlayData->w); assert(surface.h >= overlayData->h); assert(surface.format.bytesPerPixel == overlayData->format.bytesPerPixel); const byte *src = (const byte *)overlayData->getPixels(); byte *dst = (byte *)surface.getPixels(); Graphics::copyBlit(dst, src, surface.pitch, overlayData->pitch, overlayData->w, overlayData->h, overlayData->format.bytesPerPixel); } void OpenGLSdlGraphics3dManager::closeOverlay() { if (_overlayScreen) { delete _overlayScreen; _overlayScreen = nullptr; } delete _surfaceRenderer; _surfaceRenderer = nullptr; delete _frameBuffer; _frameBuffer = nullptr; OpenGLContext.reset(); } int16 OpenGLSdlGraphics3dManager::getOverlayHeight() const { return _overlayScreen->getHeight(); } int16 OpenGLSdlGraphics3dManager::getOverlayWidth() const { return _overlayScreen->getWidth(); } bool OpenGLSdlGraphics3dManager::showMouse(bool visible) { SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE); return true; } void OpenGLSdlGraphics3dManager::showSystemMouseCursor(bool visible) { // HACK: SdlGraphicsManager disables the system cursor when the mouse is in the // active draw rect, however the 3D graphics manager uses it instead of the // standard mouse graphic. } #if SDL_VERSION_ATLEAST(2, 0, 0) void OpenGLSdlGraphics3dManager::deinitializeRenderer() { SDL_GL_DeleteContext(_glContext); _glContext = nullptr; } #endif // SDL_VERSION_ATLEAST(2, 0, 0) bool OpenGLSdlGraphics3dManager::saveScreenshot(const Common::String &filename) const { // Largely based on the implementation from ScummVM uint width = _overlayScreen->getWidth(); uint height = _overlayScreen->getHeight(); uint linePaddingSize = width % 4; uint lineSize = width * 3 + linePaddingSize; Common::DumpFile out; if (!out.open(filename)) { return false; } Common::Array pixels; pixels.resize(lineSize * height); if (_frameBuffer) { _frameBuffer->detach(); } glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, &pixels.front()); if (_frameBuffer) { _frameBuffer->attach(); } #ifdef SCUMM_LITTLE_ENDIAN const Graphics::PixelFormat format(3, 8, 8, 8, 0, 0, 8, 16, 0); #else const Graphics::PixelFormat format(3, 8, 8, 8, 0, 16, 8, 0, 0); #endif Graphics::Surface data; data.init(width, height, lineSize, &pixels.front(), format); data.flipVertical(Common::Rect(width, height)); #ifdef USE_PNG return Image::writePNG(out, data); #else return Image::writeBMP(out, data); #endif } #endif