diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index afbeaa1e7b1fc9b65d4a49c1348ff1ad139f31ad..a7735449381802897a058bb734008e3a8fce156d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -255,6 +255,10 @@ else()
 endif()
 set(SRB2_CONFIG_HWRENDER ON CACHE BOOL
 	"Enable hardware rendering through OpenGL.")
+set(SRB2_CONFIG_HAVE_GLES ON CACHE BOOL
+	"Enable hardware rendering through OpenGL ES 1.1.")
+set(SRB2_CONFIG_HAVE_GLES2 ON CACHE BOOL
+	"Enable hardware rendering through OpenGL ES 2.0.")
 set(SRB2_CONFIG_USEASM OFF CACHE BOOL
 	"Enable NASM tmap implementation for software mode speedup.")
 set(SRB2_CONFIG_YASM OFF CACHE BOOL
@@ -529,19 +533,35 @@ if(${SRB2_CONFIG_HWRENDER})
 	)
 
 	set(SRB2_R_OPENGL_SOURCES
-		${CMAKE_CURRENT_SOURCE_DIR}/hardware/r_opengl/r_opengl.c
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/shaders/gl_shaders.c
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/r_glcommon/r_glcommon.c
 	)
 
 	set(SRB2_R_OPENGL_HEADERS
-		${CMAKE_CURRENT_SOURCE_DIR}/hardware/r_opengl/r_opengl.h
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/shaders/gl_shaders.h
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/r_glcommon/r_glcommon.h
 	)
 
 endif()
 
+if(${SRB2_CONFIG_HAVE_GLES})
+	set(SRB2_HAVE_GLES ON)
+	set(SRB2_HWRENDER_HEADERS ${SRB2_HWRENDER_HEADERS} ${CMAKE_CURRENT_SOURCE_DIR}/hardware/r_gles/r_gles.h)
+	set(SRB2_HWRENDER_HEADERS ${SRB2_HWRENDER_HEADERS} ${CMAKE_CURRENT_SOURCE_DIR}/hardware/r_gles/r_gleslib.h)
+	add_definitions(-DHAVE_GLES)
+	if(${SRB2_CONFIG_HAVE_GLES2})
+		set(SRB2_HAVE_GLES2 ON)
+		set(SRB2_HWRENDER_SOURCES ${SRB2_HWRENDER_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/hardware/r_gles/r_gles2.c)
+		set(SRB2_HWRENDER_HEADERS ${SRB2_HWRENDER_HEADERS} ${CMAKE_CURRENT_SOURCE_DIR}/hardware/r_gles/lzml.h)
+		add_definitions(-DHAVE_GLES2)
+	else()
+		set(SRB2_HWRENDER_SOURCES ${SRB2_HWRENDER_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/hardware/r_gles/r_gles1.c)
+	endif()
+else()
+	set(SRB2_HWRENDER_SOURCES ${SRB2_HWRENDER_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/hardware/r_opengl/r_opengl.c)
+	set(SRB2_HWRENDER_HEADERS ${SRB2_HWRENDER_HEADERS} ${CMAKE_CURRENT_SOURCE_DIR}/hardware/r_opengl/r_opengl.h)
+endif()
+
 if(${SRB2_CONFIG_HWRENDER} AND ${SRB2_CONFIG_STATIC_OPENGL})
 	find_package(OpenGL)
 	if(${OPENGL_FOUND})
diff --git a/src/Makefile b/src/Makefile
index 58e7428faf80c4a004c30c42000da4a46e2270c3..1b59099feb8092f469b65ec741b03d7ac4d833c2 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -270,7 +270,16 @@ else
 	OPTS+=-DHWRENDER
 	OBJS+=$(OBJDIR)/hw_bsp.o $(OBJDIR)/hw_draw.o $(OBJDIR)/hw_light.o \
 		 $(OBJDIR)/hw_main.o $(OBJDIR)/hw_gpu.o $(OBJDIR)/hw_clip.o $(OBJDIR)/hw_md2.o $(OBJDIR)/hw_cache.o \
-		 $(OBJDIR)/hw_md2load.o $(OBJDIR)/hw_md3load.o $(OBJDIR)/hw_model.o $(OBJDIR)/u_list.o $(OBJDIR)/hw_batching.o
+		 $(OBJDIR)/hw_md2load.o $(OBJDIR)/hw_md3load.o $(OBJDIR)/hw_model.o $(OBJDIR)/u_list.o $(OBJDIR)/hw_batching.o \
+		 $(r_opengl_o) $(OBJDIR)/r_glcommon.o $(OBJDIR)/gl_shaders.o
+
+	ifdef HAVE_GLES2
+		OPTS+=-DHAVE_GLES2 -DHAVE_GLES
+	else
+		ifdef HAVE_GLES
+			OPTS+=-DHAVE_GLES
+		endif
+	endif
 endif
 
 OPTS += -DCOMPVERSION
@@ -653,97 +662,6 @@ endif
 $(OBJDIR):
 	-$(MKDIR) $(OBJDIR)
 
-ifndef SDL
-ifdef NOHW
-dll :
-else
-dll : opengl_dll
-endif
-ifdef MINGW
-all_dll: opengl_dll ds3d_dll fmod_dll openal_dll
-
-opengl_dll: $(BIN)/r_opengl.dll
-$(BIN)/r_opengl.dll: $(OBJDIR)/ogl_win.o $(OBJDIR)/r_opengl.o $(OBJDIR)/r_glcommon.o $(OBJDIR)/gl_shaders.o
-	-$(MKDIR) $(BIN)
-	@echo Linking R_OpenGL.dll...
-	$(CC) --shared  $^ -o $@ -g -Wl,--add-stdcall-alias -lgdi32 -static-libgcc
-ifndef NOUPX
-	-$(UPX) $(UPX_OPTS) $@
-endif
-
-ds3d_dll: $(BIN)/s_ds3d.dll
-$(BIN)/s_ds3d.dll: $(OBJDIR)/s_ds3d.o
-	@echo Linking S_DS3d.dll...
-	$(CC) --shared  $^ -o $@ -g -Wl,--add-stdcall-alias -ldsound -luuid
-
-fmod_dll: $(BIN)/s_fmod.dll
-$(BIN)/s_fmod.dll: $(OBJDIR)/s_fmod.o
-	-$(MKDIR) $(BIN)
-	@echo Linking S_FMOD.dll...
-	$(CC) --shared  $^ -o $@ -g -Wl,--add-stdcall-alias -lfmod
-
-openal_dll: $(BIN)/s_openal.dll
-$(BIN)/s_openal.dll: $(OBJDIR)/s_openal.o
-	-$(MKDIR) $(BIN)
-	@echo Linking S_OpenAL.dll...
-	$(CC) --shared  $^ -o $@ -g -Wl,--add-stdcall-alias -lopenal32
-else
-all_dll: fmod_so openal_so
-
-fmod_so: $(BIN)/s_fmod.so
-$(BIN)/s_fmod.so: $(OBJDIR)/s_fmod.o
-	-$(MKDIR) $(BIN)
-	@echo Linking S_FMOD.so...
-	$(CC) --shared $^ -o $@ -g --nostartfiles -lm -lfmod
-
-openal_so: $(BIN)/s_openal.so
-$(BIN)/s_openal.so: $(OBJDIR)/s_openal.o
-	-$(MKDIR) $(BIN)
-	@echo Linking S_OpenAL.so...
-	$(CC) --shared $^ -o $@ -g --nostartfiles -lm -lopenal
-endif
-
-else
-ifdef SDL
-ifdef MINGW
-$(OBJDIR)/gl_shaders.o: hardware/shaders/gl_shaders.c hardware/shaders/gl_shaders.h \
- hardware/r_opengl/r_opengl.h doomdef.h doomtype.h doomdata.h hardware/hw_dll.h
-	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
-$(OBJDIR)/r_glcommon.o: hardware/r_glcommon/r_glcommon.c hardware/r_glcommon/r_glcommon.h \
- doomdef.h doomtype.h doomdata.h hardware/hw_dll.h
-	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
-$(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
- hardware/r_glcommon/r_glcommon.h hardware/shaders/gl_shaders.h \
- doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_gpu.h screen.h \
- command.h hardware/hw_data.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
- hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
- am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
- p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
-	$(echoName)
-	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
-else
-$(OBJDIR)/gl_shaders.o: hardware/shaders/gl_shaders.c hardware/shaders/gl_shaders.h \
- hardware/r_opengl/r_opengl.h doomdef.h doomtype.h doomdata.h hardware/hw_dll.h
-	$(CC) $(CFLAGS) $(WFLAGS) -I/usr/X11R6/include -c $< -o $@
-$(OBJDIR)/r_glcommon.o: hardware/r_glcommon/r_glcommon.c hardware/r_glcommon/r_glcommon.h \
- doomdef.h doomtype.h doomdata.h hardware/hw_dll.h
-	$(CC) $(CFLAGS) $(WFLAGS) -I/usr/X11R6/include -c $< -o $@
-$(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
- hardware/r_glcommon/r_glcommon.h hardware/shaders/gl_shaders.h \
- doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_gpu.h screen.h \
- command.h hardware/hw_data.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
- hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
- am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
- p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
-	$(echoName)
-	$(CC) $(CFLAGS) $(WFLAGS) -I/usr/X11R6/include -c $< -o $@
-endif
-endif
-
-endif
-
 #dependecy made by gcc itself !
 $(OBJS):
 ifndef DUMMY
@@ -758,6 +676,9 @@ $(OBJDIR)/depend.dep:
 	$(CC) $(CFLAGS) -MM $(INTERFACE)/*.c >> $(OBJDIR)/depend.ped
 ifndef NOHW
 	$(CC) $(CFLAGS) -MM hardware/*.c >> $(OBJDIR)/depend.ped
+	$(CC) $(CFLAGS) -MM hardware/shaders/*.c >> $(OBJDIR)/depend.ped
+	$(CC) $(CFLAGS) -MM hardware/r_glcommon/*.c >> $(OBJDIR)/depend.ped
+	$(CC) $(CFLAGS) -MM hardware/$(OPENGL_API)/*.c >> $(OBJDIR)/depend.ped
 endif
 	$(CC) $(CFLAGS) -MM blua/*.c >> $(OBJDIR)/depend.ped
 	@sed -e 's,\(.*\)\.o: ,$(subst /,\/,$(OBJDIR))\/&,g' < $(OBJDIR)/depend.ped > $(OBJDIR)/depend.dep
@@ -797,6 +718,18 @@ $(OBJDIR)/%.o: hardware/%.c
 	$(echoName)
 	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
 
+$(OBJDIR)/%.o: hardware/shaders/%.c
+	$(echoName)
+	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
+
+$(OBJDIR)/%.o: hardware/r_glcommon/%.c
+	$(echoName)
+	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
+
+$(OBJDIR)/%.o: hardware/$(OPENGL_API)/%.c
+	$(echoName)
+	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
+
 $(OBJDIR)/%.o: blua/%.c
 	$(echoName)
 	$(CC) $(CFLAGS) $(LUA_CFLAGS) $(WFLAGS) -c $< -o $@
@@ -818,40 +751,6 @@ $(OBJDIR)/SRB2.res: win32/Srb2win.rc win32/afxres.h win32/resource.h
 	$(WINDRES) -i $< -O rc $(WINDRESFLAGS) --include-dir=win32 -o $@ -O coff
 
 
-ifdef MINGW
-ifndef SDL
-ifndef NOHW
-$(OBJDIR)/gl_shaders.o: hardware/shaders/gl_shaders.c hardware/shaders/gl_shaders.h \
- hardware/r_opengl/r_opengl.h doomdef.h doomtype.h doomdata.h hardware/hw_dll.h
-	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
-$(OBJDIR)/r_glcommon.o: hardware/r_glcommon/r_glcommon.c hardware/r_glcommon/r_glcommon.h \
- doomdef.h doomtype.h doomdata.h hardware/hw_dll.h
-	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
-$(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
- hardware/r_glcommon/r_glcommon.h hardware/shaders/gl_shaders.h \
- doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_gpu.h screen.h \
- command.h hardware/hw_data.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
- hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
- am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
- p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
-	$(echoName)
-	$(CC) $(CFLAGS) $(WFLAGS) -D_WINDOWS -mwindows -c $< -o $@
-
-$(OBJDIR)/ogl_win.o: hardware/r_opengl/ogl_win.c hardware/r_opengl/r_opengl.h \
- doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_gpu.h screen.h \
- command.h hardware/hw_data.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
- hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
- am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
- p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
-	$(echoName)
-	$(CC) $(CFLAGS) $(WFLAGS) -D_WINDOWS -mwindows -c $< -o $@
-endif
-
-endif
-endif
-
 ifdef SDL
 
 ifdef MINGW
diff --git a/src/Makefile.cfg b/src/Makefile.cfg
index db7230bb4b42ffe4a21f27d94fddf73174153e1c..7368c3105ad1ff183a2a17e6a46ffbe1756f8261 100644
--- a/src/Makefile.cfg
+++ b/src/Makefile.cfg
@@ -451,6 +451,19 @@ ifdef SDL
 	OBJDIR:=$(OBJDIR)/SDL
 endif
 
+r_opengl_o=$(OBJDIR)/r_opengl.o
+OPENGL_API=r_opengl
+
+ifdef HAVE_GLES2
+	r_opengl_o=$(OBJDIR)/r_gles2.o
+	OPENGL_API=r_gles
+else
+	ifdef HAVE_GLES
+		r_opengl_o=$(OBJDIR)/r_gles1.o
+		OPENGL_API=r_gles
+	endif
+endif
+
 ifndef DUMMY
 ifdef DEBUGMODE
 	OBJDIR:=$(OBJDIR)/Debug
diff --git a/src/doomdef.h b/src/doomdef.h
index d0b7ea0c2391334c703051d02e0dae693dfdfe19..1b77280176204f96b53949f4c348f78926bf1dc0 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -624,6 +624,10 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 /// OpenGL shaders
 #define GL_SHADERS
 
+#if defined(HAVE_GLES) && !defined(HAVE_GLES2)
+#undef GL_SHADERS
+#endif
+
 /// Handle touching sector specials in P_PlayerAfterThink instead of P_PlayerThink.
 /// \note   Required for proper collision with moving sloped surfaces that have sector specials on them.
 #define SECTORSPECIALSAFTERTHINK
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index 7aecad8e3e29a1baa458a63b0a244ea39c1ca3e7..f981e22b3a6d6a0eb970a46334e650b816f23caa 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -213,19 +213,20 @@ enum EFilterMode
 // Vertex count for post processing effects
 #define GPU_POSTIMGVERTS 10
 
-#ifdef GL_SHADERS
 // Predefined shader types
-enum
+enum EShaderType
 {
 	SHADER_DEFAULT = 0,
 
-	SHADER_FLOOR,
+	SHADER_LEVEL_FIRST,
+	SHADER_FLOOR = SHADER_LEVEL_FIRST,
 	SHADER_WALL,
 	SHADER_SPRITE,
 	SHADER_MODEL, SHADER_MODEL_LIGHTING,
 	SHADER_WATER,
 	SHADER_FOG,
 	SHADER_SKY,
+	SHADER_LEVEL_LAST = SHADER_SKY,
 
 #ifdef HAVE_GLES2
 	SHADER_FADEMASK,
@@ -235,6 +236,8 @@ enum
 	NUMBASESHADERS,
 };
 
+#ifdef GL_SHADERS
+
 // Maximum amount of shader programs
 // Must be higher than NUMBASESHADERS
 #define HWR_MAXSHADERS 16
@@ -247,6 +250,8 @@ struct FShaderSource
 };
 typedef struct FShaderSource FShaderSource;
 
+#endif
+
 // Custom shader reference table
 struct FShaderReferenceArray
 {
@@ -255,8 +260,6 @@ struct FShaderReferenceArray
 };
 typedef struct FShaderReferenceArray FShaderReferenceArray;
 
-#endif
-
 struct FSkyVertex
 {
 	float x, y, z;
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index 0a0184adf77b2fad90248ef02d896f76bcb64b66..ea145265ef7877265d1488e3185d348d7b7f8842 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -1483,39 +1483,40 @@ static inline boolean saveTGA(const char *file_name, void *buffer,
 #endif
 
 // --------------------------------------------------------------------------
-// screen shot
+// Screen buffer and screenshots
+// Saves or returns either 24bit 888 RGB or 32bit 8888 RGBA
 // --------------------------------------------------------------------------
 
-UINT8 *HWR_GetScreenshot(void)
+UINT8 *HWR_GetScreenBuffer(void)
 {
-	UINT8 *buf = malloc(vid.width * vid.height * 3 * sizeof (*buf));
+	UINT8 *buf = malloc(vid.width * vid.height * SCREENSHOT_BITS * sizeof (*buf));
 
 	if (!buf)
 		return NULL;
-	// returns 24bit 888 RGB
-	GPU->ReadRect(0, 0, vid.width, vid.height, vid.width * 3, (void *)buf);
+
+	GPU->ReadRect(0, 0, vid.width, vid.height, vid.width * SCREENSHOT_BITS, (void *)buf);
 	return buf;
 }
 
-boolean HWR_Screenshot(const char *pathname)
+boolean HWR_TakeScreenshot(const char *pathname)
 {
 	boolean ret;
-	UINT8 *buf = malloc(vid.width * vid.height * 3 * sizeof (*buf));
+	UINT8 *buf = malloc(vid.width * vid.height * SCREENSHOT_BITS * sizeof (*buf));
 
 	if (!buf)
 	{
-		CONS_Debug(DBG_RENDER, "HWR_Screenshot: Failed to allocate memory\n");
+		CONS_Debug(DBG_RENDER, "HWR_TakeScreenshot: Failed to allocate memory\n");
 		return false;
 	}
 
-	// returns 24bit 888 RGB
-	GPU->ReadRect(0, 0, vid.width, vid.height, vid.width * 3, (void *)buf);
+	GPU->ReadRect(0, 0, vid.width, vid.height, vid.width * SCREENSHOT_BITS, (void *)buf);
 
 #ifdef USE_PNG
 	ret = M_SavePNG(pathname, buf, vid.width, vid.height, NULL);
 #else
 	ret = saveTGA(pathname, buf, vid.width, vid.height);
 #endif
+
 	free(buf);
 	return ret;
 }
diff --git a/src/hardware/hw_gpu.c b/src/hardware/hw_gpu.c
index 5052148931ac0c16450e3d82d73201093b6591fd..bf277dc54a41eef54abf44806051ef8fdc289fa8 100644
--- a/src/hardware/hw_gpu.c
+++ b/src/hardware/hw_gpu.c
@@ -23,4 +23,16 @@ void GPUInterface_Load(struct GPURenderingAPI **api)
 	*api = &GLInterfaceAPI;
 }
 
+const char *GPUInterface_GetAPIName(void)
+{
+	return
+#if defined(HAVE_GLES2)
+	"OpenGL ES 2.0";
+#elif defined(HAVE_GLES)
+	"OpenGL ES 1.1";
+#else
+	"OpenGL";
+#endif
+}
+
 #endif // HWRENDER
diff --git a/src/hardware/hw_gpu.h b/src/hardware/hw_gpu.h
index 03f522cfe87c1aa169c601119da7174c355bfe1a..a1b2230fe235718ed5ca54200c2f4ed88cae78c2 100644
--- a/src/hardware/hw_gpu.h
+++ b/src/hardware/hw_gpu.h
@@ -20,13 +20,14 @@
 struct GPURenderingAPI
 {
 	boolean (*Init) (void);
-	void (*FinishUpdate) (INT32 waitvbl);
 
+	void (*SetInitialStates) (void);
+	void (*SetModelView) (INT32 w, INT32 h);
 	void (*SetState) (INT32 State, INT32 Value);
 	void (*SetTransform) (FTransform *ptransform);
 	void (*SetBlend) (UINT32 PolyFlags);
 	void (*SetPalette) (RGBA_t *palette);
-	void (*ClearBuffer) (boolean ColorMask, boolean DepthMask, FRGBAFloat *ClearColor);
+	void (*SetDepthBuffer) (void);
 
 	void (*DrawPolygon) (FSurfaceInfo *pSurf, FOutVector *pOutVerts, UINT32 iNumPts, UINT32 PolyFlags);
 	void (*DrawIndexedTriangles) (FSurfaceInfo *pSurf, FOutVector *pOutVerts, UINT32 iNumPts, UINT32 PolyFlags, UINT32 *IndexArray);
@@ -45,6 +46,7 @@ struct GPURenderingAPI
 
 	void (*ReadRect) (INT32 x, INT32 y, INT32 width, INT32 height, INT32 dst_stride, UINT16 *dst_data);
 	void (*GClipRect) (INT32 minx, INT32 miny, INT32 maxx, INT32 maxy, float nearclip);
+	void (*ClearBuffer) (boolean ColorMask, boolean DepthMask, FRGBAFloat *ClearColor);
 
 	void (*MakeScreenTexture) (void);
 	void (*MakeScreenFinalTexture) (void);
@@ -53,6 +55,7 @@ struct GPURenderingAPI
 	void (*StartScreenWipe) (void);
 	void (*EndScreenWipe) (void);
 	void (*DoScreenWipe) (void);
+	void (*DoTintedWipe) (boolean istowhite, boolean isfadingin);
 	void (*DrawIntermissionBG) (void);
 	void (*DrawScreenFinalTexture) (int width, int height);
 
@@ -70,5 +73,6 @@ struct GPURenderingAPI
 extern struct GPURenderingAPI *GPU;
 
 void GPUInterface_Load(struct GPURenderingAPI **api);
+const char *GPUInterface_GetAPIName(void);
 
 #endif // __HWR_GPU_H__
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index b63d0df61a260772312f92b2d682500c4b7fe0dc..11b4eda3614c7d122d80a590d41829c3f534b80a 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -6203,6 +6203,9 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 
 	GPU->SetTransform(NULL);
 	GPU->UnSetShader();
+#ifdef HAVE_GLES
+	GPU->SetBlend(PF_Modulated|PF_Translucent|PF_NoDepthTest);
+#endif
 
 	HWR_DoPostProcessor(player);
 
@@ -6242,7 +6245,7 @@ static CV_PossibleValue_t glshearing_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Thi
 static void CV_glfiltermode_OnChange(void);
 static void CV_glanisotropic_OnChange(void);
 
-static CV_PossibleValue_t glfiltermode_cons_t[]= {{GPU_TEXFILTER_POINTSAMPLED, "Nearest"},
+static CV_PossibleValue_t glfiltermode_cons_t[] = {{GPU_TEXFILTER_POINTSAMPLED, "Nearest"},
 	{GPU_TEXFILTER_BILINEAR, "Bilinear"}, {GPU_TEXFILTER_TRILINEAR, "Trilinear"},
 	{GPU_TEXFILTER_MIXED1, "Linear_Nearest"},
 	{GPU_TEXFILTER_MIXED2, "Nearest_Linear"},
@@ -6623,8 +6626,16 @@ void HWR_DoWipe(UINT8 wipenum, UINT8 scrnnum)
 
 void HWR_DoTintedWipe(UINT8 wipenum, UINT8 scrnnum)
 {
+#ifdef HAVE_GLES2
+	if (!HWR_WipeCheck(wipenum, scrnnum))
+		return;
+
+	HWR_GetFadeMask(wipelumpnum);
+	GPU->DoTintedWipe((wipestyleflags & WSF_FADEIN), (wipestyleflags & WSF_TOWHITE));
+#else
 	// It does the same thing
 	HWR_DoWipe(wipenum, scrnnum);
+#endif
 }
 
 void HWR_MakeScreenFinalTexture(void)
@@ -6666,6 +6677,10 @@ FShaderReferenceArray shaderxlat[] =
 	{"WaterRipple", SHADER_WATER},
 	{"Fog", SHADER_FOG},
 	{"Sky", SHADER_SKY},
+#ifdef HAVE_GLES2
+	{"Wipe", SHADER_FADEMASK},
+	{"WipeAdditiveAndSubtractive", SHADER_FADEMASK_ADDITIVEANDSUBTRACTIVE},
+#endif
 	{NULL, 0},
 };
 
@@ -6748,7 +6763,13 @@ skip_lump:
 
 			for (i = 0; shaderxlat[i].type; i++)
 			{
-				if (!stricmp(shaderxlat[i].type, stoken))
+				FShaderReferenceArray *xlat = &shaderxlat[i];
+				INT32 id = xlat->id;
+
+				if (id < SHADER_LEVEL_FIRST || id > SHADER_LEVEL_LAST)
+					continue;
+
+				if (!stricmp(xlat->type, stoken))
 				{
 					size_t shader_size;
 					char *shader_source;
@@ -6781,7 +6802,7 @@ skip_lump:
 					shader_source = Z_Malloc(shader_size, PU_STATIC, NULL);
 					W_ReadLumpPwad(wadnum, shader_lumpnum, shader_source);
 
-					GPU->LoadCustomShader(shaderxlat[i].id, shader_source, shader_size, (shadertype == 2));
+					GPU->LoadCustomShader(id, shader_source, shader_size, (shadertype == 2));
 
 					Z_Free(shader_source);
 					Z_Free(shader_lumpname);
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index d1de5c328447cfbccfd4a7d63d61e9a0ef5dcbe8..03c37aef715cd45cc30a1e3e3f886f952ddc2318 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -48,8 +48,8 @@ void HWR_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color, UINT16 ac
 void HWR_DrawConsoleFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color, UINT32 actualcolor);	// Lat: separate flags from color since color needs to be an uint to work right.
 void HWR_DrawPic(INT32 x,INT32 y,lumpnum_t lumpnum);
 
-UINT8 *HWR_GetScreenshot(void);
-boolean HWR_Screenshot(const char *pathname);
+UINT8 *HWR_GetScreenBuffer(void);
+boolean HWR_TakeScreenshot(const char *pathname);
 
 void HWR_AddCommands(void);
 void HWR_AddSessionCommands(void);
diff --git a/src/hardware/r_glcommon/r_glcommon.c b/src/hardware/r_glcommon/r_glcommon.c
index cf552d2fcf69cd5195273a56e6294a3422f2773d..3e1c9ee33feb5e6bfafe31a96d72f43255348c43 100644
--- a/src/hardware/r_glcommon/r_glcommon.c
+++ b/src/hardware/r_glcommon/r_glcommon.c
@@ -1,13 +1,13 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 1998-2020 by Sonic Team Junior.
+// Copyright (C) 2020 by Jaime "Lactozilla" Passos.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
 // See the 'LICENSE' file for more details.
 //-----------------------------------------------------------------------------
 /// \file r_glcommon.c
-/// \brief Common OpenGL functions shared by OpenGL backends
+/// \brief Common OpenGL functions shared by all of the OpenGL backends
 
 #include "r_glcommon.h"
 
@@ -53,6 +53,7 @@ GLuint WipeEndTexture = 0;
 UINT32 CurrentPolyFlags;
 
 GLboolean MipmappingEnabled = GL_FALSE;
+GLboolean MipmappingSupported = GL_FALSE;
 GLboolean ModelLightingEnabled = GL_FALSE;
 
 GLint MipmapMinFilter = GL_LINEAR;
@@ -70,6 +71,7 @@ boolean GLExtension_vertex_buffer_object;
 boolean GLExtension_texture_filter_anisotropic;
 boolean GLExtension_vertex_program;
 boolean GLExtension_fragment_program;
+boolean GLExtension_framebuffer_object;
 boolean GLExtension_shaders; // Not an extension on its own, but it is set if multiple extensions are available.
 
 static FExtensionList const ExtensionList[] = {
@@ -79,6 +81,7 @@ static FExtensionList const ExtensionList[] = {
 	{"GL_EXT_texture_filter_anisotropic", &GLExtension_texture_filter_anisotropic},
 	{"GL_ARB_vertex_program", &GLExtension_vertex_program},
 	{"GL_ARB_fragment_program", &GLExtension_fragment_program},
+	{"GL_ARB_framebuffer_object", &GLExtension_framebuffer_object},
 	{NULL, NULL}
 };
 
@@ -150,20 +153,16 @@ PFNglClientActiveTexture pglClientActiveTexture;
 // Mipmapping
 //
 
-#ifdef HAVE_GLES
 PFNglGenerateMipmap pglGenerateMipmap;
-#endif
 
 //
 // Depth functions
 //
-#ifndef HAVE_GLES
-	PFNglClearDepth pglClearDepth;
-	PFNglDepthRange pglDepthRange;
-#else
-	PFNglClearDepthf pglClearDepthf;
-	PFNglDepthRangef pglDepthRangef;
-#endif
+PFNglClearDepth pglClearDepth;
+PFNglDepthRange pglDepthRange;
+
+PFNglClearDepthf pglClearDepthf;
+PFNglDepthRangef pglDepthRangef;
 
 //
 // Legacy functions
@@ -202,11 +201,8 @@ PFNglTexEnvi pglTexEnvi;
 #endif // HAVE_GLES2
 
 // Color
-#ifdef HAVE_GLES
 PFNglColor4f pglColor4f;
-#else
 PFNglColor4ubv pglColor4ubv;
-#endif
 
 /* 1.5 functions for buffers */
 PFNglGenBuffers pglGenBuffers;
@@ -221,8 +217,16 @@ PFNglBlendEquation pglBlendEquation;
 //                                                                  FUNCTIONS
 // ==========================================================================
 
+#ifdef GL_SHADERS
+static boolean ShadersInit = false;
+#endif
+
 boolean GLBackend_Init(void)
 {
+#if defined(__ANDROID__)
+	GLBackend_InitContext();
+#endif
+	GPUTextureFormat = GL_RGBA;
 	return GLBackend_LoadFunctions();
 }
 
@@ -237,6 +241,7 @@ boolean GLBackend_InitContext(void)
 		pglGetIntegerv(GL_MINOR_VERSION, &GLMinorVersion);
 
 		GL_DBG_Printf("OpenGL %s\n", GLVersion);
+		GL_DBG_Printf("Specification: %s\n", GPUInterface_GetAPIName());
 		GL_DBG_Printf("Version: %d.%d\n", GLMajorVersion, GLMinorVersion);
 		GL_DBG_Printf("GPU: %s\n", GLRenderer);
 
@@ -261,16 +266,25 @@ boolean GLBackend_InitContext(void)
 	return true;
 }
 
-boolean GLBackend_LoadCommonFunctions(void)
+boolean GLBackend_InitShaders(void)
 {
-#define GETOPENGLFUNC(func) \
-	p ## gl ## func = GLBackend_GetFunction("gl" #func); \
-	if (!(p ## gl ## func)) \
-	{ \
-		GL_MSG_Error("failed to get OpenGL function: %s", #func); \
-		return false; \
-	} \
+#ifdef GL_SHADERS
+	if (ShadersInit)
+		return true;
+
+	Shader_LoadFunctions();
 
+	if (!Shader_Compile())
+		return false;
+
+	ShadersInit = true;
+#endif
+
+	return true;
+}
+
+boolean GLBackend_LoadCommonFunctions(void)
+{
 	GETOPENGLFUNC(ClearColor)
 
 	GETOPENGLFUNC(Clear)
@@ -327,13 +341,13 @@ boolean GLBackend_LoadLegacyFunctions(void)
 	GETOPENGLFUNC(Lightfv)
 	GETOPENGLFUNC(LightModelfv)
 	GETOPENGLFUNC(Materialfv)
+
+	GETOPENGLFUNC(TexEnvi)
 #endif
 
 	return true;
 }
 
-#undef GETOPENGLFUNC
-
 INT32 GLBackend_GetShaderType(INT32 type)
 {
 #ifdef GL_SHADERS
@@ -344,9 +358,110 @@ INT32 GLBackend_GetShaderType(INT32 type)
 		return SHADER_MODEL_LIGHTING;
 #endif
 
+#ifdef HAVE_GLES2
+	if (ShadersAllowed == GPU_SHADEROPTION_OFF)
+	{
+		switch (type)
+		{
+			case SHADER_FLOOR:
+			case SHADER_WALL:
+			case SHADER_SPRITE:
+			case SHADER_MODEL:
+			case SHADER_MODEL_LIGHTING:
+			case SHADER_WATER:
+			case SHADER_FOG:
+			case SHADER_SKY:
+				return SHADER_DEFAULT;
+			default:
+				break;
+		}
+	}
+#endif
+
 	return type;
 }
 
+void GLState_SetSurface(INT32 w, INT32 h)
+{
+	GPU->SetModelView(w, h);
+	GPU->SetInitialStates();
+	pglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+void GLState_SetPalette(RGBA_t *palette)
+{
+	size_t palsize = (sizeof(RGBA_t) * 256);
+	// on a palette change, you have to reload all of the textures
+	if (memcmp(&GPUTexturePalette, palette, palsize))
+	{
+		memcpy(&GPUTexturePalette, palette, palsize);
+		GLTexture_Flush();
+	}
+}
+
+// Sets the texture filtering mode.
+void GLState_SetFilterMode(INT32 mode)
+{
+	switch (mode)
+	{
+		case GPU_TEXFILTER_TRILINEAR:
+			MipmapMinFilter = GL_LINEAR_MIPMAP_LINEAR;
+			MipmapMagFilter = GL_LINEAR;
+			MipmappingEnabled = GL_TRUE;
+			break;
+		case GPU_TEXFILTER_BILINEAR:
+			MipmapMinFilter = MipmapMagFilter = GL_LINEAR;
+			MipmappingEnabled = GL_FALSE;
+			break;
+		case GPU_TEXFILTER_POINTSAMPLED:
+			MipmapMinFilter = MipmapMagFilter = GL_NEAREST;
+			MipmappingEnabled = GL_FALSE;
+			break;
+		case GPU_TEXFILTER_MIXED1:
+			MipmapMinFilter = GL_NEAREST;
+			MipmapMagFilter = GL_LINEAR;
+			MipmappingEnabled = GL_FALSE;
+			break;
+		case GPU_TEXFILTER_MIXED2:
+			MipmapMinFilter = GL_LINEAR;
+			MipmapMagFilter = GL_NEAREST;
+			MipmappingEnabled = GL_FALSE;
+			break;
+		case GPU_TEXFILTER_MIXED3:
+			MipmapMinFilter = GL_LINEAR_MIPMAP_LINEAR;
+			MipmapMagFilter = GL_NEAREST;
+			MipmappingEnabled = GL_TRUE;
+			break;
+		default:
+			MipmapMagFilter = GL_LINEAR;
+			MipmapMinFilter = GL_NEAREST;
+	}
+}
+
+void GLState_SetClamp(GLenum pname)
+{
+#ifndef HAVE_GLES
+	pglTexParameteri(GL_TEXTURE_2D, pname, GL_CLAMP); // fallback clamp
+#endif
+	pglTexParameteri(GL_TEXTURE_2D, pname, GL_CLAMP_TO_EDGE);
+}
+
+void GLState_SetDepthBuffer(void)
+{
+	// Hurdler: all that are permanent states
+	if (pglClearDepthf)
+		pglClearDepthf(1.0f);
+	else
+		pglClearDepth(1.0f);
+
+	if (pglDepthRangef)
+		pglDepthRangef(0.0f, 1.0f);
+	else
+		pglDepthRange(0.0f, 1.0f);
+
+	pglDepthFunc(GL_LEQUAL);
+}
+
 static void SetBlendEquation(GLenum mode)
 {
 	if (pglBlendEquation)
@@ -405,7 +520,7 @@ static void SetBlendMode(UINT32 flags)
 			break;
 	}
 
-	// Alpha test
+	// Alpha testing
 	switch (flags)
 	{
 		case PF_Masked & PF_Blending:
@@ -432,7 +547,7 @@ static void SetBlendMode(UINT32 flags)
 // PF_Masked - we could use an ALPHA_TEST of GL_EQUAL, and alpha ref of 0,
 //             is it faster when pixels are discarded ?
 
-void SetBlendingStates(UINT32 PolyFlags)
+void GLState_SetBlend(UINT32 PolyFlags)
 {
 	UINT32 Xor = CurrentPolyFlags^PolyFlags;
 
@@ -470,7 +585,7 @@ void SetBlendingStates(UINT32 PolyFlags)
 		if (Xor & PF_RemoveYWrap)
 		{
 			if (PolyFlags & PF_RemoveYWrap)
-				SetClamp(GL_TEXTURE_WRAP_T);
+				GLState_SetClamp(GL_TEXTURE_WRAP_T);
 		}
 
 		if (Xor & PF_ForceWrapX)
@@ -523,18 +638,51 @@ void SetBlendingStates(UINT32 PolyFlags)
 		}
 		if (PolyFlags & PF_NoTexture)
 		{
-			SetNoTexture();
+			GLTexture_SetNoTexture();
 		}
 	}
 
 	CurrentPolyFlags = PolyFlags;
 }
 
-void SetSurface(INT32 w, INT32 h)
+void GLState_SetColor(const GLfloat red, const GLfloat green, const GLfloat blue, const GLfloat alpha)
 {
-	SetModelView(w, h);
-	SetStates();
-	pglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+#if defined(__ANDROID__)
+	if (pglColor4f)
+		pglColor4f(red, green, blue, alpha);
+	else if (pglColor4ubv)
+#endif
+	{
+		GLubyte color[4];
+
+		color[0] = (unsigned char)(red * 255);
+		color[1] = (unsigned char)(green * 255);
+		color[2] = (unsigned char)(blue * 255);
+		color[3] = (unsigned char)(alpha * 255);
+
+		pglColor4ubv(color);
+	}
+}
+
+void GLState_SetColorUBV(const GLubyte *v)
+{
+#if defined(__ANDROID__)
+	if (pglColor4f)
+	{
+		INT32 i = 0;
+		GLfloat color[4];
+
+		for (; i < 4; i++)
+		{
+			color[i] = byte2float(*v);
+			v++;
+		}
+
+		pglColor4f(color[0], color[1], color[2], color[3]);
+	}
+	else if (pglColor4ubv)
+#endif
+		pglColor4ubv(v);
 }
 
 void GLExtension_Init(void)
@@ -821,67 +969,43 @@ void GLModel_GenerateVBOs(model_t *model)
 	}
 }
 
-// Deletes all textures.
-void GLTexture_Flush(void)
+// Sets the current texture.
+void GLTexture_Set(HWRTexture_t *pTexInfo)
 {
-	while (TexCacheHead)
+	if (!pTexInfo)
 	{
-		FTextureInfo *pTexInfo = TexCacheHead;
-		HWRTexture_t *texture = pTexInfo->texture;
-
-		if (pTexInfo->name)
+		GLTexture_SetNoTexture();
+		return;
+	}
+	else if (pTexInfo->downloaded)
+	{
+		if (pTexInfo->downloaded != CurrentTexture)
 		{
-			pglDeleteTextures(1, (GLuint *)&pTexInfo->name);
-			pTexInfo->name = 0;
+			pglBindTexture(GL_TEXTURE_2D, pTexInfo->downloaded);
+			CurrentTexture = pTexInfo->downloaded;
 		}
-
-		if (texture)
-			texture->downloaded = 0;
-
-		TexCacheHead = pTexInfo->next;
-		free(pTexInfo);
 	}
+	else
+	{
+		FTextureInfo *newTex = calloc(1, sizeof (*newTex));
 
-	TexCacheTail = TexCacheHead = NULL; // Hurdler: well, TexCacheHead is already NULL
-	CurrentTexture = 0;
-}
+		GPU->UpdateTexture(pTexInfo);
 
-// Sets texture filtering mode.
-void GLTexture_SetFilterMode(INT32 mode)
-{
-	switch (mode)
-	{
-		case GPU_TEXFILTER_TRILINEAR:
-			MipmapMinFilter = GL_LINEAR_MIPMAP_LINEAR;
-			MipmapMagFilter = GL_LINEAR;
-			MipmappingEnabled = GL_TRUE;
-			break;
-		case GPU_TEXFILTER_BILINEAR:
-			MipmapMinFilter = MipmapMagFilter = GL_LINEAR;
-			MipmappingEnabled = GL_FALSE;
-			break;
-		case GPU_TEXFILTER_POINTSAMPLED:
-			MipmapMinFilter = MipmapMagFilter = GL_NEAREST;
-			MipmappingEnabled = GL_FALSE;
-			break;
-		case GPU_TEXFILTER_MIXED1:
-			MipmapMinFilter = GL_NEAREST;
-			MipmapMagFilter = GL_LINEAR;
-			MipmappingEnabled = GL_FALSE;
-			break;
-		case GPU_TEXFILTER_MIXED2:
-			MipmapMinFilter = GL_LINEAR;
-			MipmapMagFilter = GL_NEAREST;
-			MipmappingEnabled = GL_FALSE;
-			break;
-		case GPU_TEXFILTER_MIXED3:
-			MipmapMinFilter = GL_LINEAR_MIPMAP_LINEAR;
-			MipmapMagFilter = GL_NEAREST;
-			MipmappingEnabled = GL_TRUE;
-			break;
-		default:
-			MipmapMagFilter = GL_LINEAR;
-			MipmapMinFilter = GL_NEAREST;
+		newTex->texture = pTexInfo;
+		newTex->name = (UINT32)pTexInfo->downloaded;
+		newTex->width = (UINT32)pTexInfo->width;
+		newTex->height = (UINT32)pTexInfo->height;
+		newTex->format = (UINT32)pTexInfo->format;
+
+		// insertion at the tail
+		if (TexCacheTail)
+		{
+			newTex->prev = TexCacheTail;
+			TexCacheTail->next = newTex;
+			TexCacheTail = newTex;
+		}
+		else // initialization of the linked list
+			TexCacheTail = TexCacheHead = newTex;
 	}
 }
 
@@ -906,28 +1030,249 @@ void GLTexture_Delete(HWRTexture_t *pTexInfo)
 	}
 }
 
-// Calculates total memory usage by textures, excluding mipmaps.
-INT32 GLTexture_GetMemoryUsage(FTextureInfo *head)
+// Updates a texture.
+void GLTexture_Update(HWRTexture_t *pTexInfo)
 {
-	INT32 res = 0;
+	boolean update = true;
+	static RGBA_t textureBuffer[2048 * 2048];
+	const GLvoid *pTextureBuffer = textureBuffer;
+	GLuint textureName = 0;
 
-	while (head)
+	if (!pTexInfo->downloaded)
 	{
-		// Figure out the correct bytes-per-pixel for this texture
-		// This follows format2bpp in hw_cache.c
-		INT32 bpp = 1;
-		UINT32 format = head->format;
-		if (format == GPU_TEXFMT_RGBA)
-			bpp = 4;
-		else if (format == GPU_TEXFMT_ALPHA_INTENSITY_88 || format == GPU_TEXFMT_AP_88)
-			bpp = 2;
+		pglGenTextures(1, &textureName);
+		pTexInfo->downloaded = textureName;
+		update = false;
+	}
+	else
+		textureName = pTexInfo->downloaded;
+
+	if (pTexInfo->format == GPU_TEXFMT_RGBA)
+		pTextureBuffer = pTexInfo->data;
+	else if ((pTexInfo->format == GPU_TEXFMT_P_8) || (pTexInfo->format == GPU_TEXFMT_AP_88))
+		GLTexture_WritePalette(pTexInfo, textureBuffer);
+	else if (pTexInfo->format == GPU_TEXFMT_ALPHA_INTENSITY_88)
+		GLTexture_WriteLuminanceAlpha(pTexInfo, textureBuffer);
+	else if (pTexInfo->format == GPU_TEXFMT_ALPHA_8) // Used for fade masks
+	{
+#ifdef HAVE_GLES2
+		GLTexture_WriteFadeMaskR(pTexInfo, textureBuffer);
+#else
+		GLTexture_WriteFadeMaskA(pTexInfo, textureBuffer);
+#endif
+	}
+	else
+		GL_MSG_Warning("GLTexture_Update: bad format %d\n", pTexInfo->format);
 
-		// Add it up!
-		res += head->height*head->width*bpp;
-		head = head->next;
+	// the texture number was already generated by pglGenTextures
+	pglBindTexture(GL_TEXTURE_2D, textureName);
+	CurrentTexture = textureName;
+
+	// disable texture filtering on any texture that has holes so there's no dumb borders or blending issues
+	if (pTexInfo->flags & TF_TRANSPARENT)
+	{
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+	}
+	else
+	{
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, MipmapMagFilter);
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, MipmapMinFilter);
 	}
 
-	return res;
+#ifdef HAVE_GLES
+	GLTexture_UploadData(pTexInfo, pTextureBuffer, GPUTextureFormat, update);
+#else
+	if (pTexInfo->format == GPU_TEXFMT_ALPHA_INTENSITY_88)
+		GLTexture_UploadData(pTexInfo, pTextureBuffer, GL_LUMINANCE_ALPHA, update);
+	else if (pTexInfo->format == GPU_TEXFMT_ALPHA_8)
+		GLTexture_UploadData(pTexInfo, pTextureBuffer, GL_ALPHA, update);
+	else
+		GLTexture_UploadData(pTexInfo, pTextureBuffer, GPUTextureFormat, update);
+#endif
+
+	if (pTexInfo->flags & TF_WRAPX)
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+	else
+		GLState_SetClamp(GL_TEXTURE_WRAP_S);
+
+	if (pTexInfo->flags & TF_WRAPY)
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+	else
+		GLState_SetClamp(GL_TEXTURE_WRAP_T);
+
+	if (GLExtension_texture_filter_anisotropic && GPUMaximumAnisotropy)
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, AnisotropicFilter);
+
+	if (GLTexture_CanGenerateMipmaps(pTexInfo) && pglGenerateMipmap)
+		pglGenerateMipmap(GL_TEXTURE_2D);
+}
+
+// Uploads texture data.
+void GLTexture_UploadData(HWRTexture_t *pTexInfo, const GLvoid *pTextureBuffer, GLenum format, boolean update)
+{
+	INT32 w = pTexInfo->width;
+	INT32 h = pTexInfo->height;
+
+#ifndef HAVE_GLES
+	if (GLTexture_CanGenerateMipmaps(pTexInfo) && !pglGenerateMipmap)
+		pglTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
+#endif
+
+	if (update)
+		pglTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pTextureBuffer);
+	else
+		pglTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pTextureBuffer);
+}
+
+// Writes a palettized texture.
+void GLTexture_WritePalette(HWRTexture_t *pTexInfo, RGBA_t *textureBuffer)
+{
+	const GLubyte *pImgData = (const GLubyte *)pTexInfo->data;
+	INT32 w = pTexInfo->width;
+	INT32 h = pTexInfo->height;
+	INT32 i, j;
+
+	for (j = 0; j < h; j++)
+	{
+		for (i = 0; i < w; i++)
+		{
+			if ((*pImgData == GPU_PATCHES_CHROMAKEY_COLORINDEX) && (pTexInfo->flags & TF_CHROMAKEYED))
+			{
+				textureBuffer[w*j+i].s.red   = 0;
+				textureBuffer[w*j+i].s.green = 0;
+				textureBuffer[w*j+i].s.blue  = 0;
+				textureBuffer[w*j+i].s.alpha = 0;
+				pTexInfo->flags |= TF_TRANSPARENT; // there is a hole in it
+			}
+			else
+			{
+				textureBuffer[w*j+i].s.red   = GPUTexturePalette[*pImgData].s.red;
+				textureBuffer[w*j+i].s.green = GPUTexturePalette[*pImgData].s.green;
+				textureBuffer[w*j+i].s.blue  = GPUTexturePalette[*pImgData].s.blue;
+				textureBuffer[w*j+i].s.alpha = GPUTexturePalette[*pImgData].s.alpha;
+			}
+
+			pImgData++;
+
+			if (pTexInfo->format == GPU_TEXFMT_AP_88)
+			{
+				if (!(pTexInfo->flags & TF_CHROMAKEYED))
+					textureBuffer[w*j+i].s.alpha = *pImgData;
+				pImgData++;
+			}
+		}
+	}
+}
+
+// Writes a texture where every pixel is a luminance/alpha pair.
+void GLTexture_WriteLuminanceAlpha(HWRTexture_t *pTexInfo, RGBA_t *textureBuffer)
+{
+	const GLubyte *pImgData = (const GLubyte *)pTexInfo->data;
+	INT32 w = pTexInfo->width;
+	INT32 h = pTexInfo->height;
+	INT32 i, j;
+
+	for (j = 0; j < h; j++)
+	{
+		for (i = 0; i < w; i++)
+		{
+			textureBuffer[w*j+i].s.red   = *pImgData;
+			textureBuffer[w*j+i].s.green = *pImgData;
+			textureBuffer[w*j+i].s.blue  = *pImgData;
+			pImgData++;
+			textureBuffer[w*j+i].s.alpha = *pImgData;
+			pImgData++;
+		}
+	}
+}
+
+// Writes a fade mask texture where every pixel is stored in the alpha channel.
+void GLTexture_WriteFadeMaskA(HWRTexture_t *pTexInfo, RGBA_t *textureBuffer)
+{
+	const GLubyte *pImgData = (const GLubyte *)pTexInfo->data;
+	INT32 w = pTexInfo->width;
+	INT32 h = pTexInfo->height;
+	INT32 i, j;
+
+	for (j = 0; j < h; j++)
+	{
+		for (i = 0; i < w; i++)
+		{
+			textureBuffer[w*j+i].s.red   = 255; // 255 because the fade mask is modulated with the screen texture, so alpha affects it while the colours don't
+			textureBuffer[w*j+i].s.green = 255;
+			textureBuffer[w*j+i].s.blue  = 255;
+			textureBuffer[w*j+i].s.alpha = *pImgData;
+			pImgData++;
+		}
+	}
+}
+
+// Writes a fade mask texture where every pixel is stored in the red channel.
+void GLTexture_WriteFadeMaskR(HWRTexture_t *pTexInfo, RGBA_t *textureBuffer)
+{
+	const GLubyte *pImgData = (const GLubyte *)pTexInfo->data;
+	INT32 w = pTexInfo->width;
+	INT32 h = pTexInfo->height;
+	INT32 i, j;
+
+	for (j = 0; j < h; j++)
+	{
+		for (i = 0; i < w; i++)
+		{
+			textureBuffer[w*j+i].s.red   = *pImgData;
+			textureBuffer[w*j+i].s.green = 255;
+			textureBuffer[w*j+i].s.blue  = 255;
+			textureBuffer[w*j+i].s.alpha = 255;
+			pImgData++;
+		}
+	}
+}
+
+// Deletes all textures.
+void GLTexture_Flush(void)
+{
+	while (TexCacheHead)
+	{
+		FTextureInfo *pTexInfo = TexCacheHead;
+		HWRTexture_t *texture = pTexInfo->texture;
+
+		if (pTexInfo->name)
+		{
+			pglDeleteTextures(1, (GLuint *)&pTexInfo->name);
+			pTexInfo->name = 0;
+		}
+
+		if (texture)
+			texture->downloaded = 0;
+
+		TexCacheHead = pTexInfo->next;
+		free(pTexInfo);
+	}
+
+	TexCacheTail = TexCacheHead = NULL; // Hurdler: well, TexCacheHead is already NULL
+	CurrentTexture = 0;
+}
+
+// Binds the current texture to a 1x1 white pixel.
+void GLTexture_SetNoTexture(void)
+{
+	// Disable texture.
+	if (CurrentTexture != BlankTexture)
+	{
+		if (BlankTexture == 0)
+		{
+			// Generate a 1x1 white pixel for the blank texture
+			UINT8 white[4] = {255, 255, 255, 255};
+			pglGenTextures(1, &BlankTexture);
+			pglBindTexture(GL_TEXTURE_2D, BlankTexture);
+			pglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, white);
+		}
+		else
+			pglBindTexture(GL_TEXTURE_2D, BlankTexture);
+
+		CurrentTexture = BlankTexture;
+	}
 }
 
 // Generates a screen texture.
