diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6e3da126bc356e5c20e4950c91991e85cff5c872..962f8f87ac3bcd50b541b2d5f76c3e2b761e740e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -127,7 +127,8 @@ set(SRB2_CORE_RENDER_SOURCES
 	r_sky.c
 	r_splats.c
 	r_things.c
-	r_patch.c
+	r_textures.c
+	r_picformats.c
 	r_portal.c
 
 	r_bsp.h
@@ -143,7 +144,8 @@ set(SRB2_CORE_RENDER_SOURCES
 	r_splats.h
 	r_state.h
 	r_things.h
-	r_patch.h
+	r_textures.h
+	r_picformats.h
 	r_portal.h
 )
 
diff --git a/src/Makefile b/src/Makefile
index 27151cd3ec33fd0c79421c212addc016efe66685..ee0d5062546c498af81dbefb9ea0bbf0111d03c2 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -517,7 +517,8 @@ OBJS:=$(i_main_o) \
 		$(OBJDIR)/r_sky.o    \
 		$(OBJDIR)/r_splats.o \
 		$(OBJDIR)/r_things.o \
-		$(OBJDIR)/r_patch.o \
+		$(OBJDIR)/r_textures.o \
+		$(OBJDIR)/r_picformats.o \
 		$(OBJDIR)/r_portal.o \
 		$(OBJDIR)/screen.o   \
 		$(OBJDIR)/v_video.o  \
diff --git a/src/dehacked.c b/src/dehacked.c
index 4c7ffaa9627f6ca1670362cb0b5b72d9cd3d2145..600db7dbf54c4d0a8e2a8c78a6b2f66096fd188c 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -29,8 +29,9 @@
 #include "p_local.h" // for var1 and var2, and some constants
 #include "p_setup.h"
 #include "r_data.h"
+#include "r_textures.h"
 #include "r_draw.h"
-#include "r_patch.h"
+#include "r_picformats.h"
 #include "r_things.h" // R_Char2Frame
 #include "r_sky.h"
 #include "fastcmp.h"
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index ed3b6afee8010f3a8a7ce6e6f4bdb4d2eadcafce..bf99c584ed55733eae87b5afa37968a886bd0bcc 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -20,11 +20,12 @@
 #include "../doomstat.h"    //gamemode
 #include "../i_video.h"     //rendermode
 #include "../r_data.h"
+#include "../r_textures.h"
 #include "../w_wad.h"
 #include "../z_zone.h"
 #include "../v_video.h"
 #include "../r_draw.h"
-#include "../r_patch.h"
+#include "../r_picformats.h"
 #include "../p_setup.h"
 
 INT32 patchformat = GL_TEXFMT_AP_88; // use alpha for holes
@@ -99,6 +100,10 @@ static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipm
 			count--;
 
 			texel = source[yfrac>>FRACBITS];
+			alpha = 0xFF;
+			// Make pixel transparent if chroma keyed
+			if ((mipmap->flags & TF_CHROMAKEYED) && (texel == HWR_PATCHES_CHROMAKEY_COLORINDEX))
+				alpha = 0x00;
 
 			//Hurdler: 25/04/2000: now support colormap in hardware mode
 			if (mipmap->colormap)
@@ -211,17 +216,15 @@ static void HWR_DrawFlippedColumnInCache(const column_t *patchcol, UINT8 *block,
 			count--;
 
 			texel = source[yfrac>>FRACBITS];
+			alpha = 0xFF;
+			// Make pixel transparent if chroma keyed
+			if ((mipmap->flags & TF_CHROMAKEYED) && (texel == HWR_PATCHES_CHROMAKEY_COLORINDEX))
+				alpha = 0x00;
 
 			//Hurdler: 25/04/2000: now support colormap in hardware mode
 			if (mipmap->colormap)
 				texel = mipmap->colormap[texel];
 
-			// If the mipmap is chromakeyed, check if the texel's color
-			// is equivalent to the chroma key's color index.
-			alpha = 0xff;
-			if ((mipmap->flags & TF_CHROMAKEYED) && (texel == HWR_PATCHES_CHROMAKEY_COLORINDEX))
-				alpha = 0x00;
-
 			// hope compiler will get this switch out of the loops (dreams...)
 			// gcc do it ! but vcc not ! (why don't use cygwin gcc for win32 ?)
 			// Alam: SRB2 uses Mingw, HUGS
@@ -508,13 +511,17 @@ static void HWR_GenerateTexture(INT32 texnum, GLMapTexture_t *grtex)
 		realpatch = (patch_t *)pdata;
 
 #ifndef NO_PNG_LUMPS
-		if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
-			realpatch = R_PNGToPatch((UINT8 *)realpatch, lumplength, NULL);
+		if (Picture_IsLumpPNG((UINT8 *)realpatch, lumplength))
+		{
+			// Dummy variables.
+			INT32 pngwidth, pngheight;
+			realpatch = (patch_t *)Picture_PNGConvert(pdata, PICFMT_PATCH, &pngwidth, &pngheight, NULL, NULL, lumplength, NULL, 0);
+		}
 		else
 #endif
 #ifdef WALLFLATS
 		if (texture->type == TEXTURETYPE_FLAT)
-			realpatch = R_FlatToPatch(pdata, texture->width, texture->height, 0, 0, NULL, false);
+			realpatch = (patch_t *)Picture_Convert(PICFMT_FLAT, pdata, PICFMT_PATCH, 0, NULL, texture->width, texture->height, 0, 0, 0);
 		else
 #endif
 		{
@@ -550,8 +557,13 @@ void HWR_MakePatch (const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipm
 #ifndef NO_PNG_LUMPS
 	// lump is a png so convert it
 	size_t len = W_LumpLengthPwad(grPatch->wadnum, grPatch->lumpnum);
-	if ((patch != NULL) && R_IsLumpPNG((const UINT8 *)patch, len))
-		patch = R_PNGToPatch((const UINT8 *)patch, len, NULL);
+	if ((patch != NULL) && Picture_IsLumpPNG((const UINT8 *)patch, len))
+	{
+		// Dummy variables.
+		INT32 pngwidth, pngheight;
+		INT16 topoffset, leftoffset;
+		patch = (patch_t *)Picture_PNGConvert((const UINT8 *)patch, PICFMT_PATCH, &pngwidth, &pngheight, &topoffset, &leftoffset, len, NULL, 0);
+	}
 #endif
 
 	// don't do it twice (like a cache)
@@ -788,6 +800,8 @@ static void HWR_CacheFlat(GLMipmap_t *grMipmap, lumpnum_t flatlumpnum)
 static void HWR_CacheTextureAsFlat(GLMipmap_t *grMipmap, INT32 texturenum)
 {
 	UINT8 *flat;
+	UINT8 *converted;
+	size_t size;
 
 	// setup the texture info
 	grMipmap->format = GL_TEXFMT_P_8;
@@ -795,11 +809,12 @@ static void HWR_CacheTextureAsFlat(GLMipmap_t *grMipmap, INT32 texturenum)
 
 	grMipmap->width  = (UINT16)textures[texturenum]->width;
 	grMipmap->height = (UINT16)textures[texturenum]->height;
+	size = (grMipmap->width * grMipmap->height);
 
-	flat = Z_Malloc(grMipmap->width * grMipmap->height, PU_HWRCACHE, &grMipmap->data);
-	memset(flat, TRANSPARENTPIXEL, grMipmap->width * grMipmap->height);
-
-	R_TextureToFlat(texturenum, flat);
+	flat = Z_Malloc(size, PU_HWRCACHE, &grMipmap->data);
+	converted = (UINT8 *)Picture_TextureToFlat(texturenum);
+	M_Memcpy(flat, converted, size);
+	Z_Free(converted);
 }
 
 // Download a Doom 'flat' to the hardware cache and make it ready for use
@@ -837,7 +852,7 @@ void HWR_GetLevelFlat(levelflat_t *levelflat)
 		INT32 texturenum = levelflat->u.texture.num;
 #ifdef PARANOIA
 		if ((unsigned)texturenum >= gl_numtextures)
-			I_Error("HWR_GetLevelFlat: texturenum >= numtextures\n");
+			I_Error("HWR_GetLevelFlat: texturenum >= numtextures");
 #endif
 
 		// Who knows?
@@ -860,6 +875,53 @@ void HWR_GetLevelFlat(levelflat_t *levelflat)
 		// The system-memory data can be purged now.
 		Z_ChangeTag(grtex->mipmap.data, PU_HWRCACHE_UNLOCKED);
 	}
+	else if (levelflat->type == LEVELFLAT_PATCH)
+	{
+		GLPatch_t *patch = W_CachePatchNum(levelflat->u.flat.lumpnum, PU_CACHE);
+		levelflat->width = (UINT16)SHORT(patch->width);
+		levelflat->height = (UINT16)SHORT(patch->height);
+		HWR_GetPatch(patch);
+	}
+#ifndef NO_PNG_LUMPS
+	else if (levelflat->type == LEVELFLAT_PNG)
+	{
+		INT32 pngwidth, pngheight;
+		GLMipmap_t *mipmap = levelflat->mipmap;
+		UINT8 *flat;
+		size_t size;
+
+		// Cache the picture.
+		if (!levelflat->picture)
+		{
+			levelflat->picture = Picture_PNGConvert(W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_CACHE), PICFMT_FLAT, &pngwidth, &pngheight, NULL, NULL, W_LumpLength(levelflat->u.flat.lumpnum), NULL, 0);
+			levelflat->width = (UINT16)pngwidth;
+			levelflat->height = (UINT16)pngheight;
+		}
+
+		// Make the mipmap.
+		if (mipmap == NULL)
+		{
+			mipmap = Z_Calloc(sizeof(GLMipmap_t), PU_LEVEL, NULL);
+			mipmap->format = GL_TEXFMT_P_8;
+			mipmap->flags = TF_WRAPXY|TF_CHROMAKEYED;
+			levelflat->mipmap = mipmap;
+		}
+
+		if (!mipmap->data && !mipmap->downloaded)
+		{
+			mipmap->width = levelflat->width;
+			mipmap->height = levelflat->height;
+			size = (mipmap->width * mipmap->height);
+			flat = Z_Malloc(size, PU_LEVEL, &mipmap->data);
+			if (levelflat->picture == NULL)
+				I_Error("HWR_GetLevelFlat: levelflat->picture == NULL");
+			M_Memcpy(flat, levelflat->picture, size);
+		}
+
+		// Tell the hardware driver to bind the current texture to the flat's mipmap
+		HWD.pfnSetTexture(mipmap);
+	}
+#endif
 	else // set no texture
 		HWR_SetCurrentTexture(NULL);
 }
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index e18ed31ce0463c77927a0c4fae5e19bca341f54d..cbfcfff870e3ed4b181f989218d55e852bb8c0e2 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -25,7 +25,7 @@
 #include "../p_local.h"
 #include "../p_setup.h"
 #include "../r_local.h"
-#include "../r_patch.h"
+#include "../r_picformats.h"
 #include "../r_bsp.h"
 #include "../d_clisrv.h"
 #include "../w_wad.h"
@@ -358,7 +358,6 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 	float fflatwidth = 64.0f, fflatheight = 64.0f;
 	INT32 flatflag = 63;
 	boolean texflat = false;
-	size_t len;
 	float scrollx = 0.0f, scrolly = 0.0f;
 	angle_t angle = 0;
 	FSurfaceInfo    Surf;
@@ -413,16 +412,9 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 	// set texture for polygon
 	if (levelflat != NULL)
 	{
-		if (levelflat->type == LEVELFLAT_TEXTURE)
+		if (levelflat->type == LEVELFLAT_FLAT)
 		{
-			fflatwidth = textures[levelflat->u.texture.num]->width;
-			fflatheight = textures[levelflat->u.texture.num]->height;
-			texflat = true;
-		}
-		else if (levelflat->type == LEVELFLAT_FLAT)
-		{
-			len = W_LumpLength(levelflat->u.flat.lumpnum);
-
+			size_t len = W_LumpLength(levelflat->u.flat.lumpnum);
 			switch (len)
 			{
 				case 4194304: // 2048x2048 lump
@@ -447,9 +439,22 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 					fflatwidth = fflatheight = 64.0f;
 					break;
 			}
-
 			flatflag = ((INT32)fflatwidth)-1;
 		}
+		else
+		{
+			if (levelflat->type == LEVELFLAT_TEXTURE)
+			{
+				fflatwidth = textures[levelflat->u.texture.num]->width;
+				fflatheight = textures[levelflat->u.texture.num]->height;
+			}
+			else if (levelflat->type == LEVELFLAT_PATCH || levelflat->type == LEVELFLAT_PNG)
+			{
+				fflatwidth = levelflat->width;
+				fflatheight = levelflat->height;
+			}
+			texflat = true;
+		}
 	}
 	else // set no texture
 		HWR_SetCurrentTexture(NULL);
@@ -2659,7 +2664,6 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 	float fflatwidth = 64.0f, fflatheight = 64.0f;
 	INT32 flatflag = 63;
 	boolean texflat = false;
-	size_t len;
 	float scrollx = 0.0f, scrolly = 0.0f;
 	angle_t angle = 0;
 	FSurfaceInfo    Surf;
@@ -2693,16 +2697,9 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 	// set texture for polygon
 	if (levelflat != NULL)
 	{
-		if (levelflat->type == LEVELFLAT_TEXTURE)
+		if (levelflat->type == LEVELFLAT_FLAT)
 		{
-			fflatwidth = textures[levelflat->u.texture.num]->width;
-			fflatheight = textures[levelflat->u.texture.num]->height;
-			texflat = true;
-		}
-		else if (levelflat->type == LEVELFLAT_FLAT)
-		{
-			len = W_LumpLength(levelflat->u.flat.lumpnum);
-
+			size_t len = W_LumpLength(levelflat->u.flat.lumpnum);
 			switch (len)
 			{
 				case 4194304: // 2048x2048 lump
@@ -2727,9 +2724,22 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 					fflatwidth = fflatheight = 64.0f;
 					break;
 			}
-
 			flatflag = ((INT32)fflatwidth)-1;
 		}
+		else
+		{
+			if (levelflat->type == LEVELFLAT_TEXTURE)
+			{
+				fflatwidth = textures[levelflat->u.texture.num]->width;
+				fflatheight = textures[levelflat->u.texture.num]->height;
+			}
+			else if (levelflat->type == LEVELFLAT_PATCH || levelflat->type == LEVELFLAT_PNG)
+			{
+				fflatwidth = levelflat->width;
+				fflatheight = levelflat->height;
+			}
+			texflat = true;
+		}
 	}
 	else // set no texture
 		HWR_SetCurrentTexture(NULL);
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 0dee3558c99aa49328fd9c73f991d5f73e638ffa..1a184ea575c6a0045846bb3392f3f8a3bca8bd93 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -2038,9 +2038,6 @@ static void HU_DrawDemoInfo(void)
 //
 void HU_Drawer(void)
 {
-	if (needpatchrecache)
-		R_ReloadHUDGraphics();
-
 #ifndef NONET
 	// draw chat string plus cursor
 	if (chat_on)
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 830d97625df628468254c63ad80b00637a544acd..8143bda89a3928812d7be8611d1a512d56ddffda 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -17,7 +17,7 @@
 #include "p_mobj.h"
 #include "p_local.h"
 #include "z_zone.h"
-#include "r_patch.h"
+#include "r_picformats.h"
 #include "r_things.h"
 #include "r_draw.h" // R_GetColorByName
 #include "doomstat.h" // luabanks[]
diff --git a/src/m_anigif.c b/src/m_anigif.c
index 83bc3dddc0d46a682fa3df8863caf0a841de5c04..7c2bb359e081645e6ded76ec1a0f2b4b140f60e2 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -499,20 +499,22 @@ static size_t gifframe_size = 8192;
 // converts an RGB frame to a frame with a palette.
 //
 #ifdef HWRENDER
+static colorlookup_t gif_colorlookup;
+
 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);
 
-	InitColorLUT(gif_framepalette);
+	InitColorLUT(&gif_colorlookup, gif_framepalette, true);
 
 	while (src < size)
 	{
 		r = (UINT8)linear[src];
 		g = (UINT8)linear[src + 1];
 		b = (UINT8)linear[src + 2];
-		scr[dest] = colorlookup[r >> SHIFTCOLORBITS][g >> SHIFTCOLORBITS][b >> SHIFTCOLORBITS];
+		scr[dest] = GetColorLUTDirect(&gif_colorlookup, r, g, b);
 		src += (3 * scrbuf_downscaleamt);
 		dest += scrbuf_downscaleamt;
 	}
diff --git a/src/m_menu.c b/src/m_menu.c
index 9b538c66a64fc30cbaddc9ab7ed6b5d3f4b0643e..49e8211d0a66738de9d7be2296d4d9815ebb29f9 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -9005,8 +9005,6 @@ void M_ForceSaveSlotSelected(INT32 sslot)
 // ================
 // CHARACTER SELECT
 // ================
-
-// lactozilla: sometimes the renderer changes and these patches don't exist anymore
 static void M_CacheCharacterSelectEntry(INT32 i, INT32 skinnum)
 {
 	if (!(description[i].picname[0]))
@@ -9249,7 +9247,6 @@ static void M_DrawSetupChoosePlayerMenu(void)
 	INT32 x, y;
 	INT32 w = (vid.width/vid.dupx);
 
-	// lactozilla: the renderer changed so recache patches
 	if (needpatchrecache)
 		M_CacheCharacterSelect();
 
diff --git a/src/p_maputl.c b/src/p_maputl.c
index c6e064d184a722ef2c6fb371ff7e5f9767f2afcd..90718a41cbe700621c191994401925ca5ab360e3 100644
--- a/src/p_maputl.c
+++ b/src/p_maputl.c
@@ -18,6 +18,7 @@
 #include "p_local.h"
 #include "r_main.h"
 #include "r_data.h"
+#include "r_textures.h"
 #include "p_maputl.h"
 #include "p_polyobj.h"
 #include "p_slopes.h"
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 276b3eb0547341107462a373c17681364b05fcef..4f6f31803998d8cf8c19472a21d4090d779b2119 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -22,6 +22,8 @@
 #include "p_setup.h"
 #include "p_saveg.h"
 #include "r_data.h"
+#include "r_textures.h"
+#include "r_things.h"
 #include "r_skins.h"
 #include "r_state.h"
 #include "w_wad.h"
diff --git a/src/p_setup.c b/src/p_setup.c
index 302bb1947eecffc5c2fed5a48455359416f3ab23..996e9ec30d7e42eb04d9b0261f14d78f11fcdd46 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -28,7 +28,8 @@
 
 #include "r_data.h"
 #include "r_things.h" // for R_AddSpriteDefs
-#include "r_patch.h"
+#include "r_textures.h"
+#include "r_picformats.h"
 #include "r_sky.h"
 #include "r_draw.h"
 
@@ -547,6 +548,8 @@ Ploadflat (levelflat_t *levelflat, const char *flatname, boolean resize)
 
 	lumpnum_t    flatnum;
 	int       texturenum;
+	patch_t   *flatpatch;
+	size_t    lumplength;
 
 	size_t i;
 
@@ -603,7 +606,9 @@ texturefound:
 	{
 flatfound:
 		/* This could be a flat, patch, or PNG. */
-		if (R_CheckIfPatch(flatnum))
+		flatpatch = W_CacheLumpNum(flatnum, PU_CACHE);
+		lumplength = W_LumpLength(flatnum);
+		if (Picture_CheckIfPatch(flatpatch, lumplength))
 			levelflat->type = LEVELFLAT_PATCH;
 		else
 		{
@@ -613,12 +618,14 @@ flatfound:
 			FIXME: Put this elsewhere.
 			*/
 			W_ReadLumpHeader(flatnum, buffer, 8, 0);
-			if (R_IsLumpPNG(buffer, W_LumpLength(flatnum)))
+			if (Picture_IsLumpPNG(buffer, lumplength))
 				levelflat->type = LEVELFLAT_PNG;
 			else
 #endif/*NO_PNG_LUMPS*/
 				levelflat->type = LEVELFLAT_FLAT;/* phew */
 		}
+		if (flatpatch)
+			Z_Free(flatpatch);
 
 		levelflat->u.flat.    lumpnum = flatnum;
 		levelflat->u.flat.baselumpnum = LUMPERROR;
diff --git a/src/p_setup.h b/src/p_setup.h
index e7150c0ae9b5db0785a6fc6f702424065ef65f9f..ef903e103725194d058e0a8bfac1e6a929c7373b 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -37,9 +37,7 @@ enum
 	LEVELFLAT_NONE,/* HOM time my friend */
 	LEVELFLAT_FLAT,
 	LEVELFLAT_PATCH,
-#ifndef NO_PNG_LUMPS
 	LEVELFLAT_PNG,
-#endif
 	LEVELFLAT_TEXTURE,
 };
 
@@ -72,15 +70,17 @@ typedef struct
 	u;
 
 	UINT16 width, height;
-	fixed_t topoffset, leftoffset;
 
 	// for flat animation
 	INT32 animseq; // start pos. in the anim sequence
 	INT32 numpics;
 	INT32 speed;
 
-	// for patchflats
-	UINT8 *flatpatch;
+	// for textures
+	UINT8 *picture;
+#ifdef HWRENDER
+	void *mipmap;
+#endif
 } levelflat_t;
 
 extern size_t numlevelflats;
diff --git a/src/p_spec.c b/src/p_spec.c
index 1df212e1b0ea26e9a3277e2299ec5416300ecb15..2b2a5884af304907d379008b96cb6a2e67e7eedf 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -20,6 +20,7 @@
 #include "p_local.h"
 #include "p_setup.h" // levelflats for flat animation
 #include "r_data.h"
+#include "r_textures.h"
 #include "m_random.h"
 #include "p_mobj.h"
 #include "i_system.h"
diff --git a/src/r_data.c b/src/r_data.c
index befb73c20a5e5e75b8307d0ba83c5dd72454fe1d..dd36c2ee28623ce1ce16aa49709196009702e08b 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -19,7 +19,8 @@
 #include "p_local.h"
 #include "m_misc.h"
 #include "r_data.h"
-#include "r_patch.h"
+#include "r_textures.h"
+#include "r_picformats.h"
 #include "w_wad.h"
 #include "z_zone.h"
 #include "p_setup.h" // levelflats
@@ -32,64 +33,6 @@
 #include <malloc.h> // alloca(sizeof)
 #endif
 
-#ifdef HWRENDER
-#include "hardware/hw_main.h" // HWR_LoadTextures
-#endif
-
-#if defined(_MSC_VER)
-#pragma pack(1)
-#endif
-
-// Not sure if this is necessary, but it was in w_wad.c, so I'm putting it here too -Shadow Hog
-#if 0
-#define AVOID_ERRNO
-#else
-#include <errno.h>
-#endif
-
-//
-// Texture definition.
-// Each texture is composed of one or more patches,
-// with patches being lumps stored in the WAD.
-// The lumps are referenced by number, and patched
-// into the rectangular texture space using origin
-// and possibly other attributes.
-//
-typedef struct
-{
-	INT16 originx, originy;
-	INT16 patch, stepdir, colormap;
-} ATTRPACK mappatch_t;
-
-//
-// Texture definition.
-// An SRB2 wall texture is a list of patches
-// which are to be combined in a predefined order.
-//
-typedef struct
-{
-	char name[8];
-	INT32 masked;
-	INT16 width;
-	INT16 height;
-	INT32 columndirectory; // FIXTHIS: OBSOLETE
-	INT16 patchcount;
-	mappatch_t patches[1];
-} ATTRPACK maptexture_t;
-
-#if defined(_MSC_VER)
-#pragma pack()
-#endif
-
-
-// Store lists of lumps for F_START/F_END etc.
-typedef struct
-{
-	UINT16 wadfile;
-	UINT16 firstlump;
-	size_t numlumps;
-} lumplist_t;
-
 //
 // Graphics.
 // SRB2 graphics for walls and sprites
@@ -100,20 +43,6 @@ typedef struct
 
 size_t numspritelumps, max_spritelumps;
 
-// textures
-INT32 numtextures = 0; // total number of textures found,
-// size of following tables
-
-texture_t **textures = NULL;
-textureflat_t *texflats = NULL;
-static UINT32 **texturecolumnofs; // column offset lookup table for each texture
-static UINT8 **texturecache; // graphics data for each generated full-size texture
-
-INT32 *texturewidth;
-fixed_t *textureheight; // needed for texture pegging
-
-INT32 *texturetranslation;
-
 // needed for pre rendering
 sprcache_t *spritecachedinfo;
 
@@ -127,106 +56,6 @@ size_t flatmemory, spritememory, texturememory;
 INT16 color8to16[256]; // remap color index to highcolor rgb value
 INT16 *hicolormaps; // test a 32k colormap remaps high -> high
 
-// Painfully simple texture id cacheing to make maps load faster. :3
-static struct {
-	char name[9];
-	INT32 id;
-} *tidcache = NULL;
-static INT32 tidcachelen = 0;
-
-//
-// MAPTEXTURE_T CACHING
-// When a texture is first needed, it counts the number of composite columns
-//  required in the texture and allocates space for a column directory and
-//  any new columns.
-// The directory will simply point inside other patches if there is only one
-//  patch in a given column, but any columns with multiple patches will have
-//  new column_ts generated.
-//
-
-//
-// R_DrawColumnInCache
-// Clip and draw a column from a patch into a cached post.
-//
-static inline void R_DrawColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
-{
-	INT32 count, position;
-	UINT8 *source;
-	INT32 topdelta, prevdelta = -1;
-	INT32 originy = originPatch->originy;
-
-	(void)patchheight; // This parameter is unused
-
-	while (patch->topdelta != 0xff)
-	{
-		topdelta = patch->topdelta;
-		if (topdelta <= prevdelta)
-			topdelta += prevdelta;
-		prevdelta = topdelta;
-		source = (UINT8 *)patch + 3;
-		count = patch->length;
-		position = originy + topdelta;
-
-		if (position < 0)
-		{
-			count += position;
-			source -= position; // start further down the column
-			position = 0;
-		}
-
-		if (position + count > cacheheight)
-			count = cacheheight - position;
-
-		if (count > 0)
-			M_Memcpy(cache + position, source, count);
-
-		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
-	}
-}
-
-//
-// R_DrawFlippedColumnInCache
-// Similar to R_DrawColumnInCache; it draws the column inverted, however.
-//
-static inline void R_DrawFlippedColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
-{
-	INT32 count, position;
-	UINT8 *source, *dest;
-	INT32 topdelta, prevdelta = -1;
-	INT32 originy = originPatch->originy;
-
-	while (patch->topdelta != 0xff)
-	{
-		topdelta = patch->topdelta;
-		if (topdelta <= prevdelta)
-			topdelta += prevdelta;
-		prevdelta = topdelta;
-		topdelta = patchheight-patch->length-topdelta;
-		source = (UINT8 *)patch + 2 + patch->length; // patch + 3 + (patch->length-1)
-		count = patch->length;
-		position = originy + topdelta;
-
-		if (position < 0)
-		{
-			count += position;
-			source += position; // start further UP the column
-			position = 0;
-		}
-
-		if (position + count > cacheheight)
-			count = cacheheight - position;
-
-		dest = cache + position;
-		if (count > 0)
-		{
-			for (; dest < cache + position + count; --source)
-				*dest++ = *source;
-		}
-
-		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
-	}
-}
-
 // Blends two pixels together, using the equation
 // that matches the specified alpha style.
 UINT32 ASTBlendPixel(RGBA_t background, RGBA_t foreground, int style, UINT8 alpha)
@@ -376,1183 +205,6 @@ UINT8 ASTBlendPaletteIndexes(UINT8 background, UINT8 foreground, int style, UINT
 	return background;
 }
 
-//
-// R_DrawBlendColumnInCache
-// Draws a translucent column into the cache, applying a half-cooked equation to get a proper translucency value (Needs code in R_GenerateTexture()).
-//
-static inline void R_DrawBlendColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
-{
-	INT32 count, position;
-	UINT8 *source, *dest;
-	INT32 topdelta, prevdelta = -1;
-	INT32 originy = originPatch->originy;
-
-	(void)patchheight; // This parameter is unused
-
-	while (patch->topdelta != 0xff)
-	{
-		topdelta = patch->topdelta;
-		if (topdelta <= prevdelta)
-			topdelta += prevdelta;
-		prevdelta = topdelta;
-		source = (UINT8 *)patch + 3;
-		count = patch->length;
-		position = originy + topdelta;
-
-		if (position < 0)
-		{
-			count += position;
-			source -= position; // start further down the column
-			position = 0;
-		}
-
-		if (position + count > cacheheight)
-			count = cacheheight - position;
-
-		dest = cache + position;
-		if (count > 0)
-		{
-			for (; dest < cache + position + count; source++, dest++)
-				if (*source != 0xFF)
-					*dest = ASTBlendPaletteIndexes(*dest, *source, originPatch->style, originPatch->alpha);
-		}
-
-		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
-	}
-}
-
-//
-// R_DrawBlendFlippedColumnInCache
-// Similar to the one above except that the column is inverted.
-//
-static inline void R_DrawBlendFlippedColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
-{
-	INT32 count, position;
-	UINT8 *source, *dest;
-	INT32 topdelta, prevdelta = -1;
-	INT32 originy = originPatch->originy;
-
-	while (patch->topdelta != 0xff)
-	{
-		topdelta = patch->topdelta;
-		if (topdelta <= prevdelta)
-			topdelta += prevdelta;
-		prevdelta = topdelta;
-		topdelta = patchheight-patch->length-topdelta;
-		source = (UINT8 *)patch + 2 + patch->length; // patch + 3 + (patch->length-1)
-		count = patch->length;
-		position = originy + topdelta;
-
-		if (position < 0)
-		{
-			count += position;
-			source += position; // start further UP the column
-			position = 0;
-		}
-
-		if (position + count > cacheheight)
-			count = cacheheight - position;
-
-		dest = cache + position;
-		if (count > 0)
-		{
-			for (; dest < cache + position + count; --source, dest++)
-				if (*source != 0xFF)
-					*dest = ASTBlendPaletteIndexes(*dest, *source, originPatch->style, originPatch->alpha);
-		}
-
-		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
-	}
-}
-
-//
-// R_GenerateTexture
-//
-// Allocate space for full size texture, either single patch or 'composite'
-// Build the full textures from patches.
-// The texture caching system is a little more hungry of memory, but has
-// been simplified for the sake of highcolor (lol), dynamic ligthing, & speed.
-//
-// This is not optimised, but it's supposed to be executed only once
-// per level, when enough memory is available.
-//
-static UINT8 *R_GenerateTexture(size_t texnum)
-{
-	UINT8 *block;
-	UINT8 *blocktex;
-	texture_t *texture;
-	texpatch_t *patch;
-	patch_t *realpatch;
-	UINT8 *pdata;
-	int x, x1, x2, i, width, height;
-	size_t blocksize;
-	column_t *patchcol;
-	UINT8 *colofs;
-
-	UINT16 wadnum;
-	lumpnum_t lumpnum;
-	size_t lumplength;
-
-	I_Assert(texnum <= (size_t)numtextures);
-	texture = textures[texnum];
-	I_Assert(texture != NULL);
-
-	// allocate texture column offset lookup
-
-	// single-patch textures can have holes in them and may be used on
-	// 2sided lines so they need to be kept in 'packed' format
-	// BUT this is wrong for skies and walls with over 255 pixels,
-	// so check if there's holes and if not strip the posts.
-	if (texture->patchcount == 1)
-	{
-		boolean holey = false;
-		patch = texture->patches;
-
-		wadnum = patch->wad;
-		lumpnum = patch->lump;
-		lumplength = W_LumpLengthPwad(wadnum, lumpnum);
-		pdata = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
-		realpatch = (patch_t *)pdata;
-
-#ifndef NO_PNG_LUMPS
-		if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
-			goto multipatch;
-#endif
-#ifdef WALLFLATS
-		if (texture->type == TEXTURETYPE_FLAT)
-			goto multipatch;
-#endif
-
-		// Check the patch for holes.
-		if (texture->width > SHORT(realpatch->width) || texture->height > SHORT(realpatch->height))
-			holey = true;
-		colofs = (UINT8 *)realpatch->columnofs;
-		for (x = 0; x < texture->width && !holey; x++)
-		{
-			column_t *col = (column_t *)((UINT8 *)realpatch + LONG(*(UINT32 *)&colofs[x<<2]));
-			INT32 topdelta, prevdelta = -1, y = 0;
-			while (col->topdelta != 0xff)
-			{
-				topdelta = col->topdelta;
-				if (topdelta <= prevdelta)
-					topdelta += prevdelta;
-				prevdelta = topdelta;
-				if (topdelta > y)
-					break;
-				y = topdelta + col->length + 1;
-				col = (column_t *)((UINT8 *)col + col->length + 4);
-			}
-			if (y < texture->height)
-				holey = true; // this texture is HOLEy! D:
-		}
-
-		// If the patch uses transparency, we have to save it this way.
-		if (holey)
-		{
-			texture->holes = true;
-			texture->flip = patch->flip;
-			blocksize = lumplength;
-			block = Z_Calloc(blocksize, PU_STATIC, // will change tag at end of this function
-				&texturecache[texnum]);
-			M_Memcpy(block, realpatch, blocksize);
-			texturememory += blocksize;
-
-			// use the patch's column lookup
-			colofs = (block + 8);
-			texturecolumnofs[texnum] = (UINT32 *)colofs;
-			blocktex = block;
-			if (patch->flip & 1) // flip the patch horizontally
-			{
-				UINT8 *realcolofs = (UINT8 *)realpatch->columnofs;
-				for (x = 0; x < texture->width; x++)
-					*(UINT32 *)&colofs[x<<2] = realcolofs[( texture->width-1-x )<<2]; // swap with the offset of the other side of the texture
-			}
-			// we can't as easily flip the patch vertically sadly though,
-			//  we have wait until the texture itself is drawn to do that
-			for (x = 0; x < texture->width; x++)
-				*(UINT32 *)&colofs[x<<2] = LONG(LONG(*(UINT32 *)&colofs[x<<2]) + 3);
-			goto done;
-		}
-
-		// Otherwise, do multipatch format.
-	}
-
-	// multi-patch textures (or 'composite')
-	multipatch:
-	texture->holes = false;
-	texture->flip = 0;
-	blocksize = (texture->width * 4) + (texture->width * texture->height);
-	texturememory += blocksize;
-	block = Z_Malloc(blocksize+1, PU_STATIC, &texturecache[texnum]);
-
-	memset(block, TRANSPARENTPIXEL, blocksize+1); // Transparency hack
-
-	// columns lookup table
-	colofs = block;
-	texturecolumnofs[texnum] = (UINT32 *)colofs;
-
-	// texture data after the lookup table
-	blocktex = block + (texture->width*4);
-
-	// Composite the columns together.
-	for (i = 0, patch = texture->patches; i < texture->patchcount; i++, patch++)
-	{
-		boolean dealloc = true;
-		static void (*ColumnDrawerPointer)(column_t *, UINT8 *, texpatch_t *, INT32, INT32); // Column drawing function pointer.
-		if (patch->style != AST_COPY)
-			ColumnDrawerPointer = (patch->flip & 2) ? R_DrawBlendFlippedColumnInCache : R_DrawBlendColumnInCache;
-		else
-			ColumnDrawerPointer = (patch->flip & 2) ? R_DrawFlippedColumnInCache : R_DrawColumnInCache;
-
-		wadnum = patch->wad;
-		lumpnum = patch->lump;
-		pdata = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
-		lumplength = W_LumpLengthPwad(wadnum, lumpnum);
-		realpatch = (patch_t *)pdata;
-		dealloc = true;
-
-#ifndef NO_PNG_LUMPS
-		if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
-			realpatch = R_PNGToPatch((UINT8 *)realpatch, lumplength, NULL);
-		else
-#endif
-#ifdef WALLFLATS
-		if (texture->type == TEXTURETYPE_FLAT)
-			realpatch = R_FlatToPatch(pdata, texture->width, texture->height, 0, 0, NULL, false);
-		else
-#endif
-		{
-			(void)lumplength;
-			dealloc = false;
-		}
-
-		x1 = patch->originx;
-		width = SHORT(realpatch->width);
-		height = SHORT(realpatch->height);
-		x2 = x1 + width;
-
-		if (x1 > texture->width || x2 < 0)
-			continue; // patch not located within texture's x bounds, ignore
-
-		if (patch->originy > texture->height || (patch->originy + height) < 0)
-			continue; // patch not located within texture's y bounds, ignore
-
-		// patch is actually inside the texture!
-		// now check if texture is partly off-screen and adjust accordingly
-
-		// left edge
-		if (x1 < 0)
-			x = 0;
-		else
-			x = x1;
-
-		// right edge
-		if (x2 > texture->width)
-			x2 = texture->width;
-
-		for (; x < x2; x++)
-		{
-			if (patch->flip & 1)
-				patchcol = (column_t *)((UINT8 *)realpatch + LONG(realpatch->columnofs[(x1+width-1)-x]));
-			else
-				patchcol = (column_t *)((UINT8 *)realpatch + LONG(realpatch->columnofs[x-x1]));
-
-			// generate column ofset lookup
-			*(UINT32 *)&colofs[x<<2] = LONG((x * texture->height) + (texture->width*4));
-			ColumnDrawerPointer(patchcol, block + LONG(*(UINT32 *)&colofs[x<<2]), patch, texture->height, height);
-		}
-
-		if (dealloc)
-			Z_Free(realpatch);
-	}
-
-done:
-	// Now that the texture has been built in column cache, it is purgable from zone memory.
-	Z_ChangeTag(block, PU_CACHE);
-	return blocktex;
-}
-
-//
-// R_GetTextureNum
-//
-// Returns the actual texture id that we should use.
-// This can either be texnum, the current frame for texnum's anim (if animated),
-// or 0 if not valid.
-//
-INT32 R_GetTextureNum(INT32 texnum)
-{
-	if (texnum < 0 || texnum >= numtextures)
-		return 0;
-	return texturetranslation[texnum];
-}
-
-//
-// R_CheckTextureCache
-//
-// Use this if you need to make sure the texture is cached before R_GetColumn calls
-// e.g.: midtextures and FOF walls
-//
-void R_CheckTextureCache(INT32 tex)
-{
-	if (!texturecache[tex])
-		R_GenerateTexture(tex);
-}
-
-//
-// R_GetColumn
-//
-UINT8 *R_GetColumn(fixed_t tex, INT32 col)
-{
-	UINT8 *data;
-	INT32 width = texturewidth[tex];
-
-	if (width & (width - 1))
-		col = (UINT32)col % width;
-	else
-		col &= (width - 1);
-
-	data = texturecache[tex];
-	if (!data)
-		data = R_GenerateTexture(tex);
-
-	return data + LONG(texturecolumnofs[tex][col]);
-}
-
-// convert flats to hicolor as they are requested
-//
-UINT8 *R_GetFlat(lumpnum_t flatlumpnum)
-{
-	return W_CacheLumpNum(flatlumpnum, PU_CACHE);
-}
-
-//
-// Empty the texture cache (used for load wad at runtime)
-//
-void R_FlushTextureCache(void)
-{
-	INT32 i;
-
-	if (numtextures)
-		for (i = 0; i < numtextures; i++)
-			Z_Free(texturecache[i]);
-}
-
-// Need these prototypes for later; defining them here instead of r_data.h so they're "private"
-int R_CountTexturesInTEXTURESLump(UINT16 wadNum, UINT16 lumpNum);
-void R_ParseTEXTURESLump(UINT16 wadNum, UINT16 lumpNum, INT32 *index);
-
-#ifdef WALLFLATS
-static INT32
-Rloadflats (INT32 i, INT32 w)
-{
-	UINT16 j;
-	UINT16 texstart, texend;
-	texture_t *texture;
-	texpatch_t *patch;
-
-	// Yes
-	if (wadfiles[w]->type == RET_PK3)
-	{
-		texstart = W_CheckNumForFolderStartPK3("flats/", (UINT16)w, 0);
-		texend = W_CheckNumForFolderEndPK3("flats/", (UINT16)w, texstart);
-	}
-	else
-	{
-		texstart = W_CheckNumForMarkerStartPwad("F_START", (UINT16)w, 0);
-		texend = W_CheckNumForNamePwad("F_END", (UINT16)w, texstart);
-	}
-
-	if (!( texstart == INT16_MAX || texend == INT16_MAX ))
-	{
-		// Work through each lump between the markers in the WAD.
-		for (j = 0; j < (texend - texstart); j++)
-		{
-			UINT8 *flatlump;
-			UINT16 wadnum = (UINT16)w;
-			lumpnum_t lumpnum = texstart + j;
-			size_t lumplength;
-			size_t flatsize = 0;
-
-			if (wadfiles[w]->type == RET_PK3)
-			{
-				if (W_IsLumpFolder(wadnum, lumpnum)) // Check if lump is a folder
-					continue; // If it is then SKIP IT
-			}
-
-			flatlump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
-			lumplength = W_LumpLengthPwad(wadnum, lumpnum);
-
-			switch (lumplength)
-			{
-				case 4194304: // 2048x2048 lump
-					flatsize = 2048;
-					break;
-				case 1048576: // 1024x1024 lump
-					flatsize = 1024;
-					break;
-				case 262144:// 512x512 lump
-					flatsize = 512;
-					break;
-				case 65536: // 256x256 lump
-					flatsize = 256;
-					break;
-				case 16384: // 128x128 lump
-					flatsize = 128;
-					break;
-				case 1024: // 32x32 lump
-					flatsize = 32;
-					break;
-				default: // 64x64 lump
-					flatsize = 64;
-					break;
-			}
-
-			//CONS_Printf("\n\"%s\" is a flat, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),flatsize,flatsize);
-			texture = textures[i] = Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL);
-
-			// Set texture properties.
-			M_Memcpy(texture->name, W_CheckNameForNumPwad(wadnum, lumpnum), sizeof(texture->name));
-
-#ifndef NO_PNG_LUMPS
-			if (R_IsLumpPNG((UINT8 *)flatlump, lumplength))
-			{
-				INT16 width, height;
-				R_PNGDimensions((UINT8 *)flatlump, &width, &height, lumplength);
-				texture->width = width;
-				texture->height = height;
-			}
-			else
-#endif
-				texture->width = texture->height = flatsize;
-
-			texture->type = TEXTURETYPE_FLAT;
-			texture->patchcount = 1;
-			texture->holes = false;
-			texture->flip = 0;
-
-			// Allocate information for the texture's patches.
-			patch = &texture->patches[0];
-
-			patch->originx = patch->originy = 0;
-			patch->wad = (UINT16)w;
-			patch->lump = texstart + j;
-			patch->flip = 0;
-
-			Z_Unlock(flatlump);
-
-			texturewidth[i] = texture->width;
-			textureheight[i] = texture->height << FRACBITS;
-			i++;
-		}
-	}
-
-	return i;
-}
-#endif/*WALLFLATS*/
-
-#define TX_START "TX_START"
-#define TX_END "TX_END"
-
-static INT32
-Rloadtextures (INT32 i, INT32 w)
-{
-	UINT16 j;
-	UINT16 texstart, texend, texturesLumpPos;
-	texture_t *texture;
-	patch_t *patchlump;
-	texpatch_t *patch;
-
-	// Get the lump numbers for the markers in the WAD, if they exist.
-	if (wadfiles[w]->type == RET_PK3)
-	{
-		texstart = W_CheckNumForFolderStartPK3("textures/", (UINT16)w, 0);
-		texend = W_CheckNumForFolderEndPK3("textures/", (UINT16)w, texstart);
-		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
-		while (texturesLumpPos != INT16_MAX)
-		{
-			R_ParseTEXTURESLump(w, texturesLumpPos, &i);
-			texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, texturesLumpPos + 1);
-		}
-	}
-	else
-	{
-		texstart = W_CheckNumForMarkerStartPwad(TX_START, (UINT16)w, 0);
-		texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
-		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
-		if (texturesLumpPos != INT16_MAX)
-			R_ParseTEXTURESLump(w, texturesLumpPos, &i);
-	}
-
-	if (!( texstart == INT16_MAX || texend == INT16_MAX ))
-	{
-		// Work through each lump between the markers in the WAD.
-		for (j = 0; j < (texend - texstart); j++)
-		{
-			UINT16 wadnum = (UINT16)w;
-			lumpnum_t lumpnum = texstart + j;
-#ifndef NO_PNG_LUMPS
-			size_t lumplength;
-#endif
-
-			if (wadfiles[w]->type == RET_PK3)
-			{
-				if (W_IsLumpFolder(wadnum, lumpnum)) // Check if lump is a folder
-					continue; // If it is then SKIP IT
-			}
-
-			patchlump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
-#ifndef NO_PNG_LUMPS
-			lumplength = W_LumpLengthPwad(wadnum, lumpnum);
-#endif
-
-			//CONS_Printf("\n\"%s\" is a single patch, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),patchlump->width, patchlump->height);
-			texture = textures[i] = Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL);
-
-			// Set texture properties.
-			M_Memcpy(texture->name, W_CheckNameForNumPwad(wadnum, lumpnum), sizeof(texture->name));
-
-#ifndef NO_PNG_LUMPS
-			if (R_IsLumpPNG((UINT8 *)patchlump, lumplength))
-			{
-				INT16 width, height;
-				R_PNGDimensions((UINT8 *)patchlump, &width, &height, lumplength);
-				texture->width = width;
-				texture->height = height;
-			}
-			else
-#endif
-			{
-				texture->width = SHORT(patchlump->width);
-				texture->height = SHORT(patchlump->height);
-			}
-
-			texture->type = TEXTURETYPE_SINGLEPATCH;
-			texture->patchcount = 1;
-			texture->holes = false;
-			texture->flip = 0;
-
-			// Allocate information for the texture's patches.
-			patch = &texture->patches[0];
-
-			patch->originx = patch->originy = 0;
-			patch->wad = (UINT16)w;
-			patch->lump = texstart + j;
-			patch->flip = 0;
-
-			Z_Unlock(patchlump);
-
-			texturewidth[i] = texture->width;
-			textureheight[i] = texture->height << FRACBITS;
-			i++;
-		}
-	}
-
-	return i;
-}
-
-//
-// R_LoadTextures
-// Initializes the texture list with the textures from the world map.
-//
-void R_LoadTextures(void)
-{
-	INT32 i, w;
-	UINT16 j;
-	UINT16 texstart, texend, texturesLumpPos;
-
-	// Free previous memory before numtextures change.
-	if (numtextures)
-	{
-		for (i = 0; i < numtextures; i++)
-		{
-			Z_Free(textures[i]);
-			Z_Free(texturecache[i]);
-		}
-		Z_Free(texturetranslation);
-		Z_Free(textures);
-		Z_Free(texflats);
-	}
-
-	// Load patches and textures.
-
-	// Get the number of textures to check.
-	// NOTE: Make SURE the system does not process
-	// the markers.
-	// This system will allocate memory for all duplicate/patched textures even if it never uses them,
-	// but the alternative is to spend a ton of time checking and re-checking all previous entries just to skip any potentially patched textures.
-	for (w = 0, numtextures = 0; w < numwadfiles; w++)
-	{
-#ifdef WALLFLATS
-		// Count flats
-		if (wadfiles[w]->type == RET_PK3)
-		{
-			texstart = W_CheckNumForFolderStartPK3("flats/", (UINT16)w, 0);
-			texend = W_CheckNumForFolderEndPK3("flats/", (UINT16)w, texstart);
-		}
-		else
-		{
-			texstart = W_CheckNumForMarkerStartPwad("F_START", (UINT16)w, 0);
-			texend = W_CheckNumForNamePwad("F_END", (UINT16)w, texstart);
-		}
-
-		if (!( texstart == INT16_MAX || texend == INT16_MAX ))
-		{
-			// PK3s have subfolders, so we can't just make a simple sum
-			if (wadfiles[w]->type == RET_PK3)
-			{
-				for (j = texstart; j < texend; j++)
-				{
-					if (!W_IsLumpFolder((UINT16)w, j)) // Check if lump is a folder; if not, then count it
-						numtextures++;
-				}
-			}
-			else // Add all the textures between F_START and F_END
-			{
-				numtextures += (UINT32)(texend - texstart);
-			}
-		}
-#endif/*WALLFLATS*/
-
-		// Count the textures from TEXTURES lumps
-		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
-		while (texturesLumpPos != INT16_MAX)
-		{
-			numtextures += R_CountTexturesInTEXTURESLump((UINT16)w, (UINT16)texturesLumpPos);
-			texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, texturesLumpPos + 1);
-		}
-
-		// Count single-patch textures
-		if (wadfiles[w]->type == RET_PK3)
-		{
-			texstart = W_CheckNumForFolderStartPK3("textures/", (UINT16)w, 0);
-			texend = W_CheckNumForFolderEndPK3("textures/", (UINT16)w, texstart);
-		}
-		else
-		{
-			texstart = W_CheckNumForMarkerStartPwad(TX_START, (UINT16)w, 0);
-			texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
-		}
-
-		if (texstart == INT16_MAX || texend == INT16_MAX)
-			continue;
-
-		// PK3s have subfolders, so we can't just make a simple sum
-		if (wadfiles[w]->type == RET_PK3)
-		{
-			for (j = texstart; j < texend; j++)
-			{
-				if (!W_IsLumpFolder((UINT16)w, j)) // Check if lump is a folder; if not, then count it
-					numtextures++;
-			}
-		}
-		else // Add all the textures between TX_START and TX_END
-		{
-			numtextures += (UINT32)(texend - texstart);
-		}
-	}
-
-	// If no textures found by this point, bomb out
-	if (!numtextures)
-		I_Error("No textures detected in any WADs!\n");
-
-	// Allocate memory and initialize to 0 for all the textures we are initialising.
-	// There are actually 5 buffers allocated in one for convenience.
-	textures = Z_Calloc((numtextures * sizeof(void *)) * 5, PU_STATIC, NULL);
-	texflats = Z_Calloc((numtextures * sizeof(*texflats)), PU_STATIC, NULL);
-
-	// Allocate texture column offset table.
-	texturecolumnofs = (void *)((UINT8 *)textures + (numtextures * sizeof(void *)));
-	// Allocate texture referencing cache.
-	texturecache     = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 2));
-	// Allocate texture width table.
-	texturewidth     = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 3));
-	// Allocate texture height table.
-	textureheight    = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 4));
-	// Create translation table for global animation.
-	texturetranslation = Z_Malloc((numtextures + 1) * sizeof(*texturetranslation), PU_STATIC, NULL);
-
-	for (i = 0; i < numtextures; i++)
-		texturetranslation[i] = i;
-
-	for (i = 0, w = 0; w < numwadfiles; w++)
-	{
-#ifdef WALLFLATS
-		i = Rloadflats(i, w);
-#endif
-		i = Rloadtextures(i, w);
-	}
-
-#ifdef HWRENDER
-	if (rendermode == render_opengl)
-		HWR_LoadTextures(numtextures);
-#endif
-}
-
-static texpatch_t *R_ParsePatch(boolean actuallyLoadPatch)
-{
-	char *texturesToken;
-	size_t texturesTokenLength;
-	char *endPos;
-	char *patchName = NULL;
-	INT16 patchXPos;
-	INT16 patchYPos;
-	UINT8 flip = 0;
-	UINT8 alpha = 255;
-	enum patchalphastyle style = AST_COPY;
-	texpatch_t *resultPatch = NULL;
-	lumpnum_t patchLumpNum;
-
-	// Patch identifier
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch name should be");
-	}
-	texturesTokenLength = strlen(texturesToken);
-	if (texturesTokenLength>8)
-	{
-		I_Error("Error parsing TEXTURES lump: Patch name \"%s\" exceeds 8 characters",texturesToken);
-	}
-	else
-	{
-		if (patchName != NULL)
-		{
-			Z_Free(patchName);
-		}
-		patchName = (char *)Z_Malloc((texturesTokenLength+1)*sizeof(char),PU_STATIC,NULL);
-		M_Memcpy(patchName,texturesToken,texturesTokenLength*sizeof(char));
-		patchName[texturesTokenLength] = '\0';
-	}
-
-	// Comma 1
-	Z_Free(texturesToken);
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after \"%s\"'s patch name should be",patchName);
-	}
-	if (strcmp(texturesToken,",")!=0)
-	{
-		I_Error("Error parsing TEXTURES lump: Expected \",\" after %s's patch name, got \"%s\"",patchName,texturesToken);
-	}
-
-	// XPos
-	Z_Free(texturesToken);
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s x coordinate should be",patchName);
-	}
-	endPos = NULL;
-#ifndef AVOID_ERRNO
-	errno = 0;
-#endif
-	patchXPos = strtol(texturesToken,&endPos,10);
-	(void)patchXPos; //unused for now
-	if (endPos == texturesToken // Empty string
-		|| *endPos != '\0' // Not end of string
-#ifndef AVOID_ERRNO
-		|| errno == ERANGE // Number out-of-range
-#endif
-		)
-	{
-		I_Error("Error parsing TEXTURES lump: Expected an integer for patch \"%s\"'s x coordinate, got \"%s\"",patchName,texturesToken);
-	}
-
-	// Comma 2
-	Z_Free(texturesToken);
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after patch \"%s\"'s x coordinate should be",patchName);
-	}
-	if (strcmp(texturesToken,",")!=0)
-	{
-		I_Error("Error parsing TEXTURES lump: Expected \",\" after patch \"%s\"'s x coordinate, got \"%s\"",patchName,texturesToken);
-	}
-
-	// YPos
-	Z_Free(texturesToken);
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s y coordinate should be",patchName);
-	}
-	endPos = NULL;
-#ifndef AVOID_ERRNO
-	errno = 0;
-#endif
-	patchYPos = strtol(texturesToken,&endPos,10);
-	(void)patchYPos; //unused for now
-	if (endPos == texturesToken // Empty string
-		|| *endPos != '\0' // Not end of string
-#ifndef AVOID_ERRNO
-		|| errno == ERANGE // Number out-of-range
-#endif
-		)
-	{
-		I_Error("Error parsing TEXTURES lump: Expected an integer for patch \"%s\"'s y coordinate, got \"%s\"",patchName,texturesToken);
-	}
-	Z_Free(texturesToken);
-
-	// Patch parameters block (OPTIONAL)
-	// added by Monster Iestyn (22/10/16)
-
-	// Left Curly Brace
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-		; // move on and ignore, R_ParseTextures will deal with this
-	else
-	{
-		if (strcmp(texturesToken,"{")==0)
-		{
-			Z_Free(texturesToken);
-			texturesToken = M_GetToken(NULL);
-			if (texturesToken == NULL)
-			{
-				I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s parameters should be",patchName);
-			}
-			while (strcmp(texturesToken,"}")!=0)
-			{
-				if (stricmp(texturesToken, "ALPHA")==0)
-				{
-					Z_Free(texturesToken);
-					texturesToken = M_GetToken(NULL);
-					alpha = 255*strtof(texturesToken, NULL);
-				}
-				else if (stricmp(texturesToken, "STYLE")==0)
-				{
-					Z_Free(texturesToken);
-					texturesToken = M_GetToken(NULL);
-					if (stricmp(texturesToken, "TRANSLUCENT")==0)
-						style = AST_TRANSLUCENT;
-					else if (stricmp(texturesToken, "ADD")==0)
-						style = AST_ADD;
-					else if (stricmp(texturesToken, "SUBTRACT")==0)
-						style = AST_SUBTRACT;
-					else if (stricmp(texturesToken, "REVERSESUBTRACT")==0)
-						style = AST_REVERSESUBTRACT;
-					else if (stricmp(texturesToken, "MODULATE")==0)
-						style = AST_MODULATE;
-				}
-				else if (stricmp(texturesToken, "FLIPX")==0)
-					flip |= 1;
-				else if (stricmp(texturesToken, "FLIPY")==0)
-					flip |= 2;
-				Z_Free(texturesToken);
-
-				texturesToken = M_GetToken(NULL);
-				if (texturesToken == NULL)
-				{
-					I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s parameters or right curly brace should be",patchName);
-				}
-			}
-		}
-		else
-		{
-			 // this is not what we wanted...
-			 // undo last read so R_ParseTextures can re-get the token for its own purposes
-			M_UnGetToken();
-		}
-		Z_Free(texturesToken);
-	}
-
-	if (actuallyLoadPatch == true)
-	{
-		// Check lump exists
-		patchLumpNum = W_GetNumForName(patchName);
-		// If so, allocate memory for texpatch_t and fill 'er up
-		resultPatch = (texpatch_t *)Z_Malloc(sizeof(texpatch_t),PU_STATIC,NULL);
-		resultPatch->originx = patchXPos;
-		resultPatch->originy = patchYPos;
-		resultPatch->lump = patchLumpNum & 65535;
-		resultPatch->wad = patchLumpNum>>16;
-		resultPatch->flip = flip;
-		resultPatch->alpha = alpha;
-		resultPatch->style = style;
-		// Clean up a little after ourselves
-		Z_Free(patchName);
-		// Then return it
-		return resultPatch;
-	}
-	else
-	{
-		Z_Free(patchName);
-		return NULL;
-	}
-}
-
-static texture_t *R_ParseTexture(boolean actuallyLoadTexture)
-{
-	char *texturesToken;
-	size_t texturesTokenLength;
-	char *endPos;
-	INT32 newTextureWidth;
-	INT32 newTextureHeight;
-	texture_t *resultTexture = NULL;
-	texpatch_t *newPatch;
-	char newTextureName[9]; // no longer dynamically allocated
-
-	// Texture name
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where texture name should be");
-	}
-	texturesTokenLength = strlen(texturesToken);
-	if (texturesTokenLength>8)
-	{
-		I_Error("Error parsing TEXTURES lump: Texture name \"%s\" exceeds 8 characters",texturesToken);
-	}
-	else
-	{
-		memset(&newTextureName, 0, 9);
-		M_Memcpy(newTextureName, texturesToken, texturesTokenLength);
-		// ^^ we've confirmed that the token is <= 8 characters so it will never overflow a 9 byte char buffer
-		strupr(newTextureName); // Just do this now so we don't have to worry about it
-	}
-	Z_Free(texturesToken);
-
-	// Comma 1
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after texture \"%s\"'s name should be",newTextureName);
-	}
-	else if (strcmp(texturesToken,",")!=0)
-	{
-		I_Error("Error parsing TEXTURES lump: Expected \",\" after texture \"%s\"'s name, got \"%s\"",newTextureName,texturesToken);
-	}
-	Z_Free(texturesToken);
-
-	// Width
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where texture \"%s\"'s width should be",newTextureName);
-	}
-	endPos = NULL;
-#ifndef AVOID_ERRNO
-	errno = 0;
-#endif
-	newTextureWidth = strtol(texturesToken,&endPos,10);
-	if (endPos == texturesToken // Empty string
-		|| *endPos != '\0' // Not end of string
-#ifndef AVOID_ERRNO
-		|| errno == ERANGE // Number out-of-range
-#endif
-		|| newTextureWidth < 0) // Number is not positive
-	{
-		I_Error("Error parsing TEXTURES lump: Expected a positive integer for texture \"%s\"'s width, got \"%s\"",newTextureName,texturesToken);
-	}
-	Z_Free(texturesToken);
-
-	// Comma 2
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after texture \"%s\"'s width should be",newTextureName);
-	}
-	if (strcmp(texturesToken,",")!=0)
-	{
-		I_Error("Error parsing TEXTURES lump: Expected \",\" after texture \"%s\"'s width, got \"%s\"",newTextureName,texturesToken);
-	}
-	Z_Free(texturesToken);
-
-	// Height
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where texture \"%s\"'s height should be",newTextureName);
-	}
-	endPos = NULL;
-#ifndef AVOID_ERRNO
-	errno = 0;
-#endif
-	newTextureHeight = strtol(texturesToken,&endPos,10);
-	if (endPos == texturesToken // Empty string
-		|| *endPos != '\0' // Not end of string
-#ifndef AVOID_ERRNO
-		|| errno == ERANGE // Number out-of-range
-#endif
-		|| newTextureHeight < 0) // Number is not positive
-	{
-		I_Error("Error parsing TEXTURES lump: Expected a positive integer for texture \"%s\"'s height, got \"%s\"",newTextureName,texturesToken);
-	}
-	Z_Free(texturesToken);
-
-	// Left Curly Brace
-	texturesToken = M_GetToken(NULL);
-	if (texturesToken == NULL)
-	{
-		I_Error("Error parsing TEXTURES lump: Unexpected end of file where open curly brace for texture \"%s\" should be",newTextureName);
-	}
-	if (strcmp(texturesToken,"{")==0)
-	{
-		if (actuallyLoadTexture)
-		{
-			// Allocate memory for a zero-patch texture. Obviously, we'll be adding patches momentarily.
-			resultTexture = (texture_t *)Z_Calloc(sizeof(texture_t),PU_STATIC,NULL);
-			M_Memcpy(resultTexture->name, newTextureName, 8);
-			resultTexture->width = newTextureWidth;
-			resultTexture->height = newTextureHeight;
-			resultTexture->type = TEXTURETYPE_COMPOSITE;
-		}
-		Z_Free(texturesToken);
-		texturesToken = M_GetToken(NULL);
-		if (texturesToken == NULL)
-		{
-			I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch definition for texture \"%s\" should be",newTextureName);
-		}
-		while (strcmp(texturesToken,"}")!=0)
-		{
-			if (stricmp(texturesToken, "PATCH")==0)
-			{
-				Z_Free(texturesToken);
-				if (resultTexture)
-				{
-					// Get that new patch
-					newPatch = R_ParsePatch(true);
-					// Make room for the new patch
-					resultTexture = Z_Realloc(resultTexture, sizeof(texture_t) + (resultTexture->patchcount+1)*sizeof(texpatch_t), PU_STATIC, NULL);
-					// Populate the uninitialized values in the new patch entry of our array
-					M_Memcpy(&resultTexture->patches[resultTexture->patchcount], newPatch, sizeof(texpatch_t));
-					// Account for the new number of patches in the texture
-					resultTexture->patchcount++;
-					// Then free up the memory assigned to R_ParsePatch, as it's unneeded now
-					Z_Free(newPatch);
-				}
-				else
-				{
-					R_ParsePatch(false);
-				}
-			}
-			else
-			{
-				I_Error("Error parsing TEXTURES lump: Expected \"PATCH\" in texture \"%s\", got \"%s\"",newTextureName,texturesToken);
-			}
-
-			texturesToken = M_GetToken(NULL);
-			if (texturesToken == NULL)
-			{
-				I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch declaration or right curly brace for texture \"%s\" should be",newTextureName);
-			}
-		}
-		if (resultTexture && resultTexture->patchcount == 0)
-		{
-			I_Error("Error parsing TEXTURES lump: Texture \"%s\" must have at least one patch",newTextureName);
-		}
-	}
-	else
-	{
-		I_Error("Error parsing TEXTURES lump: Expected \"{\" for texture \"%s\", got \"%s\"",newTextureName,texturesToken);
-	}
-	Z_Free(texturesToken);
-
-	if (actuallyLoadTexture) return resultTexture;
-	else return NULL;
-}
-
-// Parses the TEXTURES lump... but just to count the number of textures.
-int R_CountTexturesInTEXTURESLump(UINT16 wadNum, UINT16 lumpNum)
-{
-	char *texturesLump;
-	size_t texturesLumpLength;
-	char *texturesText;
-	UINT32 numTexturesInLump = 0;
-	char *texturesToken;
-
-	// Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll
-	// need to make a space of memory where I can ensure that it will terminate
-	// correctly. Start by loading the relevant data from the WAD.
-	texturesLump = (char *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC);
-	// If that didn't exist, we have nothing to do here.
-	if (texturesLump == NULL) return 0;
-	// If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
-	texturesLumpLength = W_LumpLengthPwad(wadNum, lumpNum);
-	texturesText = (char *)Z_Malloc((texturesLumpLength+1)*sizeof(char),PU_STATIC,NULL);
-	// Now move the contents of the lump into this new location.
-	memmove(texturesText,texturesLump,texturesLumpLength);
-	// Make damn well sure the last character in our new memory location is \0.
-	texturesText[texturesLumpLength] = '\0';
-	// Finally, free up the memory from the first data load, because we really
-	// don't need it.
-	Z_Free(texturesLump);
-
-	texturesToken = M_GetToken(texturesText);
-	while (texturesToken != NULL)
-	{
-		if (stricmp(texturesToken, "WALLTEXTURE") == 0 || stricmp(texturesToken, "TEXTURE") == 0)
-		{
-			numTexturesInLump++;
-			Z_Free(texturesToken);
-			R_ParseTexture(false);
-		}
-		else
-		{
-			I_Error("Error parsing TEXTURES lump: Expected \"WALLTEXTURE\" or \"TEXTURE\", got \"%s\"",texturesToken);
-		}
-		texturesToken = M_GetToken(NULL);
-	}
-	Z_Free(texturesToken);
-	Z_Free((void *)texturesText);
-
-	return numTexturesInLump;
-}
-
-// Parses the TEXTURES lump... for real, this time.
-void R_ParseTEXTURESLump(UINT16 wadNum, UINT16 lumpNum, INT32 *texindex)
-{
-	char *texturesLump;
-	size_t texturesLumpLength;
-	char *texturesText;
-	char *texturesToken;
-	texture_t *newTexture;
-
-	I_Assert(texindex != NULL);
-
-	// Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll
-	// need to make a space of memory where I can ensure that it will terminate
-	// correctly. Start by loading the relevant data from the WAD.
-	texturesLump = (char *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC);
-	// If that didn't exist, we have nothing to do here.
-	if (texturesLump == NULL) return;
-	// If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
-	texturesLumpLength = W_LumpLengthPwad(wadNum, lumpNum);
-	texturesText = (char *)Z_Malloc((texturesLumpLength+1)*sizeof(char),PU_STATIC,NULL);
-	// Now move the contents of the lump into this new location.
-	memmove(texturesText,texturesLump,texturesLumpLength);
-	// Make damn well sure the last character in our new memory location is \0.
-	texturesText[texturesLumpLength] = '\0';
-	// Finally, free up the memory from the first data load, because we really
-	// don't need it.
-	Z_Free(texturesLump);
-
-	texturesToken = M_GetToken(texturesText);
-	while (texturesToken != NULL)
-	{
-		if (stricmp(texturesToken, "WALLTEXTURE") == 0 || stricmp(texturesToken, "TEXTURE") == 0)
-		{
-			Z_Free(texturesToken);
-			// Get the new texture
-			newTexture = R_ParseTexture(true);
-			// Store the new texture
-			textures[*texindex] = newTexture;
-			texturewidth[*texindex] = newTexture->width;
-			textureheight[*texindex] = newTexture->height << FRACBITS;
-			// Increment i back in R_LoadTextures()
-			(*texindex)++;
-		}
-		else
-		{
-			I_Error("Error parsing TEXTURES lump: Expected \"WALLTEXTURE\" or \"TEXTURE\", got \"%s\"",texturesToken);
-		}
-		texturesToken = M_GetToken(NULL);
-	}
-	Z_Free(texturesToken);
-	Z_Free((void *)texturesText);
-}
-
 #ifdef EXTRACOLORMAPLUMPS
 static lumplist_t *colormaplumps = NULL; ///\todo free leak
 static size_t numcolormaplumps = 0;
