scummvm/backends/graphics3d/surfacesdl/surfacesdl-graphics3d.cpp
2020-10-05 19:09:58 +02:00

556 lines
14 KiB
C++

/* ResidualVM - A 3D game interpreter
*
* ResidualVM 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "common/scummsys.h"
#if defined(SDL_BACKEND)
#include "backends/graphics3d/surfacesdl/surfacesdl-graphics3d.h"
#include "backends/events/sdl/sdl-events.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "engines/engine.h"
#include "graphics/pixelbuffer.h"
#include "graphics/surface.h"
#ifdef USE_PNG
#include "image/png.h"
#endif
// SDL surface flags which got removed in SDL2.
#if SDL_VERSION_ATLEAST(2, 0, 0)
#define SDL_FULLSCREEN 0x40000000
#endif
SurfaceSdlGraphics3dManager::SurfaceSdlGraphics3dManager(SdlEventSource *sdlEventSource, SdlWindow *window)
:
SdlGraphics3dManager(sdlEventSource, window),
#if SDL_VERSION_ATLEAST(2, 0, 0)
_renderer(nullptr), _screenTexture(nullptr),
#endif
_screen(0),
_subScreen(0),
_overlayscreen(0),
_overlayDirty(true),
_overlayVisible(false),
_fullscreen(false),
_lockAspectRatio(true),
_screenChangeCount(1 << (sizeof(int) * 6 - 2)),
_gameRect(),
_engineRequestedWidth(0),
_engineRequestedHeight(0),
_transactionMode(kTransactionNone) {
ConfMan.registerDefault("aspect_ratio", true);
}
SurfaceSdlGraphics3dManager::~SurfaceSdlGraphics3dManager() {
closeOverlay();
if (_subScreen) {
SDL_FreeSurface(_subScreen);
_subScreen = nullptr;
}
#if SDL_VERSION_ATLEAST(2, 0, 0)
deinitializeRenderer();
#endif
}
bool SurfaceSdlGraphics3dManager::hasFeature(OSystem::Feature f) const {
return
#if SDL_VERSION_ATLEAST(2, 0, 0)
(f == OSystem::kFeatureFullscreenToggleKeepsContext) ||
#endif
(f == OSystem::kFeatureAspectRatioCorrection) ||
(f == OSystem::kFeatureFullscreenMode);
}
bool SurfaceSdlGraphics3dManager::getFeatureState(OSystem::Feature f) const {
switch (f) {
case OSystem::kFeatureFullscreenMode:
return _fullscreen;
case OSystem::kFeatureAspectRatioCorrection:
return _lockAspectRatio;
default:
return false;
}
}
void SurfaceSdlGraphics3dManager::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 SurfaceSdlGraphics3dManager::beginGFXTransaction() {
assert(_transactionMode == kTransactionNone);
_transactionMode = kTransactionActive;
}
OSystem::TransactionError SurfaceSdlGraphics3dManager::endGFXTransaction() {
assert(_transactionMode != kTransactionNone);
setupScreen();
_transactionMode = kTransactionNone;
return OSystem::kTransactionSuccess;
}
const OSystem::GraphicsMode glGraphicsModes[] = {
{ "tinygl", "TinyGL", 0 },
{ nullptr, nullptr, 0 }
};
const OSystem::GraphicsMode *SurfaceSdlGraphics3dManager::getSupportedGraphicsModes() const {
return glGraphicsModes;
}
int SurfaceSdlGraphics3dManager::getDefaultGraphicsMode() const {
return 0;
}
bool SurfaceSdlGraphics3dManager::setGraphicsMode(int mode, uint flags) {
assert(_transactionMode != kTransactionNone);
assert(flags & OSystem::kGfxModeRender3d);
assert(!(flags & OSystem::kGfxModeAcceleration3d));
return true;
}
int SurfaceSdlGraphics3dManager::getGraphicsMode() const {
return 0;
}
void SurfaceSdlGraphics3dManager::initSize(uint w, uint h, const Graphics::PixelFormat *format) {
_engineRequestedWidth = w;
_engineRequestedHeight = h;
if (_transactionMode == kTransactionNone)
setupScreen();
}
void SurfaceSdlGraphics3dManager::setupScreen() {
assert(_transactionMode == kTransactionActive);
if (_subScreen) {
SDL_FreeSurface(_subScreen);
_subScreen = nullptr;
}
#if SDL_VERSION_ATLEAST(2, 0, 0)
deinitializeRenderer();
#endif
_lockAspectRatio = ConfMan.getBool("aspect_ratio");
createOrUpdateScreen();
SDL_PixelFormat *f = _screen->format;
_subScreen = SDL_CreateRGBSurface(SDL_SWSURFACE, _engineRequestedWidth, _engineRequestedHeight, f->BitsPerPixel, f->Rmask, f->Gmask, f->Bmask, f->Amask);
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_SetSurfaceBlendMode(_subScreen, SDL_BLENDMODE_NONE);
#endif // SDL_VERSION_ATLEAST(2, 0, 0)
}
void SurfaceSdlGraphics3dManager::createOrUpdateScreen() {
closeOverlay();
// Choose the effective window size or fullscreen mode
uint effectiveWidth;
uint effectiveHeight;
if (_fullscreen && _lockAspectRatio) {
Common::Rect fullscreenResolution = getPreferredFullscreenResolution();
effectiveWidth = fullscreenResolution.width();
effectiveHeight = fullscreenResolution.height();
} else {
effectiveWidth = _engineRequestedWidth;
effectiveHeight = _engineRequestedHeight;
}
// The game is centered inside the effective screen
_gameRect = Math::Rect2d(
Math::Vector2d((effectiveWidth - _engineRequestedWidth) / 2, (effectiveHeight - _engineRequestedHeight) / 2),
Math::Vector2d((effectiveWidth + _engineRequestedWidth) / 2, (effectiveHeight + _engineRequestedHeight) / 2)
);
uint32 sdlflags = SDL_SWSURFACE;
if (_fullscreen)
sdlflags |= SDL_FULLSCREEN;
_screen = SDL_SetVideoMode(effectiveWidth, effectiveHeight, 0, sdlflags);
if (!_screen) {
warning("SDL_SetVideoMode failed: %s", SDL_GetError());
g_system->quit();
}
SDL_PixelFormat *f = _screen->format;
_overlayscreen = SDL_CreateRGBSurface(SDL_SWSURFACE, effectiveWidth, effectiveHeight, f->BitsPerPixel,
f->Rmask, f->Gmask, f->Bmask, f->Amask);
_overlayFormat = Graphics::PixelFormat(f->BytesPerPixel, 8 - f->Rloss, 8 - f->Gloss, 8 - f->Bloss, 0,
f->Rshift, f->Gshift, f->Bshift, f->Ashift);
if (!_overlayscreen) {
warning("SDL_RGBSurface failed: %s", SDL_GetError());
g_system->quit();
}
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_SetSurfaceBlendMode(_overlayscreen, SDL_BLENDMODE_NONE);
#endif // SDL_VERSION_ATLEAST(2, 0, 0)
_screenFormat = _overlayFormat;
_screenChangeCount++;
}
Graphics::PixelBuffer SurfaceSdlGraphics3dManager::getScreenPixelBuffer() {
return Graphics::PixelBuffer(_screenFormat, (byte *)_subScreen->pixels);
}
void SurfaceSdlGraphics3dManager::drawOverlay() {
if (!_overlayscreen)
return;
SDL_BlitSurface(_overlayscreen, NULL, _screen, NULL);
}
void SurfaceSdlGraphics3dManager::updateScreen() {
SDL_Rect dstrect;
dstrect.x = _gameRect.getTopLeft().getX();
dstrect.y = _gameRect.getTopLeft().getY();
dstrect.w = _gameRect.getWidth();
dstrect.h = _gameRect.getHeight();
SDL_BlitSurface(_subScreen, NULL, _screen, &dstrect);
if (_overlayVisible) {
drawOverlay();
}
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_UpdateTexture(_screenTexture, nullptr, _screen->pixels, _screen->pitch);
SDL_RenderClear(_renderer);
SDL_RenderCopy(_renderer, _screenTexture, nullptr, nullptr);
SDL_RenderPresent(_renderer);
#else
SDL_Flip(_screen);
#endif
}
int16 SurfaceSdlGraphics3dManager::getHeight() const {
// ResidualVM specific
return _subScreen->h;
}
int16 SurfaceSdlGraphics3dManager::getWidth() const {
// ResidualVM specific
return _subScreen->w;
}
#pragma mark -
#pragma mark --- Overlays ---
#pragma mark -
void SurfaceSdlGraphics3dManager::clearOverlay() {
if (!_overlayscreen)
return;
if (!_overlayVisible)
return;
SDL_BlitSurface(_screen, NULL, _overlayscreen, NULL);
_overlayDirty = true;
}
void SurfaceSdlGraphics3dManager::showOverlay() {
if (_overlayVisible)
return;
_overlayVisible = true;
clearOverlay();
}
void SurfaceSdlGraphics3dManager::hideOverlay() {
if (!_overlayVisible)
return;
_overlayVisible = false;
clearOverlay();
}
void SurfaceSdlGraphics3dManager::grabOverlay(void *buf, int pitch) const {
if (_overlayscreen == NULL)
return;
if (SDL_LockSurface(_overlayscreen) == -1)
error("SDL_LockSurface failed: %s", SDL_GetError());
byte *src = (byte *)_overlayscreen->pixels;
byte *dst = (byte *)buf;
int h = _overlayscreen->h;
do {
memcpy(dst, src, _overlayscreen->w * _overlayscreen->format->BytesPerPixel);
src += _overlayscreen->pitch;
dst += pitch;
} while (--h);
SDL_UnlockSurface(_overlayscreen);
}
void SurfaceSdlGraphics3dManager::copyRectToOverlay(const void *buf, int pitch, int x, int y, int w, int h) {
if (_overlayscreen == NULL)
return;
const byte *src = (const byte *)buf;
// Clip the coordinates
if (x < 0) {
w += x;
src -= x * _overlayscreen->format->BytesPerPixel;
x = 0;
}
if (y < 0) {
h += y;
src -= y * pitch;
y = 0;
}
if (w > _overlayscreen->w - x) {
w = _overlayscreen->w - x;
}
if (h > _overlayscreen->h - y) {
h = _overlayscreen->h - y;
}
if (w <= 0 || h <= 0)
return;
if (SDL_LockSurface(_overlayscreen) == -1)
error("SDL_LockSurface failed: %s", SDL_GetError());
byte *dst = (byte *)_overlayscreen->pixels + y * _overlayscreen->pitch + x * _overlayscreen->format->BytesPerPixel;
do {
memcpy(dst, src, w * _overlayscreen->format->BytesPerPixel);
dst += _overlayscreen->pitch;
src += pitch;
} while (--h);
SDL_UnlockSurface(_overlayscreen);
}
void SurfaceSdlGraphics3dManager::closeOverlay() {
if (_overlayscreen) {
SDL_FreeSurface(_overlayscreen);
_overlayscreen = nullptr;
}
if (_screen) {
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_FreeSurface(_screen);
#endif
_screen = nullptr;
}
}
void SurfaceSdlGraphics3dManager::warpMouse(int x, int y) {
//ResidualVM specific
// Scale from game coordinates to screen coordinates
x = (x * _gameRect.getWidth()) / _subScreen->w;
y = (y * _gameRect.getHeight()) / _subScreen->h;
x += _gameRect.getTopLeft().getX();
y += _gameRect.getTopLeft().getY();
_window->warpMouseInWindow(x, y);
}
void SurfaceSdlGraphics3dManager::transformMouseCoordinates(Common::Point &point) {
if (_overlayVisible)
return;
// Scale from screen coordinates to game coordinates
point.x -= _gameRect.getTopLeft().getX();
point.y -= _gameRect.getTopLeft().getY();
point.x = (point.x * _subScreen->w) / _gameRect.getWidth();
point.y = (point.y * _subScreen->h) / _gameRect.getHeight();
// Make sure we only supply valid coordinates.
point.x = CLIP<int16>(point.x, 0, _subScreen->w - 1);
point.y = CLIP<int16>(point.y, 0, _subScreen->h - 1);
}
#if SDL_VERSION_ATLEAST(2, 0, 0)
void SurfaceSdlGraphics3dManager::deinitializeRenderer() {
SDL_DestroyTexture(_screenTexture);
_screenTexture = nullptr;
SDL_DestroyRenderer(_renderer);
_renderer = nullptr;
}
SDL_Surface *SurfaceSdlGraphics3dManager::SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags) {
deinitializeRenderer();
uint32 createWindowFlags = 0;
Uint32 rmask, gmask, bmask, amask, format;
int depth;
#ifdef USE_SDL_RESIZABLE_WINDOW
createWindowFlags |= SDL_WINDOW_RESIZABLE;
#endif
if ((flags & SDL_FULLSCREEN) != 0) {
// 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 ResidualVM 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(width, height, createWindowFlags);
}
createWindowFlags |= SDL_WINDOW_FULLSCREEN;
}
if (!_window->createOrUpdateWindow(width, height, createWindowFlags)) {
return nullptr;
}
_renderer = SDL_CreateRenderer(_window->getSDLWindow(), -1, 0);
if (!_renderer) {
deinitializeRenderer();
return nullptr;
}
SDL_SetRenderDrawBlendMode(_renderer, SDL_BLENDMODE_NONE);
switch (bpp) {
case 0:
case 32:
format = SDL_PIXELFORMAT_ARGB8888;
break;
case 16:
format = SDL_PIXELFORMAT_RGB565;
break;
default:
warning("Unsupported bpp value: %i", bpp);
deinitializeRenderer();
return nullptr;
}
_screenTexture = SDL_CreateTexture(_renderer, format, SDL_TEXTUREACCESS_STREAMING, width, height);
if (!_screenTexture) {
deinitializeRenderer();
return nullptr;
}
if (!SDL_PixelFormatEnumToMasks(format, &depth, &rmask, &gmask, &bmask, &amask)) {
deinitializeRenderer();
return nullptr;
}
SDL_Surface *screen = SDL_CreateRGBSurface(0, width, height, depth, rmask, gmask, bmask, amask);
if (!screen) {
deinitializeRenderer();
return nullptr;
} else {
SDL_SetSurfaceBlendMode(screen, SDL_BLENDMODE_NONE);
return screen;
}
}
#endif // SDL_VERSION_ATLEAST(2, 0, 0)
bool SurfaceSdlGraphics3dManager::saveScreenshot(const Common::String &filename) const {
// Based on the implementation from ScummVM
bool success;
SDL_Surface *screen = nullptr;
#if SDL_VERSION_ATLEAST(2, 0, 0)
int width, height;
SDL_GetRendererOutputSize(_renderer, &width, &height);
screen = SDL_CreateRGBSurface(SDL_SWSURFACE,
width,
height,
24,
#ifdef SCUMM_LITTLE_ENDIAN
0x0000FF, 0x00FF00, 0xFF0000,
#else
0xFF0000, 0x00FF00, 0x0000FF,
#endif // SCUMM_LITTLE_ENDIAN
0);
SDL_RenderReadPixels(_renderer, nullptr, SDL_PIXELFORMAT_RGB24, screen->pixels, screen->pitch);
#else
screen = _screen;
#endif // SDL_VERSION_ATLEAST(2, 0, 0)
#ifdef USE_PNG
Common::DumpFile out;
if (!out.open(filename)) {
success = false;
} else {
if (SDL_LockSurface(screen) < 0) {
warning("Could not lock the screen surface");
success = false;
}
#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(screen->w, screen->h, screen->pitch, screen->pixels, format);
success = Image::writePNG(out, data);
SDL_UnlockSurface(screen);
}
#else
success = SDL_SaveBMP(screen, filename.c_str()) == 0;
#endif
if (screen && screen != _screen) {
SDL_FreeSurface(screen);
}
return success;
}
#endif