ref: caf7461a4075352fd77b45dff58533900bd96e03
parent: f6e092005e6fd291fef0b9d7cf37ce6c4a5e1da2
author: Fabian Greffrath <[email protected]>
date: Wed Feb 11 02:56:46 EST 2015
Video: Implement hardware scaling and stretching \o/ Until now we draw the game into a 8-bit paletted 320x200 pixel buffer (I_VideoBuffer) which was scaled and stretched by software routines into a paletted SDL_Surface (screenbuffer) which had the dimensions of the selected video mode and was then blit to the screen surface. Now, we have an intermediate 320x200 32-bit RGBA surface that we blit our 8-bit paletted 320x200 pixel buffer to. This surface is then loaded into a texture that is scaled and stretched into the window or full screen by means of hardware rendering. The advantages are: 1. Rendering is much faster, since we make use of the graphic hardware's texture scaling capabilities instead of scaling and stretching each pixel by software routines. 2. Window sizes and fullscreen modes are not restricted to certain dimensions (i.e. multiples of 320x200 or 320x240 or 256x200, respectively) anymore. The graphic hardware can scale the texture into any given geometry and it still looks decent. 2a. We don't need to change the video mode at all in fullscreen mode. 2b. We don't need to adjust the video mode anymore when resizing the game window. There are still a lot of TODOs left: - The chocolate-setup tool must get adapted accordingly. That is, "Full screen" turns into a single yes/no question without "Screen mode" selection anymore and "Window size" could get turned into a pre-populated list of multiples of 320x240 (or 320x200 is aspect ratio correction is disabled) up to the current desktop resolution. - Really get rid of the software scaling and stretching routines. So far, they are merely commented out in the source code. - In this context, get rid of the concept of screen_mode_t, now that we can render the game into any given geometry. - Find a way to circumvent the 320x200 32-bit RGBA buffer and directly load the 320x200 8-bit paletted buffer into the texture? This seems to be currently limited by SDL (or even the hardware itself)? - Bring back I_BeginRead() and I_EndRead() do draw the disk icon? Will anybody miss this? - Get window resizing more cleany. Maybe restrict to multiples of 320x200 (or 320x240 for aspect ratio correction) for the benefit of a better rendering quality? But this would oppose one of the main benefits of hardware rendering! Decrease forced delay between changing the window size and clearing the window content. - Turn scaling quality selection into a config / command line option. - General clean up, remove obsolete code, improve comments. - Maybe default to 640x480 as the initial window resolution. The 320x200 window tends to get lost under my mouse cursor. ;-)
--- a/src/i_video.c
+++ b/src/i_video.c
@@ -96,6 +96,7 @@
#define LOADING_DISK_W 16
#define LOADING_DISK_H 16
+#if 0 // obsolete software scaling routines
// Non aspect ratio-corrected modes (direct multiples of 320x200)
static screen_mode_t *screen_modes[] = {
@@ -125,6 +126,7 @@
&mode_squash_3x,
&mode_squash_4x,
};
+#endif
// SDL video driver name
@@ -134,17 +136,27 @@
static char *window_position = "";
-// SDL surface for the screen.
+// These are (1) the window (or the full screen) that our game is rendered to
+// and (2) the renderer that scales the texture (see below) into this window.
static SDL_Window *screen;
+static SDL_Renderer *renderer;
// Window title
static char *window_title = "";
-// Intermediate 8-bit buffer that we draw to instead of 'screen'.
+// These are (1) the 320x200x8 paletted buffer that we draw to (i.e. the one
+// that holds I_VideoBuffer), (2) the 320x200x32 RGBA intermediate buffer that
+// we blit the former buffer to and (3) the texture that we load the RGBA
+// buffer to and that is scaled into the window by the renderer (see above).
+// TODO: Check if the intermediate RGBA buffer is still necessary in newer
+// SDL releases. It surely is in 2.0.2, i.e. it is currently impossible to
+// update the texture with the pixels from the 8-bit paletted buffer.
static SDL_Surface *screenbuffer = NULL;
+static SDL_Surface *rgbabuffer = NULL;
+static SDL_Texture *texture = NULL;
// palette
@@ -276,7 +288,9 @@
int usegamma = 0;
+#if 0 // obsolete software scaling routines
static void ApplyWindowResize(unsigned int w, unsigned int h);
+#endif
static boolean MouseShouldBeGrabbed()
{
@@ -875,6 +889,7 @@
}
+#if 0 // obsolete software scaling routines
// Update a small portion of the screen
//
// Does stretching and buffer blitting if neccessary
@@ -906,7 +921,11 @@
return result;
}
+#endif
+// TODO: needed for I_BeginRead() and I_EndRead(),
+// but let's forget about this for a while
+/*
static void UpdateRect(int x1, int y1, int x2, int y2)
{
SDL_Rect update_rect;
@@ -931,9 +950,12 @@
SDL_UpdateWindowSurfaceRects(screen, &update_rect, 1);
}
}
+*/
+// TODO: let's forget about this for a while
void I_BeginRead(void)
{
+/*
byte *screenloc = I_VideoBuffer
+ (SCREENHEIGHT - LOADING_DISK_H) * SCREENWIDTH
+ (SCREENWIDTH - LOADING_DISK_W);
@@ -958,10 +980,13 @@
UpdateRect(SCREENWIDTH - LOADING_DISK_W, SCREENHEIGHT - LOADING_DISK_H,
SCREENWIDTH, SCREENHEIGHT);
+*/
}
+// TODO: let's forget about this for a while
void I_EndRead(void)
{
+/*
byte *screenloc = I_VideoBuffer
+ (SCREENHEIGHT - LOADING_DISK_H) * SCREENWIDTH
+ (SCREENWIDTH - LOADING_DISK_W);
@@ -983,6 +1008,7 @@
UpdateRect(SCREENWIDTH - LOADING_DISK_W, SCREENHEIGHT - LOADING_DISK_H,
SCREENWIDTH, SCREENHEIGHT);
+*/
}
//
@@ -991,8 +1017,10 @@
void I_FinishUpdate (void)
{
static int lasttic;
+#if 0 // obsolete software scaling routines
SDL_Rect dst_rect;
int screen_w, screen_h;
+#endif
int tics;
int i;
@@ -1002,9 +1030,17 @@
if (noblit)
return;
+ // TODO: Decrease the forced delay: we are not changing a screen mode
+ // anymore but simply modify the texture scaling factor
if (need_resize && SDL_GetTicks() > last_resize_time + 500)
{
+#if 0 // obsolete software scaling reoutines
ApplyWindowResize(resize_w, resize_h);
+#endif
+ screen_width = resize_w;
+ screen_height = resize_h;
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
+ SDL_RenderClear(renderer);
need_resize = false;
palette_to_set = true;
}
@@ -1035,9 +1071,11 @@
I_VideoBuffer[ (SCREENHEIGHT-1)*SCREENWIDTH + i] = 0x0;
}
+#if 0 // obsolete software scaling routines
// draw to screen
BlitArea(0, 0, SCREENWIDTH, SCREENHEIGHT);
+#endif
if (palette_to_set)
{
@@ -1045,6 +1083,7 @@
palette_to_set = false;
}
+#if 0 // obsolete software scaling routines
// Blit from the fake 8-bit screen buffer to the real screen
// before doing a screen flip.
@@ -1057,6 +1096,26 @@
SDL_BlitSurface(screenbuffer, NULL,
SDL_GetWindowSurface(screen), &dst_rect);
SDL_UpdateWindowSurface(screen);
+#endif
+
+ // Blit from the fake 8-bit screen buffer to the intermediate
+ // 32-bit RGBA buffer that we can load into the texture
+
+ SDL_BlitSurface(screenbuffer, NULL, rgbabuffer, NULL);
+
+ // Update the texture with the content of the 32-bit RGBA buffer
+ // (the last argument is the pitch, i.e. 320 pixels of 4 RGBA-bytes)
+
+ SDL_UpdateTexture(texture, NULL, rgbabuffer->pixels,
+ SCREENWIDTH * sizeof(Uint32));
+
+ // Render the texture into the window
+
+ SDL_RenderCopy(renderer, texture, NULL, NULL);
+
+ // Draw!
+
+ SDL_RenderPresent(renderer);
}
@@ -1157,6 +1216,7 @@
SDL_FreeSurface(surface);
}
+#if 0 // obsolete software scaling routines
// Pick the modes list to use:
static void GetScreenModes(screen_mode_t ***modes_list, int *num_modes)
@@ -1378,6 +1438,7 @@
"configuration file.\n");
}
}
+#endif
// Set video size to a particular scale factor (1x, 2x, 3x, etc.)
@@ -1650,6 +1711,7 @@
}
}
+#if 0 // obsolete software scaling routines
static char *WindowBoxType(screen_mode_t *mode, int w, int h)
{
if (mode->width != w && mode->height != h)
@@ -1669,6 +1731,7 @@
return "...";
}
}
+#endif
static void SetVideoMode(screen_mode_t *mode, int w, int h)
{
@@ -1707,7 +1770,9 @@
if (fullscreen)
{
- flags |= SDL_WINDOW_FULLSCREEN;
+ // This flags means "Never change the screen resolution! Instead,
+ // draw to the entire screen by scaling the texture appropriately".
+ flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
else
{
@@ -1717,17 +1782,32 @@
flags |= SDL_WINDOW_RESIZABLE;
}
- screen = SDL_CreateWindow(
- "",
- SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
- w, h, flags);
+ // Set the scaling quality: "nearest" is gritty and pixelated and resembles
+ // software scaling pretty well, "linear" and "best" look much softer and
+ // smoother. TODO: Turn this into a config option / command line parameter.
- if (screen == NULL)
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
+
+ // Create window and renderer context at once. We set the window title
+ // later anyway and leave the window position "undefined". If "flags"
+ // contains the fullscreen flag (see above), then w and h are ignored.
+
+ SDL_CreateWindowAndRenderer(w, h, flags, &screen, &renderer);
+
+ if (screen == NULL || renderer == NULL)
{
I_Error("Error setting video mode %ix%i: %s\n",
w, h, SDL_GetError());
}
+ // Important: Set the "logical size" of the rendering context. At the same
+ // time this also defines the aspect ratio that is preserved while scaling
+ // and stretching the texture into the window.
+
+ SDL_RenderSetLogicalSize(renderer,
+ SCREENWIDTH,
+ aspect_ratio_correct ? SCREENHEIGHT_4_3 : SCREENHEIGHT);
+
I_InitWindowTitle();
I_InitWindowIcon();
@@ -1734,8 +1814,11 @@
// Blank out the full screen area in case there is any junk in
// the borders that won't otherwise be overwritten.
- SDL_FillRect(SDL_GetWindowSurface(screen), NULL, 0);
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
+ SDL_RenderClear(renderer);
+ SDL_RenderPresent(renderer);
+#if 0 // obsolete software scaling routines
// If mode was not set, it must be set now that we know the
// screen size.
@@ -1759,19 +1842,30 @@
mode->InitMode(doompal);
}
}
+#endif
- // Create the screenbuffer surface.
+ // Create the fake 8-bit paletted and the 32-bit RGBA screenbuffer surfaces.
screenbuffer = SDL_CreateRGBSurface(0,
- mode->width, mode->height, 8,
+ SCREENWIDTH, SCREENHEIGHT, 8,
0, 0, 0, 0);
SDL_FillRect(screenbuffer, NULL, 0);
+ rgbabuffer = SDL_CreateRGBSurface(0,
+ SCREENWIDTH, SCREENHEIGHT, 32,
+ 0, 0, 0, 0);
+ SDL_FillRect(rgbabuffer, NULL, 0);
+
+ // Create the texture that the RGBA surface gets loaded into.
+
+ texture = SDL_CreateTextureFromSurface(renderer, rgbabuffer);
+
// Save screen mode.
screen_mode = mode;
}
+#if 0 // obsolete software scaling routines
static void ApplyWindowResize(unsigned int w, unsigned int h)
{
screen_mode_t *mode;
@@ -1797,6 +1891,7 @@
screen_width = mode->width;
screen_height = mode->height;
}
+#endif
void I_InitGraphics(void)
{
@@ -1853,14 +1948,17 @@
{
int w, h;
+#if 0 // obsolete software scaling routines
if (autoadjust_video_settings)
{
I_AutoAdjustSettings();
}
+#endif
w = screen_width;
h = screen_height;
+#if 0 // obsolete software scaling routines
screen_mode = I_FindScreenMode(w, h);
if (screen_mode == NULL)
@@ -1875,6 +1973,7 @@
WindowBoxType(screen_mode, w, h),
screen_mode->width, screen_mode->height, w, h);
}
+#endif
SetVideoMode(screen_mode, w, h);
}
@@ -1905,11 +2004,12 @@
SDL_Delay(startup_delay);
}
- // If not, allocate a buffer and copy from that buffer to the
- // screen when we do an update
+ // The actual 320x200 canvas that we draw to. This is the pixel buffer of
+ // the 8-bit paletted screen buffer that gets blit on an intermediate
+ // 32-bit RGBA screen buffer that gets loaded into a texture that gets
+ // finally rendered into our window or full screen in I_FinishUpdate().
- I_VideoBuffer = (unsigned char *) Z_Malloc (SCREENWIDTH * SCREENHEIGHT,
- PU_STATIC, NULL);
+ I_VideoBuffer = screenbuffer->pixels;
V_RestoreBuffer();
// Clear the screen to black.