@@ -1607,54 +259,6 @@ static void R_InitExtraColormaps(void)
 }
 #endif
 
-// Search for flat name.
-lumpnum_t R_GetFlatNumForName(const char *name)
-{
-	INT32 i;
-	lumpnum_t lump;
-	lumpnum_t start;
-	lumpnum_t end;
-
-	// Scan wad files backwards so patched flats take preference.
-	for (i = numwadfiles - 1; i >= 0; i--)
-	{
-		switch (wadfiles[i]->type)
-		{
-		case RET_WAD:
-			if ((start = W_CheckNumForMarkerStartPwad("F_START", (UINT16)i, 0)) == INT16_MAX)
-			{
-				if ((start = W_CheckNumForMarkerStartPwad("FF_START", (UINT16)i, 0)) == INT16_MAX)
-					continue;
-				else if ((end = W_CheckNumForNamePwad("FF_END", (UINT16)i, start)) == INT16_MAX)
-					continue;
-			}
-			else
-				if ((end = W_CheckNumForNamePwad("F_END", (UINT16)i, start)) == INT16_MAX)
-					continue;
-			break;
-		case RET_PK3:
-			if ((start = W_CheckNumForFolderStartPK3("Flats/", i, 0)) == INT16_MAX)
-				continue;
-			if ((end = W_CheckNumForFolderEndPK3("Flats/", i, start)) == INT16_MAX)
-				continue;
-			break;
-		default:
-			continue;
-		}
-
-		// Now find lump with specified name in that range.
-		lump = W_CheckNumForNamePwad(name, (UINT16)i, start);
-		if (lump < end)
-		{
-			lump += (i<<16); // found it, in our constraints
-			break;
-		}
-		lump = LUMPERROR;
-	}
-
-	return lump;
-}
-
 //
 // R_InitSpriteLumps
 // Finds the width and hoffset of all sprites in the wad, so the sprite does not need to be
