summaryrefslogtreecommitdiff
path: root/src/libs/graphics/sdl/sdl2_pure.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/graphics/sdl/sdl2_pure.c')
-rw-r--r--src/libs/graphics/sdl/sdl2_pure.c465
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