2020-01-01 21:29:24 +01:00
|
|
|
#include "Screen.h"
|
|
|
|
|
|
|
|
#include "FileSystemUtils.h"
|
|
|
|
#include "GraphicsUtil.h"
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
// Used to create the window icon
|
|
|
|
extern "C"
|
|
|
|
{
|
|
|
|
extern unsigned lodepng_decode24(
|
|
|
|
unsigned char** out,
|
|
|
|
unsigned* w,
|
|
|
|
unsigned* h,
|
|
|
|
const unsigned char* in,
|
|
|
|
size_t insize
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-07-08 20:30:57 +02:00
|
|
|
void Screen::init(
|
|
|
|
int windowWidth,
|
|
|
|
int windowHeight,
|
|
|
|
bool fullscreen,
|
|
|
|
bool useVsync,
|
|
|
|
int stretch,
|
|
|
|
bool linearFilter,
|
|
|
|
bool badSignal
|
|
|
|
) {
|
2020-04-02 22:28:12 +02:00
|
|
|
m_window = NULL;
|
|
|
|
m_renderer = NULL;
|
|
|
|
m_screenTexture = NULL;
|
|
|
|
m_screen = NULL;
|
2020-07-08 20:30:57 +02:00
|
|
|
isWindowed = !fullscreen;
|
|
|
|
stretchMode = stretch;
|
|
|
|
isFiltered = linearFilter;
|
|
|
|
vsync = useVsync;
|
2020-04-02 22:28:12 +02:00
|
|
|
filterSubrect.x = 1;
|
|
|
|
filterSubrect.y = 1;
|
|
|
|
filterSubrect.w = 318;
|
|
|
|
filterSubrect.h = 238;
|
2020-07-09 03:42:57 +02:00
|
|
|
|
|
|
|
SDL_SetHintWithPriority(
|
|
|
|
SDL_HINT_RENDER_SCALE_QUALITY,
|
|
|
|
isFiltered ? "linear" : "nearest",
|
|
|
|
SDL_HINT_OVERRIDE
|
|
|
|
);
|
|
|
|
SDL_SetHintWithPriority(
|
|
|
|
SDL_HINT_RENDER_VSYNC,
|
|
|
|
vsync ? "1" : "0",
|
|
|
|
SDL_HINT_OVERRIDE
|
|
|
|
);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
// Uncomment this next line when you need to debug -flibit
|
|
|
|
// SDL_SetHintWithPriority(SDL_HINT_RENDER_DRIVER, "software", SDL_HINT_OVERRIDE);
|
Work around SDL2 bug where VSync hint only applies on renderer creation
Ugh, this is terrible and stupid and I hate myself for it.
Anyway, since the SDL2 VSync hint only applies when the renderer is
created, we have to re-create the renderer whenever VSync is toggled.
However, this also means we need to re-create m_screenTexture as well,
AND call ResizeScreen() after that or else the letterbox/integer modes
won't be applied.
Unfortunately, this means that in main(), gameScreen.init() will create
a renderer only to be destroyed later by graphics.processVsync().
There's not much we can do about this. Fixing this would require putting
graphics.processVsync() before gameScreen.init(). However, in order to
know whether the user has VSync set, we would have to call
game.loadstats() first, but wait, we can't, because game.loadstats()
mutates gameScreen! Gahhhhhh!!!!
@leo60228 suggested to fix that problem (
https://github.com/TerryCavanagh/VVVVVV/pull/220#issuecomment-624217939
) by adding NULL checks to game.loadstats() and then calling it twice,
but then you're trading wastefully creating a renderer only to be
destroyed, for wastefully opening and parsing unlock.vvv twice instead
of once. In either case, you're doing something twice and wasting work.
2020-06-19 23:12:56 +02:00
|
|
|
// FIXME: m_renderer is also created in Graphics::processVsync()!
|
2020-01-01 21:29:24 +01:00
|
|
|
SDL_CreateWindowAndRenderer(
|
|
|
|
640,
|
|
|
|
480,
|
|
|
|
SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE,
|
|
|
|
&m_window,
|
|
|
|
&m_renderer
|
|
|
|
);
|
|
|
|
SDL_SetWindowTitle(m_window, "VVVVVV");
|
|
|
|
|
|
|
|
unsigned char *fileIn = NULL;
|
|
|
|
size_t length = 0;
|
|
|
|
unsigned char *data;
|
|
|
|
unsigned int width, height;
|
|
|
|
FILESYSTEM_loadFileToMemory("VVVVVV.png", &fileIn, &length);
|
|
|
|
lodepng_decode24(&data, &width, &height, fileIn, length);
|
|
|
|
FILESYSTEM_freeMemory(&fileIn);
|
|
|
|
SDL_Surface *icon = SDL_CreateRGBSurfaceFrom(
|
|
|
|
data,
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
24,
|
|
|
|
width * 3,
|
|
|
|
0x000000FF,
|
|
|
|
0x0000FF00,
|
|
|
|
0x00FF0000,
|
|
|
|
0x00000000
|
|
|
|
);
|
|
|
|
SDL_SetWindowIcon(m_window, icon);
|
|
|
|
SDL_FreeSurface(icon);
|
|
|
|
free(data);
|
|
|
|
|
|
|
|
// FIXME: This surface should be the actual backbuffer! -flibit
|
|
|
|
m_screen = SDL_CreateRGBSurface(
|
|
|
|
0,
|
|
|
|
320,
|
|
|
|
240,
|
|
|
|
32,
|
|
|
|
0x00FF0000,
|
|
|
|
0x0000FF00,
|
|
|
|
0x000000FF,
|
|
|
|
0xFF000000
|
|
|
|
);
|
Work around SDL2 bug where VSync hint only applies on renderer creation
Ugh, this is terrible and stupid and I hate myself for it.
Anyway, since the SDL2 VSync hint only applies when the renderer is
created, we have to re-create the renderer whenever VSync is toggled.
However, this also means we need to re-create m_screenTexture as well,
AND call ResizeScreen() after that or else the letterbox/integer modes
won't be applied.
Unfortunately, this means that in main(), gameScreen.init() will create
a renderer only to be destroyed later by graphics.processVsync().
There's not much we can do about this. Fixing this would require putting
graphics.processVsync() before gameScreen.init(). However, in order to
know whether the user has VSync set, we would have to call
game.loadstats() first, but wait, we can't, because game.loadstats()
mutates gameScreen! Gahhhhhh!!!!
@leo60228 suggested to fix that problem (
https://github.com/TerryCavanagh/VVVVVV/pull/220#issuecomment-624217939
) by adding NULL checks to game.loadstats() and then calling it twice,
but then you're trading wastefully creating a renderer only to be
destroyed, for wastefully opening and parsing unlock.vvv twice instead
of once. In either case, you're doing something twice and wasting work.
2020-06-19 23:12:56 +02:00
|
|
|
// ALSO FIXME: This SDL_CreateTexture() is duplicated in Graphics::processVsync()!
|
2020-01-01 21:29:24 +01:00
|
|
|
m_screenTexture = SDL_CreateTexture(
|
|
|
|
m_renderer,
|
|
|
|
SDL_PIXELFORMAT_ARGB8888,
|
|
|
|
SDL_TEXTUREACCESS_STREAMING,
|
|
|
|
320,
|
|
|
|
240
|
|
|
|
);
|
|
|
|
|
2020-07-08 20:30:57 +02:00
|
|
|
badSignalEffect = badSignal;
|
|
|
|
|
|
|
|
ResizeScreen(windowWidth, windowHeight);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
2020-03-13 23:22:37 +01:00
|
|
|
void Screen::ResizeScreen(int x, int y)
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
static int resX = 320;
|
|
|
|
static int resY = 240;
|
|
|
|
if (x != -1 && y != -1)
|
|
|
|
{
|
|
|
|
// This is a user resize!
|
|
|
|
resX = x;
|
|
|
|
resY = y;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!isWindowed)
|
|
|
|
{
|
2020-03-13 23:22:37 +01:00
|
|
|
int result = SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
|
|
|
if (result != 0)
|
|
|
|
{
|
|
|
|
printf("Error: could not set the game to fullscreen mode: %s\n", SDL_GetError());
|
|
|
|
return;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-03-13 23:22:37 +01:00
|
|
|
int result = SDL_SetWindowFullscreen(m_window, 0);
|
|
|
|
if (result != 0)
|
|
|
|
{
|
|
|
|
printf("Error: could not set the game to windowed mode: %s\n", SDL_GetError());
|
|
|
|
return;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
if (x != -1 && y != -1)
|
|
|
|
{
|
|
|
|
SDL_SetWindowSize(m_window, resX, resY);
|
|
|
|
SDL_SetWindowPosition(m_window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (stretchMode == 1)
|
|
|
|
{
|
|
|
|
int winX, winY;
|
|
|
|
SDL_GetWindowSize(m_window, &winX, &winY);
|
2020-03-13 23:22:37 +01:00
|
|
|
int result = SDL_RenderSetLogicalSize(m_renderer, winX, winY);
|
|
|
|
if (result != 0)
|
|
|
|
{
|
|
|
|
printf("Error: could not set logical size: %s\n", SDL_GetError());
|
|
|
|
return;
|
|
|
|
}
|
Don't print useless false error message when toggling fullscreen
Whenever you would press Alt+Enter, or Alt+F, or on macOS Command+Enter,
or on macOS Command+F, or F11, the game would print this useless error
message to console, every single time: "Error: failed: " and it would
concatenate SDL_GetError() after it, but most of the time SDL_GetError()
is blank, so it would print just that.
Instead, what the fullscreen shortcut will now do is check the result of
the relevant SDL functions, BEFORE it decides to print an error message.
And when it DOES print an error message, it will be less vague and will
say instead "Error: toggling fullscreen failed: <output of
SDL_GetError()>".
This means Screen::ResizeScreen() and Screen::toggleFullScreen() are now
int-returning functions. Ideally, every function interfacing with SDL
would return an error code, but that's too much for this simple patch.
Additionally, I took the opportunity to clean up the surrounding
formatting of the code a bit, most notably dedenting the
keypress-clearing stuff by one tab level, converting the
shortcut-handling code to spaces, and removing commented-out code.
2020-03-13 08:23:48 +01:00
|
|
|
result = SDL_RenderSetIntegerScale(m_renderer, SDL_FALSE);
|
2020-03-13 23:22:37 +01:00
|
|
|
if (result != 0)
|
|
|
|
{
|
|
|
|
printf("Error: could not set scale: %s\n", SDL_GetError());
|
|
|
|
return;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SDL_RenderSetLogicalSize(m_renderer, 320, 240);
|
2020-03-13 23:22:37 +01:00
|
|
|
int result = SDL_RenderSetIntegerScale(m_renderer, (SDL_bool) (stretchMode == 2));
|
|
|
|
if (result != 0)
|
|
|
|
{
|
|
|
|
printf("Error: could not set scale: %s\n", SDL_GetError());
|
|
|
|
return;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
SDL_ShowWindow(m_window);
|
|
|
|
}
|
|
|
|
|
Add "resize to nearest" graphics option
If you want your game window to simply be exactly 320x240, or 640x480,
or 960x720 etc. then it's really annoying that there's no easy way to do
this (to clarify, this is different from integer mode, which controls
the size of the game INSIDE the window). The easiest way would be having
to close the game, go into unlock.vvv, and edit the window size
manually. VCE has a 1x/2x/3x/4x graphics option to solve this, although
it does not account for actual monitor size (those 1x/2x/3x/4x modes are
all you get, whether or not you have a monitor too small for some of
them or too big for any of them to be what you want).
I discussed this with flibit, and he said that VCE's approach (if it
accounted for monitor size) wouldn't work on high-retina displays or
high DPIs, because getting the actual multiplier to account for those
monitors is kind of a pain. So the next best thing would be to add an
option that resizes to the nearest perfect multiple of 320x240. That way
you could simply resize the window and let the game correct any
imperfect dimensions automatically.
2020-06-30 07:02:21 +02:00
|
|
|
void Screen::ResizeToNearestMultiple()
|
|
|
|
{
|
|
|
|
int w, h;
|
|
|
|
GetWindowSize(&w, &h);
|
|
|
|
|
|
|
|
// Check aspect ratio first
|
|
|
|
bool using_width;
|
|
|
|
int usethisdimension, usethisratio;
|
|
|
|
|
|
|
|
if ((float) w / (float) h > 4.0 / 3.0)
|
|
|
|
{
|
|
|
|
// Width is bigger, so it's limited by height
|
|
|
|
usethisdimension = h;
|
|
|
|
usethisratio = 240;
|
|
|
|
using_width = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Height is bigger, so it's limited by width. Or we're exactly 4:3 already
|
|
|
|
usethisdimension = w;
|
|
|
|
usethisratio = 320;
|
|
|
|
using_width = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int floor = (usethisdimension / usethisratio) * usethisratio;
|
|
|
|
int ceiling = floor + usethisratio;
|
|
|
|
|
|
|
|
int final_dimension;
|
|
|
|
|
|
|
|
if (usethisdimension - floor < ceiling - usethisdimension)
|
|
|
|
{
|
|
|
|
// Floor is nearest
|
|
|
|
final_dimension = floor;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Ceiling is nearest. Or we're exactly on a multiple already
|
|
|
|
final_dimension = ceiling;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (final_dimension == 0)
|
|
|
|
{
|
|
|
|
// We're way too small!
|
|
|
|
ResizeScreen(320, 240);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (using_width)
|
|
|
|
{
|
|
|
|
ResizeScreen(final_dimension, final_dimension / 4 * 3);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ResizeScreen(final_dimension * 4 / 3, final_dimension);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-01 21:29:24 +01:00
|
|
|
void Screen::GetWindowSize(int* x, int* y)
|
|
|
|
{
|
|
|
|
SDL_GetWindowSize(m_window, x, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Screen::UpdateScreen(SDL_Surface* buffer, SDL_Rect* rect )
|
|
|
|
{
|
2020-04-02 22:28:12 +02:00
|
|
|
if((buffer == NULL) && (m_screen == NULL) )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-02 22:28:12 +02:00
|
|
|
if(badSignalEffect)
|
|
|
|
{
|
|
|
|
buffer = ApplyFilter(buffer);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
|
2020-04-02 22:28:12 +02:00
|
|
|
FillRect(m_screen, 0x000);
|
|
|
|
BlitSurfaceStandard(buffer,NULL,m_screen,rect);
|
2020-01-01 21:29:24 +01:00
|
|
|
|
2020-04-02 22:28:12 +02:00
|
|
|
if(badSignalEffect)
|
|
|
|
{
|
|
|
|
SDL_FreeSurface(buffer);
|
|
|
|
}
|
2020-01-01 21:29:24 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const SDL_PixelFormat* Screen::GetFormat()
|
|
|
|
{
|
2020-04-02 22:28:12 +02:00
|
|
|
return m_screen->format;
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Screen::FlipScreen()
|
|
|
|
{
|
|
|
|
SDL_UpdateTexture(
|
|
|
|
m_screenTexture,
|
|
|
|
NULL,
|
|
|
|
m_screen->pixels,
|
|
|
|
m_screen->pitch
|
|
|
|
);
|
|
|
|
SDL_RenderCopy(
|
|
|
|
m_renderer,
|
|
|
|
m_screenTexture,
|
|
|
|
isFiltered ? &filterSubrect : NULL,
|
|
|
|
NULL
|
|
|
|
);
|
|
|
|
SDL_RenderPresent(m_renderer);
|
|
|
|
SDL_RenderClear(m_renderer);
|
|
|
|
SDL_FillRect(m_screen, NULL, 0x00000000);
|
|
|
|
}
|
|
|
|
|
2020-03-13 23:22:37 +01:00
|
|
|
void Screen::toggleFullScreen()
|
2020-01-01 21:29:24 +01:00
|
|
|
{
|
|
|
|
isWindowed = !isWindowed;
|
2020-03-13 23:22:37 +01:00
|
|
|
ResizeScreen(-1, -1);
|
2020-01-01 21:29:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Screen::toggleStretchMode()
|
|
|
|
{
|
|
|
|
stretchMode = (stretchMode + 1) % 3;
|
|
|
|
ResizeScreen(-1, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Screen::toggleLinearFilter()
|
|
|
|
{
|
|
|
|
isFiltered = !isFiltered;
|
2020-07-09 03:42:57 +02:00
|
|
|
SDL_SetHintWithPriority(
|
|
|
|
SDL_HINT_RENDER_SCALE_QUALITY,
|
|
|
|
isFiltered ? "linear" : "nearest",
|
|
|
|
SDL_HINT_OVERRIDE
|
|
|
|
);
|
2020-01-01 21:29:24 +01:00
|
|
|
SDL_DestroyTexture(m_screenTexture);
|
|
|
|
m_screenTexture = SDL_CreateTexture(
|
|
|
|
m_renderer,
|
|
|
|
SDL_PIXELFORMAT_ARGB8888,
|
|
|
|
SDL_TEXTUREACCESS_STREAMING,
|
|
|
|
320,
|
|
|
|
240
|
|
|
|
);
|
|
|
|
}
|
2020-07-02 06:19:40 +02:00
|
|
|
|
|
|
|
void Screen::resetRendererWorkaround()
|
|
|
|
{
|
|
|
|
SDL_SetHintWithPriority(
|
|
|
|
SDL_HINT_RENDER_VSYNC,
|
|
|
|
vsync ? "1" : "0",
|
|
|
|
SDL_HINT_OVERRIDE
|
|
|
|
);
|
|
|
|
|
|
|
|
/* FIXME: This exists because SDL_HINT_RENDER_VSYNC is not dynamic!
|
|
|
|
* As a result, our only workaround is to tear down the renderer
|
|
|
|
* and recreate everything so that it can process the variable.
|
|
|
|
* -flibit
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (m_renderer == NULL)
|
|
|
|
{
|
|
|
|
/* We haven't made it to init yet, don't worry about it */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_RendererInfo renderInfo;
|
|
|
|
SDL_GetRendererInfo(m_renderer, &renderInfo);
|
|
|
|
bool curVsync = (renderInfo.flags & SDL_RENDERER_PRESENTVSYNC) != 0;
|
|
|
|
if (vsync == curVsync)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_DestroyTexture(m_screenTexture);
|
|
|
|
SDL_DestroyRenderer(m_renderer);
|
|
|
|
|
|
|
|
m_renderer = SDL_CreateRenderer(m_window, -1, 0);
|
|
|
|
m_screenTexture = SDL_CreateTexture(
|
|
|
|
m_renderer,
|
|
|
|
SDL_PIXELFORMAT_ARGB8888,
|
|
|
|
SDL_TEXTUREACCESS_STREAMING,
|
|
|
|
320,
|
|
|
|
240
|
|
|
|
);
|
|
|
|
|
|
|
|
/* Ugh, have to make sure to re-apply graphics options after doing the
|
|
|
|
* above, otherwise letterbox/integer won't be applied...
|
|
|
|
*/
|
|
|
|
ResizeScreen(-1, -1);
|
|
|
|
}
|