@@ -2619,74 +1223,6 @@ void R_InitData(void)
 	R_InitColormaps();
 }
 
-void R_ClearTextureNumCache(boolean btell)
-{
-	if (tidcache)
-		Z_Free(tidcache);
-	tidcache = NULL;
-	if (btell)
-		CONS_Debug(DBG_SETUP, "Fun Fact: There are %d textures used in this map.\n", tidcachelen);
-	tidcachelen = 0;
-}
-
-//
-// R_CheckTextureNumForName
-//
-// Check whether texture is available. Filter out NoTexture indicator.
-//
-INT32 R_CheckTextureNumForName(const char *name)
-{
-	INT32 i;
-
-	// "NoTexture" marker.
-	if (name[0] == '-')
-		return 0;
-
-	for (i = 0; i < tidcachelen; i++)
-		if (!strncasecmp(tidcache[i].name, name, 8))
-			return tidcache[i].id;
-
-	// Need to parse the list backwards, so textures loaded more recently are used in lieu of ones loaded earlier
-	//for (i = 0; i < numtextures; i++) <- old
-	for (i = (numtextures - 1); i >= 0; i--) // <- new
-		if (!strncasecmp(textures[i]->name, name, 8))
-		{
-			tidcachelen++;
-			Z_Realloc(tidcache, tidcachelen * sizeof(*tidcache), PU_STATIC, &tidcache);
-			strncpy(tidcache[tidcachelen-1].name, name, 8);
-			tidcache[tidcachelen-1].name[8] = '\0';
-#ifndef ZDEBUG
-			CONS_Debug(DBG_SETUP, "texture #%s: %s\n", sizeu1(tidcachelen), tidcache[tidcachelen-1].name);
-#endif
-			tidcache[tidcachelen-1].id = i;
-			return i;
-		}
-
-	return -1;
-}
-
-//
-// R_TextureNumForName
-//
-// Calls R_CheckTextureNumForName, aborts with error message.
-//
-INT32 R_TextureNumForName(const char *name)
-{
-	const INT32 i = R_CheckTextureNumForName(name);
-
-	if (i == -1)
-	{
-		static INT32 redwall = -2;
-		CONS_Debug(DBG_SETUP, "WARNING: R_TextureNumForName: %.8s not found\n", name);
-		if (redwall == -2)
-			redwall = R_CheckTextureNumForName("REDWALL");
-		if (redwall != -1)
-			return redwall;
-		return 1;
-	}
-	return i;
-}
-
 //
 // R_PrecacheLevel
 //
diff --git a/src/r_data.h b/src/r_data.h
index fda342083547d46e00ff52549061fb275a94a13d..aec52b54b654bb874215097e3e508b9ea5609bab 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -22,6 +22,14 @@
 #pragma interface
 #endif
 
+// Store lists of lumps for F_START/F_END etc.
+typedef struct
+{
+	UINT16 wadfile;
+	UINT16 firstlump;
+	size_t numlumps;
+} lumplist_t;
+
 // Possible alpha types for a patch.
 enum patchalphastyle {AST_COPY, AST_TRANSLUCENT, AST_ADD, AST_SUBTRACT, AST_REVERSESUBTRACT, AST_MODULATE, AST_OVERLAY};
 
@@ -31,97 +39,17 @@ UINT8 ASTBlendPaletteIndexes(UINT8 background, UINT8 foreground, int style, UINT
 
 extern INT32 ASTTextureBlendingThreshold[2];
 
-UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b);
-
-// moved here for r_sky.c (texpatch_t is used)
-
-// A single patch from a texture definition,
-//  basically a rectangular area within
-//  the texture rectangle.
-typedef struct
-{
-	// Block origin (always UL), which has already accounted for the internal origin of the patch.
-	INT16 originx, originy;
-	UINT16 wad, lump;
-	UINT8 flip; // 1 = flipx, 2 = flipy, 3 = both
-	UINT8 alpha; // Translucency value
-	enum patchalphastyle style;
-} texpatch_t;
-
-// texture type
-enum
-{
-	TEXTURETYPE_UNKNOWN,
-	TEXTURETYPE_SINGLEPATCH,
-	TEXTURETYPE_COMPOSITE,
-#ifdef WALLFLATS
-	TEXTURETYPE_FLAT,
-#endif
-};
-
-// A maptexturedef_t describes a rectangular texture,
-//  which is composed of one or more mappatch_t structures
-//  that arrange graphic patches.
-typedef struct
-{
-	// Keep name for switch changing, etc.
-	char name[8];
-	UINT8 type; // TEXTURETYPE_
-	INT16 width, height;
-	boolean holes;
-	UINT8 flip; // 1 = flipx, 2 = flipy, 3 = both
-
-	// All the patches[patchcount] are drawn back to front into the cached texture.
-	INT16 patchcount;
-	texpatch_t patches[0];
-} texture_t;
-
-typedef struct
-{
-	UINT8 *flat;
-	INT16 width, height;
-} textureflat_t;
-
-// all loaded and prepared textures from the start of the game
-extern texture_t **textures;
-extern textureflat_t *texflats;
-
-extern INT32 *texturewidth;
-extern fixed_t *textureheight; // needed for texture pegging
-
 extern INT16 color8to16[256]; // remap color index to highcolor
 extern INT16 *hicolormaps; // remap high colors to high colors..
 
 extern CV_PossibleValue_t Color_cons_t[];
 
-// Load TEXTURES definitions, create lookup tables
-void R_LoadTextures(void);
-void R_FlushTextureCache(void);
-
-INT32 R_GetTextureNum(INT32 texnum);
-void R_CheckTextureCache(INT32 tex);
-
-// Retrieve column data for span blitting.
-UINT8 *R_GetColumn(fixed_t tex, INT32 col);
-UINT8 *R_GetFlat(lumpnum_t flatnum);
-
 // I/O, setting up the stuff.
 void R_InitData(void);
 void R_PrecacheLevel(void);
 
 extern size_t flatmemory, spritememory, texturememory;
 
-// Retrieval.
-// Floor/ceiling opaque texture tiles,
-// lookup by name. For animation?
-lumpnum_t R_GetFlatNumForName(const char *name);
-
-// Called by P_Ticker for switches and animations,
-// returns the texture number for the texture name.
-void R_ClearTextureNumCache(boolean btell);
-INT32 R_TextureNumForName(const char *name);
-INT32 R_CheckTextureNumForName(const char *name);
-
 // Extra Colormap lumps (C_START/C_END) are not used anywhere
 // Uncomment to enable
 //#define EXTRACOLORMAPLUMPS
@@ -195,6 +123,4 @@ const char *R_NameForColormap(extracolormap_t *extra_colormap);
 UINT8 NearestPaletteColor(UINT8 r, UINT8 g, UINT8 b, RGBA_t *palette);
 #define NearestColor(r, g, b) NearestPaletteColor(r, g, b, NULL)
 
-extern INT32 numtextures;
-
 #endif
diff --git a/src/r_local.h b/src/r_local.h
index 48044118d386aba9a67d8e46cf541c0484ccb54c..4ccb766cf72c903a952b75c85d2a1f313c42ebd5 100644
--- a/src/r_local.h
+++ b/src/r_local.h
@@ -31,6 +31,7 @@
 #include "r_plane.h"
 #include "r_sky.h"
 #include "r_data.h"
+#include "r_textures.h"
 #include "r_things.h"
 #include "r_draw.h"
 
diff --git a/src/r_main.c b/src/r_main.c
index 4f79dd8db4a445b40a5340dc07b9e5812901f444..eb566601330177458a5048ab0cd398caf45de5f2 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -1560,7 +1560,6 @@ void R_RenderPlayerView(player_t *player)
 	free(masks);
 }
 
-// Lactozilla: Renderer switching
 #ifdef HWRENDER
 void R_InitHardwareMode(void)
 {
@@ -1574,7 +1573,6 @@ void R_InitHardwareMode(void)
 
 void R_ReloadHUDGraphics(void)
 {
-	CONS_Debug(DBG_RENDER, "R_ReloadHUDGraphics()...\n");
 	ST_LoadGraphics();
 	HU_LoadGraphics();
 	ST_ReloadSkinFaceGraphics();
diff --git a/src/r_main.h b/src/r_main.h
index 729ec6973d595ee71ab1b59fbf2389cf62f6eb2d..ddd19fac65228f692e3339687ed0975f43f2772d 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -16,6 +16,7 @@
 
 #include "d_player.h"
 #include "r_data.h"
+#include "r_textures.h"
 
 //
 // POV related.
diff --git a/src/r_patch.h b/src/r_patch.h
deleted file mode 100644
index a2db6320ca82822f4bfc5648ab8556eaa7ada088..0000000000000000000000000000000000000000
--- a/src/r_patch.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// SONIC ROBO BLAST 2
-//-----------------------------------------------------------------------------
-// Copyright (C) 1993-1996 by id Software, Inc.
-// Copyright (C) 2018-2020 by Jaime "Lactozilla" Passos.
-// Copyright (C) 2019-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_patch.h
-/// \brief Patch generation.
-
-#ifndef __R_PATCH__
-#define __R_PATCH__
-
-#include "r_defs.h"
-#include "doomdef.h"
-
-// Structs
-typedef enum
-{
-	ROTAXIS_X, // Roll (the default)
-	ROTAXIS_Y, // Pitch
-	ROTAXIS_Z  // Yaw
-} rotaxis_t;
-
-typedef struct
-{
-	INT32 x, y;
-	rotaxis_t rotaxis;
-} spriteframepivot_t;
-
-typedef struct
-{
-	spriteframepivot_t pivot[64];
-	boolean available;
-} spriteinfo_t;
-
-// Conversions between patches / flats / textures...
-boolean R_CheckIfPatch(lumpnum_t lump);
-void R_TextureToFlat(size_t tex, UINT8 *flat);
-void R_PatchToFlat(patch_t *patch, UINT8 *flat);
-void R_PatchToMaskedFlat(patch_t *patch, UINT16 *raw, boolean flip);
-patch_t *R_FlatToPatch(UINT8 *raw, UINT16 width, UINT16 height, UINT16 leftoffset, UINT16 topoffset, size_t *destsize, boolean transparency);
-patch_t *R_MaskedFlatToPatch(UINT16 *raw, UINT16 width, UINT16 height, UINT16 leftoffset, UINT16 topoffset, size_t *destsize);
-
-// Portable Network Graphics
-boolean R_IsLumpPNG(const UINT8 *d, size_t s);
-#define W_ThrowPNGError(lumpname, wadfilename) I_Error("W_Wad: Lump \"%s\" in file \"%s\" is a .png - please convert to either Doom or Flat (raw) image format.", lumpname, wadfilename); // Fears Of LJ Sonic
-
-#ifndef NO_PNG_LUMPS
-UINT8 *R_PNGToFlat(UINT16 *width, UINT16 *height, UINT8 *png, size_t size);
-patch_t *R_PNGToPatch(const UINT8 *png, size_t size, size_t *destsize);
-boolean R_PNGDimensions(UINT8 *png, INT16 *width, INT16 *height, size_t size);
-#endif
-
-// SpriteInfo
-extern spriteinfo_t spriteinfo[NUMSPRITES];
-void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps);
-void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum);
-
-// Sprite rotation
-#ifdef ROTSPRITE
-INT32 R_GetRollAngle(angle_t rollangle);
-void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, spriteframe_t *sprframe, INT32 rot, UINT8 flip);
-void R_FreeSingleRotSprite(spritedef_t *spritedef);
-void R_FreeSkinRotSprite(size_t skinnum);
-extern fixed_t rollcosang[ROTANGLES];
-extern fixed_t rollsinang[ROTANGLES];
-void R_FreeAllRotSprite(void);
-#endif
-
-#endif // __R_PATCH__
diff --git a/src/r_patch.c b/src/r_picformats.c
similarity index 56%
rename from src/r_patch.c
rename to src/r_picformats.c
index 8980eda582b8692b1a4a8b28bb75a9aeacdfa81b..d48bbaaf2f1ff6eba4c0f172cb3fcf3ce0e68e3d 100644
--- a/src/r_patch.c
+++ b/src/r_picformats.c
@@ -9,16 +9,18 @@
 // terms of the GNU General Public License, version 2.
 // See the 'LICENSE' file for more details.
 //-----------------------------------------------------------------------------
-/// \file  r_patch.c
-/// \brief Patch generation.
+/// \file  r_picformats.c
+/// \brief Picture generation.
 
 #include "byteptr.h"
 #include "dehacked.h"
 #include "i_video.h"
 #include "r_data.h"
+#include "r_textures.h"
 #include "r_draw.h"
-#include "r_patch.h"
+#include "r_picformats.h"
 #include "r_things.h"
+#include "v_video.h"
 #include "z_zone.h"
 #include "w_wad.h"
 
@@ -50,223 +52,114 @@
 
 static unsigned char imgbuf[1<<26];
 
