sdl12-compat: Implemented SDL_OPENGLBLIT.

From 478d357a201cc442ff80e7f0515996e1bb8018ab Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Wed, 2 Jun 2021 22:30:31 -0400
Subject: [PATCH] Implemented SDL_OPENGLBLIT.

This works a little differently than SDL 1.2, because we aren't trying to
run this on a Voodoo 2 anymore.

Instead of making a 256x256 texture and uploading/drawing in pieces to
accomodate the small texture, we just make one texture the size of the window.

It's easier and clearer (and I suspect probably faster than the upload/draw
loop that 1.2 used).

This also means your GL needs NPOT textures. Any GPU from the last 15 years
should support this (either through an extension or being OpenGL >= 2.0). We
don't bother trying to deal GL implementation without.

This has only been tested with testgl -logoblit, because, as you should know:
https://discourse.libsdl.org/t/for-all-who-search-a-sdl-openglblit-example/5376/2
---
 src/SDL12_compat.c | 184 ++++++++++++++++++++++++++++++++++++++-------
 src/SDL20_syms.h   |  27 +++++++
 2 files changed, 183 insertions(+), 28 deletions(-)

diff --git a/src/SDL12_compat.c b/src/SDL12_compat.c
index 17d8d04..c24640f 100644
--- a/src/SDL12_compat.c
+++ b/src/SDL12_compat.c
@@ -815,6 +815,8 @@ static Uint8 KeyState[SDLK12_LAST];
 static SDL_bool MouseInputIsRelative = SDL_FALSE;
 static SDL_Point MousePosition = { 0, 0 };
 static OpenGLEntryPoints OpenGLFuncs;
+static int OpenGLBlitLockCount = 0;
+static GLuint OpenGLBlitTexture = 0;
 static int OpenGLLogicalScalingWidth = 0;
 static int OpenGLLogicalScalingHeight = 0;
 static GLuint OpenGLLogicalScalingFBO = 0;
@@ -3273,6 +3275,10 @@ SetupScreenSaver(const int flags12)
 static SDL12_Surface *
 EndVidModeCreate(void)
 {
+    if (OpenGLBlitTexture) {
+        OpenGLFuncs.glDeleteTextures(1, &OpenGLBlitTexture);
+        OpenGLBlitTexture = 0;
+    }
     if (VideoTexture20) {
         SDL20_DestroyTexture(VideoTexture20);
         VideoTexture20 = NULL;
@@ -3306,6 +3312,7 @@ EndVidModeCreate(void)
     }
 
     SDL_zero(OpenGLFuncs);
+    OpenGLBlitLockCount = 0;
     OpenGLLogicalScalingWidth = 0;
     OpenGLLogicalScalingHeight = 0;
     OpenGLLogicalScalingFBO = 0;
@@ -3373,6 +3380,10 @@ LoadOpenGLFunctions(void)
         OpenGLFuncs.SUPPORTS_GL_ARB_framebuffer_object = SDL_TRUE;
     }
 
+    if (major >= 2) {
+        OpenGLFuncs.SUPPORTS_GL_ARB_texture_non_power_of_two = SDL_TRUE;  /* core since 2.0 */
+    }
+
     /* load everything we can. */
     #define OPENGL_SYM(ext,rc,fn,params,args,ret) OpenGLFuncs.fn = \
            (OpenGLFuncs.SUPPORTS_##ext)? (openglfn_##fn##_t)SDL20_GL_GetProcAddress(#fn) : NULL;
@@ -3609,12 +3620,6 @@ SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags12)
         }
     }
 
-    if ((flags12 & SDL12_OPENGLBLIT) == SDL12_OPENGLBLIT) {
-        FIXME("No OPENGLBLIT support at the moment");
-        SDL20_SetError("SDL_OPENGLBLIT is (currently) unsupported");
-        return NULL;
-    }
-
     FIXME("handle SDL_ANYFORMAT");
 
     if ((width < 0) || (height < 0)) {
@@ -3664,6 +3669,8 @@ SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags12)
         SDL20_GL_DeleteContext(VideoGLContext20);
         VideoGLContext20 = NULL;
         SDL_zero(OpenGLFuncs);
+        OpenGLBlitTexture = 0;
+        OpenGLBlitLockCount = 0;
         OpenGLLogicalScalingWidth = 0;
         OpenGLLogicalScalingHeight = 0;
         OpenGLLogicalScalingFBO = 0;
@@ -3766,6 +3773,30 @@ SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags12)
             }
         }
 