@@ -951,8 +1296,8 @@ void GLTexture_GenerateScreenTexture(GLuint *name)
 	{
 		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-		SetClamp(GL_TEXTURE_WRAP_S);
-		SetClamp(GL_TEXTURE_WRAP_T);
+		GLState_SetClamp(GL_TEXTURE_WRAP_S);
+		GLState_SetClamp(GL_TEXTURE_WRAP_T);
 		pglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, texsize, texsize, 0);
 	}
 	else
@@ -961,6 +1306,58 @@ void GLTexture_GenerateScreenTexture(GLuint *name)
 	CurrentTexture = *name;
 }
 
+// Sryder:	This needs to be called whenever the screen changes resolution in order to reset the screen textures to use
+//			a new size
+void GLTexture_FlushScreenTextures(void)
+{
+	pglDeleteTextures(1, &ScreenTexture);
+	pglDeleteTextures(1, &FinalScreenTexture);
+	pglDeleteTextures(1, &WipeStartTexture);
+	pglDeleteTextures(1, &WipeEndTexture);
+
+	ScreenTexture = FinalScreenTexture = 0;
+	WipeStartTexture = WipeEndTexture = 0;
+}
+
+// Initializes mipmapping.
+boolean GLTexture_InitMipmapping(void)
+{
+	if (GLExtension_framebuffer_object)
+		pglGenerateMipmap = GLBackend_GetFunction("glGenerateMipmap");
+
+	return (pglGenerateMipmap != NULL);
+}
+
+// Returns true if the texture can have mipmaps generated for it.
+boolean GLTexture_CanGenerateMipmaps(HWRTexture_t *pTexInfo)
+{
+	return (MipmappingEnabled && !(pTexInfo->flags & TF_TRANSPARENT));
+}
+
+// Calculates total memory usage by textures, excluding mipmaps.
+int GLTexture_GetMemoryUsage(FTextureInfo *head)
+{
+	int res = 0;
+
+	while (head)
+	{
+		// Figure out the correct bytes-per-pixel for this texture
+		// This follows format2bpp in hw_cache.c
+		INT32 bpp = 1;
+		UINT32 format = head->format;
+		if (format == GPU_TEXFMT_RGBA)
+			bpp = 4;
+		else if (format == GPU_TEXFMT_ALPHA_INTENSITY_88 || format == GPU_TEXFMT_AP_88)
+			bpp = 2;
+
+		// Add it up!
+		res += head->height*head->width*bpp;
+		head = head->next;
+	}
+
+	return res;
+}
+
 // -----------------+
 // GL_DBG_Printf    : Output debug messages to debug log if DEBUG_TO_FILE is defined,
 //                  : else do nothing
diff --git a/src/hardware/r_glcommon/r_glcommon.h b/src/hardware/r_glcommon/r_glcommon.h
index 3900b79055ef188ecaa4ad58546a9aa35108371a..96856b96b5282c8df2b5b3d107858c4955c93cfa 100644
--- a/src/hardware/r_glcommon/r_glcommon.h
+++ b/src/hardware/r_glcommon/r_glcommon.h
@@ -1,60 +1,53 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 1998-2020 by Sonic Team Junior.
+// Copyright (C) 2020 by Jaime "Lactozilla" Passos.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
 // See the 'LICENSE' file for more details.
 //-----------------------------------------------------------------------------
 /// \file r_glcommon.h
-/// \brief Common OpenGL functions and structs shared by OpenGL backends
+/// \brief Common OpenGL functions shared by all of the OpenGL backends
 
 #ifndef _R_GLCOMMON_H_
 #define _R_GLCOMMON_H_
 
 #define GL_GLEXT_PROTOTYPES
 
-#if defined(HAVE_GLES2)
-	#include <GLES2/gl2.h>
-	#include <GLES2/gl2ext.h>
-#elif defined(HAVE_GLES)
-	#include <GLES/gl.h>
-	#include <GLES/glext.h>
-#else
-	#ifdef HAVE_SDL
-		#define _MATH_DEFINES_DEFINED
+#ifdef HAVE_SDL
+	#define _MATH_DEFINES_DEFINED
 
-		#ifdef _MSC_VER
-		#pragma warning(disable : 4214 4244)
-		#endif
+	#ifdef _MSC_VER
+	#pragma warning(disable : 4214 4244)
+	#endif
 
-		#include "SDL_opengl.h" //Alam_GBC: Simple, yes?
+	#include "SDL.h" // For GLExtension_Available
+	#include "SDL_opengl.h" // Alam_GBC: Simple, yes?
 
-		#ifdef _MSC_VER
-		#pragma warning(default : 4214 4244)
-		#endif
-	#else
-		#include <GL/gl.h>
-		#include <GL/glu.h>
-		#if defined(STATIC_OPENGL)
-			#include <GL/glext.h>
-		#endif
+	#ifdef _MSC_VER
+	#pragma warning(default : 4214 4244)
+	#endif
+#else
+	#include <GL/gl.h>
+	#include <GL/glu.h>
+	#if defined(STATIC_OPENGL)
+		#include <GL/glext.h>
 	#endif
 #endif
 
-// For GLExtension_Available
-#ifdef HAVE_SDL
-#define _MATH_DEFINES_DEFINED
-#include "SDL.h"
+#ifdef HAVE_GLES
+#include "../r_gles/r_gleslib.h"
 #endif
 
 #include "../../doomdata.h"
 #include "../../doomtype.h"
 #include "../../doomdef.h"
 
-#include "../../hardware/hw_data.h"  // HWRTexture_s
+#include "../../hardware/hw_gpu.h"   // GPURenderingAPI
+#include "../../hardware/hw_data.h"  // HWRTexture_t
 #include "../../hardware/hw_defs.h"  // FTextureInfo
 #include "../../hardware/hw_model.h" // model_t / mesh_t / mdlframe_t
+
 #include "../r_opengl/r_vbo.h"
 
 void GL_DBG_Printf(const char *format, ...);
@@ -216,37 +209,27 @@ extern PFNglActiveTexture pglActiveTexture;
 // Mipmapping
 //
 
-#ifdef HAVE_GLES
-/* Texture mapping */
 typedef void (R_GL_APIENTRY * PFNglGenerateMipmap) (GLenum target);
 extern PFNglGenerateMipmap pglGenerateMipmap;
-#endif
 
 //
 // Depth functions
 //
-#ifndef HAVE_GLES
-	#ifdef STATIC_OPENGL
-		#define pglClearDepth glClearDepth
-		#define pglDepthFunc glDepthFunc
-	#else
-		typedef void (R_GL_APIENTRY * PFNglClearDepth) (GLclampd depth);
-		extern PFNglClearDepth pglClearDepth;
-		typedef void (R_GL_APIENTRY * PFNglDepthRange) (GLclampd near_val, GLclampd far_val);
-		extern PFNglDepthRange pglDepthRange;
-	#endif
+#ifdef STATIC_OPENGL
+	#define pglClearDepth glClearDepth
+	#define pglDepthFunc glDepthFunc
 #else
-	#ifdef STATIC_OPENGL
-		#define pglClearDepthf glClearDepthf
-		#define pglDepthFuncf glDepthFuncf
-	#else
-		typedef void (R_GL_APIENTRY * PFNglClearDepthf) (GLclampf depth);
-		extern PFNglClearDepthf pglClearDepthf;
-		typedef void (R_GL_APIENTRY * PFNglDepthRangef) (GLclampf near_val, GLclampf far_val);
-		extern PFNglDepthRangef pglDepthRangef;
-	#endif
+	typedef void (R_GL_APIENTRY * PFNglClearDepth) (GLclampd depth);
+	extern PFNglClearDepth pglClearDepth;
+	typedef void (R_GL_APIENTRY * PFNglDepthRange) (GLclampd near_val, GLclampd far_val);
+	extern PFNglDepthRange pglDepthRange;
 #endif
 
+typedef void (R_GL_APIENTRY * PFNglClearDepthf) (GLclampf depth);
+extern PFNglClearDepthf pglClearDepthf;
+typedef void (R_GL_APIENTRY * PFNglDepthRangef) (GLclampf near_val, GLclampf far_val);
+extern PFNglDepthRangef pglDepthRangef;
+
 //
 // Legacy functions
 //
@@ -331,20 +314,18 @@ extern PFNglTexEnvi pglTexEnvi;
 #endif // HAVE_GLES2
 
 // Color
-#ifdef HAVE_GLES
-	#ifdef STATIC_OPENGL
-		#define pglColor4f glColor4f
-	#else
-		typedef void (*PFNglColor4f) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
-		extern PFNglColor4f pglColor4f;
-	#endif
+#ifdef STATIC_OPENGL
+	#define pglColor4f glColor4f
 #else
-	#ifdef STATIC_OPENGL
-		#define pglColor4ubv glColor4ubv
-	#else
-		typedef void (R_GL_APIENTRY * PFNglColor4ubv) (const GLubyte *v);
-		extern PFNglColor4ubv pglColor4ubv;
-	#endif
+	typedef void (*PFNglColor4f) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
+	extern PFNglColor4f pglColor4f;
+#endif
+
+#ifdef STATIC_OPENGL
+	#define pglColor4ubv glColor4ubv
+#else
+	typedef void (R_GL_APIENTRY * PFNglColor4ubv) (const GLubyte *v);
+	extern PFNglColor4ubv pglColor4ubv;
 #endif
 
 /* 1.5 functions for buffers */
@@ -365,36 +346,77 @@ extern PFNglBlendEquation pglBlendEquation;
 //                                                                  FUNCTIONS
 // ==========================================================================
 
-void GLModel_GenerateVBOs(model_t *model);
-void GLModel_AllocLerpBuffer(size_t size);
-void GLModel_AllocLerpTinyBuffer(size_t size);
-
-void  GLTexture_Flush(void);
-void  GLTexture_SetFilterMode(INT32 mode);
-void  GLTexture_Delete(HWRTexture_t *pTexInfo);
-INT32 GLTexture_GetMemoryUsage(FTextureInfo *head);
-void  GLTexture_GenerateScreenTexture(GLuint *name);
-
 boolean GLBackend_Init(void);
 boolean GLBackend_InitContext(void);