-//
-// R_CheckIfPatch
-//
-// Returns true if the lump is a valid patch.
-//
-boolean R_CheckIfPatch(lumpnum_t lump)
-{
-	size_t size;
-	INT16 width, height;
-	patch_t *patch;
-	boolean result;
-
-	size = W_LumpLength(lump);
-
-	// minimum length of a valid Doom patch
-	if (size < 13)
-		return false;
-
-	patch = (patch_t *)W_CacheLumpNum(lump, PU_STATIC);
-
-	width = SHORT(patch->width);
-	height = SHORT(patch->height);
-
-	result = (height > 0 && height <= 16384 && width > 0 && width <= 16384 && width < (INT16)(size / 4));
-
-	if (result)
-	{
-		// The dimensions seem like they might be valid for a patch, so
-		// check the column directory for extra security. All columns
-		// must begin after the column directory, and none of them must
-		// point past the end of the patch.
-		INT16 x;
-
-		for (x = 0; x < width; x++)
-		{
-			UINT32 ofs = LONG(patch->columnofs[x]);
-
-			// Need one byte for an empty column (but there's patches that don't know that!)
-			if (ofs < (UINT32)width * 4 + 8 || ofs >= (UINT32)size)
-			{
-				result = false;
-				break;
-			}
-		}
-	}
-
-	return result;
-}
-
-//
-// R_TextureToFlat
-//
-// Convert a texture to a flat.
-//
-void R_TextureToFlat(size_t tex, UINT8 *flat)
-{
-	texture_t *texture = textures[tex];
-
-	fixed_t col, ofs;
-	column_t *column;
-	UINT8 *desttop, *dest, *deststop;
-	UINT8 *source;
-
-	// yea
-	R_CheckTextureCache(tex);
-
-	desttop = flat;
-	deststop = desttop + (texture->width * texture->height);
-
-	for (col = 0; col < texture->width; col++, desttop++)
-	{
-		// no post_t info
-		if (!texture->holes)
-		{
-			column = (column_t *)(R_GetColumn(tex, col));
-			source = (UINT8 *)(column);
-			dest = desttop;
-			for (ofs = 0; dest < deststop && ofs < texture->height; ofs++)
-			{
-				if (source[ofs] != TRANSPARENTPIXEL)
-					*dest = source[ofs];
-				dest += texture->width;
-			}
-		}
-		else
-		{
-			INT32 topdelta, prevdelta = -1;
-			column = (column_t *)((UINT8 *)R_GetColumn(tex, col) - 3);
-			while (column->topdelta != 0xff)
-			{
-				topdelta = column->topdelta;
-				if (topdelta <= prevdelta)
-					topdelta += prevdelta;
-				prevdelta = topdelta;
-
-				dest = desttop + (topdelta * texture->width);
-				source = (UINT8 *)column + 3;
-				for (ofs = 0; dest < deststop && ofs < column->length; ofs++)
-				{
-					if (source[ofs] != TRANSPARENTPIXEL)
-						*dest = source[ofs];
-					dest += texture->width;
-				}
-				column = (column_t *)((UINT8 *)column + column->length + 4);
-			}
-		}
-	}
-}
-
-//
-// R_PatchToFlat
-//
-// Convert a patch to a flat.
-//
-void R_PatchToFlat(patch_t *patch, UINT8 *flat)
-{
-	fixed_t col, ofs;
-	column_t *column;
-	UINT8 *desttop, *dest, *deststop;
-	UINT8 *source;
-
-	desttop = flat;
-	deststop = desttop + (SHORT(patch->width) * SHORT(patch->height));
-
-	for (col = 0; col < SHORT(patch->width); col++, desttop++)
-	{
-		INT32 topdelta, prevdelta = -1;
-		column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[col]));
-
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-
-			dest = desttop + (topdelta * SHORT(patch->width));
-			source = (UINT8 *)(column) + 3;
-			for (ofs = 0; dest < deststop && ofs < column->length; ofs++)
-			{
-				*dest = source[ofs];
-				dest += SHORT(patch->width);
-			}
-			column = (column_t *)((UINT8 *)column + column->length + 4);
-		}
-	}
-}
+#ifdef PICTURE_PNG_USELOOKUP
+static colorlookup_t png_colorlookup;
+#endif
 
-//
-// R_PatchToMaskedFlat
-//
-// Convert a patch to a masked flat.
-// Now, what is a "masked" flat anyway?
-// It means the flat uses two bytes to store image data.
-// The upper byte is used to store the transparent pixel,
-// and the lower byte stores a palette index.
-//
-void R_PatchToMaskedFlat(patch_t *patch, UINT16 *raw, boolean flip)
+/** Converts a picture between two formats.
+  *
+  * \param informat Input picture format.
+  * \param picture Input picture data.
+  * \param outformat Output picture format.
+  * \param insize Input picture size.
+  * \param outsize Output picture size, as a pointer.
+  * \param inwidth Input picture width.
+  * \param inheight Input picture height.
+  * \param inleftoffset Input picture left offset, for patches.
+  * \param intopoffset Input picture top offset, for patches.
+  * \param flags Input picture flags.
+  * \return A pointer to the converted picture.
+  * \sa Picture_PatchConvert
+  * \sa Picture_FlatConvert
+  */
+void *Picture_Convert(
+	pictureformat_t informat, void *picture, pictureformat_t outformat,
+	size_t insize, size_t *outsize,
+	INT32 inwidth, INT32 inheight, INT32 inleftoffset, INT32 intopoffset,
+	pictureflags_t flags)
 {
-	fixed_t col, ofs;
-	column_t *column;
-	UINT16 *desttop, *dest, *deststop;
-	UINT8 *source;
-
-	desttop = raw;
-	deststop = desttop + (SHORT(patch->width) * SHORT(patch->height));
+	if (informat == PICFMT_NONE)
+		I_Error("Picture_Convert: input format was PICFMT_NONE!");
+	else if (outformat == PICFMT_NONE)
+		I_Error("Picture_Convert: output format was PICFMT_NONE!");
+	else if (informat == outformat)
+		I_Error("Picture_Convert: input and output formats were the same!");
+
+	if (Picture_IsPatchFormat(outformat))
+		return Picture_PatchConvert(informat, picture, outformat, insize, outsize, inwidth, inheight, inleftoffset, intopoffset, flags);
+	else if (Picture_IsFlatFormat(outformat))
+		return Picture_FlatConvert(informat, picture, outformat, insize, outsize, inwidth, inheight, inleftoffset, intopoffset, flags);
+	else
+		I_Error("Picture_Convert: unsupported input format!");
 
-	for (col = 0; col < SHORT(patch->width); col++, desttop++)
-	{
-		INT32 topdelta, prevdelta = -1;
-		column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[flip ? (patch->width-1-col) : col]));
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			dest = desttop + (topdelta * SHORT(patch->width));
-			source = (UINT8 *)(column) + 3;
-			for (ofs = 0; dest < deststop && ofs < column->length; ofs++)
-			{
-				*dest = source[ofs];
-				dest += SHORT(patch->width);
-			}
-			column = (column_t *)((UINT8 *)column + column->length + 4);
-		}
-	}
+	return NULL;
 }
 
-//
-// R_FlatToPatch
-//
-// Convert a flat to a patch.
-//
-patch_t *R_FlatToPatch(UINT8 *raw, UINT16 width, UINT16 height, UINT16 leftoffset, UINT16 topoffset, size_t *destsize, boolean transparency)
+/** Converts a picture to a patch.
+  *
+  * \param informat Input picture format.
+  * \param picture Input picture data.
+  * \param outformat Output picture format.
+  * \param insize Input picture size.
+  * \param outsize Output picture size, as a pointer.
+  * \param inwidth Input picture width.
+  * \param inheight Input picture height.
+  * \param inleftoffset Input picture left offset, for patches.
+  * \param intopoffset Input picture top offset, for patches.
+  * \param flags Input picture flags.
+  * \return A pointer to the converted picture.
+  */
+void *Picture_PatchConvert(
+	pictureformat_t informat, void *picture, pictureformat_t outformat,
+	size_t insize, size_t *outsize,
+	INT16 inwidth, INT16 inheight, INT16 inleftoffset, INT16 intopoffset,
+	pictureflags_t flags)
 {
-	UINT32 x, y;
+	INT16 x, y;
 	UINT8 *img;
 	UINT8 *imgptr = imgbuf;
 	UINT8 *colpointers, *startofspan;
 	size_t size = 0;
-
-	if (!raw)
-		return NULL;
+	patch_t *inpatch = NULL;
+	INT32 inbpp = Picture_FormatBPP(informat);
+
+	(void)insize; // ignore
+
+	if (informat == PICFMT_NONE)
+		I_Error("Picture_PatchConvert: input format was PICFMT_NONE!");
+	else if (outformat == PICFMT_NONE)
+		I_Error("Picture_PatchConvert: output format was PICFMT_NONE!");
+	else if (informat == outformat)
+		I_Error("Picture_PatchConvert: input and output formats were the same!");
+
+	if (inbpp == PICDEPTH_NONE)
+		I_Error("Picture_PatchConvert: unknown input bits per pixel?!");
+	if (Picture_FormatBPP(outformat) == PICDEPTH_NONE)
+		I_Error("Picture_PatchConvert: unknown output bits per pixel?!");
+
+	// If it's a patch, you can just figure out
+	// the dimensions from the header.
+	if (Picture_IsPatchFormat(informat))
+	{
+		inpatch = (patch_t *)picture;
+		inwidth = SHORT(inpatch->width);
+		inheight = SHORT(inpatch->height);
+		inleftoffset = SHORT(inpatch->leftoffset);
+		intopoffset = SHORT(inpatch->topoffset);
+	}
 
 	// Write image size and offset
-	WRITEINT16(imgptr, width);
-	WRITEINT16(imgptr, height);
-	WRITEINT16(imgptr, leftoffset);
-	WRITEINT16(imgptr, topoffset);
+	WRITEINT16(imgptr, inwidth);
+	WRITEINT16(imgptr, inheight);
+	WRITEINT16(imgptr, inleftoffset);
+	WRITEINT16(imgptr, intopoffset);
 
 	// Leave placeholder to column pointers
 	colpointers = imgptr;
-	imgptr += width*4;
+	imgptr += inwidth*4;
 
 	// Write columns
-	for (x = 0; x < width; x++)
+	for (x = 0; x < inwidth; x++)
 	{
 		int lastStartY = 0;
 		int spanSize = 0;
@@ -276,10 +169,58 @@ patch_t *R_FlatToPatch(UINT8 *raw, UINT16 width, UINT16 height, UINT16 leftoffse
 		WRITEINT32(colpointers, imgptr - imgbuf);
 
 		// Write pixels
-		for (y = 0; y < height; y++)
+		for (y = 0; y < inheight; y++)
 		{
-			UINT8 paletteIndex = raw[((y * width) + x)];
-			boolean opaque = transparency ? (paletteIndex != TRANSPARENTPIXEL) : true;
+			void *input = NULL;
+			boolean opaque = false;
+
+			// Read pixel
+			if (Picture_IsPatchFormat(informat))
+				input = Picture_GetPatchPixel(inpatch, informat, x, y, flags);
+			else if (Picture_IsFlatFormat(informat))
+			{
+				size_t offs = ((y * inwidth) + x);
+				switch (informat)
+				{
+					case PICFMT_FLAT32:
+						input = (UINT32 *)picture + offs;
+						break;
+					case PICFMT_FLAT16:
+						input = (UINT16 *)picture + offs;
+						break;
+					case PICFMT_FLAT:
+						input = (UINT8 *)picture + offs;
+						break;
+					default:
+						I_Error("Picture_PatchConvert: unsupported flat input format!");
+						break;
+				}
+			}
+			else
+				I_Error("Picture_PatchConvert: unsupported input format!");
+
+			// Determine opacity
+			if (input != NULL)
+			{
+				UINT8 alpha = 0xFF;
+				if (inbpp == PICDEPTH_32BPP)
+				{
+					RGBA_t px = *(RGBA_t *)input;
+					alpha = px.s.alpha;
+				}
+				else if (inbpp == PICDEPTH_16BPP)
+				{
+					UINT16 px = *(UINT16 *)input;
+					alpha = (px & 0xFF00) >> 8;
+				}
+				else if (inbpp == PICDEPTH_8BPP)
+				{
+					UINT8 px = *(UINT8 *)input;
+					if (px == TRANSPARENTPIXEL)
+						alpha = 0;
+				}
+				opaque = (alpha > 1);
+			}
 
 			// End span if we have a transparent pixel
 			if (!opaque)
@@ -331,7 +272,58 @@ patch_t *R_FlatToPatch(UINT8 *raw, UINT16 width, UINT16 height, UINT16 leftoffse
 			}
 
 			// Write the pixel
-			WRITEUINT8(imgptr, paletteIndex);
+			switch (outformat)
+			{
+				case PICFMT_PATCH32:
+				{
+					if (inbpp == PICDEPTH_32BPP)
+					{
+						RGBA_t out = *(RGBA_t *)input;
+						WRITEUINT32(imgptr, out.rgba);
+					}
+					else if (inbpp == PICDEPTH_16BPP)
+					{
+						RGBA_t out = pMasterPalette[*((UINT16 *)input) & 0xFF];
+						WRITEUINT32(imgptr, out.rgba);
+					}
+					else // PICFMT_PATCH
+					{
+						RGBA_t out = pMasterPalette[*((UINT8 *)input) & 0xFF];
+						WRITEUINT32(imgptr, out.rgba);
+					}
+					break;
+				}
+				case PICFMT_PATCH16:
+					if (inbpp == PICDEPTH_32BPP)
+					{
+						RGBA_t in = *(RGBA_t *)input;
+						UINT8 out = NearestColor(in.s.red, in.s.green, in.s.blue);
+						WRITEUINT16(imgptr, (0xFF00 | out));
+					}
+					else if (inbpp == PICDEPTH_16BPP)
+						WRITEUINT16(imgptr, *(UINT16 *)input);
+					else // PICFMT_PATCH
+						WRITEUINT16(imgptr, (0xFF00 | (*(UINT8 *)input)));
+					break;
+				default: // PICFMT_PATCH
+				{
+					if (inbpp == PICDEPTH_32BPP)
+					{
+						RGBA_t in = *(RGBA_t *)input;
+						UINT8 out = NearestColor(in.s.red, in.s.green, in.s.blue);
+						WRITEUINT8(imgptr, out);
+					}
+					else if (inbpp == PICDEPTH_16BPP)
+					{
+						UINT16 out = *(UINT16 *)input;
+						WRITEUINT8(imgptr, (out & 0xFF));
+					}
+					else // PICFMT_PATCH
+						WRITEUINT8(imgptr, *(UINT8 *)input);
+					break;
+				}
+			}
+
 			spanSize++;
 			startofspan[1] = spanSize;
 		}
@@ -346,133 +338,400 @@ patch_t *R_FlatToPatch(UINT8 *raw, UINT16 width, UINT16 height, UINT16 leftoffse
 	img = Z_Malloc(size, PU_STATIC, NULL);
 	memcpy(img, imgbuf, size);
 
-	Z_Free(raw);
+	if (outsize != NULL)
+		*outsize = size;
+	return img;
+}
+
+/** Converts a picture to a flat.
+  *
+  * \param informat Input picture format.
+  * \param picture Input picture data.
+  * \param outformat Output picture format.
+  * \param insize Input picture size.
+  * \param outsize Output picture size, as a pointer.
+  * \param inwidth Input picture width.
+  * \param inheight Input picture height.
+  * \param inleftoffset Input picture left offset, for patches.
+  * \param intopoffset Input picture top offset, for patches.
+  * \param flags Input picture flags.
+  * \return A pointer to the converted picture.
+  */
+void *Picture_FlatConvert(
+	pictureformat_t informat, void *picture, pictureformat_t outformat,
+	size_t insize, size_t *outsize,
+	INT16 inwidth, INT16 inheight, INT16 inleftoffset, INT16 intopoffset,
+	pictureflags_t flags)
+{
+	void *outflat;
+	patch_t *inpatch = NULL;
+	INT32 inbpp = Picture_FormatBPP(informat);
+	INT32 outbpp = Picture_FormatBPP(outformat);
+	INT32 x, y;
+	size_t size;
+
+	(void)insize; // ignore
+	(void)inleftoffset; // ignore
+	(void)intopoffset; // ignore
+
+	if (informat == PICFMT_NONE)
+		I_Error("Picture_FlatConvert: input format was PICFMT_NONE!");
+	else if (outformat == PICFMT_NONE)
+		I_Error("Picture_FlatConvert: output format was PICFMT_NONE!");
+	else if (informat == outformat)
+		I_Error("Picture_FlatConvert: input and output formats were the same!");
+
+	if (inbpp == PICDEPTH_NONE)
+		I_Error("Picture_FlatConvert: unknown input bits per pixel?!");
+	if (outbpp == PICDEPTH_NONE)
+		I_Error("Picture_FlatConvert: unknown output bits per pixel?!");
+
+	// If it's a patch, you can just figure out
+	// the dimensions from the header.
+	if (Picture_IsPatchFormat(informat))
+	{
+		inpatch = (patch_t *)picture;
+		inwidth = SHORT(inpatch->width);
+		inheight = SHORT(inpatch->height);
+	}
+
+	size = (inwidth * inheight) * (outbpp / 8);
+	outflat = Z_Calloc(size, PU_STATIC, NULL);
+	if (outsize)
+		*outsize = size;
+
+	// Set transparency
+	if (outbpp == PICDEPTH_8BPP)
+		memset(outflat, TRANSPARENTPIXEL, size);
+
+	for (y = 0; y < inheight; y++)
+		for (x = 0; x < inwidth; x++)
+		{
+			void *input;
+			size_t offs = ((y * inwidth) + x);
+
+			// Read pixel
+			if (Picture_IsPatchFormat(informat))
+				input = Picture_GetPatchPixel(inpatch, informat, x, y, flags);
+			else if (Picture_IsFlatFormat(informat))
+				input = (UINT8 *)picture + (offs * (inbpp / 8));
+			else
+				I_Error("Picture_FlatConvert: unsupported input format!");
 
-	if (destsize != NULL)
-		*destsize = size;
-	return (patch_t *)img;
+			if (!input)
+				continue;
+
+			switch (outformat)
+			{
+				case PICFMT_FLAT32:
+				{
+					UINT32 *f32 = (UINT32 *)outflat;
+					if (inbpp == PICDEPTH_32BPP)
+					{
+						RGBA_t out = *(RGBA_t *)input;
+						f32[offs] = out.rgba;
+					}
+					else if (inbpp == PICDEPTH_16BPP)
+					{
+						RGBA_t out = pMasterPalette[*((UINT16 *)input) & 0xFF];
+						f32[offs] = out.rgba;
+					}
+					else // PICFMT_PATCH
+					{
+						RGBA_t out = pMasterPalette[*((UINT8 *)input) & 0xFF];
+						f32[offs] = out.rgba;
+					}
+					break;
+				}
+				case PICFMT_FLAT16:
+				{
+					UINT16 *f16 = (UINT16 *)outflat;
+					if (inbpp == PICDEPTH_32BPP)
+					{
+						RGBA_t in = *(RGBA_t *)input;
+						UINT8 out = NearestColor(in.s.red, in.s.green, in.s.blue);
+						f16[offs] = (0xFF00 | out);
+					}
+					else if (inbpp == PICDEPTH_16BPP)
+						f16[offs] = *(UINT16 *)input;
+					else // PICFMT_PATCH
+						f16[offs] = (0xFF00 | *((UINT8 *)input));
+					break;
+				}
+				case PICFMT_FLAT:
+				{
+					UINT8 *f8 = (UINT8 *)outflat;
+					if (inbpp == PICDEPTH_32BPP)
+					{
+						RGBA_t in = *(RGBA_t *)input;
+						UINT8 out = NearestColor(in.s.red, in.s.green, in.s.blue);
+						f8[offs] = out;
+					}
+					else if (inbpp == PICDEPTH_16BPP)
+					{
+						UINT16 out = *(UINT16 *)input;
+						f8[offs] = (out & 0xFF);
+					}
+					else // PICFMT_PATCH
+						f8[offs] = *(UINT8 *)input;
+					break;
+				}
+				default:
+					I_Error("Picture_FlatConvert: unsupported output format!");
+			}
+		}
+
+	return outflat;
+}
+
+/** Returns a pixel from a patch.
+  *
+  * \param patch Input patch.
+  * \param informat Input picture format.
+  * \param x Pixel X position.
+  * \param y Pixel Y position.
+  * \param flags Input picture flags.
+  * \return A pointer to a pixel in the patch. Returns NULL if not opaque.
+  */
+void *Picture_GetPatchPixel(
+	patch_t *patch, pictureformat_t informat,
+	INT32 x, INT32 y,
+	pictureflags_t flags)
+{
+	fixed_t ofs;
+	column_t *column;
+	UINT8 *s8 = NULL;
+	UINT16 *s16 = NULL;
+	UINT32 *s32 = NULL;
+
+	if (patch == NULL)
+		I_Error("Picture_GetPatchPixel: patch == NULL");
+
+	if (x >= 0 && x < SHORT(patch->width))
+	{
+		INT32 topdelta, prevdelta = -1;
+		INT32 colofs = 0;
+
+		if (flags & PICFLAGS_XFLIP)
+			colofs = LONG(patch->columnofs[(SHORT(patch->width)-1)-x]);
+		else
+			colofs = LONG(patch->columnofs[x]);
+
+		// Column offsets are pointers so no casting required
+		column = (column_t *)((UINT8 *)patch + colofs);
+
+		while (column->topdelta != 0xff)
+		{
+			topdelta = column->topdelta;
+			if (topdelta <= prevdelta)
+				topdelta += prevdelta;
+			prevdelta = topdelta;
+			s8 = (UINT8 *)(column) + 3;
+			if (informat == PICFMT_PATCH32)
+				s32 = (UINT32 *)s8;
+			else if (informat == PICFMT_PATCH16)
+				s16 = (UINT16 *)s8;
+			for (ofs = 0; ofs < column->length; ofs++)
+			{
+				if ((topdelta + ofs) == y)
+				{
+					if (informat == PICFMT_PATCH32)
+						return &s32[ofs];
+					else if (informat == PICFMT_PATCH16)
+						return &s16[ofs];
+					else // PICFMT_PATCH
+						return &s8[ofs];
+				}
+			}
+			if (informat == PICFMT_PATCH32)
+				column = (column_t *)((UINT32 *)column + column->length);
+			else if (informat == PICFMT_PATCH16)
+				column = (column_t *)((UINT16 *)column + column->length);
+			else
+				column = (column_t *)((UINT8 *)column + column->length);
+			column = (column_t *)((UINT8 *)column + 4);
+		}
+	}
+
+	return NULL;
+}
+
+/** Returns the amount of bits per pixel in the specified picture format.
+  *
+  * \param format Input picture format.
+  * \return The bits per pixel amount of the picture format.
+  */
+INT32 Picture_FormatBPP(pictureformat_t format)
+{
+	INT32 bpp = PICDEPTH_NONE;
+	switch (format)
+	{
+		case PICFMT_PATCH32:
+		case PICFMT_FLAT32:
+		case PICFMT_PNG:
+			bpp = PICDEPTH_32BPP;
+			break;
+		case PICFMT_PATCH16:
+		case PICFMT_FLAT16:
+			bpp = PICDEPTH_16BPP;
+			break;
+		case PICFMT_PATCH:
+		case PICFMT_FLAT:
+			bpp = PICDEPTH_8BPP;
+			break;
+		default:
+			break;
+	}
+	return bpp;
+}
+
+/** Checks if the specified picture format is a patch.
+  *
+  * \param format Input picture format.
+  * \return True if the picture format is a patch, false if not.
+  */
+boolean Picture_IsPatchFormat(pictureformat_t format)
+{
+	return (format == PICFMT_PATCH || format == PICFMT_PATCH16 || format == PICFMT_PATCH32);
+}
+
+/** Checks if the specified picture format is a flat.
+  *
+  * \param format Input picture format.
+  * \return True if the picture format is a flat, false if not.
+  */
+boolean Picture_IsFlatFormat(pictureformat_t format)
+{
+	return (format == PICFMT_FLAT || format == PICFMT_FLAT16 || format == PICFMT_FLAT32);
+}
+
+/** Returns true if the lump is a valid patch.
+  * PICFMT_PATCH only, I think??
+  *
+  * \param patch Input patch.
+  * \param picture Input patch size.
+  * \return True if the input patch is valid.
+  */
+boolean Picture_CheckIfPatch(patch_t *patch, size_t size)
+{
+	INT16 width, height;
+	boolean result;
+
+	// minimum length of a valid Doom patch
+	if (size < 13)
+		return false;
+
+	width = SHORT(patch->width);
+	height = SHORT(patch->height);
+	result = (height > 0 && height <= 16384 && width > 0 && width <= 16384);
+
+	if (result)
+	{
+		// The dimensions seem like they might be valid for a patch, so
+		// check the column directory for extra security. All columns
+		// must begin after the column directory, and none of them must
+		// point past the end of the patch.
+		INT16 x;
+
+		for (x = 0; x < width; x++)
+		{
+			UINT32 ofs = LONG(patch->columnofs[x]);
+
+			// Need one byte for an empty column (but there's patches that don't know that!)
+			if (ofs < (UINT32)width * 4 + 8 || ofs >= (UINT32)size)
+			{
+				result = false;
+				break;
+			}
+		}
+	}
+
+	return result;
 }
 
-//
-// R_MaskedFlatToPatch
-//
-// Convert a masked flat to a patch.
-// Explanation of "masked" flats in R_PatchToMaskedFlat.
-//
-patch_t *R_MaskedFlatToPatch(UINT16 *raw, UINT16 width, UINT16 height, UINT16 leftoffset, UINT16 topoffset, size_t *destsize)
+/** Converts a texture to a flat.
+  *
+  * \param trickytex The texture number.
+  * \return The converted flat.
+  */
+void *Picture_TextureToFlat(size_t trickytex)
 {
-	UINT32 x, y;
-	UINT8 *img;
-	UINT8 *imgptr = imgbuf;
-	UINT8 *colpointers, *startofspan;
-	size_t size = 0;
-
-	if (!raw)
-		return NULL;
+	texture_t *texture;
+	size_t tex;
 
-	// Write image size and offset
-	WRITEINT16(imgptr, width);
-	WRITEINT16(imgptr, height);
-	WRITEINT16(imgptr, leftoffset);
-	WRITEINT16(imgptr, topoffset);
+	UINT8 *converted;
+	size_t flatsize;
+	fixed_t col, ofs;
+	column_t *column;
+	UINT8 *desttop, *dest, *deststop;
+	UINT8 *source;
 
-	// Leave placeholder to column pointers
-	colpointers = imgptr;
-	imgptr += width*4;
+	if (trickytex >= (unsigned)numtextures)
+		I_Error("Picture_TextureToFlat: invalid texture number!");
 
-	// Write columns
-	for (x = 0; x < width; x++)
-	{
-		int lastStartY = 0;
-		int spanSize = 0;
-		startofspan = NULL;
+	// Check the texture cache
+	// If the texture's not there, it'll be generated right now
+	tex = trickytex;
+	texture = textures[tex];
+	R_CheckTextureCache(tex);
 
-		// Write column pointer
-		WRITEINT32(colpointers, imgptr - imgbuf);
+	// Allocate the flat
+	flatsize = (texture->width * texture->height);
+	converted = Z_Malloc(flatsize, PU_STATIC, NULL);
+	memset(converted, TRANSPARENTPIXEL, flatsize);
 
-		// Write pixels
-		for (y = 0; y < height; y++)
+	// Now we're gonna write to it
+	desttop = converted;
+	deststop = desttop + flatsize;
+	for (col = 0; col < texture->width; col++, desttop++)
+	{
+		// no post_t info
+		if (!texture->holes)
 		{
-			UINT16 pixel = raw[((y * width) + x)];
-			UINT8 paletteIndex = (pixel & 0xFF);
-			UINT8 opaque = (pixel != 0xFF00); // If 1, we have a pixel
-
-			// End span if we have a transparent pixel
-			if (!opaque)
+			column = (column_t *)(R_GetColumn(tex, col));
+			source = (UINT8 *)(column);
+			dest = desttop;
+			for (ofs = 0; dest < deststop && ofs < texture->height; ofs++)
 			{
-				if (startofspan)
-					WRITEUINT8(imgptr, 0);
-				startofspan = NULL;
-				continue;
+				if (source[ofs] != TRANSPARENTPIXEL)
+					*dest = source[ofs];
+				dest += texture->width;
 			}
-
-			// Start new column if we need to
-			if (!startofspan || spanSize == 255)
+		}
+		else
+		{
+			INT32 topdelta, prevdelta = -1;
+			column = (column_t *)((UINT8 *)R_GetColumn(tex, col) - 3);
+			while (column->topdelta != 0xff)
 			{
-				int writeY = y;
-
-				// If we reached the span size limit, finish the previous span
-				if (startofspan)
-					WRITEUINT8(imgptr, 0);
+				topdelta = column->topdelta;
+				if (topdelta <= prevdelta)
+					topdelta += prevdelta;
+				prevdelta = topdelta;
 
-				if (y > 254)
+				dest = desttop + (topdelta * texture->width);
+				source = (UINT8 *)column + 3;
+				for (ofs = 0; dest < deststop && ofs < column->length; ofs++)
 				{
-					// Make sure we're aligned to 254
-					if (lastStartY < 254)
-					{
-						WRITEUINT8(imgptr, 254);
-						WRITEUINT8(imgptr, 0);
-						imgptr += 2;
-						lastStartY = 254;
-					}
-
-					// Write stopgap empty spans if needed
-					writeY = y - lastStartY;
-
-					while (writeY > 254)
-					{
-						WRITEUINT8(imgptr, 254);
-						WRITEUINT8(imgptr, 0);
-						imgptr += 2;
-						writeY -= 254;
-					}
+					if (source[ofs] != TRANSPARENTPIXEL)
+						*dest = source[ofs];
+					dest += texture->width;
 				}
-
-				startofspan = imgptr;
-				WRITEUINT8(imgptr, writeY);
-				imgptr += 2;
-				spanSize = 0;
-
-				lastStartY = y;
+				column = (column_t *)((UINT8 *)column + column->length + 4);
 			}
-
-			// Write the pixel
-			WRITEUINT8(imgptr, paletteIndex);
-			spanSize++;
-			startofspan[1] = spanSize;
 		}
-
-		if (startofspan)
-			WRITEUINT8(imgptr, 0);
-
-		WRITEUINT8(imgptr, 0xFF);
 	}
 
-	size = imgptr-imgbuf;
-	img = Z_Malloc(size, PU_STATIC, NULL);
-	memcpy(img, imgbuf, size);
-
-	if (destsize != NULL)
-		*destsize = size;
-	return (patch_t *)img;
+	return converted;
 }
 
-//
-// R_IsLumpPNG
-//
-// Returns true if the lump is a valid PNG.
-//
-boolean R_IsLumpPNG(const UINT8 *d, size_t s)
+/** Returns true if the lump is a valid PNG.
+  *
+  * \param d The lump to be checked.
+  * \param s The lump size.
+  * \return True if the lump is a PNG image.
+  */
+boolean Picture_IsLumpPNG(const UINT8 *d, size_t s)
 {
 	if (s < 67) // http://garethrees.org/2007/11/14/pngcrush/
 		return false;
@@ -539,13 +798,24 @@ static void PNG_warn(png_structp PNG, png_const_charp pngtext)
 	CONS_Debug(DBG_RENDER, "libpng warning at %p: %s", PNG, pngtext);
 }
 
-static png_bytep *PNG_Read(const UINT8 *png, UINT16 *w, UINT16 *h, INT16 *topoffset, INT16 *leftoffset, size_t size)
+static png_bytep *PNG_Read(
+	const UINT8 *png,
+	INT32 *w, INT32 *h, INT16 *topoffset, INT16 *leftoffset,
+	boolean *use_palette, size_t size)
 {
 	png_structp png_ptr;
 	png_infop png_info_ptr;
 	png_uint_32 width, height;
 	int bit_depth, color_type;
 	png_uint_32 y;
+
+	png_colorp palette;
+	int palette_size;
+
+	png_bytep trans;
+	int trans_num;
+	png_color_16p trans_values;
+
 #ifdef PNG_SETJMP_SUPPORTED
 #ifdef USE_FAR_KEYWORD
 	jmp_buf jmpbuf;
@@ -560,17 +830,13 @@ static png_bytep *PNG_Read(const UINT8 *png, UINT16 *w, UINT16 *h, INT16 *topoff
 
 	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, PNG_error, PNG_warn);
 	if (!png_ptr)
-	{
-		CONS_Debug(DBG_RENDER, "PNG_Load: Error on initialize libpng\n");
-		return NULL;
-	}
+		I_Error("PNG_Read: Couldn't initialize libpng!");
 
 	png_info_ptr = png_create_info_struct(png_ptr);
 	if (!png_info_ptr)
 	{
-		CONS_Debug(DBG_RENDER, "PNG_Load: Error on allocate for libpng\n");
 		png_destroy_read_struct(&png_ptr, NULL, NULL);
-		return NULL;
+		I_Error("PNG_Read: libpng couldn't allocate memory!");
 	}
 
 #ifdef USE_FAR_KEYWORD
@@ -579,9 +845,8 @@ static png_bytep *PNG_Read(const UINT8 *png, UINT16 *w, UINT16 *h, INT16 *topoff
 	if (setjmp(png_jmpbuf(png_ptr)))
 #endif
 	{
-		//CONS_Debug(DBG_RENDER, "libpng load error on %s\n", filename);
 		png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
-		return NULL;
+		I_Error("PNG_Read: libpng load error!");
 	}
 #ifdef USE_FAR_KEYWORD
 	png_memcpy(png_jmpbuf(png_ptr), jmpbuf, sizeof jmp_buf);
@@ -610,10 +875,48 @@ static png_bytep *PNG_Read(const UINT8 *png, UINT16 *w, UINT16 *h, INT16 *topoff
 	if (bit_depth == 16)
 		png_set_strip_16(png_ptr);
 
+	palette = NULL;
+	*use_palette = false;
+
 	if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
 		png_set_gray_to_rgb(png_ptr);
 	else if (color_type == PNG_COLOR_TYPE_PALETTE)
-		png_set_palette_to_rgb(png_ptr);
+	{
+		boolean usepal = false;
+
+		// Lactozilla: Check if the PNG has a palette, and if its color count
+		// matches the color count of SRB2's palette: 256 colors.
+		if (png_get_PLTE(png_ptr, png_info_ptr, &palette, &palette_size))
+		{
+			if (palette_size == 256)
+				usepal = true;
+		}
+
+		// If any of the tRNS colors have an alpha lower than 0xFF, and that
+		// color is present on the image, the palette flag is disabled.
+		png_get_tRNS(png_ptr, png_info_ptr, &trans, &trans_num, &trans_values);
+
+		if (trans_num == 256)
+		{
+			int i;
+			for (i = 0; i < trans_num; i++)
+			{
+				// libpng will transform this image into RGB even if
+				// the transparent index does not exist in the image,
+				// and there is no way around that.
+				if (trans[i] < 0xFF)
+				{
+					usepal = false;
+					break;
+				}
+			}
+		}
+
+		if (usepal)
+			*use_palette = true;
+		else
+			png_set_palette_to_rgb(png_ptr);
+	}
 
 	if (png_get_valid(png_ptr, png_info_ptr, PNG_INFO_tRNS))
 		png_set_tRNS_to_alpha(png_ptr);
@@ -654,106 +957,248 @@ static png_bytep *PNG_Read(const UINT8 *png, UINT16 *w, UINT16 *h, INT16 *topoff
 
 	*w = (INT32)width;
 	*h = (INT32)height;
+
 	return row_pointers;
 }
 
-// Convert a PNG to a raw image.
-static UINT8 *PNG_RawConvert(const UINT8 *png, UINT16 *w, UINT16 *h, INT16 *topoffset, INT16 *leftoffset, size_t size)
+/** Converts a PNG to a picture.
+  *
+  * \param png The PNG image.
+  * \param outformat The output picture's format.
+  * \param w The output picture's width, as a pointer.
+  * \param h The output picture's height, as a pointer.
+  * \param topoffset The output picture's top offset, for sprites, as a pointer.
+  * \param leftoffset The output picture's left offset, for sprites, as a pointer.
+  * \param insize The input picture's size.
+  * \param outsize A pointer to the output picture's size.
+  * \param flags Input picture flags.
+  * \return A pointer to the converted picture.
+  */
+void *Picture_PNGConvert(
+	const UINT8 *png, pictureformat_t outformat,
+	INT32 *w, INT32 *h,
+	INT16 *topoffset, INT16 *leftoffset,
+	size_t insize, size_t *outsize,
+	pictureflags_t flags)
 {
-	UINT8 *flat;
+	void *flat;
+	INT32 outbpp;
+	size_t flatsize;
 	png_uint_32 x, y;
-	png_bytep *row_pointers = PNG_Read(png, w, h, topoffset, leftoffset, size);
+	png_bytep row;
+	boolean palette = false;
+	png_bytep *row_pointers = PNG_Read(png, w, h, topoffset, leftoffset, &palette, insize);
 	png_uint_32 width = *w, height = *h;
 
-	if (!row_pointers)
-		I_Error("PNG_RawConvert: conversion failed");
+	if (png == NULL)
+		I_Error("Picture_PNGConvert: picture was NULL!");
 
-	// Convert the image to 8bpp
-	flat = Z_Malloc(width * height, PU_LEVEL, NULL);
-	memset(flat, TRANSPARENTPIXEL, width * height);
-	for (y = 0; y < height; y++)
+	if (row_pointers == NULL)
+		I_Error("Picture_PNGConvert: row_pointers was NULL!");
+
+	// Find the output format's bits per pixel amount
+	outbpp = Picture_FormatBPP(outformat);
+
+	// Hack for patches because you'll want to preserve transparency.
+	if (Picture_IsPatchFormat(outformat))
 	{
-		png_bytep row = row_pointers[y];
-		for (x = 0; x < width; x++)
-		{
-			png_bytep px = &(row[x * 4]);
-			if ((UINT8)px[3])
-				flat[((y * width) + x)] = NearestColor((UINT8)px[0], (UINT8)px[1], (UINT8)px[2]);
-		}
+		// Force a higher bit depth
+		if (outbpp == PICDEPTH_8BPP)
+			outbpp = PICDEPTH_16BPP;
 	}
-	free(row_pointers);
 
-	return flat;
-}
+	// Shouldn't happen.
+	if (outbpp == PICDEPTH_NONE)
+		I_Error("Picture_PNGConvert: unknown output bits per pixel?!");
 
-// Convert a PNG with transparency to a raw image.
-static UINT16 *PNG_MaskedRawConvert(const UINT8 *png, UINT16 *w, UINT16 *h, INT16 *topoffset, INT16 *leftoffset, size_t size)
-{
-	UINT16 *flat;
-	png_uint_32 x, y;
-	png_bytep *row_pointers = PNG_Read(png, w, h, topoffset, leftoffset, size);
-	png_uint_32 width = *w, height = *h;
-	size_t flatsize, i;
+	// Figure out the size
+	flatsize = (width * height) * (outbpp / 8);
+	if (outsize)
+		*outsize = flatsize;
 
-	if (!row_pointers)
-		I_Error("PNG_MaskedRawConvert: conversion failed");
+	// Convert the image
+	flat = Z_Calloc(flatsize, PU_STATIC, NULL);
 
-	// Convert the image to 16bpp
-	flatsize = (width * height);
-	flat = Z_Malloc(flatsize * sizeof(UINT16), PU_LEVEL, NULL);
+	// Set transparency
+	if (outbpp == PICDEPTH_8BPP)
+		memset(flat, TRANSPARENTPIXEL, (width * height));
 
-	// can't memset here
-	for (i = 0; i < flatsize; i++)
-		flat[i] = 0xFF00;
+#ifdef PICTURE_PNG_USELOOKUP
+	if (outbpp != PICDEPTH_32BPP)
+		InitColorLUT(&png_colorlookup, pMasterPalette, false);
+#endif
 
-	for (y = 0; y < height; y++)
+	if (outbpp == PICDEPTH_32BPP)
 	{
-		png_bytep row = row_pointers[y];
-		for (x = 0; x < width; x++)
+		RGBA_t out;
+		UINT32 *outflat = (UINT32 *)flat;
+
+		if (palette)
+		{
+			for (y = 0; y < height; y++)
+			{
+				row = row_pointers[y];
+				for (x = 0; x < width; x++)
+				{
+					out = V_GetColor(row[x]);
+					outflat[((y * width) + x)] = out.rgba;
+				}
+			}
+		}
+		else
+		{
+			for (y = 0; y < height; y++)
+			{
+				row = row_pointers[y];
+				for (x = 0; x < width; x++)
+				{
+					png_bytep px = &(row[x * 4]);
+					if ((UINT8)px[3])
+					{
+						out.s.red = (UINT8)px[0];
+						out.s.green = (UINT8)px[1];
+						out.s.blue = (UINT8)px[2];
+						out.s.alpha = (UINT8)px[3];
+						outflat[((y * width) + x)] = out.rgba;
+					}
+					else
+						outflat[((y * width) + x)] = 0x00000000;
+				}
+			}
+		}
+	}
+	else if (outbpp == PICDEPTH_16BPP)
+	{
+		UINT16 *outflat = (UINT16 *)flat;
+
+		if (palette)
+		{
+			for (y = 0; y < height; y++)
+			{
+				row = row_pointers[y];
+				for (x = 0; x < width; x++)
+					outflat[((y * width) + x)] = (0xFF << 8) | row[x];
+			}
+		}
+		else
+		{
+			for (y = 0; y < height; y++)
+			{
+				row = row_pointers[y];
+				for (x = 0; x < width; x++)
+				{
+					png_bytep px = &(row[x * 4]);
+					UINT8 red = (UINT8)px[0];
+					UINT8 green = (UINT8)px[1];
+					UINT8 blue = (UINT8)px[2];
+					UINT8 alpha = (UINT8)px[3];
+
+					if (alpha)
+					{
+#ifdef PICTURE_PNG_USELOOKUP
+						UINT8 palidx = GetColorLUT(&png_colorlookup, red, green, blue);
+#else
+						UINT8 palidx = NearestColor(red, green, blue);
+#endif
+						outflat[((y * width) + x)] = (0xFF << 8) | palidx;
+					}
+					else
+						outflat[((y * width) + x)] = 0x0000;
+				}
+			}
+		}
+	}
+	else // 8bpp
+	{
+		UINT8 *outflat = (UINT8 *)flat;
+
+		if (palette)
+		{
+			for (y = 0; y < height; y++)
+			{
+				row = row_pointers[y];
+				for (x = 0; x < width; x++)
+					outflat[((y * width) + x)] = row[x];
+			}
+		}
+		else
 		{
-			png_bytep px = &(row[x * 4]);
-			if ((UINT8)px[3])
-				flat[((y * width) + x)] = NearestColor((UINT8)px[0], (UINT8)px[1], (UINT8)px[2]);
+			for (y = 0; y < height; y++)
+			{
+				row = row_pointers[y];
+				for (x = 0; x < width; x++)
+				{
+					png_bytep px = &(row[x * 4]);
+					UINT8 red = (UINT8)px[0];
+					UINT8 green = (UINT8)px[1];
+					UINT8 blue = (UINT8)px[2];
+					UINT8 alpha = (UINT8)px[3];
+
+					if (alpha)
+					{
+#ifdef PICTURE_PNG_USELOOKUP
+						UINT8 palidx = GetColorLUT(&png_colorlookup, red, green, blue);
+#else
+						UINT8 palidx = NearestColor(red, green, blue);
+#endif
+						outflat[((y * width) + x)] = palidx;
+					}
+				}
+			}
 		}
 	}
+
+	// Free the row pointers that we allocated for libpng.
+	for (y = 0; y < height; y++)
+		free(row_pointers[y]);
 	free(row_pointers);
 
-	return flat;
-}
+	// But wait, there's more!
+	if (Picture_IsPatchFormat(outformat))
+	{
+		void *converted;
+		pictureformat_t informat = PICFMT_NONE;
+		INT16 patleftoffset = 0, pattopoffset = 0;
 
-//
-// R_PNGToFlat
-//
-// Convert a PNG to a flat.
-//
-UINT8 *R_PNGToFlat(UINT16 *width, UINT16 *height, UINT8 *png, size_t size)
-{
-	return PNG_RawConvert(png, width, height, NULL, NULL, size);
-}
+		// Figure out the format of the flat, from the bit depth of the output format
+		switch (outbpp)
+		{
+			case 32:
+				informat = PICFMT_FLAT32;
+				break;
+			case 16:
+				informat = PICFMT_FLAT16;
+				break;
+			default:
+				informat = PICFMT_FLAT;
+				break;
+		}
 
-//
-// R_PNGToPatch
-//
-// Convert a PNG to a patch.
-//
-patch_t *R_PNGToPatch(const UINT8 *png, size_t size, size_t *destsize)
-{
-	UINT16 width, height;
-	INT16 topoffset = 0, leftoffset = 0;
-	UINT16 *raw = PNG_MaskedRawConvert(png, &width, &height, &topoffset, &leftoffset, size);
+		// Also find out if leftoffset and topoffset aren't pointing to NULL.
+		if (leftoffset)
+			patleftoffset = *leftoffset;
+		if (topoffset)
+			pattopoffset = *topoffset;
 
-	if (!raw)
-		I_Error("R_PNGToPatch: conversion failed");
+		// Now, convert it!
+		converted = Picture_PatchConvert(informat, flat, outformat, insize, outsize, (INT16)width, (INT16)height, patleftoffset, pattopoffset, flags);
+		Z_Free(flat);
+		return converted;
+	}
 
-	return R_MaskedFlatToPatch(raw, width, height, leftoffset, topoffset, destsize);
+	// Return the converted flat!
+	return flat;
 }
 
-//
-// R_PNGDimensions
-//
-// Get the dimensions of a PNG file.
-//
-boolean R_PNGDimensions(UINT8 *png, INT16 *width, INT16 *height, size_t size)
+/** Returns the dimensions of a PNG image, but doesn't perform any conversions.
+  *
+  * \param png The PNG image.
+  * \param width A pointer to the input picture's width.
+  * \param height A pointer to the input picture's height.
+  * \param size The input picture's size.
+  * \return True if reading the file succeeded, false if it failed.
+  */
+boolean Picture_PNGDimensions(UINT8 *png, INT16 *width, INT16 *height, size_t size)
 {
 	png_structp png_ptr;
 	png_infop png_info_ptr;
@@ -770,17 +1215,13 @@ boolean R_PNGDimensions(UINT8 *png, INT16 *width, INT16 *height, size_t size)
 	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
 		PNG_error, PNG_warn);
 	if (!png_ptr)
-	{
-		CONS_Debug(DBG_RENDER, "PNG_Load: Error on initialize libpng\n");
-		return false;
-	}
+		I_Error("Picture_PNGDimensions: Couldn't initialize libpng!");
 
 	png_info_ptr = png_create_info_struct(png_ptr);
 	if (!png_info_ptr)
 	{
-		CONS_Debug(DBG_RENDER, "PNG_Load: Error on allocate for libpng\n");
 		png_destroy_read_struct(&png_ptr, NULL, NULL);
-		return false;
+		I_Error("Picture_PNGDimensions: libpng couldn't allocate memory!");
 	}
 
 #ifdef USE_FAR_KEYWORD
@@ -789,9 +1230,8 @@ boolean R_PNGDimensions(UINT8 *png, INT16 *width, INT16 *height, size_t size)
 	if (setjmp(png_jmpbuf(png_ptr)))
 #endif
 	{
-		//CONS_Debug(DBG_RENDER, "libpng load error on %s\n", filename);
 		png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
-		return false;
+		I_Error("Picture_PNGDimensions: libpng load error!");
 	}
 #ifdef USE_FAR_KEYWORD
 	png_memcpy(png_jmpbuf(png_ptr), jmpbuf, sizeof jmp_buf);
@@ -1137,35 +1577,6 @@ void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps)
 	}
 }
 
-static UINT16 GetPatchPixel(patch_t *patch, INT32 x, INT32 y, boolean flip)
-{
-	fixed_t ofs;
-	column_t *column;
-	UINT8 *source;
-
-	if (x >= 0 && x < SHORT(patch->width))
-	{
-		INT32 topdelta, prevdelta = -1;
-		column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[flip ? (patch->width-1-x) : x]));
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			source = (UINT8 *)(column) + 3;
-			for (ofs = 0; ofs < column->length; ofs++)
-			{
-				if ((topdelta + ofs) == y)
-					return source[ofs];
-			}
-			column = (column_t *)((UINT8 *)column + column->length + 4);
-		}
-	}
-
-	return 0xFF00;
-}
-
 #ifdef ROTSPRITE
 //
 // R_GetRollAngle
@@ -1192,13 +1603,12 @@ INT32 R_GetRollAngle(angle_t rollangle)
 //
 void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, spriteframe_t *sprframe, INT32 rot, UINT8 flip)
 {
-	UINT32 i;
 	INT32 angle;
 	patch_t *patch;
 	patch_t *newpatch;
 	UINT16 *rawdst;
 	size_t size;
-	INT32 bflip = (flip != 0x00);
+	pictureflags_t bflip = (flip) ? PICFLAGS_XFLIP : 0;
 
 #define SPRITE_XCENTER (leftoffset)
 #define SPRITE_YCENTER (height / 2)
@@ -1223,12 +1633,16 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 #ifndef NO_PNG_LUMPS
 		lumplength = W_LumpLength(lump);
 
-		if (R_IsLumpPNG((UINT8 *)patch, lumplength))
-			patch = R_PNGToPatch((UINT8 *)patch, lumplength, NULL);
+		if (Picture_IsLumpPNG((const UINT8 *)patch, lumplength))
+		{
+			INT32 pngwidth, pngheight;
+			INT16 toffs, loffs;
+			patch = (patch_t *)Picture_PNGConvert((const UINT8 *)patch, PICFMT_PATCH, &pngwidth, &pngheight, &toffs, &loffs, lumplength, NULL, 0);
+		}
 		else
 #endif
 		// Because there's something wrong with SPR_DFLM, I guess
-		if (!R_CheckIfPatch(lump))
+		if (!Picture_CheckIfPatch(patch, lumplength))
 			return;
 
 		width = SHORT(patch->width);
@@ -1324,10 +1738,7 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 			size = (newwidth * newheight);
 			if (!size)
 				size = (width * height);
-
-			rawdst = Z_Malloc(size * sizeof(UINT16), PU_STATIC, NULL);
-			for (i = 0; i < size; i++)
-				rawdst[i] = 0xFF00;
+			rawdst = Z_Calloc(size * sizeof(UINT16), PU_STATIC, NULL);
 
 			for (dy = 0; dy < newheight; dy++)
 			{
@@ -1340,12 +1751,16 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 					sx >>= FRACBITS;
 					sy >>= FRACBITS;
 					if (sx >= 0 && sy >= 0 && sx < width && sy < height)
-						rawdst[(dy*newwidth)+dx] = GetPatchPixel(patch, sx, sy, bflip);
+					{
+						void *input = Picture_GetPatchPixel(patch, PICFMT_PATCH, sx, sy, bflip);
+						if (input != NULL)
+							rawdst[(dy*newwidth)+dx] = (0xFF00 | (*(UINT8 *)input));
+					}
 				}
 			}
 
 			// make patch