+        if ((flags12 & SDL12_OPENGLBLIT) == SDL12_OPENGLBLIT) {
+            const int pixsize = VideoSurface12->format->BytesPerPixel;
+            const GLenum glfmt = (pixsize == 4) ? GL_RGBA : GL_RGB;
+            const GLenum gltype = (pixsize == 4) ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT_5_6_5;
+
+            if (!OpenGLFuncs.SUPPORTS_GL_ARB_texture_non_power_of_two) {
+                SDL20_SetError("Your OpenGL drivers don't support NPOT textures for SDL_OPENGLBLIT; please upgrade.");
+                return EndVidModeCreate();
+            }
+
+            OpenGLFuncs.glGenTextures(1, &OpenGLBlitTexture);
+            OpenGLFuncs.glBindTexture(GL_TEXTURE_2D, OpenGLBlitTexture);
+            OpenGLFuncs.glTexImage2D(GL_TEXTURE_2D, 0, (pixsize == 4) ? GL_RGBA : GL_RGB, VideoSurface12->w, VideoSurface12->h, 0, glfmt, gltype, NULL);
+
+            VideoSurface12->surface20->pixels = SDL20_malloc(height * VideoSurface12->pitch);
+            VideoSurface12->pixels = VideoSurface12->surface20->pixels;
+            if (!VideoSurface12->pixels) {
+                SDL20_OutOfMemory();
+                return EndVidModeCreate();
+            }
+            SDL20_memset(VideoSurface12->pixels, 0xFF, height * VideoSurface12->pitch);  /* SDL 1.2 default OPENGLBLIT surface to full intensity */
+            VideoSurface12->flags |= SDL12_OPENGLBLIT;
+        }
+
     } else {
         /* always use a renderer for non-OpenGL windows. */
         const char *vsync_env = SDL20_getenv("SDL12COMPAT_SYNC_TO_VBLANK");
@@ -4192,11 +4223,130 @@ UpdateRect12to20(SDL12_Surface *surface12, const SDL12_Rect *rect12, SDL_Rect *r
     }
 }
 
