diff --git a/src/AnmManager.cpp b/src/AnmManager.cpp index b6c6c52d..a81cce33 100644 --- a/src/AnmManager.cpp +++ b/src/AnmManager.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -1975,6 +1976,11 @@ void AnmManager::TakeScreenshot(i32 textureId, i32 left, i32 top, i32 width, i32 SDL_Rect stretchSrcRect; SDL_Surface *stretchedSurface = NULL; SDL_Surface *unstretchedSurface = NULL; + i32 scaledLeft; + i32 scaledTop; + i32 scaledWidth; + i32 scaledHeight; + i32 readY; // OpenGL throws an error specifically for negative W / H and pixels are undefined for 0 inputs. if (this->textures[textureId].handle == 0 || width <= 0 || height <= 0) @@ -1984,17 +1990,19 @@ void AnmManager::TakeScreenshot(i32 textureId, i32 left, i32 top, i32 width, i32 this->SetCurrentTexture(this->textures[textureId].handle); - backBufferPixels = - new u8[((u32)(width * WIDTH_RESOLUTION_SCALE + 1)) * ((u32)(height * HEIGHT_RESOLUTION_SCALE + 1)) * 4]; + scaledLeft = (i32)std::lround(left * WIDTH_RESOLUTION_SCALE + VIEWPORT_OFF_X); + scaledTop = (i32)std::lround(top * HEIGHT_RESOLUTION_SCALE + VIEWPORT_OFF_Y); + scaledWidth = std::max(1, (i32)std::lround(width * WIDTH_RESOLUTION_SCALE)); + scaledHeight = std::max(1, (i32)std::lround(height * HEIGHT_RESOLUTION_SCALE)); + readY = GAME_WINDOW_HEIGHT_REAL - (scaledTop + scaledHeight); - g_glFuncTable.glReadPixels(left * WIDTH_RESOLUTION_SCALE + VIEWPORT_OFF_X, - GAME_WINDOW_HEIGHT_REAL - ((top + height) * HEIGHT_RESOLUTION_SCALE) - VIEWPORT_OFF_Y, - width * WIDTH_RESOLUTION_SCALE, height * HEIGHT_RESOLUTION_SCALE, GL_RGBA, - GL_UNSIGNED_BYTE, backBufferPixels); + backBufferPixels = new u8[(u32)scaledWidth * (u32)scaledHeight * 4]; - unstretchedSurface = SDL_CreateRGBSurfaceWithFormatFrom(backBufferPixels, width * WIDTH_RESOLUTION_SCALE, - height * HEIGHT_RESOLUTION_SCALE, 32, - width * WIDTH_RESOLUTION_SCALE * 4, SDL_PIXELFORMAT_RGBA32); + g_glFuncTable.glReadPixels(scaledLeft, readY, scaledWidth, scaledHeight, GL_RGBA, GL_UNSIGNED_BYTE, + backBufferPixels); + + unstretchedSurface = SDL_CreateRGBSurfaceWithFormatFrom(backBufferPixels, scaledWidth, scaledHeight, 32, + scaledWidth * 4, SDL_PIXELFORMAT_RGBA32); stretchedSurface = SDL_CreateRGBSurfaceWithFormat(0, this->textures[textureId].width, this->textures[textureId].height, 32, SDL_PIXELFORMAT_RGBA32); @@ -2009,8 +2017,8 @@ void AnmManager::TakeScreenshot(i32 textureId, i32 left, i32 top, i32 width, i32 stretchSrcRect.x = 0; stretchSrcRect.y = 0; - stretchSrcRect.h = height * HEIGHT_RESOLUTION_SCALE; - stretchSrcRect.w = width * WIDTH_RESOLUTION_SCALE; + stretchSrcRect.h = scaledHeight; + stretchSrcRect.w = scaledWidth; stretchDstRect.x = 0; stretchDstRect.y = 0; diff --git a/src/Config/config.cpp b/src/Config/config.cpp index 8ec2a3fa..633c2aa1 100644 --- a/src/Config/config.cpp +++ b/src/Config/config.cpp @@ -185,7 +185,7 @@ class ConfigUI int bit; }; - const CheckboxSpec specs[11] = { + const CheckboxSpec specs[12] = { {"No Vertex Buffer", GCOS_DONT_USE_VERTEX_BUF}, {"No Fog", GCOS_DONT_USE_FOG}, {"16 Bit Textures", GCOS_FORCE_16BIT_COLOR_MODE}, @@ -197,6 +197,7 @@ class ConfigUI {"No Depth test", GCOS_TURN_OFF_DEPTH_TEST}, {"Force 60 FPS", GCOS_FORCE_60FPS}, {"No DirectInput pad", GCOS_NO_DIRECTINPUT_PAD}, + {"Scale fullscreen image (keep aspect)", GCOS_SCALE_FULLSCREEN_KEEP_ASPECT}, }; const int checkboxCount = sizeof(specs) / sizeof(specs[0]); diff --git a/src/GameWindow.cpp b/src/GameWindow.cpp index 9af609d5..3194adb4 100644 --- a/src/GameWindow.cpp +++ b/src/GameWindow.cpp @@ -18,6 +18,14 @@ GameWindow g_GameWindow; i32 g_TickCountToEffectiveFramerate; f64 g_LastFrameTime; +i32 g_GameWindowWidthReal = GAME_WINDOW_WIDTH; +i32 g_GameWindowHeightReal = GAME_WINDOW_HEIGHT; +i32 g_ViewportWidth = GAME_WINDOW_WIDTH; +i32 g_ViewportHeight = GAME_WINDOW_HEIGHT; +i32 g_ViewportOffX = 0; +i32 g_ViewportOffY = 0; +f32 g_WidthResolutionScale = 1.0f; +f32 g_HeightResolutionScale = 1.0f; #define FRAME_TIME (1000. / 60.) @@ -30,6 +38,32 @@ static struct } s_RenderBackends[] = {{"GL(ES) 2.0 / WebGL", true, WebGL::SetContextFlags, WebGL::Create}, {"Fixed function GL(ES)", false, FixedFunctionGL::SetContextFlags, FixedFunctionGL::Init}}; +static void UpdateViewportMetrics(i32 realWidth, i32 realHeight) +{ + g_GameWindowWidthReal = realWidth; + g_GameWindowHeightReal = realHeight; + + g_ViewportWidth = realWidth; + g_ViewportHeight = realHeight; + g_ViewportOffX = 0; + g_ViewportOffY = 0; + + // Keep the original 4:3 gameplay aspect in non-4:3 output windows. + if (realWidth * 3 > realHeight * 4) + { + g_ViewportWidth = (i32)((realHeight / 3.0f) * 4.0f); + g_ViewportOffX = (realWidth - g_ViewportWidth) / 2; + } + else if (realWidth * 3 < realHeight * 4) + { + g_ViewportHeight = (i32)((realWidth / 4.0f) * 3.0f); + g_ViewportOffY = (realHeight - g_ViewportHeight) / 2; + } + + g_WidthResolutionScale = (f32)g_ViewportWidth / GAME_WINDOW_WIDTH; + g_HeightResolutionScale = (f32)g_ViewportHeight / GAME_WINDOW_HEIGHT; +} + RenderResult GameWindow::Render() { i32 res; @@ -183,10 +217,13 @@ void GameWindow::CreateGameWindow() SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER); u32 flags = SDL_WINDOW_OPENGL; - i32 height = GAME_WINDOW_HEIGHT_REAL; - i32 width = GAME_WINDOW_WIDTH_REAL; + i32 height = GAME_WINDOW_HEIGHT; + i32 width = GAME_WINDOW_WIDTH; i32 x = SDL_WINDOWPOS_UNDEFINED; i32 y = SDL_WINDOWPOS_UNDEFINED; + i32 drawableWidth = GAME_WINDOW_WIDTH; + i32 drawableHeight = GAME_WINDOW_HEIGHT; + bool useFullscreenStretch = false; g_GameWindow.window = NULL; g_GameWindow.glContext = NULL; @@ -194,6 +231,16 @@ void GameWindow::CreateGameWindow() if (g_Supervisor.cfg.windowed == 0) { flags |= SDL_WINDOW_FULLSCREEN; + useFullscreenStretch = ((g_Supervisor.cfg.opts >> GCOS_SCALE_FULLSCREEN_KEEP_ASPECT) & 1) != 0; + if (useFullscreenStretch) + { + SDL_DisplayMode mode; + if (SDL_GetCurrentDisplayMode(0, &mode) == 0 && mode.w > 0 && mode.h > 0) + { + width = mode.w; + height = mode.h; + } + } } for (u32 i = 0; i < ARRAY_SIZE(s_RenderBackends); i++) @@ -221,6 +268,8 @@ void GameWindow::CreateGameWindow() utils::DebugPrint2("Using renderer backend %s", s_RenderBackends[i].name); g_glFuncTable.ResolveFunctions(s_RenderBackends[i].isEsContext); + SDL_GL_GetDrawableSize(g_GameWindow.window, &drawableWidth, &drawableHeight); + UpdateViewportMetrics(drawableWidth, drawableHeight); g_GameWindow.renderBackendIndex = i; break; fail: diff --git a/src/GameWindow.hpp b/src/GameWindow.hpp index 795ac0f1..89d3a33f 100644 --- a/src/GameWindow.hpp +++ b/src/GameWindow.hpp @@ -11,36 +11,24 @@ #define GAME_WINDOW_HEIGHT (480) // The actual resolution used for the output window and viewport scaling -// At some point there should be a method to change this without recompiling but for now -// this'll do -#ifndef GAME_WINDOW_WIDTH_REAL -#define GAME_WINDOW_WIDTH_REAL (GAME_WINDOW_WIDTH) -#endif - -#ifndef GAME_WINDOW_HEIGHT_REAL -#define GAME_WINDOW_HEIGHT_REAL (GAME_WINDOW_HEIGHT) -#endif - -#define VIEWPORT_WIDTH GAME_WINDOW_WIDTH_REAL -#define VIEWPORT_OFF_X 0 -#define VIEWPORT_HEIGHT GAME_WINDOW_HEIGHT_REAL -#define VIEWPORT_OFF_Y 0 - -// Aspect ratio wider than 4:3 -#if (GAME_WINDOW_WIDTH_REAL * 3) > (GAME_WINDOW_HEIGHT_REAL * 4) -#undef VIEWPORT_WIDTH -#undef VIEWPORT_OFF_X -#define VIEWPORT_WIDTH ((u32)((GAME_WINDOW_HEIGHT_REAL / 3.0f) * 4.0f)) -#define VIEWPORT_OFF_X ((GAME_WINDOW_WIDTH_REAL - VIEWPORT_WIDTH) / 2) -#elif (GAME_WINDOW_WIDTH_REAL * 3) < (GAME_WINDOW_HEIGHT_REAL * 4) -#undef VIEWPORT_HEIGHT -#undef VIEWPORT_OFF_Y -#define VIEWPORT_HEIGHT ((u32)((GAME_WINDOW_WIDTH_REAL / 4.0f) * 3.0f)) -#define VIEWPORT_OFF_Y ((GAME_WINDOW_HEIGHT_REAL - VIEWPORT_HEIGHT) / 2) -#endif - -#define WIDTH_RESOLUTION_SCALE (((f32)VIEWPORT_WIDTH) / GAME_WINDOW_WIDTH) -#define HEIGHT_RESOLUTION_SCALE (((f32)VIEWPORT_HEIGHT) / GAME_WINDOW_HEIGHT) +// These values are computed at runtime from the created window size. +extern i32 g_GameWindowWidthReal; +extern i32 g_GameWindowHeightReal; +extern i32 g_ViewportWidth; +extern i32 g_ViewportHeight; +extern i32 g_ViewportOffX; +extern i32 g_ViewportOffY; +extern f32 g_WidthResolutionScale; +extern f32 g_HeightResolutionScale; + +#define GAME_WINDOW_WIDTH_REAL (g_GameWindowWidthReal) +#define GAME_WINDOW_HEIGHT_REAL (g_GameWindowHeightReal) +#define VIEWPORT_WIDTH (g_ViewportWidth) +#define VIEWPORT_OFF_X (g_ViewportOffX) +#define VIEWPORT_HEIGHT (g_ViewportHeight) +#define VIEWPORT_OFF_Y (g_ViewportOffY) +#define WIDTH_RESOLUTION_SCALE (g_WidthResolutionScale) +#define HEIGHT_RESOLUTION_SCALE (g_HeightResolutionScale) enum RenderResult { diff --git a/src/Supervisor.hpp b/src/Supervisor.hpp index 3436923a..401048d7 100644 --- a/src/Supervisor.hpp +++ b/src/Supervisor.hpp @@ -27,6 +27,7 @@ enum GameConfigOptsShifts GCOS_REFERENCE_RASTERIZER_MODE = 0x9, GCOS_DONT_USE_FOG = 0xa, GCOS_NO_DIRECTINPUT_PAD = 0xb, + GCOS_SCALE_FULLSCREEN_KEEP_ASPECT = 0xc, }; struct ControllerMapping