-			newpatch = R_MaskedFlatToPatch(rawdst, newwidth, newheight, 0, 0, &size);
+			newpatch = (patch_t *)Picture_Convert(PICFMT_FLAT16, rawdst, PICFMT_PATCH, 0, &size, newwidth, newheight, 0, 0, 0);
 			{
 				newpatch->leftoffset = (newpatch->width / 2) + (leftoffset - px);
 				newpatch->topoffset = (newpatch->height / 2) + (SHORT(patch->topoffset) - py);
diff --git a/src/r_picformats.h b/src/r_picformats.h
new file mode 100644
index 0000000000000000000000000000000000000000..32754d64ea9048fa759f6fed309ba2d511b2e798
--- /dev/null
+++ b/src/r_picformats.h
@@ -0,0 +1,134 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1993-1996 by id Software, Inc.
+// Copyright (C) 2018-2020 by Jaime "Lactozilla" Passos.
+// Copyright (C) 2019-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_picformats.h
+/// \brief Patch generation.
+
+#ifndef __R_PICFORMATS__
+#define __R_PICFORMATS__
+
+#include "r_defs.h"
+#include "doomdef.h"
+
+typedef enum
+{
+	PICFMT_NONE = 0,
+
+	// Doom formats
+	PICFMT_PATCH,
+	PICFMT_FLAT,
+
+	// PNG
+	PICFMT_PNG,
+
+	// 16bpp
+	PICFMT_PATCH16,
+	PICFMT_FLAT16,
+
+	// 32bpp
+	PICFMT_PATCH32,
+	PICFMT_FLAT32
+} pictureformat_t;
+
+typedef enum
+{
+	PICFLAGS_XFLIP = 1,
+	PICFLAGS_YFLIP = 1<<1
+} pictureflags_t;
+
+enum
+{
+	PICDEPTH_NONE = 0,
+	PICDEPTH_8BPP = 8,
+	PICDEPTH_16BPP = 16,
+	PICDEPTH_32BPP = 32
+};
+
+void *Picture_Convert(
+	pictureformat_t informat, void *picture, pictureformat_t outformat,
+	size_t insize, size_t *outsize,
+	INT32 inwidth, INT32 inheight, INT32 inleftoffset, INT32 intopoffset,
+	pictureflags_t flags);
+
+void *Picture_PatchConvert(
+	pictureformat_t informat, void *picture, pictureformat_t outformat,
+	size_t insize, size_t *outsize,
+	INT16 inwidth, INT16 inheight, INT16 inleftoffset, INT16 intopoffset,
+	pictureflags_t flags);
+void *Picture_FlatConvert(
+	pictureformat_t informat, void *picture, pictureformat_t outformat,
+	size_t insize, size_t *outsize,
+	INT16 inwidth, INT16 inheight, INT16 inleftoffset, INT16 intopoffset,
+	pictureflags_t flags);
+void *Picture_GetPatchPixel(
+	patch_t *patch, pictureformat_t informat,
+	INT32 x, INT32 y,
+	pictureflags_t flags);
+
+void *Picture_TextureToFlat(size_t trickytex);
+
+INT32 Picture_FormatBPP(pictureformat_t format);
+boolean Picture_IsPatchFormat(pictureformat_t format);
+boolean Picture_IsFlatFormat(pictureformat_t format);
+boolean Picture_CheckIfPatch(patch_t *patch, size_t size);
+
+// Structs
+typedef enum
+{
+	ROTAXIS_X, // Roll (the default)
+	ROTAXIS_Y, // Pitch
+	ROTAXIS_Z  // Yaw
+} rotaxis_t;
+
+typedef struct
+{
+	INT32 x, y;
+	rotaxis_t rotaxis;
+} spriteframepivot_t;
+
+typedef struct
+{
+	spriteframepivot_t pivot[64];
+	boolean available;
+} spriteinfo_t;
+
+// Portable Network Graphics
+boolean Picture_IsLumpPNG(const UINT8 *d, size_t s);
+#define Picture_ThrowPNGError(lumpname, wadfilename) I_Error("W_Wad: Lump \"%s\" in file \"%s\" is a .png - please convert to either Doom or Flat (raw) image format.", lumpname, wadfilename); // Fears Of LJ Sonic
+
+#ifndef NO_PNG_LUMPS
+void *Picture_PNGConvert(
+	const UINT8 *png, pictureformat_t outformat,
+	INT32 *w, INT32 *h,
+	INT16 *topoffset, INT16 *leftoffset,
+	size_t insize, size_t *outsize,
+	pictureflags_t flags);
+boolean Picture_PNGDimensions(UINT8 *png, INT16 *width, INT16 *height, size_t size);
+#endif
+
+#define PICTURE_PNG_USELOOKUP
+
+// SpriteInfo
+extern spriteinfo_t spriteinfo[NUMSPRITES];
+void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps);
+void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum);
+
+// Sprite rotation
+#ifdef ROTSPRITE
+INT32 R_GetRollAngle(angle_t rollangle);
+void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, spriteframe_t *sprframe, INT32 rot, UINT8 flip);
+void R_FreeSingleRotSprite(spritedef_t *spritedef);
+void R_FreeSkinRotSprite(size_t skinnum);
+extern fixed_t rollcosang[ROTANGLES];
+extern fixed_t rollsinang[ROTANGLES];
+void R_FreeAllRotSprite(void);
+#endif
+
+#endif // __R_PATCH__
diff --git a/src/r_plane.c b/src/r_plane.c
index 92795d0fbba4b7797035c8d86a89bd5ae7daa321..6c238896ca4f867d1c49b9cfee4260de2f8315d9 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -19,6 +19,7 @@
 #include "p_setup.h" // levelflats
 #include "p_slopes.h"
 #include "r_data.h"
+#include "r_textures.h"
 #include "r_local.h"
 #include "r_state.h"
 #include "r_splats.h" // faB(21jan):testing
@@ -644,188 +645,6 @@ static void R_DrawSkyPlane(visplane_t *pl)
 	}
 }
 
