diff options
Diffstat (limited to 'src/libs/graphics/sdl/sdl2_pure.c')
-rw-r--r-- | src/libs/graphics/sdl/sdl2_pure.c | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/src/libs/graphics/sdl/sdl2_pure.c b/src/libs/graphics/sdl/sdl2_pure.c new file mode 100644 index 0000000..c6503db --- /dev/null +++ b/src/libs/graphics/sdl/sdl2_pure.c @@ -0,0 +1,465 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 +/* + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "pure.h" +#include "libs/graphics/bbox.h" +#include "libs/log.h" +#include "scalers.h" +#include "uqmversion.h" + +#if SDL_MAJOR_VERSION > 1 + +typedef struct tfb_sdl2_screeninfo_s { + SDL_Surface *scaled; + SDL_Texture *texture; + BOOLEAN dirty, active; + SDL_Rect updated; +} TFB_SDL2_SCREENINFO; + +static TFB_SDL2_SCREENINFO SDL2_Screens[TFB_GFX_NUMSCREENS]; + +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static const char *rendererBackend = NULL; + +static int ScreenFilterMode; + +static TFB_ScaleFunc scaler = NULL; + +#if SDL_BYTEORDER == SDL_BIG_ENDIAN +#define A_MASK 0xff000000 +#define B_MASK 0x00ff0000 +#define G_MASK 0x0000ff00 +#define R_MASK 0x000000ff +#else +#define A_MASK 0x000000ff +#define B_MASK 0x0000ff00 +#define G_MASK 0x00ff0000 +#define R_MASK 0xff000000 +#endif + +static void TFB_SDL2_Preprocess (int force_full_redraw, int transition_amount, int fade_amount); +static void TFB_SDL2_Postprocess (void); +static void TFB_SDL2_UploadTransitionScreen (void); +static void TFB_SDL2_Scaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect); +static void TFB_SDL2_Unscaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect); +static void TFB_SDL2_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect); + +static TFB_GRAPHICS_BACKEND sdl2_scaled_backend = { + TFB_SDL2_Preprocess, + TFB_SDL2_Postprocess, + TFB_SDL2_UploadTransitionScreen, + TFB_SDL2_Scaled_ScreenLayer, + TFB_SDL2_ColorLayer }; + +static TFB_GRAPHICS_BACKEND sdl2_unscaled_backend = { + TFB_SDL2_Preprocess, + TFB_SDL2_Postprocess, + TFB_SDL2_UploadTransitionScreen, + TFB_SDL2_Unscaled_ScreenLayer, + TFB_SDL2_ColorLayer }; + +static SDL_Surface * +Create_Screen (int w, int h) +{ + SDL_Surface *newsurf = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, + 32, R_MASK, G_MASK, B_MASK, 0); + if (newsurf == 0) + { + log_add (log_Error, "Couldn't create screen buffers: %s", + SDL_GetError()); + } + return newsurf; +} + +static int +ReInit_Screen (SDL_Surface **screen, int w, int h) +{ + if (*screen) + SDL_FreeSurface (*screen); + *screen = Create_Screen (w, h); + + return *screen == 0 ? -1 : 0; +} + +static int +FindBestRenderDriver (void) +{ + int i, n; + if (!rendererBackend) { + /* If the user has no preference, just let SDL2 choose */ + return -1; + } + n = SDL_GetNumRenderDrivers (); + log_add (log_Info, "Searching for render driver \"%s\".", rendererBackend); + + for (i = 0; i < n; i++) { + SDL_RendererInfo info; + if (SDL_GetRenderDriverInfo (i, &info) < 0) { + continue; + } + if (!strcmp(info.name, rendererBackend)) { + return i; + } + log_add (log_Info, "Skipping render driver \"%s\"", info.name); + } + /* We did not find any accelerated drivers that weren't D3D9. + * Return -1 to ask SDL2 to do its best. */ + log_add (log_Info, "Render driver \"%s\" not available, using system default", rendererBackend); + return -1; +} + +int +TFB_Pure_ConfigureVideo (int driver, int flags, int width, int height, int togglefullscreen) +{ + int i; + GraphicsDriver = driver; + (void) togglefullscreen; + if (window == NULL) + { + SDL_RendererInfo info; + char caption[200]; + + sprintf (caption, "The Ur-Quan Masters v%d.%d.%d%s", + UQM_MAJOR_VERSION, UQM_MINOR_VERSION, + UQM_PATCH_VERSION, UQM_EXTRA_VERSION); + window = SDL_CreateWindow (caption, + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + width, height, 0); + if (flags & TFB_GFXFLAGS_FULLSCREEN) + { + /* If we create the window fullscreen, it will have + * no icon if and when it becomes windowed. */ + SDL_SetWindowFullscreen (window, SDL_WINDOW_FULLSCREEN_DESKTOP); + } + if (!window) + { + return -1; + } + renderer = SDL_CreateRenderer (window, FindBestRenderDriver (), 0); + if (!renderer) + { + return -1; + } + if (SDL_GetRendererInfo (renderer, &info) == 0) + { + log_add (log_Info, "SDL2 renderer '%s' selected.\n", info.name); + } + else + { + log_add (log_Info, "SDL2 renderer had no name."); + } + SDL_RenderSetLogicalSize (renderer, ScreenWidth, ScreenHeight); + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + SDL2_Screens[i].scaled = NULL; + SDL2_Screens[i].texture = NULL; + SDL2_Screens[i].dirty = TRUE; + SDL2_Screens[i].active = TRUE; + if (0 != ReInit_Screen (&SDL_Screens[i], ScreenWidth, ScreenHeight)) + { + return -1; + } + } + SDL2_Screens[1].active = FALSE; + SDL_Screen = SDL_Screens[0]; + TransitionScreen = SDL_Screens[2]; + format_conv_surf = SDL_CreateRGBSurface(SDL_SWSURFACE, 0, 0, + 32, R_MASK, G_MASK, B_MASK, A_MASK); + if (!format_conv_surf) + { + return -1; + } + } + else + { + if (flags & TFB_GFXFLAGS_FULLSCREEN) + { + SDL_SetWindowFullscreen (window, SDL_WINDOW_FULLSCREEN_DESKTOP); + } + else + { + SDL_SetWindowFullscreen (window, 0); + SDL_SetWindowSize (window, width, height); + } + } + + if (GfxFlags & TFB_GFXFLAGS_SCALE_ANY) + { + /* Linear scaling */ + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); + } + else + { + /* Nearest-neighbor scaling */ + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); + } + + if (GfxFlags & TFB_GFXFLAGS_SCALE_SOFT_ONLY) + { + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + if (!SDL2_Screens[i].active) + { + continue; + } + if (0 != ReInit_Screen(&SDL2_Screens[i].scaled, + ScreenWidth * 2, ScreenHeight * 2)) + { + return -1; + } + if (SDL2_Screens[i].texture) + { + SDL_DestroyTexture (SDL2_Screens[i].texture); + SDL2_Screens[i].texture = NULL; + } + SDL2_Screens[i].texture = SDL_CreateTexture (renderer, SDL_PIXELFORMAT_RGBX8888, SDL_TEXTUREACCESS_STREAMING, ScreenWidth * 2, ScreenHeight * 2); + SDL_LockSurface (SDL2_Screens[i].scaled); + SDL_UpdateTexture (SDL2_Screens[i].texture, NULL, SDL2_Screens[i].scaled->pixels, SDL2_Screens[i].scaled->pitch); + SDL_UnlockSurface (SDL2_Screens[i].scaled); + } + scaler = Scale_PrepPlatform (flags, SDL2_Screens[0].scaled->format); + graphics_backend = &sdl2_scaled_backend; + } + else + { + for (i = 0; i < TFB_GFX_NUMSCREENS; i++) + { + if (SDL2_Screens[i].scaled) + { + SDL_FreeSurface (SDL2_Screens[i].scaled); + SDL2_Screens[i].scaled = NULL; + } + if (SDL2_Screens[i].texture) + { + SDL_DestroyTexture (SDL2_Screens[i].texture); + SDL2_Screens[i].texture = NULL; + } + SDL2_Screens[i].texture = SDL_CreateTexture (renderer, SDL_PIXELFORMAT_RGBX8888, SDL_TEXTUREACCESS_STREAMING, ScreenWidth, ScreenHeight); + SDL_LockSurface (SDL_Screens[i]); + SDL_UpdateTexture (SDL2_Screens[i].texture, NULL, SDL_Screens[i]->pixels, SDL_Screens[i]->pitch); + SDL_UnlockSurface (SDL_Screens[i]); + } + scaler = NULL; + graphics_backend = &sdl2_unscaled_backend; + } + + /* We succeeded, so alter the screen size to our new sizes */ + ScreenWidthActual = width; + ScreenHeightActual = height; + + return 0; +} + +int +TFB_Pure_InitGraphics (int driver, int flags, const char *renderer, int width, int height) +{ + log_add (log_Info, "Initializing SDL."); + log_add (log_Info, "SDL initialized."); + log_add (log_Info, "Initializing Screen."); + + ScreenWidth = 320; + ScreenHeight = 240; + rendererBackend = renderer; + + if (TFB_Pure_ConfigureVideo (driver, flags, width, height, 0)) + { + log_add (log_Fatal, "Could not initialize video: %s", + SDL_GetError ()); + exit (EXIT_FAILURE); + } + + /* Initialize scalers (let them precompute whatever) */ + Scale_Init (); + + return 0; +} + +void +TFB_Pure_UninitGraphics (void) +{ + if (renderer) { + SDL_DestroyRenderer (renderer); + } + if (window) { + SDL_DestroyWindow (window); + } +} + +static void +TFB_SDL2_UploadTransitionScreen (void) +{ + SDL2_Screens[TFB_SCREEN_TRANSITION].updated.x = 0; + SDL2_Screens[TFB_SCREEN_TRANSITION].updated.y = 0; + SDL2_Screens[TFB_SCREEN_TRANSITION].updated.w = ScreenWidth; + SDL2_Screens[TFB_SCREEN_TRANSITION].updated.h = ScreenHeight; + SDL2_Screens[TFB_SCREEN_TRANSITION].dirty = TRUE; +} + +static void +TFB_SDL2_UpdateTexture (SDL_Texture *dest, SDL_Surface *src, SDL_Rect *rect) +{ + char *srcBytes; + SDL_LockSurface (src); + srcBytes = src->pixels; + if (rect) + { + /* SDL2 screen surfaces are always 32bpp */ + srcBytes += (src->pitch * rect->y) + (rect->x * 4); + } + /* 2020-08-02: At time of writing, the documentation for + * SDL_UpdateTexture states this: "If the texture is intended to be + * updated often, it is preferred to create the texture as streaming + * and use [SDL_LockTexture and SDL_UnlockTexture]." Unfortunately, + * SDL_LockTexture will corrupt driver-space memory in the 32-bit + * Direct3D 9 driver on Intel Integrated graphics chips, resulting + * in an immediate crash with no detectable errors from the API up + * to that point. + * + * We also cannot simply forbid the Direct3D driver outright, because + * pre-Windows 10 machines appear to fail to initialize D3D11 even + * while claiming to support it. + * + * These bugs may be fixed in the future, but in the meantime we + * rely on this allegedly slower but definitely more reliable + * function. */ + SDL_UpdateTexture (dest, rect, srcBytes, src->pitch); + SDL_UnlockSurface (src); +} + +static void +TFB_SDL2_ScanLines (void) +{ + int y; + SDL_SetRenderDrawColor (renderer, 0, 0, 0, 64); + SDL_SetRenderDrawBlendMode (renderer, SDL_BLENDMODE_BLEND); + SDL_RenderSetLogicalSize (renderer, ScreenWidth * 2, ScreenHeight * 2); + for (y = 0; y < ScreenHeight * 2; y += 2) + { + SDL_RenderDrawLine (renderer, 0, y, ScreenWidth * 2 - 1, y); + } + SDL_RenderSetLogicalSize (renderer, ScreenWidth, ScreenHeight); +} + +static void +TFB_SDL2_Preprocess (int force_full_redraw, int transition_amount, int fade_amount) +{ + (void) transition_amount; + (void) fade_amount; + + if (force_full_redraw == TFB_REDRAW_YES) + { + SDL2_Screens[TFB_SCREEN_MAIN].updated.x = 0; + SDL2_Screens[TFB_SCREEN_MAIN].updated.y = 0; + SDL2_Screens[TFB_SCREEN_MAIN].updated.w = ScreenWidth; + SDL2_Screens[TFB_SCREEN_MAIN].updated.h = ScreenHeight; + SDL2_Screens[TFB_SCREEN_MAIN].dirty = TRUE; + } + else if (TFB_BBox.valid) + { + SDL2_Screens[TFB_SCREEN_MAIN].updated.x = TFB_BBox.region.corner.x; + SDL2_Screens[TFB_SCREEN_MAIN].updated.y = TFB_BBox.region.corner.y; + SDL2_Screens[TFB_SCREEN_MAIN].updated.w = TFB_BBox.region.extent.width; + SDL2_Screens[TFB_SCREEN_MAIN].updated.h = TFB_BBox.region.extent.height; + SDL2_Screens[TFB_SCREEN_MAIN].dirty = TRUE; + } + + SDL_SetRenderDrawBlendMode (renderer, SDL_BLENDMODE_NONE); + SDL_SetRenderDrawColor (renderer, 0, 0, 0, 255); + SDL_RenderClear (renderer); +} + +static void +TFB_SDL2_Unscaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect) +{ + SDL_Texture *texture = SDL2_Screens[screen].texture; + if (SDL2_Screens[screen].dirty) + { + TFB_SDL2_UpdateTexture (texture, SDL_Screens[screen], &SDL2_Screens[screen].updated); + } + if (a == 255) + { + SDL_SetTextureBlendMode (texture, SDL_BLENDMODE_NONE); + } + else + { + SDL_SetTextureBlendMode (texture, SDL_BLENDMODE_BLEND); + SDL_SetTextureAlphaMod (texture, a); + } + SDL_RenderCopy (renderer, texture, rect, rect); +} + +static void +TFB_SDL2_Scaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect) +{ + SDL_Texture *texture = SDL2_Screens[screen].texture; + SDL_Rect srcRect, *pSrcRect = NULL; + if (SDL2_Screens[screen].dirty) + { + SDL_Surface *src = SDL2_Screens[screen].scaled; + SDL_Rect scaled_update = SDL2_Screens[screen].updated; + scaler (SDL_Screens[screen], src, &SDL2_Screens[screen].updated); + scaled_update.x *= 2; + scaled_update.y *= 2; + scaled_update.w *= 2; + scaled_update.h *= 2; + TFB_SDL2_UpdateTexture (texture, src, &scaled_update); + } + if (a == 255) + { + SDL_SetTextureBlendMode (texture, SDL_BLENDMODE_NONE); + } + else + { + SDL_SetTextureBlendMode (texture, SDL_BLENDMODE_BLEND); + SDL_SetTextureAlphaMod (texture, a); + } + /* The texture has twice the resolution when scaled, but the + * screen's logical resolution has not changed, so the clip + * rectangle does not need to be scaled. The *source* clip + * rect, however, must be scaled to match. */ + if (rect) + { + srcRect = *rect; + srcRect.x *= 2; + srcRect.y *= 2; + srcRect.w *= 2; + srcRect.h *= 2; + pSrcRect = &srcRect; + } + SDL_RenderCopy (renderer, texture, pSrcRect, rect); +} + +static void +TFB_SDL2_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect) +{ + SDL_SetRenderDrawBlendMode (renderer, a == 255 ? SDL_BLENDMODE_NONE + : SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor (renderer, r, g, b, a); + SDL_RenderFillRect (renderer, rect); +} + +static void +TFB_SDL2_Postprocess (void) +{ + if (GfxFlags & TFB_GFXFLAGS_SCANLINES) + TFB_SDL2_ScanLines (); + + SDL_RenderPresent (renderer); +} + +#endif |