+/* SDL_OPENGLBLIT support APIs. https://discourse.libsdl.org/t/ogl-and-sdl/2775/3 */
+DECLSPEC void SDLCALL
+SDL_GL_Lock(void)
+{
+    if (!OpenGLBlitTexture) {
+        return;
+    }
+
+    if (++OpenGLBlitLockCount == 1) {
+        OpenGLFuncs.glPushAttrib(GL_ALL_ATTRIB_BITS);
+        OpenGLFuncs.glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT);
+        OpenGLFuncs.glEnable(GL_TEXTURE_2D);
+        OpenGLFuncs.glEnable(GL_BLEND);
+        OpenGLFuncs.glDisable(GL_FOG);
+        OpenGLFuncs.glDisable(GL_ALPHA_TEST);
+        OpenGLFuncs.glDisable(GL_DEPTH_TEST);
+        OpenGLFuncs.glDisable(GL_SCISSOR_TEST);
+        OpenGLFuncs.glDisable(GL_STENCIL_TEST);
+        OpenGLFuncs.glDisable(GL_CULL_FACE);
+
+        OpenGLFuncs.glBindTexture(GL_TEXTURE_2D, OpenGLBlitTexture);
+        OpenGLFuncs.glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+        OpenGLFuncs.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        OpenGLFuncs.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        OpenGLFuncs.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+        OpenGLFuncs.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+        OpenGLFuncs.glPixelStorei(GL_UNPACK_ROW_LENGTH, VideoSurface12->pitch / VideoSurface12->format->BytesPerPixel);
+        OpenGLFuncs.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+        OpenGLFuncs.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+
+        OpenGLFuncs.glViewport(0, 0, VideoSurface12->w, VideoSurface12->h);
+        OpenGLFuncs.glMatrixMode(GL_PROJECTION);
+        OpenGLFuncs.glPushMatrix();
+        OpenGLFuncs.glLoadIdentity();
+
+        OpenGLFuncs.glOrtho(0.0, (GLdouble) VideoSurface12->w, (GLdouble) VideoSurface12->h, 0.0, 0.0, 1.0);
+
+        OpenGLFuncs.glMatrixMode(GL_MODELVIEW);
+        OpenGLFuncs.glPushMatrix();
+        OpenGLFuncs.glLoadIdentity();
+    }
+}
+
+DECLSPEC void SDLCALL
+SDL_GL_UpdateRects(int numrects, SDL12_Rect *rects12)
+{
+    if (OpenGLBlitTexture) {
+        const int srcpitch = VideoSurface12->pitch;
+        const int pixsize = VideoSurface12->format->BytesPerPixel;
+        const GLenum glfmt = (pixsize == 4) ? GL_RGBA : GL_RGB;
+        const GLenum gltype = (pixsize == 4) ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT_5_6_5;
+        SDL_Rect surfacerect20;
+        int i;
+
+        surfacerect20.x = surfacerect20.y = 0;
+        surfacerect20.w = VideoSurface12->w;
+        surfacerect20.h = VideoSurface12->h;
+
+        for (i = 0; i < numrects; i++) {
+            SDL_Rect rect20;
+            SDL_Rect intersected20;
+            Uint8 *src;
+
+            SDL20_IntersectRect(Rect12to20(&rects12[i], &rect20), &surfacerect20, &intersected20);
+
+            src = (((Uint8 *) VideoSurface12->pixels) + (intersected20.y * srcpitch)) + (intersected20.x * pixsize);
+            OpenGLFuncs.glTexSubImage2D(GL_TEXTURE_2D, 0, intersected20.x, intersected20.y, intersected20.w, intersected20.h, glfmt, gltype, src);
+
+            OpenGLFuncs.glBegin(GL_TRIANGLE_STRIP);
+            {
+                const GLfloat tex_x1 = ((GLfloat) intersected20.x) / ((GLfloat) VideoSurface12->w);
+                const GLfloat tex_y1 = ((GLfloat) intersected20.y) / ((GLfloat) VideoSurface12->h);
+                const GLfloat tex_x2 = tex_x1 + ((GLfloat) intersected20.w) / ((GLfloat) VideoSurface12->w);
+                const GLfloat tex_y2 = tex_y1 + ((GLfloat) intersected20.h) / ((GLfloat) VideoSurface12->h);
+                const GLint vert_x1 = (GLint) intersected20.x;
+                const GLint vert_y1 = (GLint) intersected20.y;
+                const GLint vert_x2 = vert_x1 + (GLint) intersected20.w;
+                const GLint vert_y2 = vert_y1 + (GLint) intersected20.h;
+                OpenGLFuncs.glTexCoord2f(tex_x1, tex_y1);
+                OpenGLFuncs.glVertex2i(vert_x1, vert_y1);
+                OpenGLFuncs.glTexCoord2f(tex_x2, tex_y1);
+                OpenGLFuncs.glVertex2i(vert_x2, vert_y1);
+                OpenGLFuncs.glTexCoord2f(tex_x1, tex_y2);
+                OpenGLFuncs.glVertex2i(vert_x1, vert_y2);
+                OpenGLFuncs.glTexCoord2f(tex_x2, tex_y2);
+                OpenGLFuncs.glVertex2i(vert_x2, vert_y2);
+            }
+            OpenGLFuncs.glEnd();
+        }
+    }
+}
+
+
+DECLSPEC void SDLCALL
+SDL_GL_Unlock(void)
+{
+    if (OpenGLBlitTexture) {
+        if (OpenGLBlitLockCount > 0) {
+            if (--OpenGLBlitLockCount == 0) {
+                OpenGLFuncs.glPopMatrix();
+                OpenGLFuncs.glMatrixMode(GL_PROJECTION);
+                OpenGLFuncs.glPopMatrix();
+                OpenGLFuncs.glPopClientAttrib();
+                OpenGLFuncs.glPopAttrib();
+            }
+        }
+    }
+}
+
+
 DECLSPEC void SDLCALL
 SDL_UpdateRects(SDL12_Surface *surface12, int numrects, SDL12_Rect *rects12)
 {
     /* strangely, SDL 1.2 doesn't check if surface12 is NULL before touching it */
     /* (UpdateRect, singular, does...) */
+
+    if ((surface12 == VideoSurface12) && ((surface12->flags & SDL12_OPENGLBLIT) == SDL12_OPENGLBLIT)) {
+        SDL_GL_Lock();
+        SDL_GL_UpdateRects(numrects, rects12);
+        SDL_GL_Unlock();
+        return;
+    }
+
     if (surface12->flags & SDL12_OPENGL) {
         SDL20_SetError("Use SDL_GL_SwapBuffers() on OpenGL surfaces");
         return;
@@ -5621,28 +5771,6 @@ SDL_CloseAudio(void)
 }
 
 
-/* !!! FIXME: these are just stubs for now, but Sam thinks that maybe these
-were added at Loki for Heavy Gear 2's UI. They just make GL calls. */
-DECLSPEC void SDLCALL
-SDL_GL_Lock(void)
-{
-    FIXME("write me");
-}
-
-DECLSPEC void SDLCALL
-SDL_GL_UpdateRects(int numrects, SDL12_Rect *rects)
-{
-    (void) numrects;
-    (void) rects;
-    FIXME("write me");
-}
-
-DECLSPEC void SDLCALL
-SDL_GL_Unlock(void)
-{
-    FIXME("write me");
-}
-
 
 /* SDL_GL_DisableContext and SDL_GL_EnableContext_Thread are not real SDL 1.2
    APIs, but some idTech4 games shipped with a custom SDL 1.2 build that added
diff --git a/src/SDL20_syms.h b/src/SDL20_syms.h
index 1f964e6..29b1007 100644
--- a/src/SDL20_syms.h
+++ b/src/SDL20_syms.h
@@ -329,6 +329,31 @@ OPENGL_SYM(Core,void,glCopyTexImage2D,(GLenum a, GLint b, GLenum c, GLint d, GLi
 OPENGL_SYM(Core,void,glCopyTexSubImage2D,(GLenum a, GLint b, GLint c, GLint d, GLint e, GLint f, GLsizei g, GLsizei h),(a,b,c,d,e,f,g,h),)
 OPENGL_SYM(Core,void,glCopyTexSubImage3D,(GLenum a, GLint b, GLint c, GLint d, GLint e, GLint f, GLsizei g, GLsizei h, GLint i),(a,b,c,d,e,f,g,h,i),)
 
+OPENGL_SYM(Core,void,glDeleteTextures,(GLsizei a, const GLuint *b),(a,b),)
+OPENGL_SYM(Core,void,glGenTextures,(GLsizei a, GLuint *b),(a,b),)
+OPENGL_SYM(Core,void,glPopAttrib,(),(),)
+OPENGL_SYM(Core,void,glPopClientAttrib,(),(),)
+OPENGL_SYM(Core,void,glPopMatrix,(),(),)
+OPENGL_SYM(Core,void,glBegin,(GLenum a),(a),)
+OPENGL_SYM(Core,void,glPushAttrib,(GLbitfield a),(a),)
+OPENGL_SYM(Core,void,glPushClientAttrib,(GLbitfield a),(a),)
+OPENGL_SYM(Core,void,glBindTexture,(GLenum a, GLuint b),(a,b),)
+OPENGL_SYM(Core,void,glEnd,(),(),)
+OPENGL_SYM(Core,void,glTexEnvf,(GLenum a, GLenum b, GLfloat c),(a,b,c),)
+OPENGL_SYM(Core,void,glTexParameteri,(GLenum a, GLenum b, GLint c),(a,b,c),)
+OPENGL_SYM(Core,void,glPixelStorei,(GLenum a, GLint b),(a,b),)
+OPENGL_SYM(Core,void,glBlendFunc,(GLenum a, GLenum b),(a,b),)
+OPENGL_SYM(Core,void,glColor4f,(GLfloat a, GLfloat b, GLfloat c, GLfloat d),(a,b,c,d),)
+OPENGL_SYM(Core,void,glMatrixMode,(GLenum a),(a),)
+OPENGL_SYM(Core,void,glLoadIdentity,(),(),)
+OPENGL_SYM(Core,void,glPushMatrix,(),(),)
+OPENGL_SYM(Core,void,glOrtho,(GLdouble a, GLdouble b, GLdouble c, GLdouble d, GLdouble e, GLdouble f),(a,b,c,d,e,f),)
+OPENGL_SYM(Core,void,glTexImage2D,(GLenum a, GLint b, GLint c, GLsizei d, GLsizei e, GLint f, GLenum g, GLenum h, const GLvoid *i),(a,b,c,d,e,f,g,h,i),)
+OPENGL_SYM(Core,void,glTexSubImage2D,(GLenum a, GLint b, GLint c, GLint d, GLsizei e, GLsizei f, GLenum g, GLenum h, const GLvoid *i),(a,b,c,d,e,f,g,h,i),)
+OPENGL_SYM(Core,void,glVertex2i,(GLint a, GLint b),(a,b),)
+OPENGL_SYM(Core,void,glTexCoord2f,(GLfloat a, GLfloat b),(a,b),)
+
+
 OPENGL_EXT(GL_ARB_framebuffer_object)
 OPENGL_SYM(GL_ARB_framebuffer_object,void,glBindRenderbuffer,(GLenum a, GLuint b),(a,b),)
 OPENGL_SYM(GL_ARB_framebuffer_object,void,glDeleteRenderbuffers,(GLsizei a, const GLuint *b),(a,b),)
@@ -344,6 +369,8 @@ OPENGL_SYM(GL_ARB_framebuffer_object,GLenum,glCheckFramebufferStatus,(GLenum a),
 OPENGL_SYM(GL_ARB_framebuffer_object,void,glFramebufferRenderbuffer,(GLenum a, GLenum b, GLenum c, GLuint d),(a,b,c,d),)
 OPENGL_SYM(GL_ARB_framebuffer_object,void,glBlitFramebuffer,(GLint a, GLint b, GLint c, GLint d, GLint e, GLint f, GLint g, GLint h, GLbitfield i, GLenum j),(a,b,c,d,e,f,g,h,i,j),)
 
+OPENGL_EXT(GL_ARB_texture_non_power_of_two)
+
 #undef SDL20_SYM
 #undef SDL20_SYM_PASSTHROUGH
 #undef SDL20_SYM_VARARGS