-//
-// R_CheckPowersOfTwo
-//
-// Self-explanatory?
-//
-boolean R_CheckPowersOfTwo(void)
-{
-	boolean wpow2 = (!(ds_flatwidth & (ds_flatwidth - 1)));
-	boolean hpow2 = (!(ds_flatheight & (ds_flatheight - 1)));
-
-	// Initially, the flat isn't powers-of-two-sized.
-	ds_powersoftwo = false;
-
-	// But if the width and height are powers of two,
-	// and are EQUAL, then it's okay :]
-	if ((ds_flatwidth == ds_flatheight) && (wpow2 && hpow2))
-		ds_powersoftwo = true;
-
-	// Just return ds_powersoftwo.
-	return ds_powersoftwo;
-}
-
-//
-// R_CheckFlatLength
-//
-// Determine the flat's dimensions from the lump length.
-//
-void R_CheckFlatLength(size_t size)
-{
-	switch (size)
-	{
-		case 4194304: // 2048x2048 lump
-			nflatmask = 0x3FF800;
-			nflatxshift = 21;
-			nflatyshift = 10;
-			nflatshiftup = 5;
-			ds_flatwidth = ds_flatheight = 2048;
-			break;
-		case 1048576: // 1024x1024 lump
-			nflatmask = 0xFFC00;
-			nflatxshift = 22;
-			nflatyshift = 12;
-			nflatshiftup = 6;
-			ds_flatwidth = ds_flatheight = 1024;
-			break;
-		case 262144:// 512x512 lump
-			nflatmask = 0x3FE00;
-			nflatxshift = 23;
-			nflatyshift = 14;
-			nflatshiftup = 7;
-			ds_flatwidth = ds_flatheight = 512;
-			break;
-		case 65536: // 256x256 lump
-			nflatmask = 0xFF00;
-			nflatxshift = 24;
-			nflatyshift = 16;
-			nflatshiftup = 8;
-			ds_flatwidth = ds_flatheight = 256;
-			break;
-		case 16384: // 128x128 lump
-			nflatmask = 0x3F80;
-			nflatxshift = 25;
-			nflatyshift = 18;
-			nflatshiftup = 9;
-			ds_flatwidth = ds_flatheight = 128;
-			break;
-		case 1024: // 32x32 lump
-			nflatmask = 0x3E0;
-			nflatxshift = 27;
-			nflatyshift = 22;
-			nflatshiftup = 11;
-			ds_flatwidth = ds_flatheight = 32;
-			break;
-		default: // 64x64 lump
-			nflatmask = 0xFC0;
-			nflatxshift = 26;
-			nflatyshift = 20;
-			nflatshiftup = 10;
-			ds_flatwidth = ds_flatheight = 64;
-			break;
-	}
-}
-
-//
-// R_GenerateFlat
-//
-// Generate a flat from specified width and height.
-//
-static UINT8 *R_GenerateFlat(UINT16 width, UINT16 height)
-{
-	UINT8 *flat = Z_Malloc(width * height, PU_LEVEL, NULL);
-	memset(flat, TRANSPARENTPIXEL, width * height);
-	return flat;
-}
-
-//
-// R_GetTextureFlat
-//
-// Convert a texture or patch to a flat.
-//
-static UINT8 *R_GetTextureFlat(levelflat_t *levelflat, boolean leveltexture, boolean ispng)
-{
-	UINT8 *flat;
-	textureflat_t *texflat = &texflats[levelflat->u.texture.num];
-	patch_t *patch = NULL;
-	boolean texturechanged = (leveltexture ? (levelflat->u.texture.num != levelflat->u.texture.lastnum) : false);
-
-	(void)ispng;
-
-	// Check if the texture changed.
-	if (leveltexture && (!texturechanged))
-	{
-		if (texflat != NULL && texflat->flat)
-		{
-			flat = texflat->flat;
-			ds_flatwidth = texflat->width;
-			ds_flatheight = texflat->height;
-			texturechanged = false;
-		}
-		else
-			texturechanged = true;
-	}
-
-	// If the texture changed, or the patch doesn't exist, convert either of them to a flat.
-	if (levelflat->flatpatch == NULL || texturechanged)
-	{
-		// Level texture
-		if (leveltexture)
-		{
-			texture_t *texture = textures[levelflat->u.texture.num];
-			texflat->width = ds_flatwidth = texture->width;
-			texflat->height = ds_flatheight = texture->height;
-
-			texflat->flat = R_GenerateFlat(ds_flatwidth, ds_flatheight);
-			R_TextureToFlat(levelflat->u.texture.num, texflat->flat);
-			flat = texflat->flat;
-
-			levelflat->flatpatch = flat;
-			levelflat->width = ds_flatwidth;
-			levelflat->height = ds_flatheight;
-		}
-		// Patch (never happens yet)
-		else
-		{
-			patch = (patch_t *)ds_source;
-#ifndef NO_PNG_LUMPS
-			if (ispng)
-			{
-				levelflat->flatpatch = R_PNGToFlat(&levelflat->width, &levelflat->height, ds_source, W_LumpLength(levelflat->u.flat.lumpnum));
-				levelflat->topoffset = levelflat->leftoffset = 0;
-				ds_flatwidth = levelflat->width;
-				ds_flatheight = levelflat->height;
-			}
-			else
-#endif
-			{
-				levelflat->width = ds_flatwidth = SHORT(patch->width);
-				levelflat->height = ds_flatheight = SHORT(patch->height);
-
-				levelflat->topoffset = patch->topoffset * FRACUNIT;
-				levelflat->leftoffset = patch->leftoffset * FRACUNIT;
-
-				levelflat->flatpatch = R_GenerateFlat(ds_flatwidth, ds_flatheight);
-				R_PatchToFlat(patch, levelflat->flatpatch);
-			}
-			flat = levelflat->flatpatch;
-		}
-	}
-	else
-	{
-		flat = levelflat->flatpatch;
-		ds_flatwidth = levelflat->width;
-		ds_flatheight = levelflat->height;
-	}
-
-	xoffs += levelflat->leftoffset;
-	yoffs += levelflat->topoffset;
-
-	levelflat->u.texture.lastnum = levelflat->u.texture.num;
-	return flat;
-}
-
 static void R_SlopeVectors(visplane_t *pl, INT32 i, float fudge)
 {
 	// Potentially override other stuff for now cus we're mean. :< But draw a slope plane!
@@ -919,12 +738,11 @@ d.z = (v1.x * v2.y) - (v1.y * v2.x)
 
 void R_DrawSinglePlane(visplane_t *pl)
 {
-	UINT8 *flat;
+	levelflat_t *levelflat;
 	INT32 light = 0;
 	INT32 x;
 	INT32 stop, angle;
 	ffloor_t *rover;
-	levelflat_t *levelflat;
 	int type;
 	int spanfunctype = BASEDRAWFUNC;
 
@@ -1077,30 +895,15 @@ void R_DrawSinglePlane(visplane_t *pl)
 		case LEVELFLAT_NONE:
 			return;
 		case LEVELFLAT_FLAT:
-			ds_source = W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_CACHE);
+			ds_source = (UINT8 *)R_GetFlat(levelflat->u.flat.lumpnum);
 			R_CheckFlatLength(W_LumpLength(levelflat->u.flat.lumpnum));
 			// Raw flats always have dimensions that are powers-of-two numbers.
 			ds_powersoftwo = true;
 			break;
 		default:
-			switch (type)
-			{
-				case LEVELFLAT_TEXTURE:
-					/* Textures get cached differently and don't need ds_source */
-					ds_source = R_GetTextureFlat(levelflat, true, false);
-					break;
-				default:
-					ds_source = W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_STATIC);
-					flat      = R_GetTextureFlat(levelflat, false,
-#ifndef NO_PNG_LUMPS
-							( type == LEVELFLAT_PNG )
-#else
-							false
-#endif
-					);
-					Z_ChangeTag(ds_source, PU_CACHE);
-					ds_source = flat;
-			}
+			ds_source = (UINT8 *)R_GetLevelFlat(levelflat);
+			if (!ds_source)
+				return;
 			// Check if this texture or patch has power-of-two dimensions.
 			if (R_CheckPowersOfTwo())
 				R_CheckFlatLength(ds_flatwidth * ds_flatheight);
diff --git a/src/r_plane.h b/src/r_plane.h
index 67fa19f38bedd3535f6ad1ec6f501139aa8bd426..15f7f07f308830fd217ca6af05f516bf8d0b55b8 100644
--- a/src/r_plane.h
+++ b/src/r_plane.h
@@ -16,6 +16,7 @@
 
 #include "screen.h" // needs MAXVIDWIDTH/MAXVIDHEIGHT
 #include "r_data.h"
+#include "r_textures.h"
 #include "p_polyobj.h"
 
 #define MAXVISPLANES 512
diff --git a/src/r_portal.h b/src/r_portal.h
index 406b98d10c48a42fccdc348dc701369fc6be415e..e665a26e63d46cf0431c6e46a52cb64bddd0bbd3 100644
--- a/src/r_portal.h
+++ b/src/r_portal.h
@@ -15,6 +15,7 @@
 #define __R_PORTAL__
 
 #include "r_data.h"