+boolean GLBackend_InitShaders(void);
 boolean GLBackend_LoadLibrary(void);
 boolean GLBackend_LoadFunctions(void);
 boolean GLBackend_LoadContextFunctions(void);
 boolean GLBackend_LoadCommonFunctions(void);
 boolean GLBackend_LoadLegacyFunctions(void);
 void   *GLBackend_GetFunction(const char *proc);
-
-INT32 GLBackend_GetShaderType(INT32 type);
+INT32   GLBackend_GetShaderType(INT32 type);
+
+#define GLBackend_GetFunctionPointer(func) \
+	p ## gl ## func = GLBackend_GetFunction("gl" #func); \
+	if (!(p ## gl ## func)) \
+	{ \
+		GL_MSG_Error("Failed to get OpenGL function %s", #func); \
+		return false; \
+	}
+
+#define GLBackend_GetFunctionPointerOrFallback(func1, func2) \
+	p ## gl ## func1 = GLBackend_GetFunction("gl" #func1); \
+	if (!(p ## gl ## func1)) \
+	{ \
+		GL_DBG_Printf("Failed to get OpenGL function %s, trying %s instead\n", #func1, #func2); \
+		p ## gl ## func2 = GLBackend_GetFunction("gl" #func2); \
+		if (!(p ## gl ## func2)) \
+		{ \
+			GL_MSG_Error("Failed to get OpenGL function %s\n", #func2); \
+			return false; \
+		} \
+	}
+
+#define GETOPENGLFUNC    GLBackend_GetFunctionPointer
+#define GETOPENGLFUNCALT GLBackend_GetFunctionPointerOrFallback
+
+void GLState_SetSurface(INT32 w, INT32 h);
+void GLState_SetPalette(RGBA_t *palette);
+void GLState_SetFilterMode(INT32 mode);
+void GLState_SetClamp(GLenum pname);
+void GLState_SetDepthBuffer(void);
+void GLState_SetBlend(UINT32 PolyFlags);
+void GLState_SetColor(const GLfloat red, const GLfloat green, const GLfloat blue, const GLfloat alpha);
+void GLState_SetColorUBV(const GLubyte *v);
 
 void    GLExtension_Init(void);
 boolean GLExtension_Available(const char *extension);
 
-void SetSurface(INT32 w, INT32 h);
-void SetModelView(GLint w, GLint h);
-void SetStates(void);
-void SetBlendingStates(UINT32 PolyFlags);
-void SetNoTexture(void);
-void SetClamp(GLenum pname);
+void GLTexture_Set(HWRTexture_t *pTexInfo);
+void GLTexture_Delete(HWRTexture_t *pTexInfo);
+
+void GLTexture_Update(HWRTexture_t *pTexInfo);
+void GLTexture_UploadData(HWRTexture_t *pTexInfo, const GLvoid *pTextureBuffer, GLenum format, boolean update);
+
+void GLTexture_WritePalette(HWRTexture_t *pTexInfo, RGBA_t *textureBuffer);
+void GLTexture_WriteLuminanceAlpha(HWRTexture_t *pTexInfo, RGBA_t *textureBuffer);
+void GLTexture_WriteFadeMaskA(HWRTexture_t *pTexInfo, RGBA_t *textureBuffer);
+void GLTexture_WriteFadeMaskR(HWRTexture_t *pTexInfo, RGBA_t *textureBuffer);
+
+void GLTexture_Flush(void);
+void GLTexture_FlushScreenTextures(void);
+void GLTexture_SetNoTexture(void);
+
+boolean GLTexture_InitMipmapping(void);
+boolean GLTexture_CanGenerateMipmaps(HWRTexture_t *pTexInfo);
+
+void GLTexture_GenerateScreenTexture(GLuint *name);
+int  GLTexture_GetMemoryUsage(FTextureInfo *head);
+
+void GLModel_GenerateVBOs(model_t *model);
+void GLModel_AllocLerpBuffer(size_t size);
+void GLModel_AllocLerpTinyBuffer(size_t size);
 
 // ==========================================================================
 //                                                                  CONSTANTS
@@ -524,6 +546,7 @@ extern GLuint WipeEndTexture;
 extern UINT32 CurrentPolyFlags;
 
 extern GLboolean MipmappingEnabled;
+extern GLboolean MipmappingSupported;
 extern GLboolean ModelLightingEnabled;
 
 extern GLint MipmapMinFilter;
diff --git a/src/hardware/r_gles/lzml.h b/src/hardware/r_gles/lzml.h
new file mode 100644
index 0000000000000000000000000000000000000000..ab2f11fa6e41e8ed9569eca4a49456018c35566a
--- /dev/null
+++ b/src/hardware/r_gles/lzml.h
@@ -0,0 +1,885 @@
+/**
+	MIT License
+
+	Copyright (c) 2020 Jaime Passos
+
+	Permission is hereby granted, free of charge, to any person obtaining a copy
+	of this software and associated documentation files (the "Software"), to deal
+	in the Software without restriction, including without limitation the rights
+	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+	copies of the Software, and to permit persons to whom the Software is
+	furnished to do so, subject to the following conditions:
+
+	The above copyright notice and this permission notice shall be included in all
+	copies or substantial portions of the Software.
+
+	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+	SOFTWARE.
+**/
+
+#ifndef _LZML_H_
+#define _LZML_H_
+
+#include <stdint.h>
+#include <stddef.h>
+#include <math.h>
+#include <float.h>
+
+void lzml_vector3_add(float a[3], float b[3]);
+void lzml_vector3_add_by_scalar(float vec[3], float scalar);
+void lzml_vector3_subtract(float a[3], float b[3]);
+void lzml_vector3_subtract_by_scalar(float vec[3], float scalar);
+void lzml_vector3_multiply(float a[3], float b[3]);
+void lzml_vector3_multiply_by_scalar(float vec[3], float scalar);
+#define lzml_vector3_scale lzml_vector3_multiply_by_scalar
+void lzml_vector3_divide(float a[3], float b[3]);
+void lzml_vector3_divide_by_scalar(float vec[3], float scalar);
+void lzml_vector3_add_into(float dest[3], float a[3], float b[3]);
+void lzml_vector3_add_by_scalar_into(float dest[3], float vec[3], float scalar);
+void lzml_vector3_subtract_into(float dest[3], float a[3], float b[3]);
+void lzml_vector3_subtract_by_scalar_into(float dest[3], float vec[3], float scalar);
+void lzml_vector3_multiply_into(float dest[3], float a[3], float b[3]);
+void lzml_vector3_multiply_by_scalar_into(float dest[3], float vec[3], float scalar);
+#define lzml_vector3_scale_into lzml_vector3_multiply_by_scalar_into
+void lzml_vector3_divide_into(float dest[3], float a[3], float b[3]);
+void lzml_vector3_divide_by_scalar_into(float dest[3], float vec[3], float scalar);
+float lzml_vector3_dot_product(float a[3], float b[3]);
+float lzml_vector3_magnitude(float vec[3]);
+void lzml_vector3_normalize(float vec[3]);
+void lzml_vector3_normalize_into(float dest[3], float vec[3]);
+void lzml_vector3_copy(float dest[3], float src[3]);
+
+void lzml_vector4_add(float a[4], float b[4]);
+void lzml_vector4_add_by_scalar(float vec[4], float scalar);
+void lzml_vector4_subtract(float a[4], float b[4]);
+void lzml_vector4_subtract_by_scalar(float vec[4], float scalar);
+void lzml_vector4_multiply(float a[4], float b[4]);
+void lzml_vector4_multiply_by_scalar(float vec[4], float scalar);
+#define lzml_vector4_scale lzml_vector4_multiply_by_scalar
+void lzml_vector4_divide(float a[4], float b[4]);
+void lzml_vector4_divide_by_scalar(float vec[4], float scalar);
+void lzml_vector4_add_into(float dest[4], float a[4], float b[4]);
+void lzml_vector4_add_by_scalar_into(float dest[4], float vec[4], float scalar);
+void lzml_vector4_subtract_into(float dest[4], float a[4], float b[4]);
+void lzml_vector4_subtract_by_scalar_into(float dest[4], float vec[4], float scalar);
+void lzml_vector4_multiply_into(float dest[4], float a[4], float b[4]);
+void lzml_vector4_multiply_by_scalar_into(float dest[4], float vec[4], float scalar);
+#define lzml_vector4_scale_into lzml_vector4_multiply_by_scalar_into
+void lzml_vector4_divide_into(float dest[4], float a[4], float b[4]);
+void lzml_vector4_divide_by_scalar_into(float dest[4], float vec[4], float scalar);
+float lzml_vector4_dot_product(float a[4], float b[4]);
+float lzml_vector4_magnitude(float vec[4]);
+void lzml_vector4_normalize(float vec[4]);
+void lzml_vector4_normalize_into(float dest[4], float vec[4]);
+void lzml_vector4_copy(float dest[4], float src[4]);
+
+static float lzml_zeromatrix[4][4] = {
+	{0.0f, 0.0f, 0.0f, 0.0f},
+	{0.0f, 0.0f, 0.0f, 0.0f},
+	{0.0f, 0.0f, 0.0f, 0.0f},
+	{0.0f, 0.0f, 0.0f, 0.0f}};
+
+static float lzml_identitymatrix[4][4] = {
+	{1.0f, 0.0f, 0.0f, 0.0f},
+	{0.0f, 1.0f, 0.0f, 0.0f},
+	{0.0f, 0.0f, 1.0f, 0.0f},
+	{0.0f, 0.0f, 0.0f, 1.0f}};
+
+void lzml_matrix4_copy(float dest[4][4], float src[4][4]);
+void lzml_matrix4_clear(float mat[4][4]);
+void lzml_matrix4_identity(float mat[4][4]);
+void lzml_matrix4_multiply(float a[4][4], float b[4][4]);
+void lzml_matrix4_multiply_into(float dest[4][4], float a[4][4], float b[4][4]);
+void lzml_matrix4_translate(float mat[4][4], float vec[3]);
+void lzml_matrix4_translate_x(float mat[4][4], float x);
+void lzml_matrix4_translate_y(float mat[4][4], float y);
+void lzml_matrix4_translate_z(float mat[4][4], float z);
+void lzml_matrix4_rotate_x(float mat[4][4], float angle);
+void lzml_matrix4_rotate_y(float mat[4][4], float angle);
+void lzml_matrix4_rotate_z(float mat[4][4], float angle);
+void lzml_matrix4_rotate_by_vector(float mat[4][4], float vec[3], float angle);
+void lzml_matrix4_scale(float mat[4][4], float vec[3]);
+void lzml_matrix4_rotation(float rot[4][4], float vec[3], float angle);
+void lzml_matrix4_perspective(float mat[4][4], float fovy, float aspect_ratio, float near_clip, float far_clip);
+
+/**
+	== VECTOR3 OPERATIONS ==
+**/
+
+/** Adds a vector by another.
+  *
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+void lzml_vector3_add(float a[3], float b[3])
+{
+	lzml_vector3_add_into(a, a, b);
+}
+
+/** Adds a vector by a scalar.
+  *
+  * \param vec The destination vector.
+  * \param scalar The scalar.
+  */
+void lzml_vector3_add_by_scalar(float vec[3], float scalar)
+{
+	lzml_vector3_add_by_scalar_into(vec, vec, scalar);
+}
+
+/** Subtracts a vector by another.
+  *
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+void lzml_vector3_subtract(float a[3], float b[3])
+{
+	lzml_vector3_subtract_into(a, a, b);
+}
+
+/** Subtracts a vector by a scalar.
+  *
+  * \param vec The destination vector.
+  * \param scalar The scalar.
+  */
+void lzml_vector3_subtract_by_scalar(float vec[3], float scalar)
+{
+	lzml_vector3_subtract_by_scalar_into(vec, vec, scalar);
+}
+
+/** Multiplies a vector by another.
+  *
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+void lzml_vector3_multiply(float a[3], float b[3])
+{
+	lzml_vector3_multiply_into(a, a, b);
+}
+
+/** Multiplies a vector by a scalar.
+  *
+  * \param vec The destination vector.
+  * \param scalar The scalar.
+  */
+void lzml_vector3_multiply_by_scalar(float vec[3], float scalar)
+{
+	lzml_vector3_multiply_by_scalar_into(vec, vec, scalar);
+}
+
+/** Divides a vector by another.
+  *
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+void lzml_vector3_divide(float a[3], float b[3])
+{
+	lzml_vector3_divide_into(a, a, b);
+}
+
+/** Divides a vector by a scalar.
+  *
+  * \param vec The destination vector.
+  * \param scalar The scalar.
+  */
+void lzml_vector3_divide_by_scalar(float vec[3], float scalar)
+{
+	lzml_vector3_divide_by_scalar_into(vec, vec, scalar);
+}
+
+/** Adds a vector by another, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+void lzml_vector3_add_into(float dest[3], float a[3], float b[3])
+{
+	dest[0] = a[0] + b[0];
+	dest[1] = a[1] + b[1];
+	dest[2] = a[2] + b[2];
+}
+
+/** Adds a vector by a scalar, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param vec The vector.
+  * \param scalar The scalar.
+  */
+void lzml_vector3_add_by_scalar_into(float dest[3], float vec[3], float scalar)
+{
+	dest[0] = vec[0] + scalar;
+	dest[1] = vec[1] + scalar;
+	dest[2] = vec[2] + scalar;
+}
+
+/** Subtracts a vector by another, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+void lzml_vector3_subtract_into(float dest[3], float a[3], float b[3])
+{
+	dest[0] = a[0] - b[0];
+	dest[1] = a[1] - b[1];
+	dest[2] = a[2] - b[2];
+}
+
+/** Subtracts a vector by a scalar, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param vec The vector.
+  * \param scalar The scalar.
+  */
+void lzml_vector3_subtract_by_scalar_into(float dest[3], float vec[3], float scalar)
+{
+	dest[0] = vec[0] - scalar;
+	dest[1] = vec[1] - scalar;
+	dest[2] = vec[2] - scalar;
+}
+
+/** Multiplies a vector by another, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+void lzml_vector3_multiply_into(float dest[3], float a[3], float b[3])
+{
+	dest[0] = a[0] * b[0];
+	dest[1] = a[1] * b[1];
+	dest[2] = a[2] * b[2];
+}
+
+/** Multiplies a vector by a scalar, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param vec The vector.
+  * \param scalar The scalar.
+  */
+void lzml_vector3_multiply_by_scalar_into(float dest[3], float vec[3], float scalar)
+{
+	dest[0] = vec[0] * scalar;
+	dest[1] = vec[1] * scalar;
+	dest[2] = vec[2] * scalar;
+}
+
+/** Divides a vector by another, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+void lzml_vector3_divide_into(float dest[3], float a[3], float b[3])
+{
+	dest[0] = a[0] / b[0];
+	dest[1] = a[1] / b[1];
+	dest[2] = a[2] / b[2];
+}
+
+/** Divides a vector by a scalar, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param vec The vector.
+  * \param scalar The scalar.
+  */
+void lzml_vector3_divide_by_scalar_into(float dest[3], float vec[3], float scalar)
+{
+	lzml_vector3_multiply_by_scalar_into(dest, vec, (1.0f / scalar));
+}
+
+/** Calculates a dot product.
+  *
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+float lzml_vector3_dot_product(float a[3], float b[3])
+{
+	return (a[0] * b[0]) + (a[1] * b[1]) + (a[2] * b[2]);
+}
+
+/** Calculates the magnitude of a vector.
+  *
+  * \param vec The vector.
+  */
+float lzml_vector3_magnitude(float vec[3])
+{
+	return sqrtf(lzml_vector3_dot_product(vec, vec));
+}
+
+/** Normalizes a vector.
+  *
+  * \param vec The vector to be normalized.
+  */
+void lzml_vector3_normalize(float vec[3])
+{
+	lzml_vector3_normalize_into(vec, vec);
+}
+
+/** Normalizes a vector, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param vec The vector to be normalized.
+  */
+void lzml_vector3_normalize_into(float dest[3], float vec[3])
+{
+	float normal = lzml_vector3_magnitude(vec);
+	if (fpclassify(normal) == FP_ZERO)
+		dest[0] = dest[1] = dest[2] = 0.0f;
+	else
+		lzml_vector3_multiply_by_scalar_into(dest, vec, (1.0f / normal));
+}
+
+/** Copies a vector into another.
+  *
+  * \param dest The destination vector.
+  * \param src The source vector.
+  */
+void lzml_vector3_copy(float dest[3], float src[3])
+{
+	dest[0] = src[0];
+	dest[1] = src[1];
+	dest[2] = src[2];
+}
+
+/**
+	== VECTOR4 OPERATIONS ==
+**/
+
+/** Adds a vector by another.
+  *
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+void lzml_vector4_add(float a[4], float b[4])
+{
+	lzml_vector4_add_into(a, a, b);
+}
+
+/** Adds a vector by a scalar.
+  *
+  * \param vec The destination vector.
+  * \param scalar The scalar.
+  */
+void lzml_vector4_add_by_scalar(float vec[4], float scalar)
+{
+	lzml_vector4_add_by_scalar_into(vec, vec, scalar);
+}
+
+/** Subtracts a vector by another.
+  *
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+void lzml_vector4_subtract(float a[4], float b[4])
+{
+	lzml_vector4_subtract_into(a, a, b);
+}
+
+/** Subtracts a vector by a scalar.
+  *
+  * \param vec The destination vector.
+  * \param scalar The scalar.
+  */
+void lzml_vector4_subtract_by_scalar(float vec[4], float scalar)
+{
+	lzml_vector4_subtract_by_scalar_into(vec, vec, scalar);
+}
+
+/** Multiplies a vector by another.
+  *
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+void lzml_vector4_multiply(float a[4], float b[4])
+{
+	lzml_vector4_multiply_into(a, a, b);
+}
+
+/** Multiplies a vector by a scalar.
+  *
+  * \param vec The destination vector.
+  * \param scalar The scalar.
+  */
+void lzml_vector4_multiply_by_scalar(float vec[4], float scalar)
+{
+	lzml_vector4_multiply_by_scalar_into(vec, vec, scalar);
+}
+
+/** Divides a vector by another.
+  *
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+void lzml_vector4_divide(float a[4], float b[4])
+{
+	lzml_vector4_divide_into(a, a, b);
+}
+
+/** Divides a vector by a scalar.
+  *
+  * \param vec The destination vector.
+  * \param scalar The scalar.
+  */
+void lzml_vector4_divide_by_scalar(float vec[4], float scalar)
+{
+	lzml_vector4_multiply_by_scalar_into(vec, vec, scalar);
+}
+
+/** Adds a vector by another, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+void lzml_vector4_add_into(float dest[4], float a[4], float b[4])
+{
+	dest[0] = a[0] + b[0];
+	dest[1] = a[1] + b[1];
+	dest[2] = a[2] + b[2];
+	dest[3] = a[3] + b[3];
+}
+
+/** Adds a vector by a scalar, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param vec The vector.
+  * \param scalar The scalar.
+  */
+void lzml_vector4_add_by_scalar_into(float dest[4], float vec[4], float scalar)
+{
+	dest[0] = vec[0] + scalar;
+	dest[1] = vec[1] + scalar;
+	dest[2] = vec[2] + scalar;
+	dest[3] = vec[3] + scalar;
+}
+
+/** Subtracts a vector by another, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+void lzml_vector4_subtract_into(float dest[4], float a[4], float b[4])
+{
+	dest[0] = a[0] - b[0];
+	dest[1] = a[1] - b[1];
+	dest[2] = a[2] - b[2];
+	dest[3] = a[3] - b[3];
+}
+
+/** Subtracts a vector by a scalar, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param vec The vector.
+  * \param scalar The scalar.
+  */
+void lzml_vector4_subtract_by_scalar_into(float dest[4], float vec[4], float scalar)
+{
+	dest[0] = vec[0] - scalar;
+	dest[1] = vec[1] - scalar;
+	dest[2] = vec[2] - scalar;
+	dest[3] = vec[3] - scalar;
+}
+
+/** Multiplies a vector by another, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+void lzml_vector4_multiply_into(float dest[4], float a[4], float b[4])
+{
+	dest[0] = a[0] * b[0];
+	dest[1] = a[1] * b[1];
+	dest[2] = a[2] * b[2];
+	dest[3] = a[3] * b[3];
+}
+
+/** Multiplies a vector by a scalar, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param vec The vector.
+  * \param scalar The scalar.
+  */
+void lzml_vector4_multiply_by_scalar_into(float dest[4], float vec[4], float scalar)
+{
+	dest[0] = vec[0] * scalar;
+	dest[1] = vec[1] * scalar;
+	dest[2] = vec[2] * scalar;
+	dest[3] = vec[3] * scalar;
+}
+
+/** Divides a vector by another, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+void lzml_vector4_divide_into(float dest[4], float a[4], float b[4])
+{
+	dest[0] = a[0] / b[0];
+	dest[1] = a[1] / b[1];
+	dest[2] = a[2] / b[2];
+	dest[3] = a[3] / b[3];
+}
+
+/** Divides a vector by a scalar, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param vec The vector.
+  * \param scalar The scalar.
+  */
+void lzml_vector4_divide_by_scalar_into(float dest[4], float vec[4], float scalar)
+{
+	lzml_vector4_multiply_by_scalar_into(dest, vec, (1.0f / scalar));
+}
+
+/** Calculates a dot product.
+  *
+  * \param a The first vector.
+  * \param b The second vector.
+  */
+float lzml_vector4_dot_product(float a[4], float b[4])
+{
+	return (a[0] * b[0]) + (a[1] * b[1]) + (a[2] * b[2]) + (a[3] * b[3]);
+}
+
+/** Calculates the magnitude of a vector.
+  *
+  * \param vec The vector.
+  */
+float lzml_vector4_magnitude(float vec[4])
+{
+	return sqrtf(lzml_vector4_dot_product(vec, vec));
+}
+
+/** Normalizes a vector.
+  *
+  * \param vec The vector to be normalized.
+  */
+void lzml_vector4_normalize(float vec[4])
+{
+	lzml_vector4_normalize_into(vec, vec);
+}
+
+/** Normalizes a vector, and stores the result in dest.
+  *
+  * \param dest The destination vector.
+  * \param vec The vector to be normalized.
+  */
+void lzml_vector4_normalize_into(float dest[4], float vec[4])
+{
+	float normal = lzml_vector4_magnitude(vec);
+	if (fpclassify(normal) == FP_ZERO)
+		dest[0] = dest[1] = dest[2] = dest[3] = 0.0f;
+	else
+		lzml_vector4_multiply_by_scalar_into(dest, vec, (1.0f / normal));
+}
+
+/** Copies a vector into another.
+  *
+  * \param dest The destination vector.
+  * \param src The source vector.
+  */
+void lzml_vector4_copy(float dest[4], float src[4])
+{
+	dest[0] = src[0];
+	dest[1] = src[1];
+	dest[2] = src[2];
+	dest[3] = src[3];
+}
+
+/**
+	== MATRIX OPERATIONS ==
+**/
+
+/** Copies a matrix into another.
+  *
+  * \param dest The destination matrix.
+  * \param src The source matrix.
+  */
+void lzml_matrix4_copy(float dest[4][4], float src[4][4])
+{
+	dest[0][0] = src[0][0];
+	dest[0][1] = src[0][1];
+	dest[0][2] = src[0][2];
+	dest[0][3] = src[0][3];
+	dest[1][0] = src[1][0];
+	dest[1][1] = src[1][1];
+	dest[1][2] = src[1][2];
+	dest[1][3] = src[1][3];
+	dest[2][0] = src[2][0];
+	dest[2][1] = src[2][1];
+	dest[2][2] = src[2][2];
+	dest[2][3] = src[2][3];
+	dest[3][0] = src[3][0];
+	dest[3][1] = src[3][1];
+	dest[3][2] = src[3][2];
+	dest[3][3] = src[3][3];
+}
+
+/** Clears a matrix.
+  *
+  * \param mat The matrix to be cleared.
+  */
+void lzml_matrix4_clear(float mat[4][4])
+{
+	lzml_matrix4_copy(mat, lzml_zeromatrix);
+}
+
+/** Makes an identity matrix.
+  *
+  * \param mat The destination matrix.
+  */
+void lzml_matrix4_identity(float mat[4][4])
+{
+	lzml_matrix4_copy(mat, lzml_identitymatrix);
+}
+
+/** Multiplies a matrix by another, and stores the result into the first matrix.
+  *
+  * \param a The first matrix.
+  * \param b The second matrix.
+  */
+void lzml_matrix4_multiply(float a[4][4], float b[4][4])
+{
+	float mul[4][4];
+	lzml_matrix4_multiply_into(mul, a, b);
+	lzml_matrix4_copy(a, mul);
+}
+
+/** Multiplies a matrix by another, and stores the result into dest.
+  *
+  * \param dest The destination matrix.
+  * \param a The first matrix.
+  * \param b The second matrix.
+  */
+void lzml_matrix4_multiply_into(float dest[4][4], float a[4][4], float b[4][4])
+{
+#define MULTMAT(i, j) dest[i][j] = (a[0][j] * b[i][0]) + (a[1][j] * b[i][1]) + (a[2][j] * b[i][2]) + (a[3][j] * b[i][3]);
+	MULTMAT(0, 0)
+	MULTMAT(0, 1)
+	MULTMAT(0, 2)
+	MULTMAT(0, 3)
+	MULTMAT(1, 0)
+	MULTMAT(1, 1)
+	MULTMAT(1, 2)
+	MULTMAT(1, 3)
+	MULTMAT(2, 0)
+	MULTMAT(2, 1)
+	MULTMAT(2, 2)
+	MULTMAT(2, 3)
+	MULTMAT(3, 0)
+	MULTMAT(3, 1)
+	MULTMAT(3, 2)
+	MULTMAT(3, 3)
+#undef MULTMAT
+}
+
+/** Translates a matrix in the X axis.
+  *
+  * \param mat The matrix.
+  * \param x The X translation.
+  */
+void lzml_matrix4_translate_x(float mat[4][4], float x)
+{
+	float vec[4];
+	lzml_vector4_multiply_by_scalar_into(vec, mat[0], x);
+	lzml_vector4_add(mat[3], vec);
+}
+
+/** Translates a matrix in the Y axis.
+  *
+  * \param mat The matrix.
+  * \param y The Y translation.
+  */
+void lzml_matrix4_translate_y(float mat[4][4], float y)
+{
+	float vec[4];
+	lzml_vector4_multiply_by_scalar_into(vec, mat[1], y);
+	lzml_vector4_add(mat[3], vec);
+}
+
+/** Translates a matrix in the Z axis.
+  *
+  * \param mat The matrix.
+  * \param z The Y translation.
+  */
+void lzml_matrix4_translate_z(float mat[4][4], float z)
+{
+	float vec[4];
+	lzml_vector4_multiply_by_scalar_into(vec, mat[2], z);
+	lzml_vector4_add(mat[3], vec);
+}
+
+/** Translates a matrix by a vector.
+  *
+  * \param mat The matrix.
+  * \param vec The translation vector.
+  */
+void lzml_matrix4_translate(float mat[4][4], float vec[3])
+{
+	lzml_matrix4_translate_x(mat, vec[0]);
+	lzml_matrix4_translate_y(mat, vec[1]);
+	lzml_matrix4_translate_z(mat, vec[2]);
+}
+
+/** Rotates a matrix in the X axis.
+  *
+  * \param mat The matrix.
+  * \param angle The rotation angle.
+  */
+void lzml_matrix4_rotate_x(float mat[4][4], float angle)
+{
+	float sine = sinf(angle);
+	float cosine = cosf(angle);
+
+	float rot[4][4];
+	lzml_matrix4_identity(rot);
+
+	rot[1][1] = cosine;
+	rot[1][2] = sine;
+	rot[2][1] = -sine;
+	rot[2][2] = cosine;
+
+	lzml_matrix4_multiply(mat, rot);
+}
+
+/** Rotates a matrix in the Y axis.
+  *
+  * \param mat The matrix.
+  * \param angle The rotation angle.
+  */
+void lzml_matrix4_rotate_y(float mat[4][4], float angle)
+{
+	float sine = sinf(angle);
+	float cosine = cosf(angle);
+
+	float rot[4][4];
+	lzml_matrix4_identity(rot);
+
+	rot[0][0] = cosine;
+	rot[0][2] = -sine;
+	rot[2][0] = sine;
+	rot[2][2] = cosine;
+
+	lzml_matrix4_multiply(mat, rot);
+}
+
+/** Rotates a matrix in the Z axis.
+  *
+  * \param mat The matrix.
+  * \param angle The rotation angle.
+  */
+void lzml_matrix4_rotate_z(float mat[4][4], float angle)
+{
+	float sine = sinf(angle);
+	float cosine = cosf(angle);
+
+	float rot[4][4];
+	lzml_matrix4_identity(rot);
+
+	rot[0][0] = cosine;
+	rot[0][1] = sine;
+	rot[1][0] = -sine;
+	rot[1][1] = cosine;
+
+	lzml_matrix4_multiply(mat, rot);
+}
+
+/** Rotates a matrix by an axis.
+  *
+  * \param mat The matrix.
+  * \param vec The rotation vector.
+  * \param angle The rotation angle.
+  */
+void lzml_matrix4_rotate_by_vector(float mat[4][4], float vec[3], float angle)
+{
+	float rot[4][4];
+	lzml_matrix4_rotation(rot, vec, angle);
+	lzml_matrix4_multiply(mat, rot);
+}
+
+/** Scales a matrix by a vector.
+  *
+  * \param mat The matrix.
+  * \param vec The scaling vector.
+  */
+void lzml_matrix4_scale(float mat[4][4], float vec[3])
+{
+	lzml_vector4_multiply_by_scalar(mat[0], vec[0]);
+	lzml_vector4_multiply_by_scalar(mat[1], vec[1]);
+	lzml_vector4_multiply_by_scalar(mat[2], vec[2]);
+}
+
+/** Creates a rotation matrix.
+  *
+  * \param mat The destination rotation matrix.
+  * \param vec The rotation axis.
+  * \param angle The rotation angle.
+  */
+void lzml_matrix4_rotation(float rot[4][4], float vec[3], float angle)
+{
+	float sine = sinf(angle);
+	float cosine = cosf(angle);
+	float normalized[3];
+	float x, y, z;
+
+	lzml_vector3_normalize_into(normalized, vec);
+	x = normalized[0];
+	y = normalized[1];
+	z = normalized[2];
+
+	rot[0][0] = cosine + x * x * (1.0f - cosine);
+	rot[0][1] = x * y * (1.0f - cosine) - z * sine;
+	rot[0][2] = x * z * (1.0f - cosine) + y * sine;
+	rot[0][3] = 0.0f;
+
+	rot[1][0] = y * x * (1.0f - cosine) + z * sine;
+	rot[1][1] = cosine + y * y * (1.0f - cosine);
+	rot[1][2] = y * z * (1.0f - cosine) - x * sine;
+	rot[1][3] = 0.0f;
+
+	rot[2][0] = z * x * (1.0f - cosine) - y * sine;
+	rot[2][1] = z * y * (1.0f - cosine) + x * sine;
+	rot[2][2] = cosine + z * z * (1.0f - cosine);
+	rot[2][3] = 0.0f;
+
+	rot[3][0] = 0.0f;
+	rot[3][1] = 0.0f;
+	rot[3][2] = 0.0f;
+	rot[3][3] = 1.0f;
+}
+
+/** Makes a perspective matrix.
+  *
+  * \param mat The destination matrix.
+  * \param fovy The field of view value, in radians.
+  * \param aspect_ratio The aspect ratio.
+  * \param near The near clipping plane value.
+  * \param far The far clipping plane value.
+  */
+void lzml_matrix4_perspective(float mat[4][4], float fovy, float aspect_ratio, float near_clip, float far_clip)
+{
+	float tangent = 1.0f / tanf(fovy * 0.5f);
+	float delta = (far_clip - near_clip);
+
+	if (fpclassify(aspect_ratio) == FP_ZERO)
+		return;
+
+	lzml_matrix4_copy(mat, lzml_zeromatrix);
+
+	mat[0][0] = tangent / aspect_ratio;
+	mat[1][1] = tangent;
+	mat[2][2] = -(far_clip / delta);
+	mat[2][3] = -1.0f;
+	mat[3][2] = -((far_clip * near_clip) / delta);
+}
+
+#endif // _LZML_H_
diff --git a/src/hardware/r_gles/r_gles.h b/src/hardware/r_gles/r_gles.h
new file mode 100644
index 0000000000000000000000000000000000000000..1d5041134166b5d418b9e7627f25ab12ccc5262f
--- /dev/null
+++ b/src/hardware/r_gles/r_gles.h
@@ -0,0 +1,33 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by Jaime "Lactozilla" Passos.
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1998-2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file r_gles.h
+/// \brief OpenGL ES API for Sonic Robo Blast 2
+
+#ifndef _R_GLES_H_
+#define _R_GLES_H_
+
+#define GL_GLEXT_PROTOTYPES
+
+#include "r_gleslib.h"
+
+#define _CREATE_DLL_
+#undef DEBUG_TO_FILE
+
+#include "../../doomdef.h"
+#include "../hw_gpu.h"
+
+// ==========================================================================
+//                                                                DEFINITIONS
+// ==========================================================================
+
+#include "../r_glcommon/r_glcommon.h"
+
+#endif
diff --git a/src/hardware/r_gles/r_gles1.c b/src/hardware/r_gles/r_gles1.c
new file mode 100644
index 0000000000000000000000000000000000000000..96ff6665b467b70854cd19897427189e0045091d
--- /dev/null
+++ b/src/hardware/r_gles/r_gles1.c
@@ -0,0 +1,1297 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1998-2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file r_gles1.c
+/// \brief OpenGL ES 1.1 API for Sonic Robo Blast 2
+
+#include <stdarg.h>
+#include <math.h>
+
+#include "r_gles.h"
+#include "../r_opengl/r_vbo.h"
+
+#if defined (HWRENDER) && !defined (NOROPENGL)
+
+static const GLfloat white[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
+
+boolean GLBackend_LoadFunctions(void)
+{
+	if (!GLBackend_LoadCommonFunctions())
+		return false;
+
+	GETOPENGLFUNCALT(ClearDepthf, ClearDepth)
+	GETOPENGLFUNCALT(DepthRangef, DepthRange)
+
+	GETOPENGLFUNC(Color4f)
+#if !defined(__ANDROID__)
+	GETOPENGLFUNC(Color4ubv)
+#endif
+
+	GETOPENGLFUNC(VertexPointer)
+	GETOPENGLFUNC(NormalPointer)
+	GETOPENGLFUNC(TexCoordPointer)
+	GETOPENGLFUNC(ColorPointer)
+	GETOPENGLFUNC(EnableClientState)
+	GETOPENGLFUNC(DisableClientState)
+
+#if defined(__ANDROID__)
+	GLBackend_LoadContextFunctions();
+#endif
+
+	if (!GLBackend_LoadLegacyFunctions())
+		return false;
+
+	return true;
+}
+
+boolean GLBackend_LoadContextFunctions(void)
+{
+	GETOPENGLFUNC(ActiveTexture)
+	GETOPENGLFUNC(ClientActiveTexture)
+
+	GETOPENGLFUNC(GenBuffers)
+	GETOPENGLFUNC(BindBuffer)
+	GETOPENGLFUNC(BufferData)
+	GETOPENGLFUNC(DeleteBuffers)
+
+	GETOPENGLFUNC(BlendEquation)
+
+	if (GLTexture_InitMipmapping())
+		MipmappingSupported = GL_TRUE;
+
+	return true;
+}
+
+static void GLPerspective(GLfloat fovy, GLfloat aspect)
+{
+	GLfloat m[4][4] =
+	{
+		{ 1.0f, 0.0f, 0.0f, 0.0f},
+		{ 0.0f, 1.0f, 0.0f, 0.0f},
+		{ 0.0f, 0.0f, 1.0f,-1.0f},
+		{ 0.0f, 0.0f, 0.0f, 0.0f},
+	};
+	const GLfloat zNear = NearClippingPlane;
+	const GLfloat zFar = FAR_CLIPPING_PLANE;
+	const GLfloat radians = (GLfloat)(fovy / 2.0f * M_PIl / 180.0f);
+	const GLfloat sine = sinf(radians);
+	const GLfloat deltaZ = zFar - zNear;
+	GLfloat cotangent;
+
+	if ((fabsf((float)deltaZ) < 1.0E-36f) || fpclassify(sine) == FP_ZERO || fpclassify(aspect) == FP_ZERO)
+	{
+		return;
+	}
+	cotangent = cosf(radians) / sine;
+
+	m[0][0] = cotangent / aspect;
+	m[1][1] = cotangent;
+	m[2][2] = -(zFar + zNear) / deltaZ;
+	m[3][2] = -2.0f * zNear * zFar / deltaZ;
+
+	pglMultMatrixf(&m[0][0]);
+}
+
+// ==========================================================================
+//                                                                        API
+// ==========================================================================
+
+// -----------------+
+// Init             : Initializes the OpenGL ES interface API
+// -----------------+
+static boolean Init(void)
+{
+	return GLBackend_Init();
+}
+
+// -----------------+
+// SetInitialStates : Set permanent states
+// -----------------+
+static void SetInitialStates(void)
+{
+#ifdef GL_LIGHT_MODEL_AMBIENT
+	GLfloat LightDiffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
+#endif
+
+	pglEnableClientState(GL_VERTEX_ARRAY); // We always use this one
+
+	pglEnable(GL_TEXTURE_2D);	// two-dimensional texturing
+	pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+
+	pglEnable(GL_BLEND);		// enable color blending
+	pglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+
+	pglEnable(GL_ALPHA_TEST);
+	pglAlphaFunc(GL_NOTEQUAL, 0.0f);
+
+	pglEnable(GL_DEPTH_TEST);	// check the depth buffer
+	pglDepthMask(GL_TRUE);		// enable writing to depth buffer
+	GPU->SetDepthBuffer();
+
+	// Hurdler: not necessary, is it?
+	pglShadeModel(GL_SMOOTH);	// iterate vertex colors
+
+	// this sets CurrentPolyFlags to the actual configuration
+	CurrentPolyFlags = 0xFFFFFFFF;
+	GPU->SetBlend(0);
+
+	CurrentTexture = 0;
+	GLTexture_SetNoTexture();
+
+	pglPolygonOffset(-1.0f, -1.0f);
+
+	pglLoadIdentity();
+	pglScalef(1.0f, 1.0f, -1.0f);
+
+	// Lighting for models
+#ifdef GL_LIGHT_MODEL_AMBIENT
+	pglLightModelfv(GL_LIGHT_MODEL_AMBIENT, LightDiffuse);
+	pglEnable(GL_LIGHT0);
+#endif
+}
+
+// -----------------+
+// SetModelView     : Resets the viewport state
+// -----------------+
+static void SetModelView(INT32 w, INT32 h)
+{
+	// The screen textures need to be flushed if the width or height change so that they be remade for the correct size
+	if (GPUScreenWidth != w || GPUScreenHeight != h)
+		GPU->FlushScreenTextures();
+
+	GPUScreenWidth = (GLint)w;
+	GPUScreenHeight = (GLint)h;
+
+	pglViewport(0, 0, w, h);
+
+	pglMatrixMode(GL_PROJECTION);
+	pglLoadIdentity();
+
+	pglMatrixMode(GL_MODELVIEW);
+	pglLoadIdentity();
+
+	GLPerspective(FIELD_OF_VIEW, ASPECT_RATIO);
+}
+
+// -----------------+
+// SetBlend         : Set blend mode
+// -----------------+
+static void SetBlend(UINT32 PolyFlags)
+{
+	GLState_SetBlend(PolyFlags);
+}
+
+// -----------------+
+// SetPalette       : Changes the current texture palette
+// -----------------+
+static void SetPalette(RGBA_t *palette)
+{
+	GLState_SetPalette(palette);
+}
+
+// -----------------+
+// SetDepthBuffer   : Set depth buffer state
+// -----------------+
+static void SetDepthBuffer(void)
+{
+	GLState_SetDepthBuffer();
+}
+
+// -----------------+
+// ClearBuffer      : Clear the color/alpha/depth buffer(s)
+// -----------------+
+static void ClearBuffer(boolean ColorMask, boolean DepthMask, FRGBAFloat *ClearColor)
+{
+	GLbitfield ClearMask = 0;
+
+	if (ColorMask)
+	{
+		if (ClearColor)
+			pglClearColor(ClearColor->red, ClearColor->green, ClearColor->blue, ClearColor->alpha);
+		ClearMask |= GL_COLOR_BUFFER_BIT;
+	}
+
+	if (DepthMask)
+	{
+		SetDepthBuffer();
+		ClearMask |= GL_DEPTH_BUFFER_BIT;
+	}
+
+	SetBlend(DepthMask ? PF_Occlude | CurrentPolyFlags : CurrentPolyFlags&~PF_Occlude);
+
+	pglClear(ClearMask);
+	pglEnableClientState(GL_TEXTURE_COORD_ARRAY); // We mostly use this one
+}
+
+// -----------------+
+// ReadRect         : Read a rectangle region of the truecolor framebuffer
+//                  : store pixels as RGBA8888
+// Returns          : RGBA8888 pixel array stored in dst_data
+// -----------------+
+static void ReadRect(INT32 x, INT32 y, INT32 width, INT32 height, INT32 dst_stride, UINT16 * dst_data)
+{
+	INT32 i;
+	GLubyte*top = (GLvoid*)dst_data, *bottom = top + dst_stride * (height - 1);
+	GLubyte *row = malloc(dst_stride);
+	if (!row) return;
+	pglPixelStorei(GL_PACK_ALIGNMENT, 1);
+	pglReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, dst_data);
+	pglPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+	for(i = 0; i < height/2; i++)
+	{
+		memcpy(row, top, dst_stride);
+		memcpy(top, bottom, dst_stride);
+		memcpy(bottom, row, dst_stride);
+		top += dst_stride;
+		bottom -= dst_stride;
+	}
+	free(row);
+}
+
+// -----------------+
+// GClipRect        : Defines the 2D clipping window
+// -----------------+
+static void GClipRect(INT32 minx, INT32 miny, INT32 maxx, INT32 maxy, float nearclip)
+{
+	pglViewport(minx, GPUScreenHeight-maxy, maxx-minx, maxy-miny);
+	NearClippingPlane = nearclip;
+
+	pglMatrixMode(GL_PROJECTION);
+	pglLoadIdentity();
+	GLPerspective(FIELD_OF_VIEW, ASPECT_RATIO);
+	pglMatrixMode(GL_MODELVIEW);
+}
+
+// -----------------+
+// Draw2DLine       : Render a 2D line
+// -----------------+
+static void Draw2DLine(F2DCoord *v1, F2DCoord *v2, RGBA_t Color)
+{
+	GLfloat fcolor[4];
+	GLfloat p[12];
+	GLfloat dx, dy;
+	GLfloat angle;
+
+	pglDisable(GL_TEXTURE_2D);
+
+	// This is the preferred, 'modern' way of rendering lines -- creating a polygon.
+	if (fabsf(v2->x - v1->x) > FLT_EPSILON)
+		angle = (float)atan((v2->y-v1->y)/(v2->x-v1->x));
+	else
+		angle = (float)N_PI_DEMI;
+	dx = (float)sin(angle) / (float)GPUScreenWidth;
+	dy = (float)cos(angle) / (float)GPUScreenHeight;
+
+	p[0] = v1->x - dx;  p[1] = -(v1->y + dy); p[2] = 1;
+	p[3] = v2->x - dx;  p[4] = -(v2->y + dy); p[5] = 1;
+	p[6] = v2->x + dx;  p[7] = -(v2->y - dy); p[8] = 1;
+	p[9] = v1->x + dx;  p[10] = -(v1->y - dy); p[11] = 1;
+
+	fcolor[0] = (Color.s.red/255.0f);
+	fcolor[1] = (Color.s.green/255.0f);
+	fcolor[2] = (Color.s.blue/255.0f);
+	fcolor[3] = (Color.s.alpha/255.0f);
+
+	pglDisableClientState(GL_TEXTURE_COORD_ARRAY);
+	GLState_SetColor(fcolor[0], fcolor[1], fcolor[2], fcolor[3]);
+	pglVertexPointer(3, GL_FLOAT, 0, p);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+	pglEnableClientState(GL_TEXTURE_COORD_ARRAY);
+	pglEnable(GL_TEXTURE_2D);
+}
+
+// -----------------+
+// UpdateTexture    : Updates the texture data.
+// -----------------+
+static void UpdateTexture(HWRTexture_t *pTexInfo)
+{
+	GLTexture_Update(pTexInfo);
+}
+
+// -----------------+
+// SetTexture       : Uploads the texture, and sets it as the current one.
+// -----------------+
+static void SetTexture(HWRTexture_t *pTexInfo)
+{
+	GLTexture_Set(pTexInfo);
+}
+
+// -----------------+
+// DeleteTexture    : Deletes a texture from the GPU, and frees its data.
+// -----------------+
+static void DeleteTexture(HWRTexture_t *pTexInfo)
+{
+	if (!pTexInfo)
+		return;
+	else if (pTexInfo->downloaded)
+		pglDeleteTextures(1, (GLuint *)&pTexInfo->downloaded);
+
+	GLTexture_Delete(pTexInfo);
+	pTexInfo->downloaded = 0;
+}
+
+// -----------------+
+// ClearTextureCache: Flush OpenGL textures from memory
+// -----------------+
+static void ClearTextureCache(void)
+{
+	GLTexture_Flush();
+}
+
+// code that is common between DrawPolygon and DrawIndexedTriangles
+// the corona thing is there too, i have no idea if that stuff works with DrawIndexedTriangles and batching
+static void PreparePolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, UINT32 PolyFlags)
+{
+	(void)pOutVerts;
+	if (PolyFlags & PF_Corona)
+		PolyFlags &= ~(PF_NoDepthTest|PF_Corona);
+
+	SetBlend(PolyFlags);    //TODO: inline (#pragma..)
+
+	// If Modulated, mix the surface colour to the texture
+	if (pSurf && (CurrentPolyFlags & PF_Modulated))
+	{
+		GLRGBAFloat poly;
+
+		poly.red    = (pSurf->PolyColor.s.red/255.0f);
+		poly.green  = (pSurf->PolyColor.s.green/255.0f);
+		poly.blue   = (pSurf->PolyColor.s.blue/255.0f);
+		poly.alpha  = (pSurf->PolyColor.s.alpha/255.0f);
+
+		GLState_SetColor(poly.red, poly.green, poly.blue, poly.alpha);
+	}
+}
+
+// -----------------+
+// DrawPolygon      : Render a polygon, set the texture, set render mode
+// -----------------+
+static void DrawPolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, UINT32 iNumPts, UINT32 PolyFlags)
+{
+	PreparePolygon(pSurf, pOutVerts, PolyFlags);
+
+	pglVertexPointer(3, GL_FLOAT, sizeof(FOutVector), &pOutVerts[0].x);
+	pglTexCoordPointer(2, GL_FLOAT, sizeof(FOutVector), &pOutVerts[0].s);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, iNumPts);
+
+	if (PolyFlags & PF_RemoveYWrap)
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+	if (PolyFlags & PF_ForceWrapX)
+		GLState_SetClamp(GL_TEXTURE_WRAP_S);
+
+	if (PolyFlags & PF_ForceWrapY)
+		GLState_SetClamp(GL_TEXTURE_WRAP_T);
+}
+
+// ---------------------+
+// DrawIndexedTriangles : Renders indexed triangles
+// ---------------------+
+static void DrawIndexedTriangles(FSurfaceInfo *pSurf, FOutVector *pOutVerts, UINT32 iNumPts, UINT32 PolyFlags, UINT32 *IndexArray)
+{
+	PreparePolygon(pSurf, pOutVerts, PolyFlags);
+
+	pglVertexPointer(3, GL_FLOAT, sizeof(FOutVector), &pOutVerts[0].x);
+	pglTexCoordPointer(2, GL_FLOAT, sizeof(FOutVector), &pOutVerts[0].s);
+	pglDrawElements(GL_TRIANGLES, iNumPts, GL_UNSIGNED_INT, IndexArray);
+
+	// the DrawPolygon variant of this has some code about polyflags and wrapping here but havent noticed any problems from omitting it?
+}
+
+// -----------------+
+// DrawSkyDome      : Renders a sky dome
+// -----------------+
+static void DrawSkyDome(FSkyDome *sky)
+{
+	int i, j;
+
+	// Build the sky dome! Yes!
+	if (sky->rebuild)
+	{
+		if (GLExtension_vertex_buffer_object)
+		{
+			// delete VBO when already exists
+			if (sky->vbo)
+				pglDeleteBuffers(1, &sky->vbo);
+
+			// generate a new VBO and get the associated ID
+			pglGenBuffers(1, &sky->vbo);
+
+			// bind VBO in order to use
+			pglBindBuffer(GL_ARRAY_BUFFER, sky->vbo);
+
+			// upload data to VBO
+			pglBufferData(GL_ARRAY_BUFFER, sky->vertex_count * sizeof(sky->data[0]), sky->data, GL_STATIC_DRAW);
+		}
+
+		sky->rebuild = false;
+	}
+
+	// bind VBO in order to use
+	if (GLExtension_vertex_buffer_object)
+		pglBindBuffer(GL_ARRAY_BUFFER, sky->vbo);
+
+	// activate and specify pointers to arrays
+	pglVertexPointer(3, GL_FLOAT, sizeof(sky->data[0]), sky_vbo_x);
+	pglTexCoordPointer(2, GL_FLOAT, sizeof(sky->data[0]), sky_vbo_u);
+	pglColorPointer(4, GL_UNSIGNED_BYTE, sizeof(sky->data[0]), sky_vbo_r);
+
+	// activate color arrays
+	pglEnableClientState(GL_COLOR_ARRAY);
+
+	// set transforms
+	pglScalef(1.0f, (float)sky->height / 200.0f, 1.0f);
+	pglRotatef(270.0f, 0.0f, 1.0f, 0.0f);
+
+	for (j = 0; j < 2; j++)
+	{
+		for (i = 0; i < sky->loopcount; i++)
+		{
+			FSkyLoopDef *loop = &sky->loops[i];
+			unsigned int mode = 0;
+
+			if (j == 0 ? loop->use_texture : !loop->use_texture)
+				continue;
+
+			switch (loop->mode)
+			{
+				case GPU_SKYLOOP_FAN:
+					mode = GL_TRIANGLE_FAN;
+					break;
+				case GPU_SKYLOOP_STRIP:
+					mode = GL_TRIANGLE_STRIP;
+					break;
+				default:
+					continue;
+			}
+
+			pglDrawArrays(mode, loop->vertexindex, loop->vertexcount);
+		}
+	}
+
+	pglScalef(1.0f, 1.0f, 1.0f);
+	GLState_SetColor(white[0], white[1], white[2], white[3]);
+
+	// bind with 0, so, switch back to normal pointer operation
+	if (GLExtension_vertex_buffer_object)
+		pglBindBuffer(GL_ARRAY_BUFFER, 0);
+
+	// deactivate color array
+	pglDisableClientState(GL_COLOR_ARRAY);
+}
+
+static void SetState(INT32 State, INT32 Value)
+{
+	switch (State)
+	{
+		case GPU_STATE_MODEL_LIGHTING:
+			ModelLightingEnabled = Value ? GL_TRUE : GL_FALSE;
+			break;
+
+		case GPU_STATE_TEXTUREFILTERMODE:
+			if (MipmappingSupported)
+			{
+				GLState_SetFilterMode(Value);
+				GLTexture_Flush(); //??? if we want to change filter mode by texture, remove this
+			}
+			else
+			{
+				MipmappingEnabled = GL_FALSE;
+				MipmapMinFilter = GL_LINEAR;
+			}
+			break;
+
+		case GPU_STATE_TEXTUREANISOTROPICMODE:
+			if (GLExtension_texture_filter_anisotropic && GPUMaximumAnisotropy)
+			{
+				AnisotropicFilter = min(Value, GPUMaximumAnisotropy);
+				GLTexture_Flush(); //??? if we want to change filter mode by texture, remove this
+			}
+			break;
+
+		default:
+			break;
+	}
+}
+
+static void CreateModelVBOs(model_t *model)
+{
+	GLModel_GenerateVBOs(model);
+}
+
+#define BUFFER_OFFSET(i) ((char*)NULL + (i))
+
+static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, UINT8 hflipped, FSurfaceInfo *Surface)
+{
+	static GLRGBAFloat poly = {0,0,0,0};
+
+	float pol = 0.0f;
+	float scalex, scaley, scalez;
+
+	boolean useTinyFrames;
+
+	boolean useVBO = GLExtension_vertex_buffer_object;
+
+	UINT32 flags;
+	int i;
+
+	// Because otherwise, scaling the screen negatively vertically breaks the lighting
+	GLfloat LightPos[4] = {0.0f, 1.0f, 0.0f, 0.0f};
+#ifdef GL_LIGHT_MODEL_AMBIENT
+	float ambient[4];
+	float diffuse[4];
+#endif
+
+	// Affect input model scaling
+	scale *= 0.5f;
+	scalex = scale;
+	scaley = scale;
+	scalez = scale;
+
+	if (duration != 0 && duration != -1 && tics != -1) // don't interpolate if instantaneous or infinite in length
+	{
+		UINT32 newtime = (duration - tics); // + 1;
+
+		pol = (newtime)/(float)duration;
+
+		if (pol > 1.0f)
+			pol = 1.0f;
+
+		if (pol < 0.0f)
+			pol = 0.0f;
+	}
+
+	poly.red    = (Surface->PolyColor.s.red/255.0f);
+	poly.green  = (Surface->PolyColor.s.green/255.0f);
+	poly.blue   = (Surface->PolyColor.s.blue/255.0f);
+	poly.alpha  = (Surface->PolyColor.s.alpha/255.0f);
+
+#ifdef GL_LIGHT_MODEL_AMBIENT
+	if (ModelLightingEnabled)
+	{
+		ambient[0] = poly.red;
+		ambient[1] = poly.green;
+		ambient[2] = poly.blue;
+		ambient[3] = poly.alpha;
+
+		diffuse[0] = poly.red;
+		diffuse[1] = poly.green;
+		diffuse[2] = poly.blue;
+		diffuse[3] = poly.alpha;
+
+		if (ambient[0] > 0.75f)
+			ambient[0] = 0.75f;
+		if (ambient[1] > 0.75f)
+			ambient[1] = 0.75f;
+		if (ambient[2] > 0.75f)
+			ambient[2] = 0.75f;
+
+		pglLightfv(GL_LIGHT0, GL_POSITION, LightPos);
+		pglShadeModel(GL_SMOOTH);
+
+		pglEnable(GL_LIGHTING);
+		pglMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient);
+		pglMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
+	}
+#endif
+	else
+		GLState_SetColor(poly.red, poly.green, poly.blue, poly.alpha);
+
+	flags = (Surface->PolyFlags | PF_Modulated);
+	if (Surface->PolyFlags & (PF_Additive|PF_AdditiveSource|PF_Subtractive|PF_ReverseSubtract|PF_Multiplicative))
+		flags |= PF_Occlude;
+	else if (Surface->PolyColor.s.alpha == 0xFF)
+		flags |= (PF_Occlude | PF_Masked);
+	SetBlend(flags);
+
+	pglEnable(GL_CULL_FACE);
+	pglEnable(GL_NORMALIZE);
+
+#ifdef USE_FTRANSFORM_MIRROR
+	// flipped is if the object is vertically flipped
+	// hflipped is if the object is horizontally flipped
+	// pos->flip is if the screen is flipped vertically
+	// pos->mirror is if the screen is flipped horizontally
+	// XOR all the flips together to figure out what culling to use!
+	{
+		boolean reversecull = (flipped ^ hflipped ^ pos->flip ^ pos->mirror);
+		if (reversecull)
+			pglCullFace(GL_FRONT);
+		else
+			pglCullFace(GL_BACK);
+	}
+#else
+	// pos->flip is if the screen is flipped too
+	if (flipped ^ hflipped ^ pos->flip) // If one or three of these are active, but not two, invert the model's culling
+	{
+		pglCullFace(GL_FRONT);
+	}
+	else
+	{
+		pglCullFace(GL_BACK);
+	}
+#endif
+
+	pglPushMatrix(); // should be the same as glLoadIdentity
+	//Hurdler: now it seems to work
+	pglTranslatef(pos->x, pos->z, pos->y);
+	if (flipped)
+		scaley = -scaley;
+	if (hflipped)
+		scalez = -scalez;
+
+#ifdef USE_FTRANSFORM_ANGLEZ
+	pglRotatef(pos->anglez, 0.0f, 0.0f, -1.0f); // rotate by slope from Kart
+#endif
+	pglRotatef(pos->angley, 0.0f, -1.0f, 0.0f);
+	pglRotatef(pos->anglex, 1.0f, 0.0f, 0.0f);
+
+	if (pos->roll)
+	{
+		float roll = (1.0f * pos->rollflip);
+		pglTranslatef(pos->centerx, pos->centery, 0);
+		if (pos->rotaxis == 2) // Z
+			pglRotatef(pos->rollangle, 0.0f, 0.0f, roll);
+		else if (pos->rotaxis == 1) // Y
+			pglRotatef(pos->rollangle, 0.0f, roll, 0.0f);
+		else // X
+			pglRotatef(pos->rollangle, roll, 0.0f, 0.0f);
+		pglTranslatef(-pos->centerx, -pos->centery, 0);
+	}
+
+	pglScalef(scalex, scaley, scalez);
+
+	useTinyFrames = model->meshes[0].tinyframes != NULL;
+
+	if (useTinyFrames)
+		pglScalef(1 / 64.0f, 1 / 64.0f, 1 / 64.0f);
+
+	// Don't use the VBO if it does not have the correct texture coordinates.
+	// (Can happen when model uses a sprite as a texture and the sprite changes)
+	// Comparing floats with the != operator here should be okay because they
+	// are just copies of glpatches' max_s and max_t values.
+	// Instead of the != operator, memcmp is used to avoid a compiler warning.
+	if (memcmp(&(model->vbo_max_s), &(model->max_s), sizeof(model->max_s)) != 0 ||
+		memcmp(&(model->vbo_max_t), &(model->max_t), sizeof(model->max_t)) != 0)
+		useVBO = false;
+
+	pglEnableClientState(GL_NORMAL_ARRAY);
+
+	for (i = 0; i < model->numMeshes; i++)
+	{
+		mesh_t *mesh = &model->meshes[i];
+
+		if (useTinyFrames)
+		{
+			tinyframe_t *frame = &mesh->tinyframes[frameIndex % mesh->numFrames];
+			tinyframe_t *nextframe = NULL;
+
+			if (nextFrameIndex != -1)
+				nextframe = &mesh->tinyframes[nextFrameIndex % mesh->numFrames];
+
+			if (!nextframe || fpclassify(pol) == FP_ZERO)
+			{
+				if (useVBO)
+				{
+					pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
+					pglVertexPointer(3, GL_SHORT, sizeof(vbotiny_t), BUFFER_OFFSET(0));
+					pglNormalPointer(GL_BYTE, sizeof(vbotiny_t), BUFFER_OFFSET(sizeof(short)*3));
+					pglTexCoordPointer(2, GL_FLOAT, sizeof(vbotiny_t), BUFFER_OFFSET(sizeof(short) * 3 + sizeof(char) * 6));
+
+					pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
+					pglBindBuffer(GL_ARRAY_BUFFER, 0);
+				}
+				else
+				{
+					pglVertexPointer(3, GL_SHORT, 0, frame->vertices);
+					pglNormalPointer(GL_BYTE, 0, frame->normals);
+					pglTexCoordPointer(2, GL_FLOAT, 0, mesh->uvs);
+					pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
+				}
+			}
+			else
+			{
+				short *vertPtr;
+				char *normPtr;
+				int j;
+
+				// Dangit, I soooo want to do this in a GLSL shader...
+				GLModel_AllocLerpTinyBuffer(mesh->numVertices * sizeof(short) * 3);
+				vertPtr = vertTinyBuffer;
+				normPtr = normTinyBuffer;
+				j = 0;
+
+				for (j = 0; j < mesh->numVertices * 3; j++)
+				{
+					// Interpolate
+					*vertPtr++ = (short)(frame->vertices[j] + (pol * (nextframe->vertices[j] - frame->vertices[j])));
+					*normPtr++ = (char)(frame->normals[j] + (pol * (nextframe->normals[j] - frame->normals[j])));
+				}
+
+				pglVertexPointer(3, GL_SHORT, 0, vertTinyBuffer);
+				pglNormalPointer(GL_BYTE, 0, normTinyBuffer);
+				pglTexCoordPointer(2, GL_FLOAT, 0, mesh->uvs);
+				pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
+			}
+		}
+		else
+		{
+			mdlframe_t *frame = &mesh->frames[frameIndex % mesh->numFrames];
+			mdlframe_t *nextframe = NULL;
+
+			if (nextFrameIndex != -1)
+				nextframe = &mesh->frames[nextFrameIndex % mesh->numFrames];
+
+			if (!nextframe || fpclassify(pol) == FP_ZERO)
+			{
+				if (useVBO)
+				{
+					// Zoom! Take advantage of just shoving the entire arrays to the GPU.
+					pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
+					pglVertexPointer(3, GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(0));
+					pglNormalPointer(GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(sizeof(float) * 3));
+					pglTexCoordPointer(2, GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(sizeof(float) * 6));
+
+					pglDrawArrays(GL_TRIANGLES, 0, mesh->numTriangles * 3);
+					// No tinyframes, no mesh indices
+					//pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
+					pglBindBuffer(GL_ARRAY_BUFFER, 0);
+				}
+				else
+				{
+					pglVertexPointer(3, GL_FLOAT, 0, frame->vertices);
+					pglNormalPointer(GL_FLOAT, 0, frame->normals);
+					pglTexCoordPointer(2, GL_FLOAT, 0, mesh->uvs);
+					pglDrawArrays(GL_TRIANGLES, 0, mesh->numTriangles * 3);
+				}
+			}
+			else
+			{
+				float *vertPtr;
+				float *normPtr;
+				int j = 0;
+
+				// Dangit, I soooo want to do this in a GLSL shader...
+				GLModel_AllocLerpBuffer(mesh->numVertices * sizeof(float) * 3);
+				vertPtr = vertBuffer;
+				normPtr = normBuffer;
+
+				for (j = 0; j < mesh->numVertices * 3; j++)
+				{
+					// Interpolate
+					*vertPtr++ = frame->vertices[j] + (pol * (nextframe->vertices[j] - frame->vertices[j]));
+					*normPtr++ = frame->normals[j] + (pol * (nextframe->normals[j] - frame->normals[j]));
+				}
+
+				pglVertexPointer(3, GL_FLOAT, 0, vertBuffer);
+				pglNormalPointer(GL_FLOAT, 0, normBuffer);
+				pglTexCoordPointer(2, GL_FLOAT, 0, mesh->uvs);
+				pglDrawArrays(GL_TRIANGLES, 0, mesh->numVertices);
+			}
+		}
+	}
+
+	pglDisableClientState(GL_NORMAL_ARRAY);
+
+	pglPopMatrix(); // should be the same as glLoadIdentity
+	pglDisable(GL_CULL_FACE);
+	pglDisable(GL_NORMALIZE);
+
+#ifdef GL_LIGHT_MODEL_AMBIENT
+	if (ModelLightingEnabled)
+	{
+		pglDisable(GL_LIGHTING);
+		pglShadeModel(GL_FLAT);
+	}
+#endif
+}
+
+// -----------------+
+// DrawModel        : Renders a model
+// -----------------+
+static void DrawModel(model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, UINT8 hflipped, FSurfaceInfo *Surface)
+{
+	DrawModelEx(model, frameIndex, duration, tics, nextFrameIndex, pos, scale, flipped, hflipped, Surface);
+}
+
+// -----------------+
+// SetTransform     :
+// -----------------+
+static void SetTransform(FTransform *stransform)
+{
+	static boolean special_splitscreen;
+	boolean shearing = false;
+	float used_fov;
+
+	pglLoadIdentity();
+
+	if (stransform)
+	{
+		used_fov = stransform->fovxangle;
+#ifdef USE_FTRANSFORM_MIRROR
+		// mirroring from Kart
+		if (stransform->mirror)
+			pglScalef(-stransform->scalex, stransform->scaley, -stransform->scalez);
+		else
+#endif
+		if (stransform->flip)
+			pglScalef(stransform->scalex, -stransform->scaley, -stransform->scalez);
+		else
+			pglScalef(stransform->scalex, stransform->scaley, -stransform->scalez);
+
+		if (stransform->roll)
+			pglRotatef(stransform->rollangle, 0.0f, 0.0f, 1.0f);
+		pglRotatef(stransform->anglex       , 1.0f, 0.0f, 0.0f);
+		pglRotatef(stransform->angley+270.0f, 0.0f, 1.0f, 0.0f);
+		pglTranslatef(-stransform->x, -stransform->z, -stransform->y);
+
+		special_splitscreen = stransform->splitscreen;
+		shearing = stransform->shearing;
+	}
+	else
+	{
+		used_fov = FIELD_OF_VIEW;
+		pglScalef(1.0f, 1.0f, -1.0f);
+	}
+
+	pglMatrixMode(GL_PROJECTION);
+	pglLoadIdentity();
+
+	// jimita 14042019
+	// Simulate Software's y-shearing
+	// https://zdoom.org/wiki/Y-shearing
+	if (shearing)
+	{
+		float fdy = stransform->viewaiming * 2;
+		pglTranslatef(0.0f, -fdy/BASEVIDHEIGHT, 0.0f);
+	}
+
+	if (special_splitscreen)
+	{
+		used_fov = atan(tan(used_fov*M_PI/360)*0.8)*360/M_PI;
+		GLPerspective(used_fov, 2*ASPECT_RATIO);
+	}
+	else
+		GLPerspective(used_fov, ASPECT_RATIO);
+
+	pglMatrixMode(GL_MODELVIEW);
+}
+
+static INT32 GetTextureUsed(void)
+{
+	return GLTexture_GetMemoryUsage(TexCacheHead);
+}
+
+static void PostImgRedraw(float points[GPU_POSTIMGVERTS][GPU_POSTIMGVERTS][2])
+{
+	INT32 x, y;
+	float float_x, float_y, float_nextx, float_nexty;
+	float xfix, yfix;
+	INT32 texsize = 2048;
+
+	const float blackBack[16] =
+	{
+		-16.0f, -16.0f, 6.0f,
+		-16.0f, 16.0f, 6.0f,
+		16.0f, 16.0f, 6.0f,
+		16.0f, -16.0f, 6.0f
+	};
+
+	// Use a power of two texture, dammit
+	if(GPUScreenWidth <= 1024)
+		texsize = 1024;
+	if(GPUScreenWidth <= 512)
+		texsize = 512;
+
+	// X/Y stretch fix for all resolutions(!)
+	xfix = (float)(texsize)/((float)((GPUScreenWidth)/(float)(GPU_POSTIMGVERTS-1)));
+	yfix = (float)(texsize)/((float)((GPUScreenHeight)/(float)(GPU_POSTIMGVERTS-1)));
+
+	pglDisable(GL_DEPTH_TEST);
+	pglDisable(GL_BLEND);
+
+	// const float blackBack[16]
+
+	// Draw a black square behind the screen texture,
+	// so nothing shows through the edges
+	GLState_SetColor(white[0], white[1], white[2], white[3]);
+
+	pglVertexPointer(3, GL_FLOAT, 0, blackBack);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+	for(x=0;x<GPU_POSTIMGVERTS-1;x++)
+	{
+		for(y=0;y<GPU_POSTIMGVERTS-1;y++)
+		{
+			float stCoords[8];
+			float vertCoords[12];
+
+			// Used for texture coordinates
+			// Annoying magic numbers to scale the square texture to
+			// a non-square screen..
+			float_x = (float)(x/(xfix));
+			float_y = (float)(y/(yfix));
+			float_nextx = (float)(x+1)/(xfix);
+			float_nexty = (float)(y+1)/(yfix);
+
+			// float stCoords[8];
+			stCoords[0] = float_x;
+			stCoords[1] = float_y;
+			stCoords[2] = float_x;
+			stCoords[3] = float_nexty;
+			stCoords[4] = float_nextx;
+			stCoords[5] = float_nexty;
+			stCoords[6] = float_nextx;
+			stCoords[7] = float_y;
+
+			pglTexCoordPointer(2, GL_FLOAT, 0, stCoords);
+
+			// float vertCoords[12];
+			vertCoords[0] = points[x][y][0];
+			vertCoords[1] = points[x][y][1];
+			vertCoords[2] = 4.4f;
+			vertCoords[3] = points[x][y + 1][0];
+			vertCoords[4] = points[x][y + 1][1];
+			vertCoords[5] = 4.4f;
+			vertCoords[6] = points[x + 1][y + 1][0];
+			vertCoords[7] = points[x + 1][y + 1][1];
+			vertCoords[8] = 4.4f;
+			vertCoords[9] = points[x + 1][y][0];
+			vertCoords[10] = points[x + 1][y][1];
+			vertCoords[11] = 4.4f;
+
+			pglVertexPointer(3, GL_FLOAT, 0, vertCoords);
+
+			pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+		}
+	}
+
+	pglEnable(GL_DEPTH_TEST);
+	pglEnable(GL_BLEND);
+}
+
+static void FlushScreenTextures(void)
+{
+	GLTexture_FlushScreenTextures();
+}
+
+// Create screen to fade from
+static void StartScreenWipe(void)
+{
+	GLTexture_GenerateScreenTexture(&WipeStartTexture);
+}
+
+// Create screen to fade to
+static void EndScreenWipe(void)
+{
+	GLTexture_GenerateScreenTexture(&WipeEndTexture);
+}
+
+// Draw the last scene under the intermission
+static void DrawIntermissionBG(void)
+{
+	float xfix, yfix;
+	INT32 texsize = 2048;
+
+	const float screenVerts[12] =
+	{
+		-1.0f, -1.0f, 1.0f,
+		-1.0f, 1.0f, 1.0f,
+		1.0f, 1.0f, 1.0f,
+		1.0f, -1.0f, 1.0f
+	};
+
+	float fix[8];
+
+	if(GPUScreenWidth <= 1024)
+		texsize = 1024;
+	if(GPUScreenWidth <= 512)
+		texsize = 512;
+
+	xfix = 1/((float)(texsize)/((float)((GPUScreenWidth))));
+	yfix = 1/((float)(texsize)/((float)((GPUScreenHeight))));
+
+	// const float screenVerts[12]
+
+	// float fix[8];
+	fix[0] = 0.0f;
+	fix[1] = 0.0f;
+	fix[2] = 0.0f;
+	fix[3] = yfix;
+	fix[4] = xfix;
+	fix[5] = yfix;
+	fix[6] = xfix;
+	fix[7] = 0.0f;
+
+	pglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
+
+	pglBindTexture(GL_TEXTURE_2D, ScreenTexture);
+	GLState_SetColor(white[0], white[1], white[2], white[3]);
+
+	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
+	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+	CurrentTexture = ScreenTexture;
+}
+
+// Do screen fades!
+static void DoScreenWipe(void)
+{
+	INT32 texsize = 2048;
+	float xfix, yfix;
+
+	INT32 fademaskdownloaded = CurrentTexture; // the fade mask that has been set
+
+	const float screenVerts[12] =
+	{
+		-1.0f, -1.0f, 1.0f,
+		-1.0f, 1.0f, 1.0f,
+		1.0f, 1.0f, 1.0f,
+		1.0f, -1.0f, 1.0f
+	};
+
+	float fix[8];
+
+	const float defaultST[8] =
+	{
+		0.0f, 1.0f,
+		0.0f, 0.0f,
+		1.0f, 0.0f,
+		1.0f, 1.0f
+	};
+
+	// Use a power of two texture, dammit
+	if(GPUScreenWidth <= 1024)
+		texsize = 1024;
+	if(GPUScreenWidth <= 512)
+		texsize = 512;
+
+	xfix = 1/((float)(texsize)/((float)((GPUScreenWidth))));
+	yfix = 1/((float)(texsize)/((float)((GPUScreenHeight))));
+
+	// const float screenVerts[12]
+
+	// float fix[8];
+	fix[0] = 0.0f;
+	fix[1] = 0.0f;
+	fix[2] = 0.0f;
+	fix[3] = yfix;
+	fix[4] = xfix;
+	fix[5] = yfix;
+	fix[6] = xfix;
+	fix[7] = 0.0f;
+
+	pglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
+
+	SetBlend(PF_Modulated|PF_NoDepthTest);
+	pglEnable(GL_TEXTURE_2D);
+
+	// Draw the original screen
+	pglBindTexture(GL_TEXTURE_2D, WipeStartTexture);
+	GLState_SetColor(white[0], white[1], white[2], white[3]);
+	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
+	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+	SetBlend(PF_Modulated|PF_Translucent|PF_NoDepthTest);
+
+	// Draw the end screen that fades in
+	pglActiveTexture(GL_TEXTURE0);
+	pglEnable(GL_TEXTURE_2D);
+	pglBindTexture(GL_TEXTURE_2D, WipeEndTexture);
+	pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+
+	pglActiveTexture(GL_TEXTURE1);
+	pglEnable(GL_TEXTURE_2D);
+	pglBindTexture(GL_TEXTURE_2D, fademaskdownloaded);
+
+	pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+
+	// const float defaultST[8]
+
+	pglClientActiveTexture(GL_TEXTURE0);
+	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
+	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
+	pglClientActiveTexture(GL_TEXTURE1);
+	pglEnableClientState(GL_TEXTURE_COORD_ARRAY);
+	pglTexCoordPointer(2, GL_FLOAT, 0, defaultST);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+	pglDisable(GL_TEXTURE_2D); // disable the texture in the 2nd texture unit
+	pglDisableClientState(GL_TEXTURE_COORD_ARRAY);
+
+	pglActiveTexture(GL_TEXTURE0);
+	pglClientActiveTexture(GL_TEXTURE0);
+	CurrentTexture = WipeEndTexture;
+}
+
+// Create a texture from the screen.
+static void MakeScreenTexture(void)
+{
+	GLTexture_GenerateScreenTexture(&ScreenTexture);
+}
+
+static void MakeScreenFinalTexture(void)
+{
+	GLTexture_GenerateScreenTexture(&FinalScreenTexture);
+}
+
+static void DrawScreenFinalTexture(int width, int height)
+{
+	float xfix, yfix;
+	float origaspect, newaspect;
+	float xoff = 1, yoff = 1; // xoffset and yoffset for the polygon to have black bars around the screen
+	FRGBAFloat clearColour;
+	INT32 texsize = 2048;
+
+	float off[12];
+	float fix[8];
+
+	if(GPUScreenWidth <= 1024)
+		texsize = 1024;
+	if(GPUScreenWidth <= 512)
+		texsize = 512;
+
+	xfix = 1/((float)(texsize)/((float)((GPUScreenWidth))));
+	yfix = 1/((float)(texsize)/((float)((GPUScreenHeight))));
+
+	origaspect = (float)GPUScreenWidth / GPUScreenHeight;
+	newaspect = (float)width / height;
+	if (origaspect < newaspect)
+	{
+		xoff = origaspect / newaspect;
+		yoff = 1;
+	}
+	else if (origaspect > newaspect)
+	{
+		xoff = 1;
+		yoff = newaspect / origaspect;
+	}
+
+	// float off[12];
+	off[0] = -xoff;
+	off[1] = -yoff;
+	off[2] = 1.0f;
+	off[3] = -xoff;
+	off[4] = yoff;
+	off[5] = 1.0f;
+	off[6] = xoff;
+	off[7] = yoff;
+	off[8] = 1.0f;
+	off[9] = xoff;
+	off[10] = -yoff;
+	off[11] = 1.0f;
+
+	// float fix[8];
+	fix[0] = 0.0f;
+	fix[1] = 0.0f;
+	fix[2] = 0.0f;
+	fix[3] = yfix;
+	fix[4] = xfix;
+	fix[5] = yfix;
+	fix[6] = xfix;
+	fix[7] = 0.0f;
+
+	pglViewport(0, 0, width, height);
+
+	clearColour.red = clearColour.green = clearColour.blue = 0;
+	clearColour.alpha = 1;
+	ClearBuffer(true, false, &clearColour);
+	pglBindTexture(GL_TEXTURE_2D, FinalScreenTexture);
+
+	GLState_SetColor(white[0], white[1], white[2], white[3]);
+
+	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
+	pglVertexPointer(3, GL_FLOAT, 0, off);
+
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+	CurrentTexture = FinalScreenTexture;
+}
+
+static void SetShader(int type)
+{
+	(void)type;
+}
+
+static boolean CompileShaders(void)
+{
+	return false;
+}
+
+static void SetShaderInfo(INT32 info, INT32 value)
+{
+	(void)info;
+	(void)value;
+}
+
+static void LoadCustomShader(int number, char *shader, size_t size, boolean fragment)
+{
+	(void)number;
+	(void)shader;
+	(void)size;
+	(void)fragment;
+}
+
+static void UnSetShader(void) {}
+static void CleanShaders(void) {}
+
+struct GPURenderingAPI GLInterfaceAPI = {
+	Init,
+
+	SetInitialStates,
+	SetModelView,
+	SetState,
+	SetTransform,
+	SetBlend,
+	SetPalette,
+	SetDepthBuffer,
+
+	DrawPolygon,
+	DrawIndexedTriangles,
+	Draw2DLine,
+	DrawModel,
+	DrawSkyDome,
+
+	SetTexture,
+	UpdateTexture,
+	DeleteTexture,
+
+	ClearTextureCache,
+	GetTextureUsed,
+
+	CreateModelVBOs,
+
+	ReadRect,
+	GClipRect,
+	ClearBuffer,
+
+	MakeScreenTexture,
+	MakeScreenFinalTexture,
+	FlushScreenTextures,
+
+	StartScreenWipe,
+	EndScreenWipe,
+	DoScreenWipe,
+	NULL,
+	DrawIntermissionBG,
+	DrawScreenFinalTexture,
+
+	PostImgRedraw,
+
+	CompileShaders,
+	CleanShaders,
+	SetShader,
+	UnSetShader,
+
+	SetShaderInfo,
+	LoadCustomShader,
+};
+
+#endif //HWRENDER
diff --git a/src/hardware/r_gles/r_gles2.c b/src/hardware/r_gles/r_gles2.c
new file mode 100644
index 0000000000000000000000000000000000000000..e98a5b6f1eec43c997fe4010d3e4c6ab657991dc
--- /dev/null
+++ b/src/hardware/r_gles/r_gles2.c
@@ -0,0 +1,1364 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1998-2020 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file r_gles2.c
+/// \brief OpenGL ES 2.0 API for Sonic Robo Blast 2
+
+#include <stdarg.h>
+#include <math.h>
+
+#include "r_gles.h"
+#include "../r_opengl/r_vbo.h"
+
+#include "../shaders/gl_shaders.h"
+
+#include "lzml.h"
+
+#if defined (HWRENDER) && !defined (NOROPENGL)
+
+static GLRGBAFloat white = {1.0f, 1.0f, 1.0f, 1.0f};
+static GLRGBAFloat black = {0.0f, 0.0f, 0.0f, 1.0f};
+
+// ==========================================================================
+//                                                                  CONSTANTS
+// ==========================================================================
+
+#define Deg2Rad(x) ((x) * ((float)M_PIl / 180.0f))
+
+// ==========================================================================
+//                                                                    GLOBALS
+// ==========================================================================
+
+fmatrix4_t projMatrix;
+fmatrix4_t viewMatrix;
+fmatrix4_t modelMatrix;
+
+/* Drawing Functions */
+typedef void (R_GL_APIENTRY * PFNglEnableVertexAttribArray) (GLuint index);
+static PFNglEnableVertexAttribArray pglEnableVertexAttribArray;
+typedef void (R_GL_APIENTRY * PFNglDisableVertexAttribArray) (GLuint index);
+static PFNglDisableVertexAttribArray pglDisableVertexAttribArray;
+typedef void (R_GL_APIENTRY * PFNglVertexAttribPointer) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer);
+static PFNglVertexAttribPointer pglVertexAttribPointer;
+
+boolean GLBackend_LoadFunctions(void)
+{
+	if (!GLBackend_LoadCommonFunctions())
+		return false;
+
+#if defined(__ANDROID__)
+	GLBackend_LoadContextFunctions();
+	GLBackend_InitShaders();
+#endif
+
+	return true;
+}
+
+boolean GLBackend_LoadContextFunctions(void)
+{
+	GETOPENGLFUNC(ActiveTexture)
+
+	GETOPENGLFUNC(GenBuffers)
+	GETOPENGLFUNC(BindBuffer)
+	GETOPENGLFUNC(BufferData)
+	GETOPENGLFUNC(DeleteBuffers)
+
+	GETOPENGLFUNC(EnableVertexAttribArray)
+	GETOPENGLFUNC(DisableVertexAttribArray)
+	GETOPENGLFUNC(VertexAttribPointer)
+
+	GETOPENGLFUNC(BlendEquation)
+
+	GETOPENGLFUNCALT(ClearDepthf, ClearDepth)
+	GETOPENGLFUNCALT(DepthRangef, DepthRange)
+
+	if (GLTexture_InitMipmapping())
+		MipmappingSupported = GL_TRUE;
+
+	return true;
+}
+
+static void GLPerspective(GLfloat fovy, GLfloat aspect)
+{
+	fmatrix4_t perspectiveMatrix;
+	lzml_matrix4_perspective(perspectiveMatrix, Deg2Rad((float)fovy), (float)aspect, NearClippingPlane, FAR_CLIPPING_PLANE);
+	lzml_matrix4_multiply(projMatrix, perspectiveMatrix);
+}
+
+// ==========================================================================
+//                                                                        API
+// ==========================================================================
+
+// -----------------+
+// Init             : Initializes the OpenGL ES interface API
+// -----------------+
+static boolean Init(void)
+{
+	return GLBackend_Init();
+}
+
+// -----------------+
+// SetInitialStates : Set permanent states
+// -----------------+
+static void SetInitialStates(void)
+{
+	pglEnable(GL_BLEND);
+	pglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+
+	pglEnable(GL_DEPTH_TEST);    // check the depth buffer
+	pglDepthMask(GL_TRUE);       // enable writing to depth buffer
+	GPU->SetDepthBuffer();
+
+	pglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+	pglAlphaFunc(GL_NOTEQUAL, 0.0f);
+
+	// this sets CurrentPolyFlags to the actual configuration
+	CurrentPolyFlags = 0xFFFFFFFF;
+	GPU->SetBlend(0);
+
+	if (ShaderState.current)
+	{
+		pglEnableVertexAttribArray(Shader_AttribLoc(LOC_POSITION));
+		pglEnableVertexAttribArray(Shader_AttribLoc(LOC_TEXCOORD));
+	}
+
+	CurrentTexture = 0;
+	GLTexture_SetNoTexture();
+}
+
+// -----------------+
+// SetModelView     : Resets the viewport
+// -----------------+
+static void SetModelView(INT32 w, INT32 h)
+{
+	// The screen textures need to be flushed if the width or height change so that they be remade for the correct size
+	if (GPUScreenWidth != w || GPUScreenHeight != h)
+		GPU->FlushScreenTextures();
+
+	GPUScreenWidth = (GLint)w;
+	GPUScreenHeight = (GLint)h;
+
+	pglViewport(0, 0, w, h);
+
+	lzml_matrix4_identity(projMatrix);
+	lzml_matrix4_identity(viewMatrix);
+	lzml_matrix4_identity(modelMatrix);
+
+	Shader_SetTransform();
+}
+
+// -----------------+
+// ReadRect         : Read a rectangle region of the truecolor framebuffer
+//                  : store pixels as RGBA8888
+// Returns          : RGBA8888 pixel array stored in dst_data
+// -----------------+
+static void ReadRect(INT32 x, INT32 y, INT32 width, INT32 height, INT32 dst_stride, UINT16 * dst_data)
+{
+	INT32 i;
+	GLubyte*top = (GLvoid*)dst_data, *bottom = top + dst_stride * (height - 1);
+	GLubyte *row = malloc(dst_stride);
+	if (!row) return;
+	pglPixelStorei(GL_PACK_ALIGNMENT, 1);
+	pglReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, dst_data);
+	pglPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+	for(i = 0; i < height/2; i++)
+	{
+		memcpy(row, top, dst_stride);
+		memcpy(top, bottom, dst_stride);
+		memcpy(bottom, row, dst_stride);
+		top += dst_stride;
+		bottom -= dst_stride;
+	}
+	free(row);
+}
+
+// -----------------+
+// GClipRect        : Defines the 2D clipping window
+// -----------------+
+static void GClipRect(INT32 minx, INT32 miny, INT32 maxx, INT32 maxy, float nearclip)
+{
+	pglViewport(minx, GPUScreenHeight-maxy, maxx-minx, maxy-miny);
+	NearClippingPlane = nearclip;
+
+	lzml_matrix4_identity(projMatrix);
+	lzml_matrix4_identity(viewMatrix);
+	lzml_matrix4_identity(modelMatrix);
+
+	Shader_SetTransform();
+}
+
+// -----------------+
+// SetBlend         : Set blend mode
+// -----------------+
+static void SetBlend(UINT32 PolyFlags)
+{
+	GLState_SetBlend(PolyFlags);
+}
+
+// -----------------+
+// SetPalette       : Changes the current texture palette
+// -----------------+
+static void SetPalette(RGBA_t *palette)
+{
+	GLState_SetPalette(palette);
+}
+
+// -----------------+
+// SetDepthBuffer   : Set depth buffer state
+// -----------------+
+static void SetDepthBuffer(void)
+{
+	GLState_SetDepthBuffer();
+}
+
+// -----------------+
+// ClearBuffer      : Clear the color/alpha/depth buffer(s)
+// -----------------+
+static void ClearBuffer(boolean ColorMask, boolean DepthMask, FRGBAFloat * ClearColor)
+{
+	GLbitfield ClearMask = 0;
+
+	if (ColorMask)
+	{
+		if (ClearColor)
+			pglClearColor(ClearColor->red, ClearColor->green, ClearColor->blue, ClearColor->alpha);
+		ClearMask |= GL_COLOR_BUFFER_BIT;
+	}
+
+	if (DepthMask)
+	{
+		SetDepthBuffer();
+		ClearMask |= GL_DEPTH_BUFFER_BIT;
+	}
+
+	SetBlend(DepthMask ? PF_Occlude | CurrentPolyFlags : CurrentPolyFlags&~PF_Occlude);
+	pglClear(ClearMask);
+
+	if (ShaderState.current)
+	{
+		pglEnableVertexAttribArray(Shader_AttribLoc(LOC_POSITION));
+		pglEnableVertexAttribArray(Shader_AttribLoc(LOC_TEXCOORD));
+	}
+}
+
+// -----------------+
+// Draw2DLine       : Render a 2D line
+// -----------------+
+static void Draw2DLine(F2DCoord *v1, F2DCoord *v2, RGBA_t Color)
+{
+	GLfloat p[12];
+	GLfloat dx, dy;
+	GLfloat angle;
+
+	GLRGBAFloat fcolor = {byte2float(Color.s.red), byte2float(Color.s.green), byte2float(Color.s.blue), byte2float(Color.s.alpha)};
+
+	if (ShaderState.current == NULL)
+		return;
+
+	GLTexture_SetNoTexture();
+
+	// This is the preferred, 'modern' way of rendering lines -- creating a polygon.
+	if (fabsf(v2->x - v1->x) > FLT_EPSILON)
+		angle = (float)atan((v2->y-v1->y)/(v2->x-v1->x));
+	else
+		angle = (float)N_PI_DEMI;
+	dx = (float)sin(angle) / (float)GPUScreenWidth;
+	dy = (float)cos(angle) / (float)GPUScreenHeight;
+
+	p[0] = v1->x - dx;  p[1] = -(v1->y + dy); p[2] = 1;
+	p[3] = v2->x - dx;  p[4] = -(v2->y + dy); p[5] = 1;
+	p[6] = v2->x + dx;  p[7] = -(v2->y - dy); p[8] = 1;
+	p[9] = v1->x + dx;  p[10] = -(v1->y - dy); p[11] = 1;
+
+	Shader_SetUniforms(NULL, &fcolor, NULL, NULL);
+
+	pglVertexAttribPointer(Shader_AttribLoc(LOC_POSITION), 3, GL_FLOAT, GL_FALSE, 0, p);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+}
+
+// -----------------+
+// UpdateTexture    : Updates the texture data.
+// -----------------+
+static void UpdateTexture(HWRTexture_t *pTexInfo)
+{
+	GLTexture_Update(pTexInfo);
+}
+
+// -----------------+
+// SetTexture       : Uploads the texture, and sets it as the current one.
+// -----------------+
+static void SetTexture(HWRTexture_t *pTexInfo)
+{
+	GLTexture_Set(pTexInfo);
+}
+
+// -----------------+
+// DeleteTexture    : Deletes a texture from the GPU, and frees its data.
+// -----------------+
+static void DeleteTexture(HWRTexture_t *pTexInfo)
+{
+	if (!pTexInfo)
+		return;
+	else if (pTexInfo->downloaded)
+		pglDeleteTextures(1, (GLuint *)&pTexInfo->downloaded);
+
+	GLTexture_Delete(pTexInfo);
+	pTexInfo->downloaded = 0;
+}
+
+// -----------------+
+// ClearTextureCache: Flush OpenGL textures from memory
+// -----------------+
+static void ClearTextureCache(void)
+{
+	GLTexture_Flush();
+}
+
+// code that is common between DrawPolygon and DrawIndexedTriangles
+// the corona thing is there too, i have no idea if that stuff works with DrawIndexedTriangles and batching
+static void PreparePolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, UINT32 PolyFlags)
+{
+	GLRGBAFloat poly = {1.0f, 1.0f, 1.0f, 1.0f};
+	GLRGBAFloat tint = {1.0f, 1.0f, 1.0f, 1.0f};
+	GLRGBAFloat fade = {1.0f, 1.0f, 1.0f, 1.0f};
+
+	GLRGBAFloat *c_poly = NULL, *c_tint = NULL, *c_fade = NULL;
+
+	if (ShaderState.current)
+	{
+		pglEnableVertexAttribArray(Shader_AttribLoc(LOC_POSITION));
+		pglEnableVertexAttribArray(Shader_AttribLoc(LOC_TEXCOORD));
+	}
+
+	(void)pOutVerts;
+	if (PolyFlags & PF_Corona)
+		PolyFlags &= ~(PF_NoDepthTest|PF_Corona);
+
+	SetBlend(PolyFlags);    //TODO: inline (#pragma..)
+
+	// If Modulated, mix the surface colour to the texture
+	if (pSurf && (CurrentPolyFlags & PF_Modulated))
+	{
+		// Poly color
+		poly.red   = byte2float(pSurf->PolyColor.s.red);
+		poly.green = byte2float(pSurf->PolyColor.s.green);
+		poly.blue  = byte2float(pSurf->PolyColor.s.blue);
+		poly.alpha = byte2float(pSurf->PolyColor.s.alpha);
+
+		// Tint color
+		tint.red   = byte2float(pSurf->TintColor.s.red);
+		tint.green = byte2float(pSurf->TintColor.s.green);
+		tint.blue  = byte2float(pSurf->TintColor.s.blue);
+		tint.alpha = byte2float(pSurf->TintColor.s.alpha);
+
+		// Fade color
+		fade.red   = byte2float(pSurf->FadeColor.s.red);
+		fade.green = byte2float(pSurf->FadeColor.s.green);
+		fade.blue  = byte2float(pSurf->FadeColor.s.blue);
+		fade.alpha = byte2float(pSurf->FadeColor.s.alpha);
+
+		c_poly = &poly;
+		c_tint = &tint;
+		c_fade = &fade;
+	}
+	else
+		c_poly = &white;
+
+	Shader_SetUniforms(pSurf, c_poly, c_tint, c_fade);
+}
+
+// -----------------+
+// DrawPolygon      : Render a polygon, set the texture, set render mode
+// -----------------+
+static void DrawPolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, UINT32 iNumPts, UINT32 PolyFlags)
+{
+	if (ShaderState.current == NULL)
+		return;
+
+	PreparePolygon(pSurf, pOutVerts, PolyFlags);
+
+	pglBindBuffer(GL_ARRAY_BUFFER, 0);
+
+	pglVertexAttribPointer(Shader_AttribLoc(LOC_POSITION), 3, GL_FLOAT, GL_FALSE, sizeof(FOutVector), &pOutVerts[0].x);
+	pglVertexAttribPointer(Shader_AttribLoc(LOC_TEXCOORD), 2, GL_FLOAT, GL_FALSE, sizeof(FOutVector), &pOutVerts[0].s);
+
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, iNumPts);
+
+	if (PolyFlags & PF_RemoveYWrap)
+		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+	if (PolyFlags & PF_ForceWrapX)
+		GLState_SetClamp(GL_TEXTURE_WRAP_S);
+
+	if (PolyFlags & PF_ForceWrapY)
+		GLState_SetClamp(GL_TEXTURE_WRAP_T);
+}
+
+// ---------------------+
+// DrawIndexedTriangles : Renders indexed triangles
+// ---------------------+
+static void DrawIndexedTriangles(FSurfaceInfo *pSurf, FOutVector *pOutVerts, UINT32 iNumPts, UINT32 PolyFlags, UINT32 *IndexArray)
+{
+	if (ShaderState.current == NULL)
+		return;
+
+	PreparePolygon(pSurf, pOutVerts, PolyFlags);
+
+	pglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+	pglVertexAttribPointer(Shader_AttribLoc(LOC_POSITION), 3, GL_FLOAT, GL_FALSE, sizeof(FOutVector), &pOutVerts[0].x);
+	pglVertexAttribPointer(Shader_AttribLoc(LOC_TEXCOORD), 2, GL_FLOAT, GL_FALSE, sizeof(FOutVector), &pOutVerts[0].s);
+
+	pglDrawElements(GL_TRIANGLES, iNumPts, GL_UNSIGNED_INT, IndexArray);
+
+	// the DrawPolygon variant of this has some code about polyflags and wrapping here but havent noticed any problems from omitting it?
+}
+
+// -----------------+
+// DrawSkyDome      : Renders a sky dome
+// -----------------+
+static void DrawSkyDome(FSkyDome *sky)
+{
+	int i, j;
+
+	fvector3_t scale;
+	scale[0] = scale[2] = 1.0f;
+
+	Shader_SetUniforms(NULL, NULL, NULL, NULL);
+
+	// Build the sky dome! Yes!
+	if (sky->rebuild)
+	{
+		// delete VBO when already exists
+		if (sky->vbo)
+			pglDeleteBuffers(1, &sky->vbo);
+
+		// generate a new VBO and get the associated ID
+		pglGenBuffers(1, &sky->vbo);
+
+		// bind VBO in order to use
+		pglBindBuffer(GL_ARRAY_BUFFER, sky->vbo);
+
+		// upload data to VBO
+		pglBufferData(GL_ARRAY_BUFFER, sky->vertex_count * sizeof(sky->data[0]), sky->data, GL_STATIC_DRAW);
+
+		sky->rebuild = false;
+	}
+
+	if (ShaderState.current == NULL)
+	{
+		pglBindBuffer(GL_ARRAY_BUFFER, 0);
+		return;
+	}
+
+	// bind VBO in order to use
+	pglBindBuffer(GL_ARRAY_BUFFER, sky->vbo);
+
+	// activate and specify pointers to arrays
+	pglEnableVertexAttribArray(Shader_AttribLoc(LOC_COLORS));
+	pglVertexAttribPointer(Shader_AttribLoc(LOC_POSITION), 3, GL_FLOAT, GL_FALSE, sizeof(sky->data[0]), sky_vbo_x);
+	pglVertexAttribPointer(Shader_AttribLoc(LOC_TEXCOORD), 2, GL_FLOAT, GL_FALSE, sizeof(sky->data[0]), sky_vbo_u);
+	pglVertexAttribPointer(Shader_AttribLoc(LOC_COLORS), 4, GL_FLOAT, GL_FALSE, sizeof(sky->data[0]), sky_vbo_r);
+
+	// set transforms
+	scale[1] = (float)sky->height / 200.0f;
+	lzml_matrix4_scale(viewMatrix, scale);
+	lzml_matrix4_rotate_y(viewMatrix, Deg2Rad(270.0f));
+
+	Shader_SetTransform();
+
+	for (j = 0; j < 2; j++)
+	{
+		for (i = 0; i < sky->loopcount; i++)
+		{
+			FSkyLoopDef *loop = &sky->loops[i];
+			unsigned int mode = 0;
+
+			if (j == 0 ? loop->use_texture : !loop->use_texture)
+				continue;
+
+			switch (loop->mode)
+			{
+				case GPU_SKYLOOP_FAN:
+					mode = GL_TRIANGLE_FAN;
+					break;
+				case GPU_SKYLOOP_STRIP:
+					mode = GL_TRIANGLE_STRIP;
+					break;
+				default:
+					continue;
+			}
+
+			pglDrawArrays(mode, loop->vertexindex, loop->vertexcount);
+		}
+	}
+
+	pglDisableVertexAttribArray(Shader_AttribLoc(LOC_COLORS));
+
+	// bind with 0, so, switch back to normal pointer operation
+	pglBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+static void SetState(INT32 State, INT32 Value)
+{
+	switch (State)
+	{
+		case GPU_STATE_MODEL_LIGHTING:
+			ModelLightingEnabled = Value ? GL_TRUE : GL_FALSE;
+			break;
+
+		case GPU_STATE_SHADERS:
+			ShadersAllowed = Value ? GL_TRUE : GL_FALSE;
+			break;
+
+		case GPU_STATE_TEXTUREFILTERMODE:
+			if (MipmappingSupported)
+			{
+				GLState_SetFilterMode(Value);
+				GLTexture_Flush(); //??? if we want to change filter mode by texture, remove this
+			}
+			else
+			{
+				MipmappingEnabled = GL_FALSE;
+				MipmapMinFilter = GL_LINEAR;
+			}
+			break;
+
+		case GPU_STATE_TEXTUREANISOTROPICMODE:
+			if (GLExtension_texture_filter_anisotropic && GPUMaximumAnisotropy)
+			{
+				AnisotropicFilter = min(Value, GPUMaximumAnisotropy);
+				GLTexture_Flush(); //??? if we want to change filter mode by texture, remove this
+			}
+			break;
+
+		default:
+			break;
+	}
+}
+
+static void CreateModelVBOs(model_t *model)
+{
+	GLModel_GenerateVBOs(model);
+}
+
+#define BUFFER_OFFSET(i) ((void*)(i))
+
+static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, UINT8 hflipped, FSurfaceInfo *Surface)
+{
+	static GLRGBAFloat poly = {1.0f, 1.0f, 1.0f, 1.0f};
+	static GLRGBAFloat tint = {1.0f, 1.0f, 1.0f, 1.0f};
+	static GLRGBAFloat fade = {1.0f, 1.0f, 1.0f, 1.0f};
+
+	float pol = 0.0f;
+
+	boolean useTinyFrames;
+
+	boolean useVBO = true;
+
+	fvector3_t v_scale;
+	fvector3_t translate;
+
+	UINT32 flags;
+	int i;
+
+	if (ShaderState.current == NULL)
+		return;
+
+	// Affect input model scaling
+	scale *= 0.5f;
+	v_scale[0] = v_scale[1] = v_scale[2] = scale;
+
+	if (duration != 0 && duration != -1 && tics != -1) // don't interpolate if instantaneous or infinite in length
+	{
+		UINT32 newtime = (duration - tics); // + 1;
+
+		pol = (newtime)/(float)duration;
+
+		if (pol > 1.0f)
+			pol = 1.0f;
+
+		if (pol < 0.0f)
+			pol = 0.0f;
+	}
+
+	poly.red   = byte2float(Surface->PolyColor.s.red);
+	poly.green = byte2float(Surface->PolyColor.s.green);
+	poly.blue  = byte2float(Surface->PolyColor.s.blue);
+	poly.alpha = byte2float(Surface->PolyColor.s.alpha);
+
+	tint.red   = byte2float(Surface->TintColor.s.red);
+	tint.green = byte2float(Surface->TintColor.s.green);
+	tint.blue  = byte2float(Surface->TintColor.s.blue);
+	tint.alpha = byte2float(Surface->TintColor.s.alpha);
+
+	fade.red   = byte2float(Surface->FadeColor.s.red);
+	fade.green = byte2float(Surface->FadeColor.s.green);
+	fade.blue  = byte2float(Surface->FadeColor.s.blue);
+	fade.alpha = byte2float(Surface->FadeColor.s.alpha);
+
+	flags = (Surface->PolyFlags | PF_Modulated);
+	if (Surface->PolyFlags & (PF_Additive|PF_AdditiveSource|PF_Subtractive|PF_ReverseSubtract|PF_Multiplicative))
+		flags |= PF_Occlude;
+	else if (Surface->PolyColor.s.alpha == 0xFF)
+		flags |= (PF_Occlude | PF_Masked);
+	SetBlend(flags);
+
+	pglEnableVertexAttribArray(Shader_AttribLoc(LOC_NORMAL));
+
+	Shader_SetUniforms(Surface, &poly, &tint, &fade);
+
+	pglEnable(GL_CULL_FACE);
+
+#ifdef USE_FTRANSFORM_MIRROR
+	// flipped is if the object is vertically flipped
+	// hflipped is if the object is horizontally flipped
+	// pos->flip is if the screen is flipped vertically
+	// pos->mirror is if the screen is flipped horizontally
+	// XOR all the flips together to figure out what culling to use!
+	{
+		boolean reversecull = (flipped ^ hflipped ^ pos->flip ^ pos->mirror);
+		if (reversecull)
+			pglCullFace(GL_FRONT);
+		else
+			pglCullFace(GL_BACK);
+	}
+#else
+	// pos->flip is if the screen is flipped too
+	if (flipped ^ hflipped ^ pos->flip) // If one or three of these are active, but not two, invert the model's culling
+	{
+		pglCullFace(GL_FRONT);
+	}
+	else
+	{
+		pglCullFace(GL_BACK);
+	}
+#endif
+
+	lzml_matrix4_identity(modelMatrix);
+
+	translate[0] = pos->x;
+	translate[1] = pos->z;
+	translate[2] = pos->y;
+	lzml_matrix4_translate(modelMatrix, translate);
+
+	if (flipped)
+		v_scale[1] = -v_scale[1];
+	if (hflipped)
+		v_scale[2] = -v_scale[2];
+
+	if (pos->roll)
+	{
+		float roll = (1.0f * pos->rollflip);
+		fvector3_t rotate;
+
+		translate[0] = pos->centerx;
+		translate[1] = pos->centery;
+		translate[2] = 0.0f;
+		lzml_matrix4_translate(modelMatrix, translate);
+
+		rotate[0] = rotate[1] = rotate[2] = 0.0f;
+
+		if (pos->rotaxis == 2) // Z
+			rotate[2] = roll;
+		else if (pos->rotaxis == 1) // Y
+			rotate[1] = roll;
+		else // X
+			rotate[0] = roll;
+
+		lzml_matrix4_rotate_by_vector(modelMatrix, rotate, Deg2Rad(pos->rollangle));
+
+		translate[0] = -translate[0];
+		translate[1] = -translate[1];
+		lzml_matrix4_translate(modelMatrix, translate);
+	}
+
+#ifdef USE_FTRANSFORM_ANGLEZ
+	lzml_matrix4_rotate_z(modelMatrix, -Deg2Rad(pos->anglez)); // rotate by slope from Kart
+#endif
+	lzml_matrix4_rotate_y(modelMatrix, -Deg2Rad(pos->angley));
+	lzml_matrix4_rotate_x(modelMatrix, Deg2Rad(pos->anglex));
+
+	lzml_matrix4_scale(modelMatrix, v_scale);
+
+	useTinyFrames = (model->meshes[0].tinyframes != NULL);
+	if (useTinyFrames)
+	{
+		v_scale[0] = v_scale[1] = v_scale[2] = (1 / 64.0f);
+		lzml_matrix4_scale(modelMatrix, v_scale);
+	}
+
+	// Don't use the VBO if it does not have the correct texture coordinates.
+	// (Can happen when model uses a sprite as a texture and the sprite changes)
+	// Comparing floats with the != operator here should be okay because they
+	// are just copies of glpatches' max_s and max_t values.
+	// Instead of the != operator, memcmp is used to avoid a compiler warning.
+	if (memcmp(&(model->vbo_max_s), &(model->max_s), sizeof(model->max_s)) != 0 ||
+		memcmp(&(model->vbo_max_t), &(model->max_t), sizeof(model->max_t)) != 0)
+		useVBO = false;
+
+	Shader_SetTransform();
+
+	for (i = 0; i < model->numMeshes; i++)
+	{
+		mesh_t *mesh = &model->meshes[i];
+
+		if (useTinyFrames)
+		{
+			tinyframe_t *frame = &mesh->tinyframes[frameIndex % mesh->numFrames];
+			tinyframe_t *nextframe = NULL;
+
+			if (nextFrameIndex != -1)
+				nextframe = &mesh->tinyframes[nextFrameIndex % mesh->numFrames];
+
+			if (!nextframe || fpclassify(pol) == FP_ZERO)
+			{
+				if (useVBO)
+				{
+					pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
+
+					pglVertexAttribPointer(Shader_AttribLoc(LOC_POSITION), 3, GL_SHORT, GL_FALSE, sizeof(vbotiny_t), BUFFER_OFFSET(0));
+					pglVertexAttribPointer(Shader_AttribLoc(LOC_TEXCOORD), 2, GL_FLOAT, GL_FALSE, sizeof(vbotiny_t), BUFFER_OFFSET(sizeof(short) * 3 + sizeof(char) * 6));
+					pglVertexAttribPointer(Shader_AttribLoc(LOC_NORMAL), 3, GL_BYTE, GL_FALSE, sizeof(vbotiny_t), BUFFER_OFFSET(sizeof(short)*3));
+
+					pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
+					pglBindBuffer(GL_ARRAY_BUFFER, 0);
+				}
+				else
+				{
+					pglVertexAttribPointer(Shader_AttribLoc(LOC_POSITION), 3, GL_SHORT, GL_FALSE, 0, frame->vertices);
+					pglVertexAttribPointer(Shader_AttribLoc(LOC_TEXCOORD), 2, GL_FLOAT, GL_FALSE, 0, frame->normals);
+					pglVertexAttribPointer(Shader_AttribLoc(LOC_NORMAL), 3, GL_BYTE, GL_FALSE, 0, mesh->uvs);
+
+					pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
+				}
+			}
+			else
+			{
+				short *vertPtr;
+				char *normPtr;
+				int j = 0;
+
+				// Dangit, I soooo want to do this in a GLSL shader...
+				GLModel_AllocLerpTinyBuffer(mesh->numVertices * sizeof(short) * 3);
+				vertPtr = vertTinyBuffer;
+				normPtr = normTinyBuffer;
+
+				for (j = 0; j < mesh->numVertices * 3; j++)
+				{
+					// Interpolate
+					*vertPtr++ = (short)(frame->vertices[j] + (pol * (nextframe->vertices[j] - frame->vertices[j])));
+					*normPtr++ = (char)(frame->normals[j] + (pol * (nextframe->normals[j] - frame->normals[j])));
+				}
+
+				pglVertexAttribPointer(Shader_AttribLoc(LOC_POSITION), 3, GL_SHORT, GL_FALSE, 0, vertTinyBuffer);
+				pglVertexAttribPointer(Shader_AttribLoc(LOC_TEXCOORD), 2, GL_FLOAT, GL_FALSE, 0, mesh->uvs);
+				pglVertexAttribPointer(Shader_AttribLoc(LOC_NORMAL), 3, GL_BYTE, GL_FALSE, 0, normTinyBuffer);
+
+				pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
+			}
+		}
+		else
+		{
+			mdlframe_t *frame = &mesh->frames[frameIndex % mesh->numFrames];
+			mdlframe_t *nextframe = NULL;
+
+			if (nextFrameIndex != -1)
+				nextframe = &mesh->frames[nextFrameIndex % mesh->numFrames];
+
+			if (!nextframe || fpclassify(pol) == FP_ZERO)
+			{
+				if (useVBO)
+				{
+					// Zoom! Take advantage of just shoving the entire arrays to the GPU.
+					pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
+
+					pglVertexAttribPointer(Shader_AttribLoc(LOC_POSITION), 3, GL_FLOAT, GL_FALSE, sizeof(vbo64_t), BUFFER_OFFSET(0));
+					pglVertexAttribPointer(Shader_AttribLoc(LOC_TEXCOORD), 2, GL_FLOAT, GL_FALSE, sizeof(vbo64_t), BUFFER_OFFSET(sizeof(float) * 6));
+					pglVertexAttribPointer(Shader_AttribLoc(LOC_NORMAL), 3, GL_FLOAT, GL_FALSE, sizeof(vbo64_t), BUFFER_OFFSET(sizeof(float) * 3));
+
+					pglDrawArrays(GL_TRIANGLES, 0, mesh->numTriangles * 3);
+
+					// No tinyframes, no mesh indices
+					pglBindBuffer(GL_ARRAY_BUFFER, 0);
+				}
+				else
+				{
+					pglVertexAttribPointer(Shader_AttribLoc(LOC_POSITION), 3, GL_FLOAT, GL_FALSE, 0, frame->vertices);
+					pglVertexAttribPointer(Shader_AttribLoc(LOC_TEXCOORD), 2, GL_FLOAT, GL_FALSE, 0, frame->normals);
+					pglVertexAttribPointer(Shader_AttribLoc(LOC_NORMAL), 3, GL_FLOAT, GL_FALSE, 0, mesh->uvs);
+
+					pglDrawArrays(GL_TRIANGLES, 0, mesh->numTriangles * 3);
+				}
+			}
+			else
+			{
+				float *vertPtr;
+				float *normPtr;
+				int j = 0;
+
+				// Dangit, I soooo want to do this in a GLSL shader...
+				GLModel_AllocLerpBuffer(mesh->numVertices * sizeof(float) * 3);
+				vertPtr = vertBuffer;
+				normPtr = normBuffer;
+
+				for (j = 0; j < mesh->numVertices * 3; j++)
+				{
+					// Interpolate
+					*vertPtr++ = frame->vertices[j] + (pol * (nextframe->vertices[j] - frame->vertices[j]));
+					*normPtr++ = frame->normals[j] + (pol * (nextframe->normals[j] - frame->normals[j]));
+				}
+
+				pglVertexAttribPointer(Shader_AttribLoc(LOC_POSITION), 3, GL_FLOAT, GL_FALSE, 0, vertBuffer);
+				pglVertexAttribPointer(Shader_AttribLoc(LOC_TEXCOORD), 2, GL_FLOAT, GL_FALSE, 0, mesh->uvs);
+				pglVertexAttribPointer(Shader_AttribLoc(LOC_NORMAL), 3, GL_FLOAT, GL_FALSE, 0, normBuffer);
+
+				pglDrawArrays(GL_TRIANGLES, 0, mesh->numVertices);
+			}
+		}
+	}
+
+	lzml_matrix4_identity(modelMatrix);
+	Shader_SetTransform();
+
+	pglDisableVertexAttribArray(Shader_AttribLoc(LOC_NORMAL));
+
+	pglDisable(GL_CULL_FACE);
+}
+
+// -----------------+
+// DrawModel        : Renders a model
+// -----------------+
+static void DrawModel(model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, UINT8 hflipped, FSurfaceInfo *Surface)
+{
+	DrawModelEx(model, frameIndex, duration, tics, nextFrameIndex, pos, scale, flipped, hflipped, Surface);
+}
+
+// -----------------+
+// SetTransform     :
+// -----------------+
+static void SetTransform(FTransform *stransform)
+{
+	static boolean special_splitscreen;
+	boolean shearing = false;
+	float used_fov;
+
+	fvector3_t scale;
+
+	lzml_matrix4_identity(viewMatrix);
+	lzml_matrix4_identity(modelMatrix);
+
+	if (stransform)
+	{
+		used_fov = stransform->fovxangle;
+
+#ifdef USE_FTRANSFORM_MIRROR
+		// mirroring from Kart
+		if (stransform->mirror)
+		{
+			scale[0] = -stransform->scalex;
+			scale[1] = -stransform->scaley;
+			scale[2] = -stransform->scalez;
+		}
+		else
+#endif
+		if (stransform->flip)
+		{
+			scale[0] = stransform->scalex;
+			scale[1] = -stransform->scaley;
+			scale[2] = -stransform->scalez;
+		}
+		else
+		{
+			scale[0] = stransform->scalex;
+			scale[1] = stransform->scaley;
+			scale[2] = -stransform->scalez;
+		}
+
+		lzml_matrix4_scale(viewMatrix, scale);
+
+		if (stransform->roll)
+			lzml_matrix4_rotate_z(viewMatrix, Deg2Rad(stransform->rollangle));
+		lzml_matrix4_rotate_x(viewMatrix, Deg2Rad(stransform->anglex));
+		lzml_matrix4_rotate_y(viewMatrix, Deg2Rad(stransform->angley + 270.0f));
+
+		lzml_matrix4_translate_x(viewMatrix, -stransform->x);
+		lzml_matrix4_translate_y(viewMatrix, -stransform->z);
+		lzml_matrix4_translate_z(viewMatrix, -stransform->y);
+
+		special_splitscreen = stransform->splitscreen;
+		shearing = stransform->shearing;
+	}
+	else
+		used_fov = FIELD_OF_VIEW;
+
+	lzml_matrix4_identity(projMatrix);
+
+	if (stransform)
+	{
+		// jimita 14042019
+		// Simulate Software's y-shearing
+		// https://zdoom.org/wiki/Y-shearing
+		if (shearing)
+		{
+			float fdy = stransform->viewaiming * 2;
+			if (stransform->flip)
+				fdy *= -1.0f;
+			lzml_matrix4_translate_y(projMatrix, (-fdy / BASEVIDHEIGHT));
+		}
+
+		if (special_splitscreen)
+		{
+			used_fov = atan(tan(used_fov*M_PI/360)*0.8)*360/M_PI;
+			GLPerspective(used_fov, 2*ASPECT_RATIO);
+		}
+		else
+			GLPerspective(used_fov, ASPECT_RATIO);
+	}
+
+	Shader_SetTransform();
+}
+
+static INT32 GetTextureUsed(void)
+{
+	return GLTexture_GetMemoryUsage(TexCacheHead);
+}
+
+static void PostImgRedraw(float points[GPU_POSTIMGVERTS][GPU_POSTIMGVERTS][2])
+{
+	INT32 x, y;
+	float float_x, float_y, float_nextx, float_nexty;
+	float xfix, yfix;
+	INT32 texsize = 2048;
+
+	const float blackBack[16] =
+	{
+		-1.0f, -1.0f, 1.0f,
+		-1.0f, 1.0f, 1.0f,
+		1.0f, 1.0f, 1.0f,
+		1.0f, -1.0f, 1.0f
+	};
+
+	if (ShaderState.current == NULL)
+		return;
+
+	// Use a power of two texture, dammit
+	if(GPUScreenWidth <= 1024)
+		texsize = 1024;
+	if(GPUScreenWidth <= 512)
+		texsize = 512;
+
+	// X/Y stretch fix for all resolutions(!)
+	xfix = (float)(texsize)/((float)((GPUScreenWidth)/(float)(GPU_POSTIMGVERTS-1)));
+	yfix = (float)(texsize)/((float)((GPUScreenHeight)/(float)(GPU_POSTIMGVERTS-1)));
+
+	pglDisable(GL_DEPTH_TEST);
+	pglDisable(GL_BLEND);
+
+	pglDisableVertexAttribArray(Shader_AttribLoc(LOC_TEXCOORD));
+
+	// Draw a black square behind the screen texture,
+	// so nothing shows through the edges
+	Shader_SetUniforms(NULL, &black, NULL, NULL);
+	pglVertexAttribPointer(Shader_AttribLoc(LOC_POSITION), 3, GL_FLOAT, GL_FALSE, 0, blackBack);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+	pglEnableVertexAttribArray(Shader_AttribLoc(LOC_TEXCOORD));
+	Shader_SetUniforms(NULL, &white, NULL, NULL);
+
+	for(x=0;x<GPU_POSTIMGVERTS-1;x++)
+	{
+		for(y=0;y<GPU_POSTIMGVERTS-1;y++)
+		{
+			float stCoords[8];
+			float vertCoords[12];
+
+			// Used for texture coordinates
+			// Annoying magic numbers to scale the square texture to
+			// a non-square screen..
+			float_x = (float)(x/(xfix));
+			float_y = (float)(y/(yfix));
+			float_nextx = (float)(x+1)/(xfix);
+			float_nexty = (float)(y+1)/(yfix);
+
+			// float stCoords[8];
+			stCoords[0] = float_x;
+			stCoords[1] = float_y;
+			stCoords[2] = float_x;
+			stCoords[3] = float_nexty;
+			stCoords[4] = float_nextx;
+			stCoords[5] = float_nexty;
+			stCoords[6] = float_nextx;
+			stCoords[7] = float_y;
+
+			pglVertexAttribPointer(Shader_AttribLoc(LOC_TEXCOORD), 2, GL_FLOAT, GL_FALSE, 0, stCoords);
+
+			// float vertCoords[12];
+			vertCoords[0] = points[x][y][0] / 4.5f;
+			vertCoords[1] = points[x][y][1] / 4.5f;
+			vertCoords[2] = 1.0f;
+			vertCoords[3] = points[x][y + 1][0] / 4.5f;
+			vertCoords[4] = points[x][y + 1][1] / 4.5f;
+			vertCoords[5] = 1.0f;
+			vertCoords[6] = points[x + 1][y + 1][0] / 4.5f;
+			vertCoords[7] = points[x + 1][y + 1][1] / 4.5f;
+			vertCoords[8] = 1.0f;
+			vertCoords[9] = points[x + 1][y][0] / 4.5f;
+			vertCoords[10] = points[x + 1][y][1] / 4.5f;
+			vertCoords[11] = 1.0f;
+
+			pglVertexAttribPointer(Shader_AttribLoc(LOC_POSITION), 3, GL_FLOAT, GL_FALSE, 0, vertCoords);
+
+			pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+		}
+	}
+
+	pglEnable(GL_DEPTH_TEST);
+	pglEnable(GL_BLEND);
+}
+
+static void FlushScreenTextures(void)
+{
+	GLTexture_FlushScreenTextures();
+}
+
+// Create screen to fade from
+static void StartScreenWipe(void)
+{
+	GLTexture_GenerateScreenTexture(&WipeStartTexture);
+}
+
+// Create screen to fade to
+static void EndScreenWipe(void)
+{
+	GLTexture_GenerateScreenTexture(&WipeEndTexture);
+}
+
+// Draw the last scene under the intermission
+static void DrawIntermissionBG(void)
+{
+	float xfix, yfix;
+	INT32 texsize = 2048;
+
+	const float screenVerts[12] =
+	{
+		-1.0f, -1.0f, 1.0f,
+		-1.0f, 1.0f, 1.0f,
+		1.0f, 1.0f, 1.0f,
+		1.0f, -1.0f, 1.0f
+	};
+
+	float fix[8];
+
+	if (ShaderState.current == NULL)
+		return;
+
+	if(GPUScreenWidth <= 1024)
+		texsize = 1024;
+	if(GPUScreenWidth <= 512)
+		texsize = 512;
+
+	xfix = 1/((float)(texsize)/((float)((GPUScreenWidth))));
+	yfix = 1/((float)(texsize)/((float)((GPUScreenHeight))));
+
+	// const float screenVerts[12]
+
+	// float fix[8];
+	fix[0] = 0.0f;
+	fix[1] = 0.0f;
+	fix[2] = 0.0f;
+	fix[3] = yfix;
+	fix[4] = xfix;
+	fix[5] = yfix;
+	fix[6] = xfix;
+	fix[7] = 0.0f;
+
+	pglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
+
+	pglBindTexture(GL_TEXTURE_2D, ScreenTexture);
+	Shader_SetUniforms(NULL, &white, NULL, NULL);
+
+	pglVertexAttribPointer(Shader_AttribLoc(LOC_POSITION), 3, GL_FLOAT, GL_FALSE, 0, screenVerts);
+	pglVertexAttribPointer(Shader_AttribLoc(LOC_TEXCOORD), 2, GL_FLOAT, GL_FALSE, 0, fix);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+	CurrentTexture = ScreenTexture;
+}
+
+// Do screen fades!
+static void DoWipe(boolean tinted, boolean isfadingin, boolean istowhite)
+{
+	INT32 texsize = 2048;
+	float xfix, yfix;
+
+	INT32 fademaskdownloaded = CurrentTexture; // the fade mask that has been set
+
+	const float screenVerts[12] =
+	{
+		-1.0f, -1.0f, 1.0f,
+		-1.0f, 1.0f, 1.0f,
+		1.0f, 1.0f, 1.0f,
+		1.0f, -1.0f, 1.0f
+	};
+
+	float fix[8];
+
+	const float defaultST[8] =
+	{
+		0.0f, 1.0f,
+		0.0f, 0.0f,
+		1.0f, 0.0f,
+		1.0f, 1.0f
+	};
+
+	if (ShaderState.current == NULL)
+		return;
+
+	// Use a power of two texture, dammit
+	if(GPUScreenWidth <= 1024)
+		texsize = 1024;
+	if(GPUScreenWidth <= 512)
+		texsize = 512;
+
+	xfix = 1/((float)(texsize)/((float)((GPUScreenWidth))));
+	yfix = 1/((float)(texsize)/((float)((GPUScreenHeight))));
+
+	// const float screenVerts[12]
+
+	// float fix[8];
+	fix[0] = 0.0f;
+	fix[1] = 0.0f;
+	fix[2] = 0.0f;
+	fix[3] = yfix;
+	fix[4] = xfix;
+	fix[5] = yfix;
+	fix[6] = xfix;
+	fix[7] = 0.0f;
+
+	pglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
+	SetBlend(PF_Modulated|PF_Translucent|PF_NoDepthTest);
+
+	Shader_Set(tinted ? SHADER_FADEMASK_ADDITIVEANDSUBTRACTIVE : SHADER_FADEMASK);
+	Shader_SetProgram();
+
+	pglDisableVertexAttribArray(Shader_AttribLoc(LOC_COLORS));
+	pglEnableVertexAttribArray(Shader_AttribLoc(LOC_TEXCOORD1));
+
+	Shader_SetSampler(uniform_startscreen, 0);
+	Shader_SetSampler(uniform_endscreen, 1);
+	Shader_SetSampler(uniform_fademask, 2);
+
+	if (tinted)
+	{
+		Shader_SetIntegerUniform(uniform_isfadingin, isfadingin);
+		Shader_SetIntegerUniform(uniform_istowhite, istowhite);
+	}
+
+	Shader_SetUniforms(NULL, &white, NULL, NULL);
+	Shader_SetTransform();
+
+	pglActiveTexture(GL_TEXTURE0 + 0);
+	pglBindTexture(GL_TEXTURE_2D, WipeStartTexture);
+	pglActiveTexture(GL_TEXTURE0 + 1);
+	pglBindTexture(GL_TEXTURE_2D, WipeEndTexture);
+	pglActiveTexture(GL_TEXTURE0 + 2);
+	pglBindTexture(GL_TEXTURE_2D, fademaskdownloaded);
+
+	pglVertexAttribPointer(Shader_AttribLoc(LOC_POSITION), 3, GL_FLOAT, GL_FALSE, 0, screenVerts);
+	pglVertexAttribPointer(Shader_AttribLoc(LOC_TEXCOORD0), 2, GL_FLOAT, GL_FALSE, 0, fix);
+	pglVertexAttribPointer(Shader_AttribLoc(LOC_TEXCOORD1), 2, GL_FLOAT, GL_FALSE, 0, defaultST);
+
+	pglActiveTexture(GL_TEXTURE0);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+	pglDisableVertexAttribArray(Shader_AttribLoc(LOC_TEXCOORD1));
+
+	Shader_UnSet();
+	CurrentTexture = WipeEndTexture;
+}
+
+static void DoScreenWipe(void)
+{
+	DoWipe(false, false, false);
+}
+
+static void DoTintedWipe(boolean isfadingin, boolean istowhite)
+{
+	DoWipe(true, isfadingin, istowhite);
+}
+
+// Create a texture from the screen.
+static void MakeScreenTexture(void)
+{
+	GLTexture_GenerateScreenTexture(&ScreenTexture);
+}
+
+static void MakeScreenFinalTexture(void)
+{
+	GLTexture_GenerateScreenTexture(&FinalScreenTexture);
+}
+
+static void DrawScreenFinalTexture(int width, int height)
+{
+	float xfix, yfix;
+	float origaspect, newaspect;
+	float xoff = 1, yoff = 1; // xoffset and yoffset for the polygon to have black bars around the screen
+	FRGBAFloat clearColour;
+	INT32 texsize = 2048;
+
+	float off[12];
+	float fix[8];
+
+	if (ShaderState.current == NULL)
+		return;
+
+	if(GPUScreenWidth <= 1024)
+		texsize = 1024;
+	if(GPUScreenWidth <= 512)
+		texsize = 512;
+
+	xfix = 1/((float)(texsize)/((float)((GPUScreenWidth))));
+	yfix = 1/((float)(texsize)/((float)((GPUScreenHeight))));
+
+	origaspect = (float)GPUScreenWidth / GPUScreenHeight;
+	newaspect = (float)width / height;
+	if (origaspect < newaspect)
+	{
+		xoff = origaspect / newaspect;
+		yoff = 1;
+	}
+	else if (origaspect > newaspect)
+	{
+		xoff = 1;
+		yoff = newaspect / origaspect;
+	}
+
+	// float off[12];
+	off[0] = -xoff;
+	off[1] = -yoff;
+	off[2] = 1.0f;
+	off[3] = -xoff;
+	off[4] = yoff;
+	off[5] = 1.0f;
+	off[6] = xoff;
+	off[7] = yoff;
+	off[8] = 1.0f;
+	off[9] = xoff;
+	off[10] = -yoff;
+	off[11] = 1.0f;
+
+	// float fix[8];
+	fix[0] = 0.0f;
+	fix[1] = 0.0f;
+	fix[2] = 0.0f;
+	fix[3] = yfix;
+	fix[4] = xfix;
+	fix[5] = yfix;
+	fix[6] = xfix;
+	fix[7] = 0.0f;
+
+	pglViewport(0, 0, width, height);
+
+	clearColour.red = clearColour.green = clearColour.blue = 0;
+	clearColour.alpha = 1;
+	ClearBuffer(true, false, &clearColour);
+	pglBindTexture(GL_TEXTURE_2D, FinalScreenTexture);
+
+	Shader_SetUniforms(NULL, &white, NULL, NULL);
+
+	pglBindBuffer(GL_ARRAY_BUFFER, 0);
+	pglVertexAttribPointer(Shader_AttribLoc(LOC_POSITION), 3, GL_FLOAT, GL_FALSE, 0, off);
+	pglVertexAttribPointer(Shader_AttribLoc(LOC_TEXCOORD), 2, GL_FLOAT, GL_FALSE, 0, fix);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+	CurrentTexture = FinalScreenTexture;
+}
+
+static void SetShader(int type)
+{
+	Shader_Set(GLBackend_GetShaderType(type));
+}
+
+static boolean CompileShaders(void)
+{
+	return Shader_Compile();
+}
+
+static void SetShaderInfo(INT32 info, INT32 value)
+{
+	Shader_SetInfo(info, value);
+}
+
+static void LoadCustomShader(int number, char *shader, size_t size, boolean fragment)
+{
+	Shader_LoadCustom(number, shader, size, fragment);
+}
+
+static void UnSetShader(void)
+{
+	Shader_UnSet();
+}
+
+static void CleanShaders(void)
+{
+	Shader_Clean();
+}
+
+struct GPURenderingAPI GLInterfaceAPI = {
+	Init,
+
+	SetInitialStates,
+	SetModelView,
+	SetState,
+	SetTransform,
+	SetBlend,
+	SetPalette,
+	SetDepthBuffer,
+
+	DrawPolygon,
+	DrawIndexedTriangles,
+	Draw2DLine,
+	DrawModel,
+	DrawSkyDome,
+
+	SetTexture,
+	UpdateTexture,
+	DeleteTexture,
+
+	ClearTextureCache,
+	GetTextureUsed,
+
+	CreateModelVBOs,
+
+	ReadRect,
+	GClipRect,
+	ClearBuffer,
+
+	MakeScreenTexture,
+	MakeScreenFinalTexture,
+	FlushScreenTextures,
+
+	StartScreenWipe,
+	EndScreenWipe,
+	DoScreenWipe,
+	DoTintedWipe,
+	DrawIntermissionBG,
+	DrawScreenFinalTexture,
+
+	PostImgRedraw,
+
+	CompileShaders,
+	CleanShaders,
+	SetShader,
+	UnSetShader,
+
+	SetShaderInfo,
+	LoadCustomShader,
+};
+
+#endif //HWRENDER
diff --git a/src/hardware/r_gles/r_gleslib.h b/src/hardware/r_gles/r_gleslib.h
new file mode 100644
index 0000000000000000000000000000000000000000..d362c705503762c22821c44eb8d864378585e0ce
--- /dev/null
+++ b/src/hardware/r_gles/r_gleslib.h
@@ -0,0 +1,40 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by Jaime "Lactozilla" Passos.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file r_gleslib.h
+/// \brief OpenGL ES API libraries
+
+#if defined(__ANDROID__)
+	#if defined(HAVE_GLES2)
+		#include <GLES2/gl2.h>
+		#include <GLES2/gl2ext.h>
+	#elif defined(HAVE_GLES)
+		#include <GLES/gl.h>
+		#include <GLES/glext.h>
+	#endif
+#elif defined(HAVE_SDL)
+	#define _MATH_DEFINES_DEFINED
+
+	#ifdef _MSC_VER
+	#pragma warning(disable : 4214 4244)
+	#endif
+
+	#if defined(__ANDROID__)
+		#if defined(HAVE_GLES2)
+			#include "SDL_opengles2.h"
+		#elif defined(HAVE_GLES)
+			#include "SDL_opengles.h"
+		#endif
+	#else
+		#include "SDL_opengl.h"
+	#endif
+
+	#ifdef _MSC_VER
+	#pragma warning(default : 4214 4244)
+	#endif
+#endif
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 99ffc64c5e414b38eacdb40709358b587776ae20..43b979c1768435654af6cff6d651aadf4d8b107d 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -1,5 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
 // Copyright (C) 1998-2020 by Sonic Team Junior.
 //
 // This program is free software distributed under the