+#include "r_textures.h"
 #include "r_plane.h" // visplanes
 
 /** Portal structure for the software renderer.
diff --git a/src/r_skins.h b/src/r_skins.h
index 45c90bdb4b9e5964547c579abcbdd3d80ed91162..04ce459a37889d9455be439da72e3cbd34318966 100644
--- a/src/r_skins.h
+++ b/src/r_skins.h
@@ -17,7 +17,7 @@
 #include "info.h"
 #include "sounds.h"
 #include "d_player.h" // skinflags
-#include "r_patch.h" // spriteinfo_t
+#include "r_picformats.h" // spriteinfo_t
 #include "r_defs.h" // spritedef_t
 
 /// Defaults
diff --git a/src/r_textures.c b/src/r_textures.c
new file mode 100644
index 0000000000000000000000000000000000000000..ef45863a2ff94239abe1226a0cea758fda2a5559
--- /dev/null
+++ b/src/r_textures.c
@@ -0,0 +1,1652 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1993-1996 by id Software, Inc.
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-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_textures.c
+/// \brief Texture generation.
+
+#include "doomdef.h"
+#include "g_game.h"
+#include "i_video.h"
+#include "r_local.h"
+#include "r_sky.h"
+#include "p_local.h"
+#include "m_misc.h"
+#include "r_data.h"
+#include "r_textures.h"
+#include "r_picformats.h"
+#include "w_wad.h"
+#include "z_zone.h"
+#include "p_setup.h" // levelflats
+#include "byteptr.h"
+#include "dehacked.h"
+
+// I don't know what this is even for, but r_data.c had it.
+#ifdef _WIN32
+#include <malloc.h> // alloca(sizeof)
+#endif
+
+#ifdef HWRENDER
+#include "hardware/hw_main.h" // HWR_LoadTextures
+#endif
+
+#include <errno.h>
+
+//
+// TEXTURE_T CACHING
+// When a texture is first needed, it counts the number of composite columns
+//  required in the texture and allocates space for a column directory and
+//  any new columns.
+// The directory will simply point inside other patches if there is only one
+//  patch in a given column, but any columns with multiple patches will have
+//  new column_ts generated.
+//
+
+INT32 numtextures = 0; // total number of textures found,
+// size of following tables
+
+texture_t **textures = NULL;
+UINT32 **texturecolumnofs; // column offset lookup table for each texture
+UINT8 **texturecache; // graphics data for each generated full-size texture
+
+INT32 *texturewidth;
+fixed_t *textureheight; // needed for texture pegging
+
+INT32 *texturetranslation;
+
+// Painfully simple texture id cacheing to make maps load faster. :3
+static struct {
+	char name[9];
+	INT32 id;
+} *tidcache = NULL;
+static INT32 tidcachelen = 0;
+
+//
+// MAPTEXTURE_T CACHING
+// When a texture is first needed, it counts the number of composite columns
+//  required in the texture and allocates space for a column directory and
+//  any new columns.
+// The directory will simply point inside other patches if there is only one
+//  patch in a given column, but any columns with multiple patches will have
+//  new column_ts generated.
+//
+
+//
+// R_DrawColumnInCache
+// Clip and draw a column from a patch into a cached post.
+//
+static inline void R_DrawColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
+{
+	INT32 count, position;
+	UINT8 *source;
+	INT32 topdelta, prevdelta = -1;
+	INT32 originy = originPatch->originy;
+
+	(void)patchheight; // This parameter is unused
+
+	while (patch->topdelta != 0xff)
+	{
+		topdelta = patch->topdelta;
+		if (topdelta <= prevdelta)
+			topdelta += prevdelta;
+		prevdelta = topdelta;
+		source = (UINT8 *)patch + 3;
+		count = patch->length;
+		position = originy + topdelta;
+
+		if (position < 0)
+		{
+			count += position;
+			source -= position; // start further down the column
+			position = 0;
+		}
+
+		if (position + count > cacheheight)
+			count = cacheheight - position;
+
+		if (count > 0)
+			M_Memcpy(cache + position, source, count);
+
+		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
+	}
+}
+
+//
+// R_DrawFlippedColumnInCache
+// Similar to R_DrawColumnInCache; it draws the column inverted, however.
+//
+static inline void R_DrawFlippedColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
+{
+	INT32 count, position;
+	UINT8 *source, *dest;
+	INT32 topdelta, prevdelta = -1;
+	INT32 originy = originPatch->originy;
+
+	while (patch->topdelta != 0xff)
+	{
+		topdelta = patch->topdelta;
+		if (topdelta <= prevdelta)
+			topdelta += prevdelta;
+		prevdelta = topdelta;
+		topdelta = patchheight-patch->length-topdelta;
+		source = (UINT8 *)patch + 2 + patch->length; // patch + 3 + (patch->length-1)
+		count = patch->length;
+		position = originy + topdelta;
+
+		if (position < 0)
+		{
+			count += position;
+			source += position; // start further UP the column
+			position = 0;
+		}
+
+		if (position + count > cacheheight)
+			count = cacheheight - position;
+
+		dest = cache + position;
+		if (count > 0)
+		{
+			for (; dest < cache + position + count; --source)
+				*dest++ = *source;
+		}
+
+		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
+	}
+}
+
+//
+// R_DrawBlendColumnInCache
+// Draws a translucent column into the cache, applying a half-cooked equation to get a proper translucency value (Needs code in R_GenerateTexture()).
+//
+static inline void R_DrawBlendColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
+{
+	INT32 count, position;
+	UINT8 *source, *dest;
+	INT32 topdelta, prevdelta = -1;
+	INT32 originy = originPatch->originy;
+
+	(void)patchheight; // This parameter is unused
+
+	while (patch->topdelta != 0xff)
+	{
+		topdelta = patch->topdelta;
+		if (topdelta <= prevdelta)
+			topdelta += prevdelta;
+		prevdelta = topdelta;
+		source = (UINT8 *)patch + 3;
+		count = patch->length;
+		position = originy + topdelta;
+
+		if (position < 0)
+		{
+			count += position;
+			source -= position; // start further down the column
+			position = 0;
+		}
+
+		if (position + count > cacheheight)
+			count = cacheheight - position;
+
+		dest = cache + position;
+		if (count > 0)
+		{
+			for (; dest < cache + position + count; source++, dest++)
+				if (*source != 0xFF)
+					*dest = ASTBlendPaletteIndexes(*dest, *source, originPatch->style, originPatch->alpha);
+		}
+
+		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
+	}
+}
+
+//
+// R_DrawBlendFlippedColumnInCache
+// Similar to the one above except that the column is inverted.
+//
+static inline void R_DrawBlendFlippedColumnInCache(column_t *patch, UINT8 *cache, texpatch_t *originPatch, INT32 cacheheight, INT32 patchheight)
+{
+	INT32 count, position;
+	UINT8 *source, *dest;
+	INT32 topdelta, prevdelta = -1;
+	INT32 originy = originPatch->originy;
+
+	while (patch->topdelta != 0xff)
+	{
+		topdelta = patch->topdelta;
+		if (topdelta <= prevdelta)
+			topdelta += prevdelta;
+		prevdelta = topdelta;
+		topdelta = patchheight-patch->length-topdelta;
+		source = (UINT8 *)patch + 2 + patch->length; // patch + 3 + (patch->length-1)
+		count = patch->length;
+		position = originy + topdelta;
+
+		if (position < 0)
+		{
+			count += position;
+			source += position; // start further UP the column
+			position = 0;
+		}
+
+		if (position + count > cacheheight)
+			count = cacheheight - position;
+
+		dest = cache + position;
+		if (count > 0)
+		{
+			for (; dest < cache + position + count; --source, dest++)
+				if (*source != 0xFF)
+					*dest = ASTBlendPaletteIndexes(*dest, *source, originPatch->style, originPatch->alpha);
+		}
+
+		patch = (column_t *)((UINT8 *)patch + patch->length + 4);
+	}
+}
+
+//
+// R_GenerateTexture
+//
+// Allocate space for full size texture, either single patch or 'composite'
+// Build the full textures from patches.
+// The texture caching system is a little more hungry of memory, but has
+// been simplified for the sake of highcolor (lol), dynamic ligthing, & speed.
+//
+// This is not optimised, but it's supposed to be executed only once
+// per level, when enough memory is available.
+//
+UINT8 *R_GenerateTexture(size_t texnum)
+{
+	UINT8 *block;
+	UINT8 *blocktex;
+	texture_t *texture;
+	texpatch_t *patch;
+	patch_t *realpatch;
+	UINT8 *pdata;
+	int x, x1, x2, i, width, height;
+	size_t blocksize;
+	column_t *patchcol;
+	UINT8 *colofs;
+
+	UINT16 wadnum;
+	lumpnum_t lumpnum;
+	size_t lumplength;
+
+	I_Assert(texnum <= (size_t)numtextures);
+	texture = textures[texnum];
+	I_Assert(texture != NULL);
+
+	// allocate texture column offset lookup
+
+	// single-patch textures can have holes in them and may be used on
+	// 2sided lines so they need to be kept in 'packed' format
+	// BUT this is wrong for skies and walls with over 255 pixels,
+	// so check if there's holes and if not strip the posts.
+	if (texture->patchcount == 1)
+	{
+		boolean holey = false;
+		patch = texture->patches;
+
+		wadnum = patch->wad;
+		lumpnum = patch->lump;
+		lumplength = W_LumpLengthPwad(wadnum, lumpnum);
+		pdata = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
+		realpatch = (patch_t *)pdata;
+
+#ifndef NO_PNG_LUMPS
+		if (Picture_IsLumpPNG((UINT8 *)realpatch, lumplength))
+			goto multipatch;
+#endif
+#ifdef WALLFLATS
+		if (texture->type == TEXTURETYPE_FLAT)
+			goto multipatch;
+#endif
+
+		// Check the patch for holes.
+		if (texture->width > SHORT(realpatch->width) || texture->height > SHORT(realpatch->height))
+			holey = true;
+		colofs = (UINT8 *)realpatch->columnofs;
+		for (x = 0; x < texture->width && !holey; x++)
+		{
+			column_t *col = (column_t *)((UINT8 *)realpatch + LONG(*(UINT32 *)&colofs[x<<2]));
+			INT32 topdelta, prevdelta = -1, y = 0;
+			while (col->topdelta != 0xff)
+			{
+				topdelta = col->topdelta;
+				if (topdelta <= prevdelta)
+					topdelta += prevdelta;
+				prevdelta = topdelta;
+				if (topdelta > y)
+					break;
+				y = topdelta + col->length + 1;
+				col = (column_t *)((UINT8 *)col + col->length + 4);
+			}
+			if (y < texture->height)
+				holey = true; // this texture is HOLEy! D:
+		}
+
+		// If the patch uses transparency, we have to save it this way.
+		if (holey)
+		{
+			texture->holes = true;
+			texture->flip = patch->flip;
+			blocksize = lumplength;
+			block = Z_Calloc(blocksize, PU_STATIC, // will change tag at end of this function
+				&texturecache[texnum]);
+			M_Memcpy(block, realpatch, blocksize);
+			texturememory += blocksize;
+
+			// use the patch's column lookup
+			colofs = (block + 8);
+			texturecolumnofs[texnum] = (UINT32 *)colofs;
+			blocktex = block;
+			if (patch->flip & 1) // flip the patch horizontally
+			{
+				UINT8 *realcolofs = (UINT8 *)realpatch->columnofs;
+				for (x = 0; x < texture->width; x++)
+					*(UINT32 *)&colofs[x<<2] = realcolofs[( texture->width-1-x )<<2]; // swap with the offset of the other side of the texture
+			}
+			// we can't as easily flip the patch vertically sadly though,
+			//  we have wait until the texture itself is drawn to do that
+			for (x = 0; x < texture->width; x++)
+				*(UINT32 *)&colofs[x<<2] = LONG(LONG(*(UINT32 *)&colofs[x<<2]) + 3);
+			goto done;
+		}
+
+		// Otherwise, do multipatch format.
+	}
+
+	// multi-patch textures (or 'composite')
+	multipatch:
+	texture->holes = false;
+	texture->flip = 0;
+	blocksize = (texture->width * 4) + (texture->width * texture->height);
+	texturememory += blocksize;
+	block = Z_Malloc(blocksize+1, PU_STATIC, &texturecache[texnum]);
+
+	memset(block, TRANSPARENTPIXEL, blocksize+1); // Transparency hack
+
+	// columns lookup table
+	colofs = block;
+	texturecolumnofs[texnum] = (UINT32 *)colofs;
+
+	// texture data after the lookup table
+	blocktex = block + (texture->width*4);
+
+	// Composite the columns together.
+	for (i = 0, patch = texture->patches; i < texture->patchcount; i++, patch++)
+	{
+		boolean dealloc = true;
+		static void (*ColumnDrawerPointer)(column_t *, UINT8 *, texpatch_t *, INT32, INT32); // Column drawing function pointer.
+		if (patch->style != AST_COPY)
+			ColumnDrawerPointer = (patch->flip & 2) ? R_DrawBlendFlippedColumnInCache : R_DrawBlendColumnInCache;
+		else
+			ColumnDrawerPointer = (patch->flip & 2) ? R_DrawFlippedColumnInCache : R_DrawColumnInCache;
+
+		wadnum = patch->wad;
+		lumpnum = patch->lump;
+		pdata = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
+		lumplength = W_LumpLengthPwad(wadnum, lumpnum);
+		realpatch = (patch_t *)pdata;
+		dealloc = true;
+
+#ifndef NO_PNG_LUMPS
+		if (Picture_IsLumpPNG((UINT8 *)realpatch, lumplength))
+		{
+			// Dummy variables.
+			INT32 pngwidth, pngheight;
+			realpatch = (patch_t *)Picture_PNGConvert((UINT8 *)realpatch, PICFMT_PATCH, &pngwidth, &pngheight, NULL, NULL, lumplength, NULL, 0);
+		}
+		else
+#endif
+#ifdef WALLFLATS
+		if (texture->type == TEXTURETYPE_FLAT)
+			realpatch = (patch_t *)Picture_Convert(PICFMT_FLAT, pdata, PICFMT_PATCH, 0, NULL, texture->width, texture->height, 0, 0, 0);
+		else
+#endif
+		{
+			(void)lumplength;
+			dealloc = false;
+		}
+
+		x1 = patch->originx;
+		width = SHORT(realpatch->width);
+		height = SHORT(realpatch->height);
+		x2 = x1 + width;
+
+		if (x1 > texture->width || x2 < 0)
+		{
+			if (dealloc)
+				Z_Free(realpatch);
+			continue; // patch not located within texture's x bounds, ignore
+		}
+
+		if (patch->originy > texture->height || (patch->originy + height) < 0)
+		{
+			if (dealloc)
+				Z_Free(realpatch);
+			continue; // patch not located within texture's y bounds, ignore
+		}
+
+		// patch is actually inside the texture!
+		// now check if texture is partly off-screen and adjust accordingly
+
+		// left edge
+		if (x1 < 0)
+			x = 0;
+		else
+			x = x1;
+
+		// right edge
+		if (x2 > texture->width)
+			x2 = texture->width;
+
+		for (; x < x2; x++)
+		{
+			if (patch->flip & 1)
+				patchcol = (column_t *)((UINT8 *)realpatch + LONG(realpatch->columnofs[(x1+width-1)-x]));
+			else
+				patchcol = (column_t *)((UINT8 *)realpatch + LONG(realpatch->columnofs[x-x1]));
+
+			// generate column ofset lookup
+			*(UINT32 *)&colofs[x<<2] = LONG((x * texture->height) + (texture->width*4));
+			ColumnDrawerPointer(patchcol, block + LONG(*(UINT32 *)&colofs[x<<2]), patch, texture->height, height);
+		}
+
+		if (dealloc)
+			Z_Free(realpatch);
+	}
+
+done:
+	// Now that the texture has been built in column cache, it is purgable from zone memory.
+	Z_ChangeTag(block, PU_CACHE);
+	return blocktex;
+}
+
+//
+// R_GenerateTextureAsFlat
+//
+// Generates a flat picture for a texture.
+//
+UINT8 *R_GenerateTextureAsFlat(size_t texnum)
+{
+	texture_t *texture = textures[texnum];
+	UINT8 *converted = NULL;
+	size_t size = (texture->width * texture->height);
+
+	// The flat picture for this texture was not generated yet.
+	if (!texture->flat)
+	{
+		// Well, let's do it now, then.
+		texture->flat = Z_Malloc(size, PU_STATIC, NULL);
+
+		// Picture_TextureToFlat handles everything for us.
+		converted = (UINT8 *)Picture_TextureToFlat(texnum);
+		M_Memcpy(texture->flat, converted, size);
+		Z_Free(converted);
+	}
+
+	return texture->flat;
+}
+
+//
+// R_GetTextureNum
+//
+// Returns the actual texture id that we should use.
+// This can either be texnum, the current frame for texnum's anim (if animated),
+// or 0 if not valid.
+//
+INT32 R_GetTextureNum(INT32 texnum)
+{
+	if (texnum < 0 || texnum >= numtextures)
+		return 0;
+	return texturetranslation[texnum];
+}
+
+//
+// R_CheckTextureCache
+//
+// Use this if you need to make sure the texture is cached before R_GetColumn calls
+// e.g.: midtextures and FOF walls
+//
+void R_CheckTextureCache(INT32 tex)
+{
+	if (!texturecache[tex])
+		R_GenerateTexture(tex);
+}
+
+//
+// R_GetColumn
+//
+UINT8 *R_GetColumn(fixed_t tex, INT32 col)
+{
+	UINT8 *data;
+	INT32 width = texturewidth[tex];
+
+	if (width & (width - 1))
+		col = (UINT32)col % width;
+	else
+		col &= (width - 1);
+
+	data = texturecache[tex];
+	if (!data)
+		data = R_GenerateTexture(tex);
+
+	return data + LONG(texturecolumnofs[tex][col]);
+}
+
+void *R_GetFlat(lumpnum_t flatlumpnum)
+{
+	return W_CacheLumpNum(flatlumpnum, PU_CACHE);
+}
+
+//
+// R_GetLevelFlat
+//
+// If needed, convert a texture or patch to a flat.
+//
+void *R_GetLevelFlat(levelflat_t *levelflat)
+{
+	boolean isleveltexture = (levelflat->type == LEVELFLAT_TEXTURE);
+	texture_t *texture = (isleveltexture ? textures[levelflat->u.texture.num] : NULL);
+	boolean texturechanged = (isleveltexture ? (levelflat->u.texture.num != levelflat->u.texture.lastnum) : false);
+	UINT8 *flatdata = NULL;
+
+	// Check if the texture changed.
+	if (isleveltexture && (!texturechanged))
+	{
+		if (texture->flat)
+		{
+			flatdata = texture->flat;
+			ds_flatwidth = texture->width;
+			ds_flatheight = texture->height;
+			texturechanged = false;
+		}
+		else
+			texturechanged = true;
+	}
+
+	// If the texture changed, or the flat wasn't generated, convert.
+	if (levelflat->picture == NULL || texturechanged)
+	{
+		// Level texture
+		if (isleveltexture)
+		{
+			levelflat->picture = R_GenerateTextureAsFlat(levelflat->u.texture.num);
+			ds_flatwidth = levelflat->width = texture->width;
+			ds_flatheight = levelflat->height = texture->height;
+		}
+		else
+		{
+#ifndef NO_PNG_LUMPS
+			if (levelflat->type == LEVELFLAT_PNG)
+			{
+				INT32 pngwidth, pngheight;
+
+				levelflat->picture = Picture_PNGConvert(W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_CACHE), PICFMT_FLAT, &pngwidth, &pngheight, NULL, NULL, W_LumpLength(levelflat->u.flat.lumpnum), NULL, 0);
+				levelflat->width = (UINT16)pngwidth;
+				levelflat->height = (UINT16)pngheight;
+
+				ds_flatwidth = levelflat->width;
+				ds_flatheight = levelflat->height;
+			}
+			else
+#endif
+			if (levelflat->type == LEVELFLAT_PATCH)
+			{
+				UINT8 *converted;
+				size_t size;
+				patch_t *patch = W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_CACHE);
+
+				levelflat->width = ds_flatwidth = SHORT(patch->width);
+				levelflat->height = ds_flatheight = SHORT(patch->height);
+
+				levelflat->picture = Z_Malloc(levelflat->width * levelflat->height, PU_LEVEL, NULL);
+				converted = Picture_FlatConvert(PICFMT_PATCH, patch, PICFMT_FLAT, 0, &size, levelflat->width, levelflat->height, patch->topoffset, patch->leftoffset, 0);
+				M_Memcpy(levelflat->picture, converted, size);
+				Z_Free(converted);
+			}
+		}
+	}
+	else
+	{
+		ds_flatwidth = levelflat->width;
+		ds_flatheight = levelflat->height;
+	}
+
+	levelflat->u.texture.lastnum = levelflat->u.texture.num;
+
+	if (flatdata == NULL)
+		flatdata = levelflat->picture;
+	return flatdata;
+}
+
+//
+// R_CheckPowersOfTwo
+//
+// Self-explanatory?
+//
+boolean R_CheckPowersOfTwo(void)
+{
+	boolean wpow2 = (!(ds_flatwidth & (ds_flatwidth - 1)));
+	boolean hpow2 = (!(ds_flatheight & (ds_flatheight - 1)));
+
+	// Initially, the flat isn't powers-of-two-sized.
+	ds_powersoftwo = false;
+
+	// But if the width and height are powers of two,
+	// and are EQUAL, then it's okay :]
+	if ((ds_flatwidth == ds_flatheight) && (wpow2 && hpow2))
+		ds_powersoftwo = true;
+
+	// Just return ds_powersoftwo.
+	return ds_powersoftwo;
+}
+
+//
+// R_CheckFlatLength
+//
+// Determine the flat's dimensions from its lump length.
+//
+void R_CheckFlatLength(size_t size)
+{
+	switch (size)
+	{
+		case 4194304: // 2048x2048 lump
+			nflatmask = 0x3FF800;
+			nflatxshift = 21;
+			nflatyshift = 10;
+			nflatshiftup = 5;
+			ds_flatwidth = ds_flatheight = 2048;
+			break;
+		case 1048576: // 1024x1024 lump
+			nflatmask = 0xFFC00;
+			nflatxshift = 22;
+			nflatyshift = 12;
+			nflatshiftup = 6;
+			ds_flatwidth = ds_flatheight = 1024;
+			break;
+		case 262144:// 512x512 lump
+			nflatmask = 0x3FE00;
+			nflatxshift = 23;
+			nflatyshift = 14;
+			nflatshiftup = 7;
+			ds_flatwidth = ds_flatheight = 512;
+			break;
+		case 65536: // 256x256 lump
+			nflatmask = 0xFF00;
+			nflatxshift = 24;
+			nflatyshift = 16;
+			nflatshiftup = 8;
+			ds_flatwidth = ds_flatheight = 256;
+			break;
+		case 16384: // 128x128 lump
+			nflatmask = 0x3F80;
+			nflatxshift = 25;
+			nflatyshift = 18;
+			nflatshiftup = 9;
+			ds_flatwidth = ds_flatheight = 128;
+			break;
+		case 1024: // 32x32 lump
+			nflatmask = 0x3E0;
+			nflatxshift = 27;
+			nflatyshift = 22;
+			nflatshiftup = 11;
+			ds_flatwidth = ds_flatheight = 32;
+			break;
+		default: // 64x64 lump
+			nflatmask = 0xFC0;
+			nflatxshift = 26;
+			nflatyshift = 20;
+			nflatshiftup = 10;
+			ds_flatwidth = ds_flatheight = 64;
+			break;
+	}
+}
+
+//
+// Empty the texture cache (used for load wad at runtime)
+//
+void R_FlushTextureCache(void)
+{
+	INT32 i;
+
+	if (numtextures)
+		for (i = 0; i < numtextures; i++)
+			Z_Free(texturecache[i]);
+}
+
+// Need these prototypes for later; defining them here instead of r_textures.h so they're "private"
+int R_CountTexturesInTEXTURESLump(UINT16 wadNum, UINT16 lumpNum);
+void R_ParseTEXTURESLump(UINT16 wadNum, UINT16 lumpNum, INT32 *index);
+
+#ifdef WALLFLATS
+static INT32
+Rloadflats (INT32 i, INT32 w)
+{
+	UINT16 j;
+	UINT16 texstart, texend;
+	texture_t *texture;
+	texpatch_t *patch;
+
+	// Yes
+	if (wadfiles[w]->type == RET_PK3)
+	{
+		texstart = W_CheckNumForFolderStartPK3("flats/", (UINT16)w, 0);
+		texend = W_CheckNumForFolderEndPK3("flats/", (UINT16)w, texstart);
+	}
+	else
+	{
+		texstart = W_CheckNumForMarkerStartPwad("F_START", (UINT16)w, 0);
+		texend = W_CheckNumForNamePwad("F_END", (UINT16)w, texstart);
+	}
+
+	if (!( texstart == INT16_MAX || texend == INT16_MAX ))
+	{
+		// Work through each lump between the markers in the WAD.
+		for (j = 0; j < (texend - texstart); j++)
+		{
+			UINT8 *flatlump;
+			UINT16 wadnum = (UINT16)w;
+			lumpnum_t lumpnum = texstart + j;
+			size_t lumplength;
+			size_t flatsize = 0;
+
+			if (wadfiles[w]->type == RET_PK3)
+			{
+				if (W_IsLumpFolder(wadnum, lumpnum)) // Check if lump is a folder
+					continue; // If it is then SKIP IT
+			}
+
+			flatlump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
+			lumplength = W_LumpLengthPwad(wadnum, lumpnum);
+
+			switch (lumplength)
+			{
+				case 4194304: // 2048x2048 lump
+					flatsize = 2048;
+					break;
+				case 1048576: // 1024x1024 lump
+					flatsize = 1024;
+					break;
+				case 262144:// 512x512 lump
+					flatsize = 512;
+					break;
+				case 65536: // 256x256 lump
+					flatsize = 256;
+					break;
+				case 16384: // 128x128 lump
+					flatsize = 128;
+					break;
+				case 1024: // 32x32 lump
+					flatsize = 32;
+					break;
+				default: // 64x64 lump
+					flatsize = 64;
+					break;
+			}
+
+			//CONS_Printf("\n\"%s\" is a flat, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),flatsize,flatsize);
+			texture = textures[i] = Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL);
+
+			// Set texture properties.
+			M_Memcpy(texture->name, W_CheckNameForNumPwad(wadnum, lumpnum), sizeof(texture->name));
+
+#ifndef NO_PNG_LUMPS
+			if (Picture_IsLumpPNG((UINT8 *)flatlump, lumplength))
+			{
+				INT16 width, height;
+				Picture_PNGDimensions((UINT8 *)flatlump, &width, &height, lumplength);
+				texture->width = width;
+				texture->height = height;
+			}
+			else
+#endif
+				texture->width = texture->height = flatsize;
+
+			texture->type = TEXTURETYPE_FLAT;
+			texture->patchcount = 1;
+			texture->holes = false;
+			texture->flip = 0;
+
+			// Allocate information for the texture's patches.
+			patch = &texture->patches[0];
+
+			patch->originx = patch->originy = 0;
+			patch->wad = (UINT16)w;
+			patch->lump = texstart + j;
+			patch->flip = 0;
+
+			Z_Unlock(flatlump);
+
+			texturewidth[i] = texture->width;
+			textureheight[i] = texture->height << FRACBITS;
+			i++;
+		}
+	}
+
+	return i;
+}
+#endif/*WALLFLATS*/
+
+#define TX_START "TX_START"
+#define TX_END "TX_END"
+
+static INT32
+Rloadtextures (INT32 i, INT32 w)
+{
+	UINT16 j;
+	UINT16 texstart, texend, texturesLumpPos;
+	texture_t *texture;
+	patch_t *patchlump;
+	texpatch_t *patch;
+
+	// Get the lump numbers for the markers in the WAD, if they exist.
+	if (wadfiles[w]->type == RET_PK3)
+	{
+		texstart = W_CheckNumForFolderStartPK3("textures/", (UINT16)w, 0);
+		texend = W_CheckNumForFolderEndPK3("textures/", (UINT16)w, texstart);
+		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
+		while (texturesLumpPos != INT16_MAX)
+		{
+			R_ParseTEXTURESLump(w, texturesLumpPos, &i);
+			texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, texturesLumpPos + 1);
+		}
+	}
+	else
+	{
+		texstart = W_CheckNumForMarkerStartPwad(TX_START, (UINT16)w, 0);
+		texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
+		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
+		if (texturesLumpPos != INT16_MAX)
+			R_ParseTEXTURESLump(w, texturesLumpPos, &i);
+	}
+
+	if (!( texstart == INT16_MAX || texend == INT16_MAX ))
+	{
+		// Work through each lump between the markers in the WAD.
+		for (j = 0; j < (texend - texstart); j++)
+		{
+			UINT16 wadnum = (UINT16)w;
+			lumpnum_t lumpnum = texstart + j;
+#ifndef NO_PNG_LUMPS
+			size_t lumplength;
+#endif
+
+			if (wadfiles[w]->type == RET_PK3)
+			{
+				if (W_IsLumpFolder(wadnum, lumpnum)) // Check if lump is a folder
+					continue; // If it is then SKIP IT
+			}
+
+			patchlump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
+#ifndef NO_PNG_LUMPS
+			lumplength = W_LumpLengthPwad(wadnum, lumpnum);
+#endif
+
+			//CONS_Printf("\n\"%s\" is a single patch, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),patchlump->width, patchlump->height);
+			texture = textures[i] = Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL);
+
+			// Set texture properties.
+			M_Memcpy(texture->name, W_CheckNameForNumPwad(wadnum, lumpnum), sizeof(texture->name));
+
+#ifndef NO_PNG_LUMPS
+			if (Picture_IsLumpPNG((UINT8 *)patchlump, lumplength))
+			{
+				INT16 width, height;
+				Picture_PNGDimensions((UINT8 *)patchlump, &width, &height, lumplength);
+				texture->width = width;
+				texture->height = height;
+			}
+			else
+#endif
+			{
+				texture->width = SHORT(patchlump->width);
+				texture->height = SHORT(patchlump->height);
+			}
+
+			texture->type = TEXTURETYPE_SINGLEPATCH;
+			texture->patchcount = 1;
+			texture->holes = false;
+			texture->flip = 0;
+
+			// Allocate information for the texture's patches.
+			patch = &texture->patches[0];
+
+			patch->originx = patch->originy = 0;
+			patch->wad = (UINT16)w;
+			patch->lump = texstart + j;
+			patch->flip = 0;
+
+			Z_Unlock(patchlump);
+
+			texturewidth[i] = texture->width;
+			textureheight[i] = texture->height << FRACBITS;
+			i++;
+		}
+	}
+
+	return i;
+}
+
+//
+// R_LoadTextures
+// Initializes the texture list with the textures from the world map.
+//
+void R_LoadTextures(void)
+{
+	INT32 i, w;
+	UINT16 j;
+	UINT16 texstart, texend, texturesLumpPos;
+
+	// Free previous memory before numtextures change.
+	if (numtextures)
+	{
+		for (i = 0; i < numtextures; i++)
+		{
+			Z_Free(textures[i]);
+			Z_Free(texturecache[i]);
+		}
+		Z_Free(texturetranslation);
+		Z_Free(textures);
+	}
+
+	// Load patches and textures.
+
+	// Get the number of textures to check.
+	// NOTE: Make SURE the system does not process
+	// the markers.
+	// This system will allocate memory for all duplicate/patched textures even if it never uses them,
+	// but the alternative is to spend a ton of time checking and re-checking all previous entries just to skip any potentially patched textures.
+	for (w = 0, numtextures = 0; w < numwadfiles; w++)
+	{
+#ifdef WALLFLATS
+		// Count flats
+		if (wadfiles[w]->type == RET_PK3)
+		{
+			texstart = W_CheckNumForFolderStartPK3("flats/", (UINT16)w, 0);
+			texend = W_CheckNumForFolderEndPK3("flats/", (UINT16)w, texstart);
+		}
+		else
+		{
+			texstart = W_CheckNumForMarkerStartPwad("F_START", (UINT16)w, 0);
+			texend = W_CheckNumForNamePwad("F_END", (UINT16)w, texstart);
+		}
+
+		if (!( texstart == INT16_MAX || texend == INT16_MAX ))
+		{
+			// PK3s have subfolders, so we can't just make a simple sum
+			if (wadfiles[w]->type == RET_PK3)
+			{
+				for (j = texstart; j < texend; j++)
+				{
+					if (!W_IsLumpFolder((UINT16)w, j)) // Check if lump is a folder; if not, then count it
+						numtextures++;
+				}
+			}
+			else // Add all the textures between F_START and F_END
+			{
+				numtextures += (UINT32)(texend - texstart);
+			}
+		}
+#endif/*WALLFLATS*/
+
+		// Count the textures from TEXTURES lumps
+		texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, 0);
+		while (texturesLumpPos != INT16_MAX)
+		{
+			numtextures += R_CountTexturesInTEXTURESLump((UINT16)w, (UINT16)texturesLumpPos);
+			texturesLumpPos = W_CheckNumForNamePwad("TEXTURES", (UINT16)w, texturesLumpPos + 1);
+		}
+
+		// Count single-patch textures
+		if (wadfiles[w]->type == RET_PK3)
+		{
+			texstart = W_CheckNumForFolderStartPK3("textures/", (UINT16)w, 0);
+			texend = W_CheckNumForFolderEndPK3("textures/", (UINT16)w, texstart);
+		}
+		else
+		{
+			texstart = W_CheckNumForMarkerStartPwad(TX_START, (UINT16)w, 0);
+			texend = W_CheckNumForNamePwad(TX_END, (UINT16)w, 0);
+		}
+
+		if (texstart == INT16_MAX || texend == INT16_MAX)
+			continue;
+
+		// PK3s have subfolders, so we can't just make a simple sum
+		if (wadfiles[w]->type == RET_PK3)
+		{
+			for (j = texstart; j < texend; j++)
+			{
+				if (!W_IsLumpFolder((UINT16)w, j)) // Check if lump is a folder; if not, then count it
+					numtextures++;
+			}
+		}
+		else // Add all the textures between TX_START and TX_END
+		{
+			numtextures += (UINT32)(texend - texstart);
+		}
+	}
+
+	// If no textures found by this point, bomb out
+	if (!numtextures)
+		I_Error("No textures detected in any WADs!\n");
+
+	// Allocate memory and initialize to 0 for all the textures we are initialising.
+	// There are actually 5 buffers allocated in one for convenience.
+	textures = Z_Calloc((numtextures * sizeof(void *)) * 5, PU_STATIC, NULL);
+
+	// Allocate texture column offset table.
+	texturecolumnofs = (void *)((UINT8 *)textures + (numtextures * sizeof(void *)));
+	// Allocate texture referencing cache.
+	texturecache     = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 2));
+	// Allocate texture width table.
+	texturewidth     = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 3));
+	// Allocate texture height table.
+	textureheight    = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 4));
+	// Create translation table for global animation.
+	texturetranslation = Z_Malloc((numtextures + 1) * sizeof(*texturetranslation), PU_STATIC, NULL);
+
+	for (i = 0; i < numtextures; i++)
+		texturetranslation[i] = i;
+
+	for (i = 0, w = 0; w < numwadfiles; w++)
+	{
+#ifdef WALLFLATS
+		i = Rloadflats(i, w);
+#endif
+		i = Rloadtextures(i, w);
+	}
+
+#ifdef HWRENDER
+	if (rendermode == render_opengl)
+		HWR_LoadTextures(numtextures);
+#endif
+}
+
+static texpatch_t *R_ParsePatch(boolean actuallyLoadPatch)
+{
+	char *texturesToken;
+	size_t texturesTokenLength;
+	char *endPos;
+	char *patchName = NULL;
+	INT16 patchXPos;
+	INT16 patchYPos;
+	UINT8 flip = 0;
+	UINT8 alpha = 255;
+	enum patchalphastyle style = AST_COPY;
+	texpatch_t *resultPatch = NULL;
+	lumpnum_t patchLumpNum;
+
+	// Patch identifier
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch name should be");
+	}
+	texturesTokenLength = strlen(texturesToken);
+	if (texturesTokenLength>8)
+	{
+		I_Error("Error parsing TEXTURES lump: Patch name \"%s\" exceeds 8 characters",texturesToken);
+	}
+	else
+	{
+		if (patchName != NULL)
+		{
+			Z_Free(patchName);
+		}
+		patchName = (char *)Z_Malloc((texturesTokenLength+1)*sizeof(char),PU_STATIC,NULL);
+		M_Memcpy(patchName,texturesToken,texturesTokenLength*sizeof(char));
+		patchName[texturesTokenLength] = '\0';
+	}
+
+	// Comma 1
+	Z_Free(texturesToken);
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after \"%s\"'s patch name should be",patchName);
+	}
+	if (strcmp(texturesToken,",")!=0)
+	{
+		I_Error("Error parsing TEXTURES lump: Expected \",\" after %s's patch name, got \"%s\"",patchName,texturesToken);
+	}
+
+	// XPos
+	Z_Free(texturesToken);
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s x coordinate should be",patchName);
+	}
+	endPos = NULL;
+#ifndef AVOID_ERRNO
+	errno = 0;
+#endif
+	patchXPos = strtol(texturesToken,&endPos,10);
+	(void)patchXPos; //unused for now
+	if (endPos == texturesToken // Empty string
+		|| *endPos != '\0' // Not end of string
+#ifndef AVOID_ERRNO
+		|| errno == ERANGE // Number out-of-range
+#endif
+		)
+	{
+		I_Error("Error parsing TEXTURES lump: Expected an integer for patch \"%s\"'s x coordinate, got \"%s\"",patchName,texturesToken);
+	}
+
+	// Comma 2
+	Z_Free(texturesToken);
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after patch \"%s\"'s x coordinate should be",patchName);
+	}
+	if (strcmp(texturesToken,",")!=0)
+	{
+		I_Error("Error parsing TEXTURES lump: Expected \",\" after patch \"%s\"'s x coordinate, got \"%s\"",patchName,texturesToken);
+	}
+
+	// YPos
+	Z_Free(texturesToken);
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s y coordinate should be",patchName);
+	}
+	endPos = NULL;
+#ifndef AVOID_ERRNO
+	errno = 0;
+#endif
+	patchYPos = strtol(texturesToken,&endPos,10);
+	(void)patchYPos; //unused for now
+	if (endPos == texturesToken // Empty string
+		|| *endPos != '\0' // Not end of string
+#ifndef AVOID_ERRNO
+		|| errno == ERANGE // Number out-of-range
+#endif
+		)
+	{
+		I_Error("Error parsing TEXTURES lump: Expected an integer for patch \"%s\"'s y coordinate, got \"%s\"",patchName,texturesToken);
+	}
+	Z_Free(texturesToken);
+
+	// Patch parameters block (OPTIONAL)
+	// added by Monster Iestyn (22/10/16)
+
+	// Left Curly Brace
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+		; // move on and ignore, R_ParseTextures will deal with this
+	else
+	{
+		if (strcmp(texturesToken,"{")==0)
+		{
+			Z_Free(texturesToken);
+			texturesToken = M_GetToken(NULL);
+			if (texturesToken == NULL)
+			{
+				I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s parameters should be",patchName);
+			}
+			while (strcmp(texturesToken,"}")!=0)
+			{
+				if (stricmp(texturesToken, "ALPHA")==0)
+				{
+					Z_Free(texturesToken);
+					texturesToken = M_GetToken(NULL);
+					alpha = 255*strtof(texturesToken, NULL);
+				}
+				else if (stricmp(texturesToken, "STYLE")==0)
+				{
+					Z_Free(texturesToken);
+					texturesToken = M_GetToken(NULL);
+					if (stricmp(texturesToken, "TRANSLUCENT")==0)
+						style = AST_TRANSLUCENT;
+					else if (stricmp(texturesToken, "ADD")==0)
+						style = AST_ADD;
+					else if (stricmp(texturesToken, "SUBTRACT")==0)
+						style = AST_SUBTRACT;
+					else if (stricmp(texturesToken, "REVERSESUBTRACT")==0)
+						style = AST_REVERSESUBTRACT;
+					else if (stricmp(texturesToken, "MODULATE")==0)
+						style = AST_MODULATE;
+				}
+				else if (stricmp(texturesToken, "FLIPX")==0)
+					flip |= 1;
+				else if (stricmp(texturesToken, "FLIPY")==0)
+					flip |= 2;
+				Z_Free(texturesToken);
+
+				texturesToken = M_GetToken(NULL);
+				if (texturesToken == NULL)
+				{
+					I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch \"%s\"'s parameters or right curly brace should be",patchName);
+				}
+			}
+		}
+		else
+		{
+			 // this is not what we wanted...
+			 // undo last read so R_ParseTextures can re-get the token for its own purposes
+			M_UnGetToken();
+		}
+		Z_Free(texturesToken);
+	}
+
+	if (actuallyLoadPatch == true)
+	{
+		// Check lump exists
+		patchLumpNum = W_GetNumForName(patchName);
+		// If so, allocate memory for texpatch_t and fill 'er up
+		resultPatch = (texpatch_t *)Z_Malloc(sizeof(texpatch_t),PU_STATIC,NULL);
+		resultPatch->originx = patchXPos;
+		resultPatch->originy = patchYPos;
+		resultPatch->lump = patchLumpNum & 65535;
+		resultPatch->wad = patchLumpNum>>16;
+		resultPatch->flip = flip;
+		resultPatch->alpha = alpha;
+		resultPatch->style = style;
+		// Clean up a little after ourselves
+		Z_Free(patchName);
+		// Then return it
+		return resultPatch;
+	}
+	else
+	{
+		Z_Free(patchName);
+		return NULL;
+	}
+}
+
+static texture_t *R_ParseTexture(boolean actuallyLoadTexture)
+{
+	char *texturesToken;
+	size_t texturesTokenLength;
+	char *endPos;
+	INT32 newTextureWidth;
+	INT32 newTextureHeight;
+	texture_t *resultTexture = NULL;
+	texpatch_t *newPatch;
+	char newTextureName[9]; // no longer dynamically allocated
+
+	// Texture name
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where texture name should be");
+	}
+	texturesTokenLength = strlen(texturesToken);
+	if (texturesTokenLength>8)
+	{
+		I_Error("Error parsing TEXTURES lump: Texture name \"%s\" exceeds 8 characters",texturesToken);
+	}
+	else
+	{
+		memset(&newTextureName, 0, 9);
+		M_Memcpy(newTextureName, texturesToken, texturesTokenLength);
+		// ^^ we've confirmed that the token is <= 8 characters so it will never overflow a 9 byte char buffer
+		strupr(newTextureName); // Just do this now so we don't have to worry about it
+	}
+	Z_Free(texturesToken);
+
+	// Comma 1
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after texture \"%s\"'s name should be",newTextureName);
+	}
+	else if (strcmp(texturesToken,",")!=0)
+	{
+		I_Error("Error parsing TEXTURES lump: Expected \",\" after texture \"%s\"'s name, got \"%s\"",newTextureName,texturesToken);
+	}
+	Z_Free(texturesToken);
+
+	// Width
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where texture \"%s\"'s width should be",newTextureName);
+	}
+	endPos = NULL;
+#ifndef AVOID_ERRNO
+	errno = 0;
+#endif
+	newTextureWidth = strtol(texturesToken,&endPos,10);
+	if (endPos == texturesToken // Empty string
+		|| *endPos != '\0' // Not end of string
+#ifndef AVOID_ERRNO
+		|| errno == ERANGE // Number out-of-range
+#endif
+		|| newTextureWidth < 0) // Number is not positive
+	{
+		I_Error("Error parsing TEXTURES lump: Expected a positive integer for texture \"%s\"'s width, got \"%s\"",newTextureName,texturesToken);
+	}
+	Z_Free(texturesToken);
+
+	// Comma 2
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where comma after texture \"%s\"'s width should be",newTextureName);
+	}
+	if (strcmp(texturesToken,",")!=0)
+	{
+		I_Error("Error parsing TEXTURES lump: Expected \",\" after texture \"%s\"'s width, got \"%s\"",newTextureName,texturesToken);
+	}
+	Z_Free(texturesToken);
+
+	// Height
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where texture \"%s\"'s height should be",newTextureName);
+	}
+	endPos = NULL;
+#ifndef AVOID_ERRNO
+	errno = 0;
+#endif
+	newTextureHeight = strtol(texturesToken,&endPos,10);
+	if (endPos == texturesToken // Empty string
+		|| *endPos != '\0' // Not end of string
+#ifndef AVOID_ERRNO
+		|| errno == ERANGE // Number out-of-range
+#endif
+		|| newTextureHeight < 0) // Number is not positive
+	{
+		I_Error("Error parsing TEXTURES lump: Expected a positive integer for texture \"%s\"'s height, got \"%s\"",newTextureName,texturesToken);
+	}
+	Z_Free(texturesToken);
+
+	// Left Curly Brace
+	texturesToken = M_GetToken(NULL);
+	if (texturesToken == NULL)
+	{
+		I_Error("Error parsing TEXTURES lump: Unexpected end of file where open curly brace for texture \"%s\" should be",newTextureName);
+	}
+	if (strcmp(texturesToken,"{")==0)
+	{
+		if (actuallyLoadTexture)
+		{
+			// Allocate memory for a zero-patch texture. Obviously, we'll be adding patches momentarily.
+			resultTexture = (texture_t *)Z_Calloc(sizeof(texture_t),PU_STATIC,NULL);
+			M_Memcpy(resultTexture->name, newTextureName, 8);
+			resultTexture->width = newTextureWidth;
+			resultTexture->height = newTextureHeight;
+			resultTexture->type = TEXTURETYPE_COMPOSITE;
+		}
+		Z_Free(texturesToken);
+		texturesToken = M_GetToken(NULL);
+		if (texturesToken == NULL)
+		{
+			I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch definition for texture \"%s\" should be",newTextureName);
+		}
+		while (strcmp(texturesToken,"}")!=0)
+		{
+			if (stricmp(texturesToken, "PATCH")==0)
+			{
+				Z_Free(texturesToken);
+				if (resultTexture)
+				{
+					// Get that new patch
+					newPatch = R_ParsePatch(true);
+					// Make room for the new patch
+					resultTexture = Z_Realloc(resultTexture, sizeof(texture_t) + (resultTexture->patchcount+1)*sizeof(texpatch_t), PU_STATIC, NULL);
+					// Populate the uninitialized values in the new patch entry of our array
+					M_Memcpy(&resultTexture->patches[resultTexture->patchcount], newPatch, sizeof(texpatch_t));
+					// Account for the new number of patches in the texture
+					resultTexture->patchcount++;
+					// Then free up the memory assigned to R_ParsePatch, as it's unneeded now
+					Z_Free(newPatch);
+				}
+				else
+				{
+					R_ParsePatch(false);
+				}
+			}
+			else
+			{
+				I_Error("Error parsing TEXTURES lump: Expected \"PATCH\" in texture \"%s\", got \"%s\"",newTextureName,texturesToken);
+			}
+
+			texturesToken = M_GetToken(NULL);
+			if (texturesToken == NULL)
+			{
+				I_Error("Error parsing TEXTURES lump: Unexpected end of file where patch declaration or right curly brace for texture \"%s\" should be",newTextureName);
+			}
+		}
+		if (resultTexture && resultTexture->patchcount == 0)
+		{
+			I_Error("Error parsing TEXTURES lump: Texture \"%s\" must have at least one patch",newTextureName);
+		}
+	}
+	else
+	{
+		I_Error("Error parsing TEXTURES lump: Expected \"{\" for texture \"%s\", got \"%s\"",newTextureName,texturesToken);
+	}
+	Z_Free(texturesToken);
+
+	if (actuallyLoadTexture) return resultTexture;
+	else return NULL;
+}
+
+// Parses the TEXTURES lump... but just to count the number of textures.
+int R_CountTexturesInTEXTURESLump(UINT16 wadNum, UINT16 lumpNum)
+{
+	char *texturesLump;
+	size_t texturesLumpLength;
+	char *texturesText;
+	UINT32 numTexturesInLump = 0;
+	char *texturesToken;
+
+	// Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll
+	// need to make a space of memory where I can ensure that it will terminate
+	// correctly. Start by loading the relevant data from the WAD.
+	texturesLump = (char *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC);
+	// If that didn't exist, we have nothing to do here.
+	if (texturesLump == NULL) return 0;
+	// If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
+	texturesLumpLength = W_LumpLengthPwad(wadNum, lumpNum);
+	texturesText = (char *)Z_Malloc((texturesLumpLength+1)*sizeof(char),PU_STATIC,NULL);
+	// Now move the contents of the lump into this new location.
+	memmove(texturesText,texturesLump,texturesLumpLength);
+	// Make damn well sure the last character in our new memory location is \0.
+	texturesText[texturesLumpLength] = '\0';
+	// Finally, free up the memory from the first data load, because we really
+	// don't need it.
+	Z_Free(texturesLump);
+
+	texturesToken = M_GetToken(texturesText);
+	while (texturesToken != NULL)
+	{
+		if (stricmp(texturesToken, "WALLTEXTURE") == 0 || stricmp(texturesToken, "TEXTURE") == 0)
+		{
+			numTexturesInLump++;
+			Z_Free(texturesToken);
+			R_ParseTexture(false);
+		}
+		else
+		{
+			I_Error("Error parsing TEXTURES lump: Expected \"WALLTEXTURE\" or \"TEXTURE\", got \"%s\"",texturesToken);
+		}
+		texturesToken = M_GetToken(NULL);
+	}
+	Z_Free(texturesToken);
+	Z_Free((void *)texturesText);
+
+	return numTexturesInLump;
+}
+
+// Parses the TEXTURES lump... for real, this time.
+void R_ParseTEXTURESLump(UINT16 wadNum, UINT16 lumpNum, INT32 *texindex)
+{
+	char *texturesLump;
+	size_t texturesLumpLength;
+	char *texturesText;
+	char *texturesToken;
+	texture_t *newTexture;
+
+	I_Assert(texindex != NULL);
+
+	// Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll
+	// need to make a space of memory where I can ensure that it will terminate
+	// correctly. Start by loading the relevant data from the WAD.
+	texturesLump = (char *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC);
+	// If that didn't exist, we have nothing to do here.
+	if (texturesLump == NULL) return;
+	// If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly.
+	texturesLumpLength = W_LumpLengthPwad(wadNum, lumpNum);
+	texturesText = (char *)Z_Malloc((texturesLumpLength+1)*sizeof(char),PU_STATIC,NULL);
+	// Now move the contents of the lump into this new location.
+	memmove(texturesText,texturesLump,texturesLumpLength);
+	// Make damn well sure the last character in our new memory location is \0.
+	texturesText[texturesLumpLength] = '\0';
+	// Finally, free up the memory from the first data load, because we really
+	// don't need it.
+	Z_Free(texturesLump);
+
+	texturesToken = M_GetToken(texturesText);
+	while (texturesToken != NULL)
+	{
+		if (stricmp(texturesToken, "WALLTEXTURE") == 0 || stricmp(texturesToken, "TEXTURE") == 0)
+		{
+			Z_Free(texturesToken);
+			// Get the new texture
+			newTexture = R_ParseTexture(true);
+			// Store the new texture
+			textures[*texindex] = newTexture;
+			texturewidth[*texindex] = newTexture->width;
+			textureheight[*texindex] = newTexture->height << FRACBITS;
+			// Increment i back in R_LoadTextures()
+			(*texindex)++;
+		}
+		else
+		{
+			I_Error("Error parsing TEXTURES lump: Expected \"WALLTEXTURE\" or \"TEXTURE\", got \"%s\"",texturesToken);
+		}
+		texturesToken = M_GetToken(NULL);
+	}
+	Z_Free(texturesToken);
+	Z_Free((void *)texturesText);
+}
+
+// Search for flat name.
+lumpnum_t R_GetFlatNumForName(const char *name)
+{
+	INT32 i;
+	lumpnum_t lump;
+	lumpnum_t start;
+	lumpnum_t end;
+
+	// Scan wad files backwards so patched flats take preference.
+	for (i = numwadfiles - 1; i >= 0; i--)
+	{
+		switch (wadfiles[i]->type)
+		{
+		case RET_WAD:
+			if ((start = W_CheckNumForMarkerStartPwad("F_START", (UINT16)i, 0)) == INT16_MAX)
+			{
+				if ((start = W_CheckNumForMarkerStartPwad("FF_START", (UINT16)i, 0)) == INT16_MAX)
+					continue;
+				else if ((end = W_CheckNumForNamePwad("FF_END", (UINT16)i, start)) == INT16_MAX)
+					continue;
+			}
+			else
+				if ((end = W_CheckNumForNamePwad("F_END", (UINT16)i, start)) == INT16_MAX)
+					continue;
+			break;
+		case RET_PK3:
+			if ((start = W_CheckNumForFolderStartPK3("Flats/", i, 0)) == INT16_MAX)
+				continue;
+			if ((end = W_CheckNumForFolderEndPK3("Flats/", i, start)) == INT16_MAX)
+				continue;
+			break;
+		default:
+			continue;
+		}
+
+		// Now find lump with specified name in that range.
+		lump = W_CheckNumForNamePwad(name, (UINT16)i, start);
+		if (lump < end)
+		{
+			lump += (i<<16); // found it, in our constraints
+			break;
+		}
+		lump = LUMPERROR;
+	}
+
+	return lump;
+}
+
+void R_ClearTextureNumCache(boolean btell)
+{
+	if (tidcache)
+		Z_Free(tidcache);
+	tidcache = NULL;
+	if (btell)
+		CONS_Debug(DBG_SETUP, "Fun Fact: There are %d textures used in this map.\n", tidcachelen);
+	tidcachelen = 0;
+}
+
+//
+// R_CheckTextureNumForName
+//
+// Check whether texture is available. Filter out NoTexture indicator.
+//
+INT32 R_CheckTextureNumForName(const char *name)
+{
+	INT32 i;
+
+	// "NoTexture" marker.
+	if (name[0] == '-')
+		return 0;
+
+	for (i = 0; i < tidcachelen; i++)
+		if (!strncasecmp(tidcache[i].name, name, 8))
+			return tidcache[i].id;
+
+	// Need to parse the list backwards, so textures loaded more recently are used in lieu of ones loaded earlier
+	//for (i = 0; i < numtextures; i++) <- old
+	for (i = (numtextures - 1); i >= 0; i--) // <- new
+		if (!strncasecmp(textures[i]->name, name, 8))
+		{
+			tidcachelen++;
+			Z_Realloc(tidcache, tidcachelen * sizeof(*tidcache), PU_STATIC, &tidcache);
+			strncpy(tidcache[tidcachelen-1].name, name, 8);
+			tidcache[tidcachelen-1].name[8] = '\0';
+#ifndef ZDEBUG
+			CONS_Debug(DBG_SETUP, "texture #%s: %s\n", sizeu1(tidcachelen), tidcache[tidcachelen-1].name);
+#endif
+			tidcache[tidcachelen-1].id = i;
+			return i;
+		}
+
+	return -1;
+}
+
+//
+// R_TextureNumForName
+//
+// Calls R_CheckTextureNumForName, aborts with error message.
+//
+INT32 R_TextureNumForName(const char *name)
+{
+	const INT32 i = R_CheckTextureNumForName(name);
+
+	if (i == -1)
+	{
+		static INT32 redwall = -2;
+		CONS_Debug(DBG_SETUP, "WARNING: R_TextureNumForName: %.8s not found\n", name);
+		if (redwall == -2)
+			redwall = R_CheckTextureNumForName("REDWALL");
+		if (redwall != -1)
+			return redwall;
+		return 1;
+	}
+	return i;
+}
diff --git a/src/r_textures.h b/src/r_textures.h
new file mode 100644
index 0000000000000000000000000000000000000000..74a94a9ededc42ca2a7a66413141de9d8f98535d
--- /dev/null
+++ b/src/r_textures.h
@@ -0,0 +1,103 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1993-1996 by id Software, Inc.
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-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_textures.h
+/// \brief Texture generation.
+
+#ifndef __R_TEXTURES__
+#define __R_TEXTURES__
+
+#include "r_defs.h"
+#include "r_state.h"
+#include "p_setup.h" // levelflats
+#include "r_data.h"
+
+#ifdef __GNUG__
+#pragma interface
+#endif
+
+// A single patch from a texture definition,
+//  basically a rectangular area within
+//  the texture rectangle.
+typedef struct
+{
+	// Block origin (always UL), which has already accounted for the internal origin of the patch.
+	INT16 originx, originy;
+	UINT16 wad, lump;
+	UINT8 flip; // 1 = flipx, 2 = flipy, 3 = both
+	UINT8 alpha; // Translucency value
+	enum patchalphastyle style;
+} texpatch_t;
+
+// texture type
+enum
+{
+	TEXTURETYPE_UNKNOWN,
+	TEXTURETYPE_SINGLEPATCH,
+	TEXTURETYPE_COMPOSITE,
+#ifdef WALLFLATS
+	TEXTURETYPE_FLAT,
+#endif
+};
+
+// A texture_t describes a rectangular texture,
+//  which is composed of one or more texpatch_t structures
+//  that arrange graphic patches.
+typedef struct
+{
+	// Keep name for switch changing, etc.
+	char name[8];
+	UINT8 type; // TEXTURETYPE_
+	INT16 width, height;
+	boolean holes;
+	UINT8 flip; // 1 = flipx, 2 = flipy, 3 = both
+	void *flat; // The texture, as a flat.
+
+	// All the patches[patchcount] are drawn back to front into the cached texture.
+	INT16 patchcount;
+	texpatch_t patches[0];
+} texture_t;
+
+// all loaded and prepared textures from the start of the game
+extern texture_t **textures;
+
+extern INT32 *texturewidth;
+extern fixed_t *textureheight; // needed for texture pegging
+
+extern UINT32 **texturecolumnofs; // column offset lookup table for each texture
+extern UINT8 **texturecache; // graphics data for each generated full-size texture
+
+// Load TEXTURES definitions, create lookup tables
+void R_LoadTextures(void);
+void R_FlushTextureCache(void);
+
+// Texture generation
+UINT8 *R_GenerateTexture(size_t texnum);
+UINT8 *R_GenerateTextureAsFlat(size_t texnum);
+INT32 R_GetTextureNum(INT32 texnum);
+void R_CheckTextureCache(INT32 tex);
+void R_ClearTextureNumCache(boolean btell);
+
+// Retrieve texture data.
+void *R_GetLevelFlat(levelflat_t *levelflat);
+UINT8 *R_GetColumn(fixed_t tex, INT32 col);
+void *R_GetFlat(lumpnum_t flatnum);
+
+boolean R_CheckPowersOfTwo(void);
+void R_CheckFlatLength(size_t size);
+
+// Returns the texture number for the texture name.
+INT32 R_TextureNumForName(const char *name);
+INT32 R_CheckTextureNumForName(const char *name);
+lumpnum_t R_GetFlatNumForName(const char *name);
+
+extern INT32 numtextures;
+
+#endif
diff --git a/src/r_things.c b/src/r_things.c
index c0795acd5a68708884f5908dcbb83ca64fce5e85..a3ce90991af9c627fbd26ac9276e578f19a2ca47 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -24,7 +24,7 @@
 #include "i_video.h" // rendermode
 #include "i_system.h"
 #include "r_things.h"
-#include "r_patch.h"
+#include "r_picformats.h"
 #include "r_plane.h"
 #include "r_portal.h"
 #include "p_tick.h"
@@ -280,10 +280,14 @@ boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef, UINT16
 				patch_t *png = W_CacheLumpNumPwad(wadnum, l, PU_STATIC);
 				size_t len = W_LumpLengthPwad(wadnum, l);
 				// lump is a png so convert it
-				if (R_IsLumpPNG((UINT8 *)png, len))
+				if (Picture_IsLumpPNG((UINT8 *)png, len))
 				{
-					png = R_PNGToPatch((UINT8 *)png, len, NULL);
-					M_Memcpy(&patch, png, sizeof(INT16)*4);
+					// Dummy variables.
+					INT32 pngwidth, pngheight;
+					INT16 topoffset, leftoffset;
+					patch_t *converted = (patch_t *)Picture_PNGConvert((UINT8 *)png, PICFMT_PATCH, &pngwidth, &pngheight, &topoffset, &leftoffset, len, NULL, 0);
+					M_Memcpy(&patch, converted, sizeof(INT16)*4); // only copy the header because that's all we need
+					Z_Free(converted);
 				}
 				Z_Free(png);
 			}
diff --git a/src/r_things.h b/src/r_things.h
index 7a0fe3a60ee557bc358e95e75c49dc5f53d6ef3f..b13c5dc55ccc208c52c01a04376af98c11eda375 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -15,7 +15,7 @@
 #define __R_THINGS__
 
 #include "r_plane.h"
-#include "r_patch.h"
+#include "r_picformats.h"
 #include "r_portal.h"
 #include "r_defs.h"
 #include "r_skins.h"
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj
index c6cef56ded19f164ad7dffdfdc356a26127946e7..755fa68e6768e84d8ec2563d863591ccac1854ee 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj
+++ b/src/sdl/Srb2SDL-vc10.vcxproj
@@ -282,14 +282,15 @@
     <ClInclude Include="..\r_draw.h" />
     <ClInclude Include="..\r_local.h" />
     <ClInclude Include="..\r_main.h" />
+    <ClInclude Include="..\r_picformats.h" />
     <ClInclude Include="..\r_plane.h" />
-    <ClInclude Include="..\r_patch.h" />
     <ClInclude Include="..\r_portal.h" />
     <ClInclude Include="..\r_segs.h" />
     <ClInclude Include="..\r_skins.h" />
     <ClInclude Include="..\r_sky.h" />
     <ClInclude Include="..\r_splats.h" />
     <ClInclude Include="..\r_state.h" />
+    <ClInclude Include="..\r_textures.h" />
     <ClInclude Include="..\r_things.h" />
     <ClInclude Include="..\screen.h" />
     <ClInclude Include="..\sounds.h" />
@@ -448,13 +449,14 @@
       <ExcludedFromBuild>true</ExcludedFromBuild>
     </ClCompile>
     <ClCompile Include="..\r_main.c" />
+    <ClCompile Include="..\r_picformats.c" />
     <ClCompile Include="..\r_plane.c" />
-	<ClCompile Include="..\r_patch.c" />
     <ClCompile Include="..\r_portal.c" />
     <ClCompile Include="..\r_segs.c" />
     <ClCompile Include="..\r_skins.c" />
     <ClCompile Include="..\r_sky.c" />
     <ClCompile Include="..\r_splats.c" />
+    <ClCompile Include="..\r_textures.c" />
     <ClCompile Include="..\r_things.c" />
     <ClCompile Include="..\screen.c" />
     <ClCompile Include="..\sounds.c" />
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters
index 04a1b5fa55d91d93d3ef3c25819c4e31f5698678..3bbcd9cb57efeb3f725edacfa05de81ccbc54691 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj.filters
+++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters
@@ -474,7 +474,10 @@
     <ClInclude Include="..\hardware\hw_clip.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
-    <ClInclude Include="..\r_patch.h">
+    <ClInclude Include="..\r_textures.h">
+      <Filter>R_Rend</Filter>
+    </ClInclude>
+    <ClInclude Include="..\r_picformats.h">
       <Filter>R_Rend</Filter>
     </ClInclude>
     <ClInclude Include="..\r_portal.h">
@@ -949,7 +952,10 @@
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
     <ClCompile Include="..\apng.c" />
-    <ClCompile Include="..\r_patch.c">
+    <ClCompile Include="..\r_textures.c">
+      <Filter>R_Rend</Filter>
+    </ClCompile>
+    <ClCompile Include="..\r_picformats.c">
       <Filter>R_Rend</Filter>
     </ClCompile>
     <ClCompile Include="..\r_portal.c">
diff --git a/src/v_video.c b/src/v_video.c
index 81c1d4d665121c82a54b3da3421e2ef301f1c172..74659d45ca7208df66f2d98b06888b4b1cb03fcb 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -3664,28 +3664,51 @@ Unoptimized version
 #endif
 }
 
-// Generates a color look-up table
-// which has up to 64 colors at each channel
-// (see the defines in v_video.h)
-
-UINT8 colorlookup[CLUTSIZE][CLUTSIZE][CLUTSIZE];
-
-void InitColorLUT(RGBA_t *palette)
+// Generates a RGB565 color look-up table
+void InitColorLUT(colorlookup_t *lut, RGBA_t *palette, boolean makecolors)
 {
-	UINT8 r, g, b;
-	static boolean clutinit = false;
-	static RGBA_t *lastpalette = NULL;
-	if ((!clutinit) || (lastpalette != palette))
+	size_t palsize = (sizeof(RGBA_t) * 256);
+
+	if (!lut->init || memcmp(lut->palette, palette, palsize))
 	{
-		for (r = 0; r < CLUTSIZE; r++)
-			for (g = 0; g < CLUTSIZE; g++)
-				for (b = 0; b < CLUTSIZE; b++)
-					colorlookup[r][g][b] = NearestPaletteColor(r << SHIFTCOLORBITS, g << SHIFTCOLORBITS, b << SHIFTCOLORBITS, palette);
-		clutinit = true;
-		lastpalette = palette;
+		INT32 i;
+
+		lut->init = true;
+		memcpy(lut->palette, palette, palsize);
+
+		for (i = 0; i < 0xFFFF; i++)
+			lut->table[i] = 0xFFFF;
+
+		if (makecolors)
+		{
+			UINT8 r, g, b;
+
+			for (r = 0; r < 0xFF; r++)
+			for (g = 0; g < 0xFF; g++)
+			for (b = 0; b < 0xFF; b++)
+			{
+				i = CLUTINDEX(r, g, b);
+				if (lut->table[i] == 0xFFFF)
+					lut->table[i] = NearestPaletteColor(r, g, b, palette);
+			}
+		}
 	}
 }
 
+UINT8 GetColorLUT(colorlookup_t *lut, UINT8 r, UINT8 g, UINT8 b)
+{
+	INT32 i = CLUTINDEX(r, g, b);
+	if (lut->table[i] == 0xFFFF)
+		lut->table[i] = NearestPaletteColor(r, g, b, lut->palette);
+	return lut->table[i];
+}
+
+UINT8 GetColorLUTDirect(colorlookup_t *lut, UINT8 r, UINT8 g, UINT8 b)
+{
+	INT32 i = CLUTINDEX(r, g, b);
+	return lut->table[i];
+}
+
 // V_Init
 // old software stuff, buffers are allocated at video mode setup
 // here we set the screens[x] pointers accordingly
diff --git a/src/v_video.h b/src/v_video.h
index 59d05dcd0d3d4b5fb01959bdef9fcb2c9d0a0da0..2af4fe29313055edab8478dfcb912ca28a8435aa 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -37,13 +37,18 @@ cv_rsaturation, cv_ysaturation, cv_gsaturation, cv_csaturation, cv_bsaturation,
 void V_Init(void);
 
 // Color look-up table
-#define COLORBITS 6
-#define SHIFTCOLORBITS (8-COLORBITS)
-#define CLUTSIZE (1<<COLORBITS)
-
-extern UINT8 colorlookup[CLUTSIZE][CLUTSIZE][CLUTSIZE];
-
-void InitColorLUT(RGBA_t *palette);
+#define CLUTINDEX(r, g, b) (((r) >> 3) << 11) | (((g) >> 2) << 5) | ((b) >> 3)
+
+typedef struct
+{
+	boolean init;
+	RGBA_t palette[256];
+	UINT16 table[0xFFFF];
+} colorlookup_t;
+
+void InitColorLUT(colorlookup_t *lut, RGBA_t *palette, boolean makecolors);
+UINT8 GetColorLUT(colorlookup_t *lut, UINT8 r, UINT8 g, UINT8 b);
+UINT8 GetColorLUTDirect(colorlookup_t *lut, UINT8 r, UINT8 g, UINT8 b);
 
 // Set the current RGB palette lookup to use for palettized graphics
 void V_SetPalette(INT32 palettenum);
diff --git a/src/w_wad.c b/src/w_wad.c
index 548d1bc0022aa8a7b9a4f89c418e8e94aecf5acd..29c5764f99e16e17644bb511cdf184ff34b14f2b 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -56,6 +56,8 @@
 #include "d_clisrv.h"
 #include "r_defs.h"
 #include "r_data.h"
+#include "r_textures.h"
+#include "r_picformats.h"
 #include "i_system.h"
 #include "md5.h"
 #include "lua_script.h"
@@ -65,7 +67,6 @@
 #include "m_misc.h" // M_MapNumber
 
 #ifdef HWRENDER
-#include "r_data.h"
 #include "hardware/hw_main.h"
 #include "hardware/hw_glob.h"
 #endif
@@ -1383,8 +1384,8 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si
 #ifdef NO_PNG_LUMPS
 		{
 			size_t bytesread = fread(dest, 1, size, handle);
-			if (R_IsLumpPNG((UINT8 *)dest, bytesread))
-				W_ThrowPNGError(l->fullname, wadfiles[wad]->filename);
+			if (Picture_IsLumpPNG((UINT8 *)dest, bytesread))
+				Picture_ThrowPNGError(l->fullname, wadfiles[wad]->filename);
 			return bytesread;
 		}
 #else
@@ -1425,8 +1426,8 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si
 			Z_Free(rawData);
 			Z_Free(decData);
 #ifdef NO_PNG_LUMPS
-			if (R_IsLumpPNG((UINT8 *)dest, size))
-				W_ThrowPNGError(l->fullname, wadfiles[wad]->filename);
+			if (Picture_IsLumpPNG((UINT8 *)dest, size))
+				Picture_ThrowPNGError(l->fullname, wadfiles[wad]->filename);
 #endif
 			return size;
 #else
@@ -1488,8 +1489,8 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si
 			Z_Free(decData);
 
 #ifdef NO_PNG_LUMPS
-			if (R_IsLumpPNG((UINT8 *)dest, size))
-				W_ThrowPNGError(l->fullname, wadfiles[wad]->filename);
+			if (Picture_IsLumpPNG((UINT8 *)dest, size))
+				Picture_ThrowPNGError(l->fullname, wadfiles[wad]->filename);
 #endif
 			return size;
 		}
@@ -1683,10 +1684,12 @@ void *W_CacheSoftwarePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 
 #ifndef NO_PNG_LUMPS
 		// lump is a png so convert it
-		if (R_IsLumpPNG((UINT8 *)lumpdata, len))
+		if (Picture_IsLumpPNG((UINT8 *)lumpdata, len))
 		{
+			// Dummy variables.
 			size_t newlen;
-			srcdata = R_PNGToPatch((UINT8 *)lumpdata, len, &newlen);
+			INT32 pngwidth, pngheight;
+			srcdata = Picture_PNGConvert((UINT8 *)lumpdata, PICFMT_PATCH, &pngwidth, &pngheight, NULL, NULL, len, &newlen, 0);
 			ptr = Z_Realloc(ptr, newlen, tag, &lumpcache[lump]);
 			M_Memcpy(ptr, srcdata, newlen);
 			Z_Free(srcdata);
diff --git a/src/win32/Srb2win-vc10.vcxproj b/src/win32/Srb2win-vc10.vcxproj
index 6855a4135b000fdae1b56d19fe9a237ec293cbc2..52617037b210f0eb913daf9a4e7eab6d1bee973f 100644
--- a/src/win32/Srb2win-vc10.vcxproj
+++ b/src/win32/Srb2win-vc10.vcxproj
@@ -298,12 +298,13 @@
       <ExcludedFromBuild>true</ExcludedFromBuild>
     </ClCompile>
     <ClCompile Include="..\r_main.c" />
+    <ClCompile Include="..\r_picformats.c" />
     <ClCompile Include="..\r_plane.c" />
-    <ClCompile Include="..\r_patch.c" />
     <ClCompile Include="..\r_portal.c" />
     <ClCompile Include="..\r_segs.c" />
     <ClCompile Include="..\r_sky.c" />
     <ClCompile Include="..\r_splats.c" />
+    <ClCompile Include="..\r_textures.c" />
     <ClCompile Include="..\r_things.c" />
     <ClCompile Include="..\screen.c" />
     <ClCompile Include="..\sounds.c" />
@@ -453,13 +454,14 @@
     <ClInclude Include="..\r_draw.h" />
     <ClInclude Include="..\r_local.h" />
     <ClInclude Include="..\r_main.h" />
+    <ClInclude Include="..\r_picformats.h" />
     <ClInclude Include="..\r_plane.h" />
-	<ClInclude Include="..\r_patch.h" />
     <ClInclude Include="..\r_portal.h" />
     <ClInclude Include="..\r_segs.h" />
     <ClInclude Include="..\r_sky.h" />
     <ClInclude Include="..\r_splats.h" />
     <ClInclude Include="..\r_state.h" />
+    <ClInclude Include="..\r_textures.h" />
     <ClInclude Include="..\r_things.h" />
     <ClInclude Include="..\screen.h" />
     <ClInclude Include="..\sounds.h" />
diff --git a/src/win32/Srb2win-vc10.vcxproj.filters b/src/win32/Srb2win-vc10.vcxproj.filters
index 4a980c6bd298eb8526626f9552055522093fa644..0689a4ac0893e10c660a72ed32c9bb0c3c054c82 100644
--- a/src/win32/Srb2win-vc10.vcxproj.filters
+++ b/src/win32/Srb2win-vc10.vcxproj.filters
@@ -469,7 +469,7 @@
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
     <ClCompile Include="..\apng.c" />
-    <ClCompile Include="..\r_patch.c">
+    <ClCompile Include="..\r_picformats.c">
       <Filter>R_Rend</Filter>
     </ClCompile>
     <ClCompile Include="..\r_portal.c">
@@ -886,7 +886,10 @@
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
     <ClInclude Include="..\apng.h" />
-    <ClInclude Include="..\r_patch.h">
+    <ClInclude Include="..\r_textures.h">
+      <Filter>R_Rend</Filter>
+    </ClInclude>
+    <ClInclude Include="..\r_picformats.h">
       <Filter>R_Rend</Filter>
     </ClInclude>
     <ClInclude Include="..\r_portal.h">
diff --git a/src/z_zone.c b/src/z_zone.c
index 2387a11433592e99fb149b85d88863ace01ab016..2c7384c3da22fead194f365dc8fea0a4768949b9 100644
--- a/src/z_zone.c
+++ b/src/z_zone.c
@@ -27,7 +27,7 @@
 
 #include "doomdef.h"
 #include "doomstat.h"
-#include "r_patch.h"
+#include "r_picformats.h"
 #include "i_system.h" // I_GetFreeMem
 #include "i_video.h" // rendermode
 #include "z_zone.h"
@@ -517,7 +517,6 @@ void Z_FlushCachedPatches(void)
 	Z_FreeTag(PU_HWRMODELTEXTURE_UNLOCKED);
 }
 
-// happens before a renderer switch
 void Z_PreparePatchFlush(void)
 {
 	CONS_Debug(DBG_RENDER, "Z_PreparePatchFlush()...\n");