@@ -38,21 +39,9 @@ static GLfloat ModelMatrix[16];
 static GLfloat ProjectionMatrix[16];
 static GLint   SceneViewport[4];
 
-//* GLU functions */
-typedef GLint (APIENTRY * PFNgluBuild2DMipmaps) (GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *data);
-static PFNgluBuild2DMipmaps pgluBuild2DMipmaps;
-
 boolean GLBackend_LoadFunctions(void)
 {
 #ifndef STATIC_OPENGL
-#define GETOPENGLFUNC(func) \
-	p ## gl ## func = GLBackend_GetFunction("gl" #func); \
-	if (!(p ## gl ## func)) \
-	{ \
-		GL_MSG_Error("failed to get OpenGL function: %s", #func); \
-		return false; \
-	} \
-
 	if (!GLBackend_LoadCommonFunctions())
 		return false;
 
@@ -68,12 +57,10 @@ boolean GLBackend_LoadFunctions(void)
 	GETOPENGLFUNC(EnableClientState)
 	GETOPENGLFUNC(DisableClientState)
 
-	GETOPENGLFUNC(TexEnvi)
-
 	if (!GLBackend_LoadLegacyFunctions())
 		return false;
-
 #endif
+
 	return true;
 }
 
@@ -96,19 +83,17 @@ boolean GLBackend_LoadContextFunctions(void)
 	if (GLMajorVersion >= 2)
 		GETOPENGLFUNC(BlendEquation)
 
+	if (GLTexture_InitMipmapping())
+		MipmappingSupported = GL_TRUE;
+
 #ifdef GL_SHADERS
 	if (GLExtension_shaders)
 		Shader_LoadFunctions();
 #endif
 
-	// GLU
-	pgluBuild2DMipmaps = GLBackend_GetFunction("gluBuild2DMipmaps");
-
 	return true;
 }
 
-#undef GETOPENGLFUNC
-
 static void GLPerspective(GLfloat fovy, GLfloat aspect)
 {
 	GLfloat m[4][4] =
@@ -192,47 +177,75 @@ static boolean Init(void)
 }
 
 // -----------------+
-// SetNoTexture     : Disable texture
+// SetInitialStates : Set permanent states
 // -----------------+
-void SetNoTexture(void)
+static void SetInitialStates(void)
 {
-	// Disable texture.
-	if (CurrentTexture != BlankTexture)
-	{
-		if (BlankTexture == 0)
-		{
-			// Generate a 1x1 white pixel for the blank texture
-			pglGenTextures(1, &BlankTexture);
-			pglBindTexture(GL_TEXTURE_2D, BlankTexture);
-			pglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, white);
-		}
-		else
-			pglBindTexture(GL_TEXTURE_2D, BlankTexture);
+#ifdef GL_LIGHT_MODEL_AMBIENT
+	GLfloat LightDiffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
+#endif
 
-		CurrentTexture = BlankTexture;
-	}
-}
+	pglEnable(GL_TEXTURE_2D);	// two-dimensional texturing
+	pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
 
-// -----------------+
-// DeleteTexture    : Deletes a texture from the GPU and frees its data
-// -----------------+
-static void DeleteTexture(HWRTexture_t *pTexInfo)
-{
-	if (!pTexInfo)
-		return;
-	else if (pTexInfo->downloaded)
-		pglDeleteTextures(1, (GLuint *)&pTexInfo->downloaded);
+	pglEnable(GL_BLEND);		// enable color blending
+	pglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
 
-	GLTexture_Delete(pTexInfo);
-	pTexInfo->downloaded = 0;
+	pglEnable(GL_ALPHA_TEST);
+	pglAlphaFunc(GL_NOTEQUAL, 0.0f);
+
+	pglEnable(GL_DEPTH_TEST);	// check the depth buffer
+	pglDepthMask(GL_TRUE);		// enable writing to depth buffer
+	GPU->SetDepthBuffer();
+
+	// Hurdler: not necessary, is it?
+	pglShadeModel(GL_SMOOTH);	// iterate vertex colors
+
+	// this sets CurrentPolyFlags to the actual configuration
+	CurrentPolyFlags = 0xFFFFFFFF;
+	GPU->SetBlend(0);
+
+	CurrentTexture = 0;
+	GLTexture_SetNoTexture();
+
+	pglPolygonOffset(-1.0f, -1.0f);
+
+	pglLoadIdentity();
+	pglScalef(1.0f, 1.0f, -1.0f);
+	pglGetFloatv(GL_MODELVIEW_MATRIX, ModelMatrix); // added for new coronas' code (without depth buffer)
+
+	// Lighting for models
+#ifdef GL_LIGHT_MODEL_AMBIENT
+	pglLightModelfv(GL_LIGHT_MODEL_AMBIENT, LightDiffuse);
+	pglEnable(GL_LIGHT0);
+#endif
 }
 
 // -----------------+
-// ClearTextureCache: Flush OpenGL textures from memory
+// SetModelView     : Resets the viewport state
 // -----------------+
-static void ClearTextureCache(void)
+static void SetModelView(INT32 w, INT32 h)
 {
-	GLTexture_Flush();
+	// The screen textures need to be flushed if the width or height change so that they be remade for the correct size
+	if (GPUScreenWidth != w || GPUScreenHeight != h)
+		GPU->FlushScreenTextures();
+
+	GPUScreenWidth = (GLint)w;
+	GPUScreenHeight = (GLint)h;
+
+	pglViewport(0, 0, w, h);
+
+	pglMatrixMode(GL_PROJECTION);
+	pglLoadIdentity();
+
+	pglMatrixMode(GL_MODELVIEW);
+	pglLoadIdentity();
+
+	GLPerspective(FIELD_OF_VIEW, ASPECT_RATIO);
+
+	// added for new coronas' code (without depth buffer)
+	pglGetIntegerv(GL_VIEWPORT, SceneViewport);
+	pglGetFloatv(GL_PROJECTION_MATRIX, ProjectionMatrix);
 }
 
 // -----------------+
@@ -240,11 +253,9 @@ static void ClearTextureCache(void)
 //                  : store pixels as 16bit 565 RGB
 // Returns          : 16bit 565 RGB pixel array stored in dst_data
 // -----------------+
-static void ReadRect(INT32 x, INT32 y, INT32 width, INT32 height,
-                                INT32 dst_stride, UINT16 * dst_data)
+static void ReadRect(INT32 x, INT32 y, INT32 width, INT32 height, INT32 dst_stride, UINT16 * dst_data)
 {
 	INT32 i;
-	// GL_DBG_Printf ("ReadRect()\n");
 	if (dst_stride == width*3)
 	{
 		GLubyte*top = (GLvoid*)dst_data, *bottom = top + dst_stride * (height - 1);
@@ -287,16 +298,13 @@ static void ReadRect(INT32 x, INT32 y, INT32 width, INT32 height,
 }
 
 // -----------------+
-// GClipRect        : Defines the 2D hardware clipping window
+// GClipRect        : Defines the 2D clipping window
 // -----------------+
 static void GClipRect(INT32 minx, INT32 miny, INT32 maxx, INT32 maxy, float nearclip)
 {
-	// GL_DBG_Printf ("GClipRect(%d, %d, %d, %d)\n", minx, miny, maxx, maxy);
-
 	pglViewport(minx, GPUScreenHeight-maxy, maxx-minx, maxy-miny);
 	NearClippingPlane = nearclip;
 
-	//pglScissor(minx, GPUScreenHeight-maxy, maxx-minx, maxy-miny);
 	pglMatrixMode(GL_PROJECTION);
 	pglLoadIdentity();
 	GLPerspective(FIELD_OF_VIEW, ASPECT_RATIO);
@@ -308,31 +316,27 @@ static void GClipRect(INT32 minx, INT32 miny, INT32 maxx, INT32 maxy, float near
 }
 
 // -----------------+
-// SetPalette       : Changes the current texture palette
+// SetBlend         : Set blend mode
 // -----------------+
-static void SetPalette(RGBA_t *palette)
+static void SetBlend(UINT32 PolyFlags)
 {
-	size_t palsize = (sizeof(RGBA_t) * 256);
-	// on a palette change, you have to reload all of the textures
-	if (memcmp(&GPUTexturePalette, palette, palsize))
-	{
-		memcpy(&GPUTexturePalette, palette, palsize);
-		GLTexture_Flush();
-	}
+	GLState_SetBlend(PolyFlags);
 }
 
-void SetClamp(GLenum pname)
+// -----------------+
+// SetPalette       : Changes the current texture palette
+// -----------------+
+static void SetPalette(RGBA_t *palette)
 {
-	pglTexParameteri(GL_TEXTURE_2D, pname, GL_CLAMP); // fallback clamp
-	pglTexParameteri(GL_TEXTURE_2D, pname, GL_CLAMP_TO_EDGE);
+	GLState_SetPalette(palette);
 }
 
 // -----------------+
-// SetBlend         : Set render mode
+// SetDepthBuffer   : Set depth buffer state
 // -----------------+
-static void SetBlend(UINT32 PolyFlags)
+static void SetDepthBuffer(void)
 {
-	SetBlendingStates(PolyFlags);
+	GLState_SetDepthBuffer();
 }
 
 // -----------------+
@@ -340,7 +344,6 @@ static void SetBlend(UINT32 PolyFlags)
 // -----------------+
 static void ClearBuffer(boolean ColorMask, boolean DepthMask, FRGBAFloat *ClearColor)
 {
-	// GL_DBG_Printf ("ClearBuffer(%d)\n", alpha);
 	GLbitfield ClearMask = 0;
 
 	if (ColorMask)
@@ -349,11 +352,10 @@ static void ClearBuffer(boolean ColorMask, boolean DepthMask, FRGBAFloat *ClearC
 			pglClearColor(ClearColor->red, ClearColor->green, ClearColor->blue, ClearColor->alpha);
 		ClearMask |= GL_COLOR_BUFFER_BIT;
 	}
+
 	if (DepthMask)
 	{
-		pglClearDepth(1.0f);     //Hurdler: all that are permanen states
-		pglDepthRange(0.0f, 1.0f);
-		pglDepthFunc(GL_LEQUAL);
+		SetDepthBuffer();
 		ClearMask |= GL_DEPTH_BUFFER_BIT;
 	}
 
@@ -367,18 +369,12 @@ static void ClearBuffer(boolean ColorMask, boolean DepthMask, FRGBAFloat *ClearC
 // -----------------+
 // Draw2DLine       : Render a 2D line
 // -----------------+
-static void Draw2DLine(F2DCoord * v1,
-                                   F2DCoord * v2,
-                                   RGBA_t Color)
+static void Draw2DLine(F2DCoord *v1, F2DCoord *v2, RGBA_t Color)
 {
-	// GL_DBG_Printf ("DrawLine() (%f %f %f) %d\n", v1->x, -v1->y, -v1->z, v1->argb);
 	GLfloat p[12];
 	GLfloat dx, dy;
 	GLfloat angle;
 
-	// BP: we should reflect the new state in our variable
-	//SetBlend(PF_Modulated|PF_NoTexture);
-
 	pglDisable(GL_TEXTURE_2D);
 
 	// This is the preferred, 'modern' way of rendering lines -- creating a polygon.
@@ -395,7 +391,7 @@ static void Draw2DLine(F2DCoord * v1,
 	p[9] = v1->x + dx;  p[10] = -(v1->y - dy); p[11] = 1;
 
 	pglDisableClientState(GL_TEXTURE_COORD_ARRAY);
-	pglColor4ubv((GLubyte*)&Color.s);
+	GLState_SetColorUBV((GLubyte*)&Color.s);
 	pglVertexPointer(3, GL_FLOAT, 0, p);
 	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
@@ -406,215 +402,39 @@ static void Draw2DLine(F2DCoord * v1,
 // -----------------+
 // UpdateTexture    : Updates the texture data.
 // -----------------+
-static void UploadTexture(HWRTexture_t *pTexInfo, const GLvoid *pTextureBuffer, GLenum format, boolean update)
+static void UpdateTexture(HWRTexture_t *pTexInfo)
 {
-	INT32 w = pTexInfo->width;
-	INT32 h = pTexInfo->height;
-
-	GLint maxLOD = 5;
-	if (pTexInfo->format == GPU_TEXFMT_ALPHA_INTENSITY_88 || pTexInfo->format == GPU_TEXFMT_ALPHA_8)
-		maxLOD = 4;
-
-	if (MipmappingEnabled)
-	{
-		pgluBuild2DMipmaps(GL_TEXTURE_2D, format, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pTextureBuffer);
-
-		// Control the mipmap level of detail
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0); // the lower the number, the higher the detail
-
-		if (pTexInfo->flags & TF_TRANSPARENT)
-			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0); // No mipmaps on transparent stuff
-		else
-			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, maxLOD);
-	}
-	else
-	{
-		if (update)
-			pglTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pTextureBuffer);
-		else
-			pglTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pTextureBuffer);
-	}
+	GLTexture_Update(pTexInfo);
 }
 
-static void UpdateTexture(HWRTexture_t *pTexInfo)
+// -----------------+
+// SetTexture       : Uploads the texture, and sets it as the current one.
+// -----------------+
+static void SetTexture(HWRTexture_t *pTexInfo)
 {
-	boolean update = true;
-	static RGBA_t textureBuffer[2048 * 2048];
-	const GLvoid *pTextureBuffer = textureBuffer;
-	INT32 w = pTexInfo->width;
-	INT32 h = pTexInfo->height;
-	GLuint textureName = 0;
-
-	if (!pTexInfo->downloaded)
-	{
-		pglGenTextures(1, &textureName);
-		pTexInfo->downloaded = textureName;
-		update = false;
-	}
-	else
-		textureName = pTexInfo->downloaded;
-
-	//GL_DBG_Printf ("UpdateTexture %d %x\n",(INT32)textureName,pTexInfo->data);
-
-	if ((pTexInfo->format == GPU_TEXFMT_P_8) ||
-		(pTexInfo->format == GPU_TEXFMT_AP_88))
-	{
-		const GLubyte *pImgData = (const GLubyte *)pTexInfo->data;
-		INT32 i, j;
-
-		for (j = 0; j < h; j++)
-		{
-			for (i = 0; i < w; i++)
-			{
-				if ((*pImgData == GPU_PATCHES_CHROMAKEY_COLORINDEX) &&
-					(pTexInfo->flags & TF_CHROMAKEYED))
-				{
-					textureBuffer[w*j+i].s.red   = 0;
-					textureBuffer[w*j+i].s.green = 0;
-					textureBuffer[w*j+i].s.blue  = 0;
-					textureBuffer[w*j+i].s.alpha = 0;
-					pTexInfo->flags |= TF_TRANSPARENT; // there is a hole in it
-				}
-				else
-				{
-					textureBuffer[w*j+i].s.red   = GPUTexturePalette[*pImgData].s.red;
-					textureBuffer[w*j+i].s.green = GPUTexturePalette[*pImgData].s.green;
-					textureBuffer[w*j+i].s.blue  = GPUTexturePalette[*pImgData].s.blue;
-					textureBuffer[w*j+i].s.alpha = GPUTexturePalette[*pImgData].s.alpha;
-				}
-
-				pImgData++;
-
-				if (pTexInfo->format == GPU_TEXFMT_AP_88)
-				{
-					if (!(pTexInfo->flags & TF_CHROMAKEYED))
-						textureBuffer[w*j+i].s.alpha = *pImgData;
-					pImgData++;
-				}
-			}
-		}
-	}
-	else if (pTexInfo->format == GPU_TEXFMT_RGBA)
-	{
-		// corona test : passed as ARGB 8888, which is not in glide formats
-		// Hurdler: not used for coronas anymore, just for dynamic lighting
-		pTextureBuffer = pTexInfo->data;
-	}
-	else if (pTexInfo->format == GPU_TEXFMT_ALPHA_INTENSITY_88)
-	{
-		const GLubyte *pImgData = (const GLubyte *)pTexInfo->data;
-		INT32 i, j;
-
-		for (j = 0; j < h; j++)
-		{
-			for (i = 0; i < w; i++)
-			{
-				textureBuffer[w*j+i].s.red   = *pImgData;
-				textureBuffer[w*j+i].s.green = *pImgData;
-				textureBuffer[w*j+i].s.blue  = *pImgData;
-				pImgData++;
-				textureBuffer[w*j+i].s.alpha = *pImgData;
-				pImgData++;
-			}
-		}
-	}
-	else if (pTexInfo->format == GPU_TEXFMT_ALPHA_8) // Used for fade masks
-	{
-		const GLubyte *pImgData = (const GLubyte *)pTexInfo->data;
-		INT32 i, j;
-
-		for (j = 0; j < h; j++)
-		{
-			for (i = 0; i < w; i++)
-			{
-				textureBuffer[w*j+i].s.red   = 255; // 255 because the fade mask is modulated with the screen texture, so alpha affects it while the colours don't
-				textureBuffer[w*j+i].s.green = 255;
-				textureBuffer[w*j+i].s.blue  = 255;
-				textureBuffer[w*j+i].s.alpha = *pImgData;
-				pImgData++;
-			}
-		}
-	}
-	else
-		GL_MSG_Warning ("SetTexture(bad format) %ld\n", pTexInfo->format);
-
-	// the texture number was already generated by pglGenTextures
-	pglBindTexture(GL_TEXTURE_2D, textureName);
-	CurrentTexture = textureName;
-
-	// disable texture filtering on any texture that has holes so there's no dumb borders or blending issues
-	if (pTexInfo->flags & TF_TRANSPARENT)
-	{
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-	}
-	else
-	{
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, MipmapMagFilter);
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, MipmapMinFilter);
-	}
-
-	if (pTexInfo->format == GPU_TEXFMT_ALPHA_INTENSITY_88)
-		UploadTexture(pTexInfo, pTextureBuffer, GL_LUMINANCE_ALPHA, update);
-	else if (pTexInfo->format == GPU_TEXFMT_ALPHA_8)
-		UploadTexture(pTexInfo, pTextureBuffer, GL_ALPHA, update);
-	else
-		UploadTexture(pTexInfo, pTextureBuffer, GPUTextureFormat, update);
-
-	if (pTexInfo->flags & TF_WRAPX)
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
-	else
-		SetClamp(GL_TEXTURE_WRAP_S);
-
-	if (pTexInfo->flags & TF_WRAPY)
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
-	else
-		SetClamp(GL_TEXTURE_WRAP_T);
-
-	if (GLExtension_texture_filter_anisotropic && GPUMaximumAnisotropy)
-		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, AnisotropicFilter);
+	GLTexture_Set(pTexInfo);
 }
 
 // -----------------+
-// SetTexture       : The texture becomes the current source
+// DeleteTexture    : Deletes a texture from the GPU, and frees its data.
 // -----------------+
-static void SetTexture(HWRTexture_t *pTexInfo)
+static void DeleteTexture(HWRTexture_t *pTexInfo)
 {
 	if (!pTexInfo)
-	{
-		SetNoTexture();
 		return;
-	}
 	else if (pTexInfo->downloaded)
-	{
-		if (pTexInfo->downloaded != CurrentTexture)
-		{
-			pglBindTexture(GL_TEXTURE_2D, pTexInfo->downloaded);
-			CurrentTexture = pTexInfo->downloaded;
-		}
-	}
-	else
-	{
-		FTextureInfo *newTex = calloc(1, sizeof (*newTex));
-
-		UpdateTexture(pTexInfo);
+		pglDeleteTextures(1, (GLuint *)&pTexInfo->downloaded);
 
-		newTex->texture = pTexInfo;
-		newTex->name = (UINT32)pTexInfo->downloaded;
-		newTex->width = (UINT32)pTexInfo->width;
-		newTex->height = (UINT32)pTexInfo->height;
-		newTex->format = (UINT32)pTexInfo->format;
+	GLTexture_Delete(pTexInfo);
+	pTexInfo->downloaded = 0;
+}
 
-		// insertion at the tail
-		if (TexCacheTail)
-		{
-			newTex->prev = TexCacheTail;
-			TexCacheTail->next = newTex;
-			TexCacheTail = newTex;
-		}
-		else // initialization of the linked list
-			TexCacheTail = TexCacheHead = newTex;
-	}
+// -----------------+
+// ClearTextureCache: Flush OpenGL textures from memory
+// -----------------+
+static void ClearTextureCache(void)
+{
+	GLTexture_Flush();
 }
 
 // code that is common between DrawPolygon and DrawIndexedTriangles
@@ -639,7 +459,7 @@ static void PreparePolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, UINT32 Po
 			poly.blue   = byte2float(pSurf->PolyColor.s.blue);
 			poly.alpha  = byte2float(pSurf->PolyColor.s.alpha);
 
-			pglColor4ubv((GLubyte*)&pSurf->PolyColor.s);
+			GLState_SetColorUBV((GLubyte*)&pSurf->PolyColor.s);
 		}
 
 		// Tint color
@@ -659,14 +479,13 @@ static void PreparePolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, UINT32 Po
 	// I think I should do a separate function for drawing coronas, so it will be a little faster
 	if (PolyFlags & PF_Corona) // check to see if we need to draw the corona
 	{
-		UINT32 i;
-		UINT32 j;
+		UINT32 i, j;
 
 		//rem: all 8 (or 8.0f) values are hard coded: it can be changed to a higher value
-		GLfloat     buf[8][8];
-		GLfloat    cx, cy, cz;
-		GLfloat    px = 0.0f, py = 0.0f, pz = -1.0f;
-		GLfloat     scalef = 0.0f;
+		GLfloat buf[8][8];
+		GLfloat cx, cy, cz;
+		GLfloat px = 0.0f, py = 0.0f, pz = -1.0f;
+		GLfloat scalef = 0.0f;
 
 		GLubyte c[4];
 
@@ -715,7 +534,7 @@ static void PreparePolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, UINT32 Po
 		alpha = byte2float(pSurf->PolyColor.s.alpha);
 		alpha *= scalef; // change the alpha value (it seems better than changing the size of the corona)
 		c[3] = (unsigned char)(alpha * 255);
-		pglColor4ubv(c);
+		GLState_SetColorUBV(c);
 	}
 
 	Shader_SetUniforms(pSurf, &poly, &tint, &fade);
@@ -736,12 +555,15 @@ static void DrawPolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, UINT32 iNumP
 		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
 
 	if (PolyFlags & PF_ForceWrapX)
-		SetClamp(GL_TEXTURE_WRAP_S);
+		GLState_SetClamp(GL_TEXTURE_WRAP_S);
 
 	if (PolyFlags & PF_ForceWrapY)
-		SetClamp(GL_TEXTURE_WRAP_T);
+		GLState_SetClamp(GL_TEXTURE_WRAP_T);
 }
 
+// ---------------------+
+// DrawIndexedTriangles : Renders indexed triangles
+// ---------------------+
 static void DrawIndexedTriangles(FSurfaceInfo *pSurf, FOutVector *pOutVerts, UINT32 iNumPts, UINT32 PolyFlags, UINT32 *IndexArray)
 {
 	PreparePolygon(pSurf, pOutVerts, PolyFlags);
@@ -753,6 +575,9 @@ static void DrawIndexedTriangles(FSurfaceInfo *pSurf, FOutVector *pOutVerts, UIN
 	// the DrawPolygon variant of this has some code about polyflags and wrapping here but havent noticed any problems from omitting it?
 }
 
+// -----------------+
+// DrawSkyDome      : Renders a sky dome
+// -----------------+
 static void DrawSkyDome(FSkyDome *sky)
 {
 	int i, j;
@@ -762,15 +587,12 @@ static void DrawSkyDome(FSkyDome *sky)
 	// Build the sky dome! Yes!
 	if (sky->rebuild)
 	{
-		// delete VBO when already exists
 		if (GLExtension_vertex_buffer_object)
 		{
+			// delete VBO when already exists
 			if (sky->vbo)
 				pglDeleteBuffers(1, &sky->vbo);
-		}
 
-		if (GLExtension_vertex_buffer_object)
-		{
 			// generate a new VBO and get the associated ID
 			pglGenBuffers(1, &sky->vbo);
 
@@ -827,7 +649,7 @@ static void DrawSkyDome(FSkyDome *sky)
 	}
 
 	pglScalef(1.0f, 1.0f, 1.0f);
-	pglColor4ubv(white);
+	GLState_SetColorUBV(white);
 
 	// bind with 0, so, switch back to normal pointer operation
 	if (GLExtension_vertex_buffer_object)
@@ -850,13 +672,16 @@ static void SetState(INT32 State, INT32 Value)
 			break;
 
 		case GPU_STATE_TEXTUREFILTERMODE:
-			GLTexture_SetFilterMode(Value);
-			if (!pgluBuild2DMipmaps)
+			if (MipmappingSupported)
+			{
+				GLState_SetFilterMode(Value);
+				GLTexture_Flush(); //??? if we want to change filter mode by texture, remove this
+			}
+			else
 			{
 				MipmappingEnabled = GL_FALSE;
 				MipmapMinFilter = GL_LINEAR;
 			}
-			GLTexture_Flush(); //??? if we want to change filter mode by texture, remove this
 			break;
 
 		case GPU_STATE_TEXTUREANISOTROPICMODE:
@@ -958,7 +783,7 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 	}
 #endif
 	else
-		pglColor4ubv((GLubyte*)&Surface->PolyColor.s);
+		GLState_SetColorUBV((GLubyte*)&Surface->PolyColor.s);
 
 	tint.red   = byte2float(Surface->TintColor.s.red);
 	tint.green = byte2float(Surface->TintColor.s.green);
@@ -1120,6 +945,7 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 			{
 				if (useVBO)
 				{
+					// Zoom! Take advantage of just shoving the entire arrays to the GPU.
 					pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
 					pglVertexPointer(3, GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(0));
 					pglNormalPointer(GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(sizeof(float) * 3));
@@ -1127,7 +953,6 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 
 					pglDrawArrays(GL_TRIANGLES, 0, mesh->numTriangles * 3);
 					// No tinyframes, no mesh indices
-					//pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
 					pglBindBuffer(GL_ARRAY_BUFFER, 0);
 				}
 				else
@@ -1148,7 +973,6 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 				GLModel_AllocLerpBuffer(mesh->numVertices * sizeof(float) * 3);
 				vertPtr = vertBuffer;
 				normPtr = normBuffer;
-				//int j = 0;
 
 				for (j = 0; j < mesh->numVertices * 3; j++)
 				{
@@ -1295,7 +1119,7 @@ static void PostImgRedraw(float points[GPU_POSTIMGVERTS][GPU_POSTIMGVERTS][2])
 
 	// Draw a black square behind the screen texture,
 	// so nothing shows through the edges
-	pglColor4ubv(white);
+	GLState_SetColorUBV(white);
 
 	pglVertexPointer(3, GL_FLOAT, 0, blackBack);
 	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
@@ -1352,17 +1176,9 @@ static void PostImgRedraw(float points[GPU_POSTIMGVERTS][GPU_POSTIMGVERTS][2])
 	pglEnable(GL_BLEND);
 }
 
-// Sryder:	This needs to be called whenever the screen changes resolution in order to reset the screen textures to use
-//			a new size
 static void FlushScreenTextures(void)
 {
-	pglDeleteTextures(1, &ScreenTexture);
-	pglDeleteTextures(1, &FinalScreenTexture);
-	pglDeleteTextures(1, &WipeStartTexture);
-	pglDeleteTextures(1, &WipeEndTexture);
-
-	ScreenTexture = FinalScreenTexture = 0;
-	WipeStartTexture = WipeEndTexture = 0;
+	GLTexture_FlushScreenTextures();
 }
 
 // Create screen to fade from
@@ -1383,7 +1199,7 @@ static void DrawIntermissionBG(void)
 	float xfix, yfix;
 	INT32 texsize = 2048;
 
-	const float GPU_POSTIMGVERTS[12] =
+	const float screenVerts[12] =
 	{
 		-1.0f, -1.0f, 1.0f,
 		-1.0f, 1.0f, 1.0f,
@@ -1416,10 +1232,10 @@ static void DrawIntermissionBG(void)
 	pglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
 
 	pglBindTexture(GL_TEXTURE_2D, ScreenTexture);
-	pglColor4ubv(white);
+	GLState_SetColorUBV(white);
 
 	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
-	pglVertexPointer(3, GL_FLOAT, 0, GPU_POSTIMGVERTS);
+	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
 	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
 	CurrentTexture = ScreenTexture;
@@ -1433,7 +1249,7 @@ static void DoScreenWipe(void)
 
 	INT32 fademaskdownloaded = CurrentTexture; // the fade mask that has been set
 
-	const float GPU_POSTIMGVERTS[12] =
+	const float screenVerts[12] =
 	{
 		-1.0f, -1.0f, 1.0f,
 		-1.0f, 1.0f, 1.0f,
@@ -1463,7 +1279,7 @@ static void DoScreenWipe(void)
 	xfix = 1/((float)(texsize)/((float)((GPUScreenWidth))));
 	yfix = 1/((float)(texsize)/((float)((GPUScreenHeight))));
 
-	// const float GPU_POSTIMGVERTS[12]
+	// const float screenVerts[12]
 
 	// float fix[8];
 	fix[0] = 0.0f;
@@ -1482,9 +1298,9 @@ static void DoScreenWipe(void)
 
 	// Draw the original screen
 	pglBindTexture(GL_TEXTURE_2D, WipeStartTexture);
-	pglColor4ubv(white);
+	GLState_SetColorUBV(white);
 	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
-	pglVertexPointer(3, GL_FLOAT, 0, GPU_POSTIMGVERTS);
+	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
 	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
 	SetBlend(PF_Modulated|PF_Translucent|PF_NoDepthTest);
@@ -1505,7 +1321,7 @@ static void DoScreenWipe(void)
 
 	pglClientActiveTexture(GL_TEXTURE0);
 	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
-	pglVertexPointer(3, GL_FLOAT, 0, GPU_POSTIMGVERTS);
+	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
 	pglClientActiveTexture(GL_TEXTURE1);
 	pglEnableClientState(GL_TEXTURE_COORD_ARRAY);
 	pglTexCoordPointer(2, GL_FLOAT, 0, defaultST);
@@ -1593,7 +1409,7 @@ static void DrawScreenFinalTexture(int width, int height)
 	ClearBuffer(true, false, &clearColour);
 	pglBindTexture(GL_TEXTURE_2D, FinalScreenTexture);
 
-	pglColor4ubv(white);
+	GLState_SetColorUBV(white);
 
 	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
 	pglVertexPointer(3, GL_FLOAT, 0, off);
@@ -1658,13 +1474,14 @@ static void CleanShaders(void)
 
 struct GPURenderingAPI GLInterfaceAPI = {
 	Init,
-	NULL,
 
+	SetInitialStates,
+	SetModelView,
 	SetState,
 	SetTransform,
 	SetBlend,
 	SetPalette,
-	ClearBuffer,
+	SetDepthBuffer,
 
 	DrawPolygon,
 	DrawIndexedTriangles,
@@ -1683,6 +1500,7 @@ struct GPURenderingAPI GLInterfaceAPI = {
 
 	ReadRect,
 	GClipRect,
+	ClearBuffer,
 
 	MakeScreenTexture,
 	MakeScreenFinalTexture,
@@ -1691,6 +1509,7 @@ struct GPURenderingAPI GLInterfaceAPI = {
 	StartScreenWipe,
 	EndScreenWipe,
 	DoScreenWipe,
+	NULL,
 	DrawIntermissionBG,
 	DrawScreenFinalTexture,
 
@@ -1705,82 +1524,4 @@ struct GPURenderingAPI GLInterfaceAPI = {
 	LoadCustomShader,
 };
 
-// -----------------+
-// SetModelView     :
-// -----------------+
-void SetModelView(GLint w, GLint h)
-{
-//	GL_DBG_Printf("SetModelView(): %dx%d\n", (int)w, (int)h);
-
-	// The screen textures need to be flushed if the width or height change so that they be remade for the correct size
-	if (GPUScreenWidth != w || GPUScreenHeight != h)
-		FlushScreenTextures();
-
-	GPUScreenWidth = w;
-	GPUScreenHeight = h;
-
-	pglViewport(0, 0, w, h);
-
-	pglMatrixMode(GL_PROJECTION);
-	pglLoadIdentity();
-
-	pglMatrixMode(GL_MODELVIEW);
-	pglLoadIdentity();
-
-	GLPerspective(FIELD_OF_VIEW, ASPECT_RATIO);
-
-	// added for new coronas' code (without depth buffer)
-	pglGetIntegerv(GL_VIEWPORT, SceneViewport);
-	pglGetFloatv(GL_PROJECTION_MATRIX, ProjectionMatrix);
-}
-
-
-// -----------------+
-// SetStates        : Set permanent states
-// -----------------+
-void SetStates(void)
-{
-#ifdef GL_LIGHT_MODEL_AMBIENT
-	GLfloat LightDiffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
-#endif
-
-	pglEnable(GL_TEXTURE_2D);      // two-dimensional texturing
-	pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
-
-	pglEnable(GL_BLEND);           // enable color blending
-	pglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
-
-	pglEnable(GL_ALPHA_TEST);
-	pglAlphaFunc(GL_NOTEQUAL, 0.0f);
-
-	pglEnable(GL_DEPTH_TEST);    // check the depth buffer
-	pglDepthMask(GL_TRUE);             // enable writing to depth buffer
-
-	pglClearDepth(1.0f);
-	pglDepthRange(0.0f, 1.0f);
-	pglDepthFunc(GL_LEQUAL);
-
-	// Hurdler: not necessary, is it?
-	pglShadeModel(GL_SMOOTH);      // iterate vertice colors
-
-	// this set CurrentPolyFlags to the actual configuration
-	CurrentPolyFlags = 0xFFFFFFFF;
-	SetBlend(0);
-
-	CurrentTexture = 0;
-	SetNoTexture();
-
-	pglPolygonOffset(-1.0f, -1.0f);
-
-	pglLoadIdentity();
-	pglScalef(1.0f, 1.0f, -1.0f);
-	pglGetFloatv(GL_MODELVIEW_MATRIX, ModelMatrix); // added for new coronas' code (without depth buffer)
-
-	// Lighting for models
-#ifdef GL_LIGHT_MODEL_AMBIENT
-	pglLightModelfv(GL_LIGHT_MODEL_AMBIENT, LightDiffuse);
-	pglEnable(GL_LIGHT0);
-#endif
-}
-
 #endif //HWRENDER
diff --git a/src/hardware/r_opengl/r_opengl.h b/src/hardware/r_opengl/r_opengl.h
index 6c864373019dba29ab20e0bb29d04e2445edb0a8..43588ddd79bf6b51db709f1968f50b22c027ead0 100644
--- a/src/hardware/r_opengl/r_opengl.h
+++ b/src/hardware/r_opengl/r_opengl.h
@@ -13,30 +13,10 @@
 #ifndef _R_OPENGL_H_
 #define _R_OPENGL_H_
 
-#ifdef HAVE_SDL
-#define _MATH_DEFINES_DEFINED
-
-#ifdef _MSC_VER
-#pragma warning(disable : 4214 4244)
-#endif
-
-#ifndef HAVE_GLES2
-#include "SDL_opengl.h" //Alam_GBC: Simple, yes?
-#endif
-
-#ifdef _MSC_VER
-#pragma warning(default : 4214 4244)
-#endif
-
-#else
-#include <GL/gl.h>
-#include <GL/glu.h>
-
-#ifdef STATIC_OPENGL // Because of the 1.3 functions, you'll need GLext to compile it if static
+#if !defined(HAVE_SDL) && defined(STATIC_OPENGL) // Because of the 1.3 functions, you'll need GLext to compile it if static
 #define GL_GLEXT_PROTOTYPES
 #include <GL/glext.h>
 #endif
-#endif
 
 #define  _CREATE_DLL_  // necessary for Unix AND Windows
 #include "../../doomdef.h"
diff --git a/src/hardware/shaders/gl_shaders.c b/src/hardware/shaders/gl_shaders.c
index 756300cd7b41e3e7d66791720ec2fcb98a243c32..76c5517a29457ae8bd475491aa4b0ed07d4136bb 100644
--- a/src/hardware/shaders/gl_shaders.c
+++ b/src/hardware/shaders/gl_shaders.c
@@ -10,11 +10,26 @@
 /// \brief OpenGL shaders
 
 #include "gl_shaders.h"
+
 #include "../r_glcommon/r_glcommon.h"
 
+#include "../hw_dll.h"
+
+#ifdef HAVE_GLES
+#include "../r_gles/r_gles.h"
+#else
+#include "../r_opengl/r_opengl.h"
+#endif
+
+#include "../../doomdef.h"
+#include "../../doomtype.h"
+#include "../../doomdata.h"
+
 GLboolean ShadersEnabled = GL_FALSE;
 INT32 ShadersAllowed = GPU_SHADEROPTION_OFF;
 
+#ifdef GL_SHADERS
+
 typedef GLuint (R_GL_APIENTRY *PFNglCreateShader)       (GLenum);
 typedef void   (R_GL_APIENTRY *PFNglShaderSource)       (GLuint, GLsizei, const GLchar**, GLint*);
 typedef void   (R_GL_APIENTRY *PFNglCompileShader)      (GLuint);
@@ -115,6 +130,12 @@ static struct {
 	// Sky shader
 	{GLSL_DEFAULT_VERTEX_SHADER, GLSL_SKY_FRAGMENT_SHADER},
 
+#ifdef HAVE_GLES2
+	// Fade mask shaders
+	{GLSL_FADEMASK_VERTEX_SHADER, GLSL_FADEMASK_FRAGMENT_SHADER},
+	{GLSL_FADEMASK_VERTEX_SHADER, GLSL_FADEMASK_ADDITIVEANDSUBTRACTIVE_FRAGMENT_SHADER},
+#endif
+
 	{NULL, NULL},
 };
 
@@ -434,7 +455,7 @@ static boolean Shader_CompileProgram(FShaderObject *shader, GLint i, const GLcha
 	shader->attributes[attrib_texcoord]     = GETATTRIB("a_texcoord");
 	shader->attributes[attrib_normal]       = GETATTRIB("a_normal");
 	shader->attributes[attrib_colors]       = GETATTRIB("a_colors");
-	shader->attributes[attrib_fadetexcoord] = GETATTRIB("a_fadetexcoord");
+	shader->attributes[attrib_fadetexcoord] = GETATTRIB("a_fademasktexcoord");
 
 #undef GETATTRIB
 
@@ -503,11 +524,16 @@ boolean Shader_Compile(void)
 	return true;
 }
 
-static void Shader_SetIfChanged(FShaderObject *shader)
+void Shader_SetProgram(void)
+{
+	pglUseProgram(ShaderState.current->program);
+}
+
+void Shader_SetProgramIfChanged(void)
 {
 	if (ShaderState.changed)
 	{
-		pglUseProgram(shader->program);
+		pglUseProgram(ShaderState.current->program);
 		ShaderState.changed = false;
 	}
 }
@@ -519,7 +545,7 @@ void Shader_SetTransform(void)
 	if (!shader)
 		return;
 
-	Shader_SetIfChanged(shader);
+	Shader_SetProgramIfChanged();
 
 	if (memcmp(projMatrix, shader->projMatrix, sizeof(fmatrix4_t)))
 	{
@@ -553,7 +579,7 @@ void Shader_SetUniforms(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAFloat *t
 			return;
 		}
 
-		Shader_SetIfChanged(shader);
+		Shader_SetProgramIfChanged();
 
 		// Color uniforms can be left NULL and will be set to white (1.0f, 1.0f, 1.0f, 1.0f)
 		if (poly == NULL)
@@ -579,7 +605,6 @@ void Shader_SetUniforms(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAFloat *t
 			if (uniform != -1) \
 				function (uniform, a, b, c, d);
 
-		// polygon
 		UNIFORM_4(shader->uniforms[uniform_poly_color], poly->red, poly->green, poly->blue, poly->alpha, pglUniform4f);
 		UNIFORM_4(shader->uniforms[uniform_tint_color], tint->red, tint->green, tint->blue, tint->alpha, pglUniform4f);
 		UNIFORM_4(shader->uniforms[uniform_fade_color], fade->red, fade->green, fade->blue, fade->alpha, pglUniform4f);
@@ -606,8 +631,10 @@ void Shader_SetSampler(EShaderUniform uniform, GLint value)
 	if (!shader)
 		return;
 
-	Shader_SetIfChanged(shader);
+	Shader_SetProgramIfChanged();
 
 	if (shader->uniforms[uniform] != -1)
 		pglUniform1i(shader->uniforms[uniform], value);
 }
+
+#endif // GL_SHADERS
diff --git a/src/hardware/shaders/gl_shaders.h b/src/hardware/shaders/gl_shaders.h
index f925c291fa52196c9ec9093f202967526a774217..de43f0904d07af7ffe8076d38da40f3bf309c002 100644
--- a/src/hardware/shaders/gl_shaders.h
+++ b/src/hardware/shaders/gl_shaders.h
@@ -93,7 +93,7 @@ typedef struct FShaderObject
 
 	GLint uniforms[uniform_max+1];
 #ifdef HAVE_GLES2
-	GLint attributes[attribute_max+1];
+	GLint attributes[attrib_max+1];
 #endif
 
 #ifdef HAVE_GLES2
@@ -120,6 +120,9 @@ extern FShaderState ShaderState;
 void Shader_Set(int type);
 void Shader_UnSet(void);
 
+void Shader_SetProgram(void);
+void Shader_SetProgramIfChanged(void);
+
 #ifdef HAVE_GLES2
 void Shader_SetTransform(void);
 #endif
diff --git a/src/hardware/shaders/shaders_gles2.h b/src/hardware/shaders/shaders_gles2.h
index 3b2e99ff36fd19cae75979bd69f4e1ae7a29e4f0..8481d95a5f56bab32ff407bf3bcefa51ed79d6d8 100644
--- a/src/hardware/shaders/shaders_gles2.h
+++ b/src/hardware/shaders/shaders_gles2.h
@@ -46,13 +46,13 @@
 	"attribute vec2 a_texcoord;\n" \
 	"attribute vec2 a_fademasktexcoord;\n" \
 	"varying vec2 v_texcoord;\n" \
-	"varying vec3 v_fademasktexcoord;\n" \
+	"varying vec2 v_fademasktexcoord;\n" \
 	"uniform mat4 u_projection;\n" \
 	"void main()\n" \
 	"{\n" \
 		"gl_Position = u_projection * vec4(a_position, 1.0f);\n" \
 		"v_texcoord = vec2(a_texcoord.x, a_texcoord.y);\n" \
-		"v_fademasktexcoord = vec2(a_fadetex.x, a_fadetex.y);\n" \
+		"v_fademasktexcoord = vec2(a_fademasktexcoord.x, a_fademasktexcoord.y);\n" \
 	"}\0"
 
 // replicates the way fixed function lighting is used by the model lighting option,
@@ -84,6 +84,7 @@
 // ==================
 
 #define GLSL_BASE_SAMPLER "uniform sampler2D t_texsampler;\n"
+
 #define GLSL_BASE_UNIFORMS \
 	GLSL_BASE_SAMPLER \
 	"uniform vec4 poly_color;\n" \
@@ -112,7 +113,7 @@
 
 #define GLSL_FADEMASK_BASE_IN \
 	"varying vec2 v_texcoord;\n" \
-	"varying vec3 v_fademasktexcoord;\n" \
+	"varying vec2 v_fademasktexcoord;\n" \
 	"uniform sampler2D t_startscreen;\n" \
 	"uniform sampler2D t_endscreen;\n" \
 	"uniform sampler2D t_fademask;\n"
diff --git a/src/m_anigif.c b/src/m_anigif.c
index 41f99254eff59fea3d80f7592168723d9de7a975..33afee2a4c1167274eec4aff8b90f8815de343b5 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -512,7 +512,7 @@ static void GIF_rgbconvert(UINT8 *linear, UINT8 *scr)
 {
 	UINT8 r, g, b;
 	size_t src = 0, dest = 0;
-	size_t size = (vid.width * vid.height * 3);
+	size_t size = (vid.width * vid.height * SCREENSHOT_BITS);
 
 	InitColorLUT(&gif_colorlookup, (gif_localcolortable) ? gif_framepalette : gif_headerpalette, true);
 
@@ -522,7 +522,7 @@ static void GIF_rgbconvert(UINT8 *linear, UINT8 *scr)
 		g = (UINT8)linear[src + 1];
 		b = (UINT8)linear[src + 2];
 		scr[dest] = GetColorLUTDirect(&gif_colorlookup, r, g, b);
-		src += (3 * scrbuf_downscaleamt);
+		src += (SCREENSHOT_BITS * scrbuf_downscaleamt);
 		dest += scrbuf_downscaleamt;
 	}
 }
@@ -569,7 +569,7 @@ static void GIF_framewrite(void)
 #ifdef HWRENDER
 		else if (rendermode == render_opengl)
 		{
-			UINT8 *linear = HWR_GetScreenshot();
+			UINT8 *linear = HWR_GetScreenBuffer();
 			GIF_rgbconvert(linear, movie_screen);
 			free(linear);
 		}
@@ -585,7 +585,7 @@ static void GIF_framewrite(void)
 		// Copy the current OpenGL frame into the base screen
 		if (rendermode == render_opengl)
 		{
-			UINT8 *linear = HWR_GetScreenshot();
+			UINT8 *linear = HWR_GetScreenBuffer();
 			GIF_rgbconvert(linear, screens[0]);
 			free(linear);
 		}
diff --git a/src/m_misc.c b/src/m_misc.c
index d97d8f94be36972a82880f79316ac8d2f7149131..fba5697e3df2e2cbd9e5eef3834cfc918e0737ed 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -49,6 +49,7 @@
 
 #ifdef HWRENDER
 #include "hardware/hw_main.h"
+#include "hardware/hw_gpu.h"	// GPUInterface_GetAPIName
 #endif
 
 #ifdef HAVE_SDL
@@ -768,7 +769,12 @@ static void M_PNGhdr(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png_
 	}
 	else
 	{
-		png_set_IHDR(png_ptr, png_info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB,
+		png_set_IHDR(png_ptr, png_info_ptr, width, height, 8,
+#ifdef SCREENSHOT_USE_RGBA
+		PNG_COLOR_TYPE_RGBA,
+#else
+		PNG_COLOR_TYPE_RGB,
+#endif
 		 png_interlace, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
 		png_write_info_before_PLTE(png_ptr, png_info_ptr);
 		png_set_compression_strategy(png_ptr, Z_FILTERED);
@@ -810,7 +816,7 @@ static void M_PNGText(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png
 			strcpy(rendermodetxt, "Software");
 			break;
 		case render_opengl:
-			strcpy(rendermodetxt, "OpenGL");
+			strcpy(rendermodetxt, GPUInterface_GetAPIName());
 			break;
 		default: // Just in case
 			strcpy(rendermodetxt, "None");
@@ -1257,7 +1263,7 @@ void M_SaveFrame(void)
 				}
 #ifdef HWRENDER
 				else
-					linear = HWR_GetScreenshot();
+					linear = HWR_GetScreenBuffer();
 #endif
 				M_PNGFrame(apng_ptr, apng_info_ptr, (png_bytep)linear);
 #ifdef HWRENDER
@@ -1518,7 +1524,7 @@ void M_ScreenShot(void)
   * The screenshot is saved as "srb2xxxx.png" where xxxx is the lowest
   * four-digit number for which a file does not already exist.
   *
-  * \sa HWR_ScreenShot
+  * \sa HWR_TakeScreenshot
   */
 void M_DoScreenShot(void)
 {
@@ -1572,7 +1578,7 @@ void M_DoScreenShot(void)
 	// save the pcx file
 #ifdef HWRENDER
 	if (rendermode == render_opengl)
-		ret = HWR_Screenshot(va(pandf,pathname,freename));
+		ret = HWR_TakeScreenshot(va(pandf,pathname,freename));
 	else
 #endif
 	{
diff --git a/src/m_misc.h b/src/m_misc.h
index dbded37d0a47fdc81c0488098e5bf7ac8e677143..7daf1342b6b26464b461c61f5a124b5edcf22d10 100644
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -39,6 +39,13 @@ void M_StartMovie(void);
 void M_SaveFrame(void);
 void M_StopMovie(void);
 
+#ifdef HAVE_GLES
+#define SCREENSHOT_USE_RGBA
+#define SCREENSHOT_BITS 4
+#else
+#define SCREENSHOT_BITS 3
+#endif
+
 // the file where game vars and settings are saved
 #define CONFIGFILENAME "config.cfg"
 
diff --git a/src/sdl/Makefile.cfg b/src/sdl/Makefile.cfg
index 86957caaffc06e7acd16b8de8a12f57f80fe6b7d..1ef171dff51f93177f585330569d311579e58651 100644
--- a/src/sdl/Makefile.cfg
+++ b/src/sdl/Makefile.cfg
@@ -50,7 +50,7 @@ endif
 	OPTS+=-DDIRECTFULLSCREEN -DHAVE_SDL
 
 ifndef NOHW
-	OBJS+=$(OBJDIR)/r_opengl.o $(OBJDIR)/r_glcommon.o $(OBJDIR)/gl_shaders.o $(OBJDIR)/ogl_sdl.o
+	OBJS+=$(OBJDIR)/ogl_sdl.o
 endif
 
 ifdef NOMIXER
diff --git a/src/sdl/ogl_sdl.c b/src/sdl/ogl_sdl.c
index 0df9fc051d603591839d852d0267c358381bf68b..56e41f3e4cf2bb7b20f6664764fa92f5ec92aef3 100644
--- a/src/sdl/ogl_sdl.c
+++ b/src/sdl/ogl_sdl.c
@@ -36,11 +36,19 @@
 #include "../doomdef.h"
 
 #ifdef HWRENDER
+
+#ifdef HAVE_GLES
+#include "../hardware/r_gles/r_gles.h"
+#else
 #include "../hardware/r_opengl/r_opengl.h"
+#endif
+
 #include "../hardware/hw_main.h"
+
 #include "ogl_sdl.h"
-#include "../i_system.h"
 #include "hwsym_sdl.h"
+
+#include "../i_system.h"
 #include "../m_argv.h"
 
 #ifdef DEBUG_TO_FILE
@@ -54,19 +62,6 @@
 #include <sys/stat.h>
 #endif
 
-#ifdef USE_WGL_SWAP
-PFNWGLEXTSWAPCONTROLPROC wglSwapIntervalEXT = NULL;
-#else
-typedef int (*PFNGLXSWAPINTERVALPROC) (int);
-PFNGLXSWAPINTERVALPROC glXSwapIntervalSGIEXT = NULL;
-#endif
-
-#ifndef STATIC_OPENGL
-PFNglClear pglClear;
-PFNglGetIntegerv pglGetIntegerv;
-PFNglGetString pglGetString;
-#endif
-
 /**	\brief SDL video display surface
 */
 void *GLUhandle = NULL;
@@ -81,12 +76,19 @@ void *GLBackend_GetFunction(const char *proc)
 		else
 			return NULL;
 	}
+
 	return SDL_GL_GetProcAddress(proc);
 }
 
 boolean GLBackend_LoadLibrary(void)
 {
-#ifndef STATIC_OPENGL
+#if defined(__ANDROID__)
+	if (SDL_GL_LoadLibrary(NULL) != 0)
+	{
+		CONS_Alert(CONS_ERROR, "Could not load OpenGL Library: %s\nFalling back to Software mode.\n", SDL_GetError());
+		return false;
+	}
+#elif !defined(STATIC_OPENGL)
 	const char *OGLLibname = NULL;
 	const char *GLULibname = NULL;
 
@@ -138,9 +140,9 @@ boolean GLBackend_LoadLibrary(void)
 		CONS_Alert(CONS_ERROR, "Could not load GLU Library\n");
 		CONS_Alert(CONS_ERROR, "If you know what is the GLU library's name, use -GLUlib\n");
 	}
+#endif
 
 	return true;
-#endif
 }
 
 /**	\brief	The OglSdlSurface function
@@ -153,19 +155,20 @@ boolean GLBackend_LoadLibrary(void)
 */
 boolean OglSdlSurface(INT32 w, INT32 h)
 {
-	INT32 cbpp = cv_scr_depth.value < 16 ? 16 : cv_scr_depth.value;
-
+#if !defined(__ANDROID__)
 	GLBackend_InitContext();
 	GLBackend_LoadContextFunctions();
+#ifdef HAVE_GLES2
+	GLBackend_InitShaders();
+#endif
+#endif
 
-	SetSurface(w, h);
+	GLState_SetSurface(w, h);
 
 	glanisotropicmode_cons_t[1].value = GPUMaximumAnisotropy;
-
 	SDL_GL_SetSwapInterval(cv_vidwait.value ? 1 : 0);
 
 	HWR_Startup();
-	GPUTextureFormat = cbpp > 16 ? GL_RGBA : GL_RGB5_A1;
 
 	return true;
 }
diff --git a/src/w_wad.c b/src/w_wad.c
index 2429eaf927f9e9ffe9dd777e9a1e9589e1f18bb8..6566800c03ce0594898d4bb50026b5bc55eb3f67 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -1747,6 +1747,9 @@ void *W_CachePatchNum(lumpnum_t lumpnum, INT32 tag)
 
 void W_UnlockCachedPatch(void *patch)
 {
+	if (!patch)
+		return;
+
 	// The hardware code does its own memory management, as its patches
 	// have different lifetimes from software's.
 #ifdef HWRENDER
@@ -2144,7 +2147,7 @@ int W_VerifyNMUSlumps(const char *filename, boolean exit_on_error)
 		{"LT", 2}, // Titlecard changes
 
 		{"SLID", 4}, // Continue
-		{"CONT", 4}, 
+		{"CONT", 4},
 
 		{"MINICAPS", 8}, // NiGHTS graphics here and below
 		{"BLUESTAT", 8}, // Sphere status