diff --git a/src/android/i_video.c b/src/android/i_video.c
index 2d0151f5e27cd906e4856109fd8529772c704955..b8bb4fefbf408970af9c23ace95f5217548dc43e 100644
--- a/src/android/i_video.c
+++ b/src/android/i_video.c
@@ -19,6 +19,7 @@ boolean allow_fullscreen = false;
 consvar_t cv_vidwait = {"vid_wait", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 void I_StartupGraphics(void){}
+void I_StartupHardwareGraphics(void){}
 
 void I_ShutdownGraphics(void){}
 
@@ -51,6 +52,11 @@ INT32 VID_SetMode(INT32 modenum)
   return 0;
 }
 
+void VID_CheckRenderer(void)
+{
+	// ..............
+}
+
 const char *VID_GetModeName(INT32 modenum)
 {
   return "A320x240";
diff --git a/src/command.c b/src/command.c
index 33d8ead96a24fc326777f5dd5aeab2f3bcd68c61..40efa1eeea78c54bca70c12e115cf413f2889a42 100644
--- a/src/command.c
+++ b/src/command.c
@@ -61,6 +61,8 @@ static consvar_t *consvar_vars; // list of registered console variables
 static char com_token[1024];
 static char *COM_Parse(char *data);
 
+static char * COM_Purge (char *text, int *lenp);
+
 CV_PossibleValue_t CV_OnOff[] = {{0, "Off"}, {1, "On"}, {0, NULL}};
 CV_PossibleValue_t CV_YesNo[] = {{0, "No"}, {1, "Yes"}, {0, NULL}};
 CV_PossibleValue_t CV_Unsigned[] = {{0, "MIN"}, {999999999, "MAX"}, {0, NULL}};
@@ -100,31 +102,61 @@ static cmdalias_t *com_alias; // aliases list
 
 static vsbuf_t com_text; // variable sized buffer
 
+/** Purges control characters out of some text.
+  *
+  * \param s The text.
+  * \param np Optionally a pointer to fill with the new string length.
+  * \return The text.
+  * \sa COM_ExecuteString
+  */
+static char *
+COM_Purge (char *s, int *np)
+{
+	char *t;
+	char *p;
+	int n;
+	n = strlen(s);
+	t = s + n + 1;
+	p = s;
+	while (( p = strchr(p, '\033') ))
+	{
+		memmove(p, &p[1], t - p - 1);
+		n--;
+	}
+	if (np)
+		(*np) = n;
+	return s;
+}
+
 /** Adds text into the command buffer for later execution.
   *
   * \param ptext The text to add.
-  * \sa COM_BufInsertText
+  * \sa COM_BufInsertTextEx
   */
-void COM_BufAddText(const char *ptext)
+void COM_BufAddTextEx(const char *ptext, int flags)
 {
-	size_t l;
+	int l;
+	char *text;
 
-	l = strlen(ptext);
+	text = COM_Purge(Z_StrDup(ptext), &l);
 
-	if (com_text.cursize + l >= com_text.maxsize)
+	if (com_text.cursize + 2 + l >= com_text.maxsize)
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("Command buffer full!\n"));
 		return;
 	}
-	VS_Write(&com_text, ptext, l);
+
+	VS_WriteEx(&com_text, text, l, flags);
+
+	Z_Free(text);
 }
 
 /** Adds command text and executes it immediately.
   *
   * \param ptext The text to execute. A newline is automatically added.
-  * \sa COM_BufAddText
+  * \sa COM_BufAddTextEx
   */
-void COM_BufInsertText(const char *ptext)
+void COM_BufInsertTextEx(const char *ptext, int flags)
 {
 	char *temp = NULL;
 	size_t templen;
@@ -138,7 +170,7 @@ void COM_BufInsertText(const char *ptext)
 	}
 
 	// add the entire text of the file (or alias)
-	COM_BufAddText(ptext);
+	COM_BufAddTextEx(ptext, flags);
 	COM_BufExecute(); // do it right away
 
 	// add the copied off data
@@ -272,6 +304,7 @@ static size_t com_argc;
 static char *com_argv[MAX_ARGS];
 static const char *com_null_string = "";
 static char *com_args = NULL; // current command args or NULL
+static int com_flags;
 
 static void Got_NetVar(UINT8 **p, INT32 playernum);
 
@@ -395,6 +428,14 @@ static void COM_TokenizeString(char *ptext)
 	com_argc = 0;
 	com_args = NULL;
 
+	if (ptext[0] == '\033')
+	{
+		com_flags = (unsigned)ptext[1];
+		ptext += 2;
+	}
+	else
+		com_flags = 0;
+
 	while (com_argc < MAX_ARGS)
 	{
 		// Skip whitespace up to a newline.
@@ -1016,6 +1057,15 @@ void VS_Write(vsbuf_t *buf, const void *data, size_t length)
 	M_Memcpy(VS_GetSpace(buf, length), data, length);
 }
 
+void VS_WriteEx(vsbuf_t *buf, const void *data, size_t length, int flags)
+{
+	char *p;
+	p = VS_GetSpace(buf, 2 + length);
+	p[0] = '\033';
+	p[1] = flags;
+	M_Memcpy(&p[2], data, length);
+}
+
 /** Prints text in a variable buffer. Like VS_Write() plus a
   * trailing NUL.
   *
@@ -2015,6 +2065,9 @@ static boolean CV_Command(void)
 	if (!v)
 		return false;
 
+	if (( com_flags & COM_SAFE ) && ( v->flags & CV_NOLUA ))
+		return false;
+
 	// perform a variable print or set
 	if (COM_Argc() == 1)
 	{
diff --git a/src/command.h b/src/command.h
index 51e161cd05943c7211d4e26f8b8cb78674a89511..b1026437f131e75cc4b1fb84d12458f02d1f5d22 100644
--- a/src/command.h
+++ b/src/command.h
@@ -20,6 +20,11 @@
 // Command buffer & command execution
 //===================================
 
+enum
+{
+	COM_SAFE = 1,
+};
+
 typedef void (*com_func_t)(void);
 
 void COM_AddCommand(const char *name, com_func_t func);
@@ -36,10 +41,12 @@ size_t COM_FirstOption(void);
 const char *COM_CompleteCommand(const char *partial, INT32 skips);
 
 // insert at queu (at end of other command)
-void COM_BufAddText(const char *btext);
+#define COM_BufAddText(s) COM_BufAddTextEx(s, 0)
+void COM_BufAddTextEx(const char *btext, int flags);
 
 // insert in head (before other command)
-void COM_BufInsertText(const char *btext);
+#define COM_BufInsertText(s) COM_BufInsertTextEx(s, 0)
+void COM_BufInsertTextEx(const char *btext, int flags);
 
 // don't bother inserting, just do immediately
 void COM_ImmedExecute(const char *ptext);
@@ -71,6 +78,7 @@ void VS_Free(vsbuf_t *buf);
 void VS_Clear(vsbuf_t *buf);
 void *VS_GetSpace(vsbuf_t *buf, size_t length);
 void VS_Write(vsbuf_t *buf, const void *data, size_t length);
+void VS_WriteEx(vsbuf_t *buf, const void *data, size_t length, int flags);
 void VS_Print(vsbuf_t *buf, const char *data); // strcats onto the sizebuf
 
 //==================
@@ -100,7 +108,8 @@ typedef enum
 	CV_HIDEN = 1024, // variable is not part of the cvar list so cannot be accessed by the console
 	                 // can only be set when we have the pointer to it
                    // used on menus
-	CV_CHEAT = 2048 // Don't let this be used in multiplayer unless cheats are on.
+	CV_CHEAT = 2048, // Don't let this be used in multiplayer unless cheats are on.
+	CV_NOLUA = 4096,/* don't let this be called from Lua */
 } cvflags_t;
 
 typedef struct CV_PossibleValue_s
diff --git a/src/console.c b/src/console.c
index 1fbca75b4cf6fcacbeaa2b8948bdf1525faa4036..6549370eeda227a1dc48b0ff062261115277f3cd 100644
--- a/src/console.c
+++ b/src/console.c
@@ -20,6 +20,7 @@
 #include "g_input.h"
 #include "hu_stuff.h"
 #include "keys.h"
+#include "r_main.h"
 #include "r_defs.h"
 #include "sounds.h"
 #include "st_stuff.h"
@@ -1285,10 +1286,10 @@ void CONS_Printf(const char *fmt, ...)
 	con_scrollup = 0;
 
 	// if not in display loop, force screen update
-	if (con_startup)
+	if (con_startup && (!setrenderneeded))
 	{
 #ifdef _WINDOWS
-		patch_t *con_backpic = W_CachePatchName("CONSBACK", PU_CACHE);
+		patch_t *con_backpic = W_CachePatchName("CONSBACK", PU_PATCH);
 
 		// Jimita: CON_DrawBackpic just called V_DrawScaledPatch
 		V_DrawScaledPatch(0, 0, 0, con_backpic);
@@ -1545,7 +1546,7 @@ static void CON_DrawConsole(void)
 	// draw console background
 	if (cons_backpic.value || con_forcepic)
 	{
-		patch_t *con_backpic = W_CachePatchName("CONSBACK", PU_CACHE);
+		patch_t *con_backpic = W_CachePatchName("CONSBACK", PU_PATCH);
 
 		// Jimita: CON_DrawBackpic just called V_DrawScaledPatch
 		V_DrawScaledPatch(0, 0, 0, con_backpic);
@@ -1602,8 +1603,18 @@ void CON_Drawer(void)
 	if (!con_started || !graphics_started)
 		return;
 
+	if (needpatchrecache)
+	{
+		W_FlushCachedPatches();
+		HU_LoadGraphics();
+	}
+
 	if (con_recalc)
+	{
 		CON_RecalcSize();
+		if (con_curlines <= 0)
+			CON_ClearHUD();
+	}
 
 	if (con_curlines > 0)
 		CON_DrawConsole();
diff --git a/src/d_main.c b/src/d_main.c
index c4b0d7117e77873349541489cc22f75af80658f7..65b1c8ccc6f9120ddccc398faa70397214767ef0 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -108,6 +108,8 @@ boolean devparm = false; // started game with -devparm
 boolean singletics = false; // timedemo
 boolean lastdraw = false;
 
+static void D_CheckRendererState(void);
+
 postimg_t postimgtype = postimg_none;
 INT32 postimgparam;
 postimg_t postimgtype2 = postimg_none;
@@ -212,6 +214,7 @@ INT16 wipetypepost = -1;
 
 static void D_Display(void)
 {
+	INT32 setrenderstillneeded = 0;
 	boolean forcerefresh = false;
 	static boolean wipe = false;
 	INT32 wipedefindex = 0;
@@ -222,11 +225,38 @@ static void D_Display(void)
 	if (nodrawers)
 		return; // for comparative timing/profiling
 
-	// check for change of screen size (video mode)
-	if (setmodeneeded && !wipe)
+	// Lactozilla: Switching renderers works by checking
+	// if the game has to do it right when the frame
+	// needs to render. If so, five things will happen:
+	// 1. Interface functions will be called so
+	//    that switching to OpenGL creates a
+	//    GL context, and switching to Software
+	//    allocates screen buffers.
+	// 2. Software will set drawer functions,
+	//    and OpenGL will load textures and
+	//    create plane polygons, if necessary.
+	// 3. Functions related to switching video
+	//    modes (resolution) are called.
+	// 4. Patch data is freed from memory,
+	//    and recached if necessary.
+	// 5. The frame is ready to be drawn!
+
+	// stop movie if needs to change renderer
+	if (setrenderneeded && (moviemode == MM_APNG))
+		M_StopMovie();
+
+	// check for change of renderer or screen size (video mode)
+	if ((setrenderneeded || setmodeneeded) && !wipe)
+	{
+		if (setrenderneeded)
+		{
+			CONS_Debug(DBG_RENDER, "setrenderneeded set (%d)\n", setrenderneeded);
+			setrenderstillneeded = setrenderneeded;
+		}
 		SCR_SetMode(); // change video mode
+	}
 
-	if (vid.recalc)
+	if (vid.recalc || setrenderstillneeded)
 	{
 		SCR_Recalc(); // NOTE! setsizeneeded is set by SCR_Recalc()
 #ifdef HWRENDER
@@ -237,12 +267,15 @@ static void D_Display(void)
 	}
 
 	// change the view size if needed
-	if (setsizeneeded)
+	if (setsizeneeded || setrenderstillneeded)
 	{
 		R_ExecuteSetViewSize();
 		forcerefresh = true; // force background redraw
 	}
 
+	// Lactozilla: Renderer switching
+	D_CheckRendererState();
+
 	// draw buffered stuff to screen
 	// Used only by linux GGI version
 	I_UpdateNoBlit();
@@ -457,7 +490,7 @@ static void D_Display(void)
 			py = 4;
 		else
 			py = viewwindowy + 4;
-		patch = W_CachePatchName("M_PAUSE", PU_CACHE);
+		patch = W_CachePatchName("M_PAUSE", PU_PATCH);
 		V_DrawScaledPatch(viewwindowx + (BASEVIDWIDTH - SHORT(patch->width))/2, py, 0, patch);
 #else
 		INT32 y = ((automapactive) ? (32) : (BASEVIDHEIGHT/2));
@@ -557,6 +590,25 @@ static void D_Display(void)
 
 		I_FinishUpdate(); // page flip or blit buffer
 	}
+
+	needpatchflush = false;
+	needpatchrecache = false;
+}
+
+// Lactozilla: Check the renderer's state
+// after a possible renderer switch.
+void D_CheckRendererState(void)
+{
+	// flush all patches from memory
+	// (also frees memory tagged with PU_CACHE)
+	// (which are not necessarily patches but I don't care)
+	if (needpatchflush)
+		Z_FlushCachedPatches();
+
+	// some patches have been freed,
+	// so cache them again
+	if (needpatchrecache)
+		R_ReloadHUDGraphics();
 }
 
 // =========================================================================
@@ -600,8 +652,7 @@ void D_SRB2Loop(void)
 	// hack to start on a nice clear console screen.
 	COM_ImmedExecute("cls;version");
 
-	if (rendermode == render_soft)
-		V_DrawScaledPatch(0, 0, 0, (patch_t *)W_CacheLumpNum(W_GetNumForName("CONSBACK"), PU_CACHE));
+	V_DrawScaledPatch(0, 0, 0, W_CachePatchNum(W_GetNumForName("CONSBACK"), PU_CACHE));
 	I_FinishUpdate(); // page flip or blit buffer
 
 	for (;;)
@@ -1239,6 +1290,16 @@ void D_SRB2Main(void)
 	// set user default mode or mode set at cmdline
 	SCR_CheckDefaultMode();
 
+	// Lactozilla: Does the render mode need to change?
+	if ((setrenderneeded != 0) && (setrenderneeded != rendermode))
+	{
+		needpatchflush = true;
+		needpatchrecache = true;
+		VID_CheckRenderer();
+		SCR_ChangeRendererCVars(setrenderneeded);
+	}
+	D_CheckRendererState();
+
 	wipegamestate = gamestate;
 
 	savedata.lives = 0; // flag this as not-used
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 262d0d1bc4d592a2acdde70f7a1985fed3b1433f..28843c0d7be6dd09ee3c60531223f97e2b34a139 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -841,6 +841,8 @@ void D_RegisterClientCommands(void)
 	// screen.c
 	CV_RegisterVar(&cv_fullscreen);
 	CV_RegisterVar(&cv_renderview);
+	CV_RegisterVar(&cv_renderer);
+	CV_RegisterVar(&cv_newrenderer);
 	CV_RegisterVar(&cv_scr_depth);
 	CV_RegisterVar(&cv_scr_width);
 	CV_RegisterVar(&cv_scr_height);
diff --git a/src/dehacked.c b/src/dehacked.c
index 9abb708a9495b10075b4c7a239afd934508aa707..c9e10c06495b41f9c11a682623f47d91fe58ec69 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -9462,6 +9462,7 @@ struct {
 	{"CV_HIDEN",CV_HIDEN},
 	{"CV_HIDDEN",CV_HIDEN},
 	{"CV_CHEAT",CV_CHEAT},
+	{"CV_NOLUA",CV_NOLUA},
 
 	// v_video flags
 	{"V_NOSCALEPATCH",V_NOSCALEPATCH},
diff --git a/src/djgppdos/i_video.c b/src/djgppdos/i_video.c
index 56570b7bf8daf871fca8db3a03909823c33ad605..02c7a842bc2c0aa77fb7d8d7180a04d9029878b5 100644
--- a/src/djgppdos/i_video.c
+++ b/src/djgppdos/i_video.c
@@ -338,3 +338,8 @@ void I_StartupGraphics(void)
 	graphics_started = true;
 
 }
+
+void I_StartupHardwareGraphics(void)
+{
+	// oh yeah woo yeah oh yeah woo yeah oh yeah woo yeah oh yeah woo yeah oh yeah woo yeah oh yeah woo yeah oh yeah woo y
+}
diff --git a/src/djgppdos/vid_vesa.c b/src/djgppdos/vid_vesa.c
index ec7b8b886343d0d8fe9c8d9e66eff3c1aeaf1793..c8ce7dae52e645372455b76cbad40ffd03c53a0e 100644
--- a/src/djgppdos/vid_vesa.c
+++ b/src/djgppdos/vid_vesa.c
@@ -378,6 +378,11 @@ INT32 VID_SetMode (INT32 modenum)  //, UINT8 *palette)
 	return 1;
 }
 
+void VID_CheckRenderer(void)
+{
+	// ..............
+}
+
 
 
 // converts a segm:offs 32bit pair to a 32bit flat ptr
diff --git a/src/doomdef.h b/src/doomdef.h
index fe6a5faa3a36bbda215c77240067140f3edb66e6..55ad8abedc5d36a39806fbfb85690d8f0110470c 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -624,6 +624,9 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 ///      	SRB2CB itself ported this from PrBoom+
 #define NEWCLIP
 
+/// Cache patches in Lua in a way that renderer switching will work flawlessly.
+//#define LUA_PATCH_SAFETY
+
 /// Sprite rotation
 #define ROTSPRITE
 #define ROTANGLES 24	// Needs to be a divisor of 360 (45, 60, 90, 120...)
diff --git a/src/dummy/i_video.c b/src/dummy/i_video.c
index e167e833fb152d6fcc5daa7a490cede554944d3b..fafeee0001400ef868b6c8d91042870c6c2e3773 100644
--- a/src/dummy/i_video.c
+++ b/src/dummy/i_video.c
@@ -11,6 +11,7 @@ boolean allow_fullscreen = false;
 consvar_t cv_vidwait = {"vid_wait", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 void I_StartupGraphics(void){}
+void I_StartupHardwareGraphics(void){}
 
 void I_ShutdownGraphics(void){}
 
@@ -39,6 +40,11 @@ INT32 VID_SetMode(INT32 modenum)
 	return 0;
 }
 
+void VID_CheckRenderer(void)
+{
+	// ..............
+}
+
 const char *VID_GetModeName(INT32 modenum)
 {
 	(void)modenum;
diff --git a/src/f_finale.c b/src/f_finale.c
index 1436a159b1285cb40ba302a15f767412f0c0f77c..bf5f2ba40bf8a7e0bd0f3517a939b8d2a5293246 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -530,78 +530,78 @@ static void F_IntroDrawScene(void)
 		case 0:
 			break;
 		case 1:
-			background = W_CachePatchName("INTRO1", PU_CACHE);
+			background = W_CachePatchName("INTRO1", PU_PATCH);
 			break;
 		case 2:
-			background = W_CachePatchName("INTRO2", PU_CACHE);
+			background = W_CachePatchName("INTRO2", PU_PATCH);
 			break;
 		case 3:
-			background = W_CachePatchName("INTRO3", PU_CACHE);
+			background = W_CachePatchName("INTRO3", PU_PATCH);
 			break;
 		case 4:
-			background = W_CachePatchName("INTRO4", PU_CACHE);
+			background = W_CachePatchName("INTRO4", PU_PATCH);
 			break;
 		case 5:
 			if (intro_curtime >= 5*TICRATE)
-				background = W_CachePatchName("RADAR", PU_CACHE);
+				background = W_CachePatchName("RADAR", PU_PATCH);
 			else
-				background = W_CachePatchName("DRAT", PU_CACHE);
+				background = W_CachePatchName("DRAT", PU_PATCH);
 			break;
 		case 6:
-			background = W_CachePatchName("INTRO6", PU_CACHE);
+			background = W_CachePatchName("INTRO6", PU_PATCH);
 			cx = 180;
 			cy = 8;
 			break;
 		case 7:
 		{
 			if (intro_curtime >= 7*TICRATE + ((TICRATE/7)*2))
-				background = W_CachePatchName("SGRASS5", PU_CACHE);
+				background = W_CachePatchName("SGRASS5", PU_PATCH);
 			else if (intro_curtime >= 7*TICRATE + (TICRATE/7))
-				background = W_CachePatchName("SGRASS4", PU_CACHE);
+				background = W_CachePatchName("SGRASS4", PU_PATCH);
 			else if (intro_curtime >= 7*TICRATE)
-				background = W_CachePatchName("SGRASS3", PU_CACHE);
+				background = W_CachePatchName("SGRASS3", PU_PATCH);
 			else if (intro_curtime >= 6*TICRATE)
-				background = W_CachePatchName("SGRASS2", PU_CACHE);
+				background = W_CachePatchName("SGRASS2", PU_PATCH);
 			else
-				background = W_CachePatchName("SGRASS1", PU_CACHE);
+				background = W_CachePatchName("SGRASS1", PU_PATCH);
 			break;
 		}
 		case 8:
-			background = W_CachePatchName("WATCHING", PU_CACHE);
+			background = W_CachePatchName("WATCHING", PU_PATCH);
 			break;
 		case 9:
-			background = W_CachePatchName("ZOOMING", PU_CACHE);
+			background = W_CachePatchName("ZOOMING", PU_PATCH);
 			break;
 		case 10:
 			break;
 		case 11:
-			background = W_CachePatchName("INTRO5", PU_CACHE);
+			background = W_CachePatchName("INTRO5", PU_PATCH);
 			break;
 		case 12:
-			background = W_CachePatchName("REVENGE", PU_CACHE);
+			background = W_CachePatchName("REVENGE", PU_PATCH);
 			cx = 208;
 			cy = 8;
 			break;
 		case 13:
-			background = W_CachePatchName("CONFRONT", PU_CACHE);
+			background = W_CachePatchName("CONFRONT", PU_PATCH);
 			cy += 48;
 			break;
 		case 14:
-			background = W_CachePatchName("TAILSSAD", PU_CACHE);
+			background = W_CachePatchName("TAILSSAD", PU_PATCH);
 			bgxoffs = 144;
 			cx = 8;
 			cy = 8;
 			break;
 		case 15:
 			if (intro_curtime >= 7*TICRATE)
-				background = W_CachePatchName("SONICDO2", PU_CACHE);
+				background = W_CachePatchName("SONICDO2", PU_PATCH);
 			else
-				background = W_CachePatchName("SONICDO1", PU_CACHE);
+				background = W_CachePatchName("SONICDO1", PU_PATCH);
 			cx = 224;
 			cy = 8;
 			break;
 		case 16:
-			background = W_CachePatchName("INTRO7", PU_CACHE);
+			background = W_CachePatchName("INTRO7", PU_PATCH);
 			break;
 		default:
 			break;
@@ -631,30 +631,30 @@ static void F_IntroDrawScene(void)
 					S_ChangeMusicInternal("_stjr", false);
 				x = (BASEVIDWIDTH<<FRACBITS)/2 - FixedMul(334<<FRACBITS, aspect)/2;
 				y = (BASEVIDHEIGHT<<FRACBITS)/2 - FixedMul(358<<FRACBITS, aspect)/2;
-				V_DrawSciencePatch(x, y, 0, (patch = W_CachePatchName("WAHH1", PU_CACHE)), aspect);
+				V_DrawSciencePatch(x, y, 0, (patch = W_CachePatchName("WAHH1", PU_PATCH)), aspect);
 				W_UnlockCachedPatch(patch);
 				if (finalecount > 6) {
-					V_DrawSciencePatch(x, y, 0, (patch = W_CachePatchName("WAHH2", PU_CACHE)), aspect);
+					V_DrawSciencePatch(x, y, 0, (patch = W_CachePatchName("WAHH2", PU_PATCH)), aspect);
 					W_UnlockCachedPatch(patch);
 				}
 				if (finalecount > 10) {
-					V_DrawSciencePatch(x, y, 0, (patch = W_CachePatchName("WAHH3", PU_CACHE)), aspect);
+					V_DrawSciencePatch(x, y, 0, (patch = W_CachePatchName("WAHH3", PU_PATCH)), aspect);
 					W_UnlockCachedPatch(patch);
 				}
 				if (finalecount > 14) {
-					V_DrawSciencePatch(x, y, 0, (patch = W_CachePatchName("WAHH4", PU_CACHE)), aspect);
+					V_DrawSciencePatch(x, y, 0, (patch = W_CachePatchName("WAHH4", PU_PATCH)), aspect);
 					W_UnlockCachedPatch(patch);
 				}
 			}
 			else if (finalecount-30 < 20) { // Big eggy
-				background = W_CachePatchName("FEEDIN", PU_CACHE);
+				background = W_CachePatchName("FEEDIN", PU_PATCH);
 				x = (BASEVIDWIDTH<<FRACBITS)/2 - FixedMul(560<<FRACBITS, aspect)/2;
 				y = (BASEVIDHEIGHT<<FRACBITS) - FixedMul(477<<FRACBITS, aspect);
 				V_DrawSciencePatch(x, y, V_SNAPTOBOTTOM, background, aspect);
 			}
 			else if (finalecount-50 < 30) { // Zoom out
 				fixed_t scale = FixedDiv(aspect, FixedDiv((finalecount-50)<<FRACBITS, (15<<FRACBITS))+FRACUNIT);
-				background = W_CachePatchName("FEEDIN", PU_CACHE);
+				background = W_CachePatchName("FEEDIN", PU_PATCH);
 				x = (BASEVIDWIDTH<<FRACBITS)/2 - FixedMul(560<<FRACBITS, aspect)/2 + (FixedMul(560<<FRACBITS, aspect) - FixedMul(560<<FRACBITS, scale));
 				y = (BASEVIDHEIGHT<<FRACBITS) - FixedMul(477<<FRACBITS, scale);
 				V_DrawSciencePatch(x, y, V_SNAPTOBOTTOM, background, scale);
@@ -664,7 +664,7 @@ static void F_IntroDrawScene(void)
 				{
 					// Draw tiny eggy
 					fixed_t scale = FixedMul(FRACUNIT/3, aspect);
-					background = W_CachePatchName("FEEDIN", PU_CACHE);
+					background = W_CachePatchName("FEEDIN", PU_PATCH);
 					x = (BASEVIDWIDTH<<FRACBITS)/2 - FixedMul(560<<FRACBITS, aspect)/2 + (FixedMul(560<<FRACBITS, aspect) - FixedMul(560<<FRACBITS, scale));
 					y = (BASEVIDHEIGHT<<FRACBITS) - FixedMul(477<<FRACBITS, scale);
 					V_DrawSciencePatch(x, y, V_SNAPTOBOTTOM, background, scale);
@@ -675,34 +675,34 @@ static void F_IntroDrawScene(void)
 					x = (-189*FRACUNIT) + (FixedMul((6<<FRACBITS)+FRACUNIT/3, ftime<<FRACBITS) - FixedMul((6<<FRACBITS)+FRACUNIT/3, FixedDiv(FixedMul(ftime<<FRACBITS, ftime<<FRACBITS), 120<<FRACBITS)));
 					y = (BASEVIDHEIGHT<<FRACBITS) - FixedMul(417<<FRACBITS, aspect);
 					// Draw the body
-					V_DrawSciencePatch(x, y, V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT1", PU_CACHE)), aspect);
+					V_DrawSciencePatch(x, y, V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT1", PU_PATCH)), aspect);
 					W_UnlockCachedPatch(patch);
 					// Draw the door
-					V_DrawSciencePatch(x+FixedMul(344<<FRACBITS, aspect), y+FixedMul(292<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT2", PU_CACHE)), aspect);
+					V_DrawSciencePatch(x+FixedMul(344<<FRACBITS, aspect), y+FixedMul(292<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT2", PU_PATCH)), aspect);
 					W_UnlockCachedPatch(patch);
 					// Draw the wheel
-					V_DrawSciencePatch(x+FixedMul(178<<FRACBITS, aspect), y+FixedMul(344<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName(va("TYRE%02u",(abs(finalecount-144)/3)%16), PU_CACHE)), aspect);
+					V_DrawSciencePatch(x+FixedMul(178<<FRACBITS, aspect), y+FixedMul(344<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName(va("TYRE%02u",(abs(finalecount-144)/3)%16), PU_PATCH)), aspect);
 					W_UnlockCachedPatch(patch);
 					// Draw the wheel cover
-					V_DrawSciencePatch(x+FixedMul(88<<FRACBITS, aspect), y+FixedMul(238<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT3", PU_CACHE)), aspect);
+					V_DrawSciencePatch(x+FixedMul(88<<FRACBITS, aspect), y+FixedMul(238<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT3", PU_PATCH)), aspect);
 					W_UnlockCachedPatch(patch);
 				} else { // Pure Fat has stopped!
 					y = (BASEVIDHEIGHT<<FRACBITS) - FixedMul(417<<FRACBITS, aspect);
 					// Draw the body
-					V_DrawSciencePatch(0, y, V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT1", PU_CACHE)), aspect);
+					V_DrawSciencePatch(0, y, V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT1", PU_PATCH)), aspect);
 					W_UnlockCachedPatch(patch);
 					// Draw the wheel
-					V_DrawSciencePatch(FixedMul(178<<FRACBITS, aspect), y+FixedMul(344<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("TYRE00", PU_CACHE)), aspect);
+					V_DrawSciencePatch(FixedMul(178<<FRACBITS, aspect), y+FixedMul(344<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("TYRE00", PU_PATCH)), aspect);
 					W_UnlockCachedPatch(patch);
 					// Draw the wheel cover
-					V_DrawSciencePatch(FixedMul(88<<FRACBITS, aspect), y+FixedMul(238<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT3", PU_CACHE)), aspect);
+					V_DrawSciencePatch(FixedMul(88<<FRACBITS, aspect), y+FixedMul(238<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT3", PU_PATCH)), aspect);
 					W_UnlockCachedPatch(patch);
 					// Draw the door
 					if (finalecount-TICRATE/2 > 4*TICRATE) { // Door is being raised!
 						int ftime = (finalecount-TICRATE/2-4*TICRATE);
 						y -= FixedDiv((ftime*ftime)<<FRACBITS, 23<<FRACBITS);
 					}
-					V_DrawSciencePatch(FixedMul(344<<FRACBITS, aspect), y+FixedMul(292<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT2", PU_CACHE)), aspect);
+					V_DrawSciencePatch(FixedMul(344<<FRACBITS, aspect), y+FixedMul(292<<FRACBITS, aspect), V_SNAPTOLEFT|V_SNAPTOBOTTOM, (patch = W_CachePatchName("PUREFAT2", PU_PATCH)), aspect);
 					W_UnlockCachedPatch(patch);
 				}
 			}
@@ -716,27 +716,27 @@ static void F_IntroDrawScene(void)
 		if (timetonext > 5*TICRATE && timetonext < 6*TICRATE)
 		{
 			if (!(finalecount & 3))
-				background = W_CachePatchName("BRITEGG1", PU_CACHE);
+				background = W_CachePatchName("BRITEGG1", PU_PATCH);
 			else
-				background = W_CachePatchName("DARKEGG1", PU_CACHE);
+				background = W_CachePatchName("DARKEGG1", PU_PATCH);
 
 			V_DrawSmallScaledPatch(0, 0, 0, background);
 		}
 		else if (timetonext > 3*TICRATE && timetonext < 4*TICRATE)
 		{
 			if (!(finalecount & 3))
-				background = W_CachePatchName("BRITEGG2", PU_CACHE);
+				background = W_CachePatchName("BRITEGG2", PU_PATCH);
 			else
-				background = W_CachePatchName("DARKEGG2", PU_CACHE);
+				background = W_CachePatchName("DARKEGG2", PU_PATCH);
 
 			V_DrawSmallScaledPatch(0, 0, 0, background);
 		}
 		else if (timetonext > 1*TICRATE && timetonext < 2*TICRATE)
 		{
 			if (!(finalecount & 3))
-				background = W_CachePatchName("BRITEGG3", PU_CACHE);
+				background = W_CachePatchName("BRITEGG3", PU_PATCH);
 			else
-				background = W_CachePatchName("DARKEGG3", PU_CACHE);
+				background = W_CachePatchName("DARKEGG3", PU_PATCH);
 
 			V_DrawSmallScaledPatch(0, 0, 0, background);
 		}
@@ -768,79 +768,79 @@ static void F_IntroDrawScene(void)
 			knucklesx += sonicx;
 			sonicx += P_ReturnThrustX(NULL, finalecount * ANG10, 3);
 
-			V_DrawSmallScaledPatch(skyx, 0, 0, (patch = W_CachePatchName("INTROSKY", PU_CACHE)));
+			V_DrawSmallScaledPatch(skyx, 0, 0, (patch = W_CachePatchName("INTROSKY", PU_PATCH)));
 			V_DrawSmallScaledPatch(skyx - 320, 0, 0, patch);
 			W_UnlockCachedPatch(patch);
-			V_DrawSmallScaledPatch(grassx, 0, 0, (patch = W_CachePatchName("INTROGRS", PU_CACHE)));
+			V_DrawSmallScaledPatch(grassx, 0, 0, (patch = W_CachePatchName("INTROGRS", PU_PATCH)));
 			V_DrawSmallScaledPatch(grassx - 320, 0, 0, patch);
 			W_UnlockCachedPatch(patch);
 
 			if (finalecount & 1)
 			{
 				// Sonic
-				V_DrawSmallScaledPatch(sonicx, 54, 0, (patch = W_CachePatchName("RUN2", PU_CACHE)));
+				V_DrawSmallScaledPatch(sonicx, 54, 0, (patch = W_CachePatchName("RUN2", PU_PATCH)));
 				W_UnlockCachedPatch(patch);
 
 				// Appendages
 				if (finalecount & 2)
 				{
 					// Sonic's feet
-					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT4", PU_CACHE)));
+					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT4", PU_PATCH)));
 					W_UnlockCachedPatch(patch);
 					// Tails' tails
-					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP2", PU_CACHE)));
+					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP2", PU_PATCH)));
 					W_UnlockCachedPatch(patch);
 				}
 				else
 				{
 					// Sonic's feet
-					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT2", PU_CACHE)));
+					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT2", PU_PATCH)));
 					W_UnlockCachedPatch(patch);
 					// Tails' tails
-					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP1", PU_CACHE)));
+					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP1", PU_PATCH)));
 					W_UnlockCachedPatch(patch);
 				}
 
 				// Tails
-				V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("FLY2", PU_CACHE)));
+				V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("FLY2", PU_PATCH)));
 				W_UnlockCachedPatch(patch);
 
 				// Knuckles
-				V_DrawSmallScaledPatch(knucklesx, knucklesy, 0, (patch = W_CachePatchName("GLIDE2", PU_CACHE)));
+				V_DrawSmallScaledPatch(knucklesx, knucklesy, 0, (patch = W_CachePatchName("GLIDE2", PU_PATCH)));
 				W_UnlockCachedPatch(patch);
 			}
 			else
 			{
 				// Sonic
-				V_DrawSmallScaledPatch(sonicx, 54, 0, (patch = W_CachePatchName("RUN1", PU_CACHE)));
+				V_DrawSmallScaledPatch(sonicx, 54, 0, (patch = W_CachePatchName("RUN1", PU_PATCH)));
 				W_UnlockCachedPatch(patch);
 
 				// Appendages
 				if (finalecount & 2)
 				{
 					// Sonic's feet
-					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT3", PU_CACHE)));
+					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT3", PU_PATCH)));
 					W_UnlockCachedPatch(patch);
 					// Tails' tails
-					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP2", PU_CACHE)));
+					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP2", PU_PATCH)));
 					W_UnlockCachedPatch(patch);
 				}
 				else
 				{
 					// Sonic's feet
-					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT1", PU_CACHE)));
+					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT1", PU_PATCH)));
 					W_UnlockCachedPatch(patch);
 					// Tails' tails
-					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP1", PU_CACHE)));
+					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP1", PU_PATCH)));
 					W_UnlockCachedPatch(patch);
 				}
 
 				// Tails
-				V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("FLY1", PU_CACHE)));
+				V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("FLY1", PU_PATCH)));
 				W_UnlockCachedPatch(patch);
 
 				// Knuckles
-				V_DrawSmallScaledPatch(knucklesx, knucklesy, 0, (patch = W_CachePatchName("GLIDE1", PU_CACHE)));
+				V_DrawSmallScaledPatch(knucklesx, knucklesy, 0, (patch = W_CachePatchName("GLIDE1", PU_PATCH)));
 				W_UnlockCachedPatch(patch);
 			}
 
@@ -873,8 +873,8 @@ static void F_IntroDrawScene(void)
 				y += (30*(FRACUNIT-scale));
 			}
 
-			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (worktics % 35)), PU_LEVEL);
-			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(worktics & 1)), PU_LEVEL);
+			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (worktics % 35)), PU_PATCH);
+			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(worktics & 1)), PU_PATCH);
 
 			if (worktics >= 5)
 				trans = (worktics-5)>>1;
@@ -988,7 +988,7 @@ void F_IntroDrawer(void)
 	{
 		if (intro_scenenum == 5 && intro_curtime == 5*TICRATE)
 		{
-			patch_t *radar = W_CachePatchName("RADAR", PU_CACHE);
+			patch_t *radar = W_CachePatchName("RADAR", PU_PATCH);
 
 			F_WipeStartScreen();
 			F_WipeColorFill(31);
@@ -1001,7 +1001,7 @@ void F_IntroDrawer(void)
 		}
 		else if (intro_scenenum == 7 && intro_curtime == 6*TICRATE) // Force a wipe here
 		{
-			patch_t *grass = W_CachePatchName("SGRASS2", PU_CACHE);
+			patch_t *grass = W_CachePatchName("SGRASS2", PU_PATCH);
 
 			F_WipeStartScreen();
 			F_WipeColorFill(31);
@@ -1014,7 +1014,7 @@ void F_IntroDrawer(void)
 		}
 		/*else if (intro_scenenum == 12 && intro_curtime == 7*TICRATE)
 		{
-			patch_t *confront = W_CachePatchName("CONFRONT", PU_CACHE);
+			patch_t *confront = W_CachePatchName("CONFRONT", PU_PATCH);
 
 			F_WipeStartScreen();
 			F_WipeColorFill(31);
@@ -1027,7 +1027,7 @@ void F_IntroDrawer(void)
 		}*/
 		if (intro_scenenum == 15 && intro_curtime == 7*TICRATE)
 		{
-			patch_t *sdo = W_CachePatchName("SONICDO2", PU_CACHE);
+			patch_t *sdo = W_CachePatchName("SONICDO2", PU_PATCH);
 
 			F_WipeStartScreen();
 			F_WipeColorFill(31);
@@ -1359,14 +1359,14 @@ void F_CreditDrawer(void)
 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 
 	// Zig Zagz
-	V_DrawScaledPatch(-16,               zagpos,       V_SNAPTOLEFT,         W_CachePatchName("LTZIGZAG", PU_CACHE));
-	V_DrawScaledPatch(-16,               zagpos - 320, V_SNAPTOLEFT,         W_CachePatchName("LTZIGZAG", PU_CACHE));
-	V_DrawScaledPatch(BASEVIDWIDTH + 16, zagpos,       V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_CACHE));
-	V_DrawScaledPatch(BASEVIDWIDTH + 16, zagpos - 320, V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_CACHE));
+	V_DrawScaledPatch(-16,               zagpos,       V_SNAPTOLEFT,         W_CachePatchName("LTZIGZAG", PU_PATCH));
+	V_DrawScaledPatch(-16,               zagpos - 320, V_SNAPTOLEFT,         W_CachePatchName("LTZIGZAG", PU_PATCH));
+	V_DrawScaledPatch(BASEVIDWIDTH + 16, zagpos,       V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_PATCH));
+	V_DrawScaledPatch(BASEVIDWIDTH + 16, zagpos - 320, V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_PATCH));
 
 	// Draw background pictures first
 	for (i = 0; credits_pics[i].patch; i++)
-		V_DrawSciencePatch(credits_pics[i].x<<FRACBITS, (280<<FRACBITS) + (((i*credits_height)<<FRACBITS)/(credits_numpics)) - 4*(animtimer<<FRACBITS)/5, 0, W_CachePatchName(credits_pics[i].patch, PU_CACHE), FRACUNIT>>1);
+		V_DrawSciencePatch(credits_pics[i].x<<FRACBITS, (280<<FRACBITS) + (((i*credits_height)<<FRACBITS)/(credits_numpics)) - 4*(animtimer<<FRACBITS)/5, 0, W_CachePatchName(credits_pics[i].patch, PU_PATCH), FRACUNIT>>1);
 
 	// Dim the background
 	V_DrawFadeScreen(0xFF00, 16);
@@ -1574,14 +1574,14 @@ void F_GameEvaluationDrawer(void)
 
 		if (goodending)
 		{
-			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (finalecount % 35)), PU_LEVEL);
-			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(finalecount & 1)), PU_LEVEL);
+			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (finalecount % 35)), PU_PATCH);
+			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(finalecount & 1)), PU_PATCH);
 			x -= FRACUNIT;
 		}
 		else
 		{
 			rockpat = W_CachePatchName("ROID0000", PU_LEVEL);
-			glow = W_CachePatchName(va("ENDGLOW%.1d", (finalecount & 1)), PU_LEVEL);
+			glow = W_CachePatchName(va("ENDGLOW%.1d", (finalecount & 1)), PU_PATCH);
 		}
 
 		if (finalecount >= 5)
@@ -1613,20 +1613,20 @@ void F_GameEvaluationDrawer(void)
 					// if j == 0 - alternate between 0 and 1
 					//         1 -                   1 and 2
 					//         2 -                   2 and not rendered
-					V_DrawFixedPatch(x+sparkloffs[j-1][0], y+sparkloffs[j-1][1], FRACUNIT, 0, W_CachePatchName(va("ENDSPKL%.1d", (j - ((sparklloop & 1) ? 0 : 1))), PU_LEVEL), R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_AQUA, GTC_CACHE));
+					V_DrawFixedPatch(x+sparkloffs[j-1][0], y+sparkloffs[j-1][1], FRACUNIT, 0, W_CachePatchName(va("ENDSPKL%.1d", (j - ((sparklloop & 1) ? 0 : 1))), PU_PATCH), R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_AQUA, GTC_CACHE));
 				}
 				j--;
 			}
 		}
 		else
 		{
-			patch_t *eggrock = W_CachePatchName("ENDEGRK5", PU_LEVEL);
+			patch_t *eggrock = W_CachePatchName("ENDEGRK5", PU_PATCH);
 			V_DrawFixedPatch(x, y, scale, 0, eggrock, colormap[0]);
 			if (trans < 10)
 				V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, eggrock, colormap[1]);
 			else if (sparklloop)
 				V_DrawFixedPatch(x, y, scale, (10-sparklloop)<<V_ALPHASHIFT,
-					W_CachePatchName("ENDEGRK0", PU_LEVEL), colormap[1]);
+					W_CachePatchName("ENDEGRK0", PU_PATCH), colormap[1]);
 		}
 	}
 
@@ -1640,7 +1640,7 @@ void F_GameEvaluationDrawer(void)
 		eemeralds_cur += (360<<FRACBITS)/7;
 
 		patchname[4] = 'A'+(char)i;
-		V_DrawFixedPatch(x, y, FRACUNIT, ((emeralds & (1<<i)) ? 0 : V_80TRANS), W_CachePatchName(patchname, PU_LEVEL), NULL);
+		V_DrawFixedPatch(x, y, FRACUNIT, ((emeralds & (1<<i)) ? 0 : V_80TRANS), W_CachePatchName(patchname, PU_PATCH), NULL);
 	}
 
 	V_DrawCreditString((BASEVIDWIDTH - V_CreditStringWidth(endingtext))<<(FRACBITS-1), (BASEVIDHEIGHT-100)<<(FRACBITS-1), 0, endingtext);
@@ -1753,55 +1753,34 @@ void F_GameEvaluationTicker(void)
 #define STOPPINGPOINT (14*TICRATE)
 #define SPARKLLOOPTIME 15 // must be odd
 
-void F_StartEnding(void)
+static void F_CacheEnding(void)
 {
-	G_SetGamestate(GS_ENDING);
-	wipetypepost = INT16_MAX;
-
-	// Just in case they're open ... somehow
-	M_ClearMenus(true);
-
-	// Save before the credits sequence.
-	if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && cursaveslot > 0)
-		G_SaveGame((UINT32)cursaveslot);
-
-	gameaction = ga_nothing;
-	paused = false;
-	CON_ToggleOff();
-	S_StopMusic();
-	S_StopSounds();
-
-	finalecount = -10; // what? this totally isn't a hack. why are you asking?
-
-	memset(sparkloffs, 0, sizeof(INT32)*3*2);
-	sparklloop = 0;
-
-	endbrdr[1] = W_CachePatchName("ENDBRDR1", PU_LEVEL);
+	endbrdr[1] = W_CachePatchName("ENDBRDR1", PU_PATCH);
 
-	endegrk[0] = W_CachePatchName("ENDEGRK0", PU_LEVEL);
-	endegrk[1] = W_CachePatchName("ENDEGRK1", PU_LEVEL);
+	endegrk[0] = W_CachePatchName("ENDEGRK0", PU_PATCH);
+	endegrk[1] = W_CachePatchName("ENDEGRK1", PU_PATCH);
 
-	endglow[0] = W_CachePatchName("ENDGLOW0", PU_LEVEL);
-	endglow[1] = W_CachePatchName("ENDGLOW1", PU_LEVEL);
+	endglow[0] = W_CachePatchName("ENDGLOW0", PU_PATCH);
+	endglow[1] = W_CachePatchName("ENDGLOW1", PU_PATCH);
 
-	endbgsp[0] = W_CachePatchName("ENDBGSP0", PU_LEVEL);
-	endbgsp[1] = W_CachePatchName("ENDBGSP1", PU_LEVEL);
-	endbgsp[2] = W_CachePatchName("ENDBGSP2", PU_LEVEL);
+	endbgsp[0] = W_CachePatchName("ENDBGSP0", PU_PATCH);
+	endbgsp[1] = W_CachePatchName("ENDBGSP1", PU_PATCH);
+	endbgsp[2] = W_CachePatchName("ENDBGSP2", PU_PATCH);
 
-	endspkl[0] = W_CachePatchName("ENDSPKL0", PU_LEVEL);
-	endspkl[1] = W_CachePatchName("ENDSPKL1", PU_LEVEL);
-	endspkl[2] = W_CachePatchName("ENDSPKL2", PU_LEVEL);
+	endspkl[0] = W_CachePatchName("ENDSPKL0", PU_PATCH);
+	endspkl[1] = W_CachePatchName("ENDSPKL1", PU_PATCH);
+	endspkl[2] = W_CachePatchName("ENDSPKL2", PU_PATCH);
 
-	endxpld[0] = W_CachePatchName("ENDXPLD0", PU_LEVEL);
-	endxpld[1] = W_CachePatchName("ENDXPLD1", PU_LEVEL);
-	endxpld[2] = W_CachePatchName("ENDXPLD2", PU_LEVEL);
-	endxpld[3] = W_CachePatchName("ENDXPLD3", PU_LEVEL);
+	endxpld[0] = W_CachePatchName("ENDXPLD0", PU_PATCH);
+	endxpld[1] = W_CachePatchName("ENDXPLD1", PU_PATCH);
+	endxpld[2] = W_CachePatchName("ENDXPLD2", PU_PATCH);
+	endxpld[3] = W_CachePatchName("ENDXPLD3", PU_PATCH);
 
-	endescp[0] = W_CachePatchName("ENDESCP0", PU_LEVEL);
-	endescp[1] = W_CachePatchName("ENDESCP1", PU_LEVEL);
-	endescp[2] = W_CachePatchName("ENDESCP2", PU_LEVEL);
-	endescp[3] = W_CachePatchName("ENDESCP3", PU_LEVEL);
-	endescp[4] = W_CachePatchName("ENDESCP4", PU_LEVEL);
+	endescp[0] = W_CachePatchName("ENDESCP0", PU_PATCH);
+	endescp[1] = W_CachePatchName("ENDESCP1", PU_PATCH);
+	endescp[2] = W_CachePatchName("ENDESCP2", PU_PATCH);
+	endescp[3] = W_CachePatchName("ENDESCP3", PU_PATCH);
+	endescp[4] = W_CachePatchName("ENDESCP4", PU_PATCH);
 
 	// so we only need to check once
 	if ((goodending = ALL7EMERALDS(emeralds)))
@@ -1814,32 +1793,69 @@ void F_StartEnding(void)
 			sprdef = &skins[skinnum].sprites[SPR2_XTRA];
 			// character head, skin specific
 			sprframe = &sprdef->spriteframes[XTRA_ENDING];
-			endfwrk[0] = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
+			endfwrk[0] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
 			sprframe = &sprdef->spriteframes[XTRA_ENDING+1];
-			endfwrk[1] = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
+			endfwrk[1] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
 			sprframe = &sprdef->spriteframes[XTRA_ENDING+2];
-			endfwrk[2] = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
+			endfwrk[2] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
 		}
 		else // Show a star if your character doesn't have an ending firework display. (Basically the MISSINGs for this)
 		{
-			endfwrk[0] = W_CachePatchName("ENDFWRK3", PU_LEVEL);
-			endfwrk[1] = W_CachePatchName("ENDFWRK4", PU_LEVEL);
-			endfwrk[2] = W_CachePatchName("ENDFWRK5", PU_LEVEL);
+			endfwrk[0] = W_CachePatchName("ENDFWRK3", PU_PATCH);
+			endfwrk[1] = W_CachePatchName("ENDFWRK4", PU_PATCH);
+			endfwrk[2] = W_CachePatchName("ENDFWRK5", PU_PATCH);
 		}
 
-		endbrdr[0] = W_CachePatchName("ENDBRDR2", PU_LEVEL);
+		endbrdr[0] = W_CachePatchName("ENDBRDR2", PU_PATCH);
 	}
 	else
 	{
 		// eggman, skin nonspecific
-		endfwrk[0] = W_CachePatchName("ENDFWRK0", PU_LEVEL);
-		endfwrk[1] = W_CachePatchName("ENDFWRK1", PU_LEVEL);
-		endfwrk[2] = W_CachePatchName("ENDFWRK2", PU_LEVEL);
+		endfwrk[0] = W_CachePatchName("ENDFWRK0", PU_PATCH);
+		endfwrk[1] = W_CachePatchName("ENDFWRK1", PU_PATCH);
+		endfwrk[2] = W_CachePatchName("ENDFWRK2", PU_PATCH);
 
 		endbrdr[0] = W_CachePatchName("ENDBRDR0", PU_LEVEL);
 	}
 }
 
+static void F_CacheGoodEnding(void)
+{
+	endegrk[0] = W_CachePatchName("ENDEGRK2", PU_PATCH);
+	endegrk[1] = W_CachePatchName("ENDEGRK3", PU_PATCH);
+
+	endglow[0] = W_CachePatchName("ENDGLOW2", PU_PATCH);
+	endglow[1] = W_CachePatchName("ENDGLOW3", PU_PATCH);
+
+	endxpld[0] = W_CachePatchName("ENDEGRK4", PU_PATCH);
+}
+
+void F_StartEnding(void)
+{
+	G_SetGamestate(GS_ENDING);
+	wipetypepost = INT16_MAX;
+
+	// Just in case they're open ... somehow
+	M_ClearMenus(true);
+
+	// Save before the credits sequence.
+	if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && cursaveslot > 0)
+		G_SaveGame((UINT32)cursaveslot);
+
+	gameaction = ga_nothing;
+	paused = false;
+	CON_ToggleOff();
+	S_StopMusic(); // todo: placeholder
+	S_StopSounds();
+
+	finalecount = -10; // what? this totally isn't a hack. why are you asking?
+
+	memset(sparkloffs, 0, sizeof(INT32)*3*2);
+	sparklloop = 0;
+
+	F_CacheEnding();
+}
+
 void F_EndingTicker(void)
 {
 	if (++finalecount > STOPPINGPOINT)
@@ -1853,15 +1869,7 @@ void F_EndingTicker(void)
 		S_ChangeMusicInternal((goodending ? "_endg" : "_endb"), false);
 
 	if (goodending && finalecount == INFLECTIONPOINT) // time to swap some assets
-	{
-		endegrk[0] = W_CachePatchName("ENDEGRK2", PU_LEVEL);
-		endegrk[1] = W_CachePatchName("ENDEGRK3", PU_LEVEL);
-
-		endglow[0] = W_CachePatchName("ENDGLOW2", PU_LEVEL);
-		endglow[1] = W_CachePatchName("ENDGLOW3", PU_LEVEL);
-
-		endxpld[0] = W_CachePatchName("ENDEGRK4", PU_LEVEL);
-	}
+		F_CacheGoodEnding();
 
 	if (++sparklloop == SPARKLLOOPTIME) // time to roll the randomisation again
 	{
@@ -1880,10 +1888,17 @@ void F_EndingDrawer(void)
 	INT32 x, y, i, j, parallaxticker;
 	patch_t *rockpat;
 
+	if (needpatchrecache)
+	{
+		F_CacheEnding();
+		if (goodending && finalecount >= INFLECTIONPOINT) // time to swap some assets
+			F_CacheGoodEnding();
+	}
+
 	if (!goodending || finalecount < INFLECTIONPOINT)
-		rockpat = W_CachePatchName("ROID0000", PU_LEVEL);
+		rockpat = W_CachePatchName("ROID0000", PU_PATCH);
 	else
-		rockpat = W_CachePatchName(va("ROID00%.2d", 34 - ((finalecount - INFLECTIONPOINT) % 35)), PU_LEVEL);
+		rockpat = W_CachePatchName(va("ROID00%.2d", 34 - ((finalecount - INFLECTIONPOINT) % 35)), PU_PATCH);
 
 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 
@@ -2367,11 +2382,11 @@ void F_SkyScroll(INT32 scrollxspeed, INT32 scrollyspeed, const char *patchname)
 
 	if (!scrollxspeed && !scrollyspeed)
 	{
-		V_DrawPatchFill(W_CachePatchName(patchname, PU_CACHE));
+		V_DrawPatchFill(W_CachePatchName(patchname, PU_PATCH));
 		return;
 	}
 
-	pat = W_CachePatchName(patchname, PU_CACHE);
+	pat = W_CachePatchName(patchname, PU_PATCH);
 
 	patwidth = SHORT(pat->width);
 	patheight = SHORT(pat->height);
@@ -2432,6 +2447,45 @@ else if (strlen(name) <= 6) \
 else \
 	arr[0] = 0;
 
+static void F_CacheTitleScreen(void)
+{
+	switch(curttmode)
+	{
+		case TTMODE_OLD:
+		case TTMODE_NONE:
+			ttbanner = W_CachePatchName("TTBANNER", PU_LEVEL);
+			ttwing = W_CachePatchName("TTWING", PU_LEVEL);
+			ttsonic = W_CachePatchName("TTSONIC", PU_LEVEL);
+			ttswave1 = W_CachePatchName("TTSWAVE1", PU_LEVEL);
+			ttswave2 = W_CachePatchName("TTSWAVE2", PU_LEVEL);
+			ttswip1 = W_CachePatchName("TTSWIP1", PU_LEVEL);
+			ttsprep1 = W_CachePatchName("TTSPREP1", PU_LEVEL);
+			ttsprep2 = W_CachePatchName("TTSPREP2", PU_LEVEL);
+			ttspop1 = W_CachePatchName("TTSPOP1", PU_LEVEL);
+			ttspop2 = W_CachePatchName("TTSPOP2", PU_LEVEL);
+			ttspop3 = W_CachePatchName("TTSPOP3", PU_LEVEL);
+			ttspop4 = W_CachePatchName("TTSPOP4", PU_LEVEL);
+			ttspop5 = W_CachePatchName("TTSPOP5", PU_LEVEL);
+			ttspop6 = W_CachePatchName("TTSPOP6", PU_LEVEL);
+			ttspop7 = W_CachePatchName("TTSPOP7", PU_LEVEL);
+			break;
+
+		// don't load alacroix gfx yet; we do that upon first draw.
+		case TTMODE_ALACROIX:
+			break;
+
+		case TTMODE_USER:
+		{
+			UINT16 i;
+			lumpnum_t lumpnum;
+			char lumpname[9];
+
+			LOADTTGFX(ttuser, curttname, TTMAX_USER)
+			break;
+		}
+	}
+}
+
 void F_StartTitleScreen(void)
 {
 	if (menupres[MN_MAIN].musname[0])
@@ -2528,41 +2582,7 @@ void F_StartTitleScreen(void)
 	demoDelayLeft = demoDelayTime;
 	demoIdleLeft = demoIdleTime;
 
-	switch(curttmode)
-	{
-		case TTMODE_OLD:
-		case TTMODE_NONE:
-			ttbanner = W_CachePatchName("TTBANNER", PU_LEVEL);
-			ttwing = W_CachePatchName("TTWING", PU_LEVEL);
-			ttsonic = W_CachePatchName("TTSONIC", PU_LEVEL);
-			ttswave1 = W_CachePatchName("TTSWAVE1", PU_LEVEL);
-			ttswave2 = W_CachePatchName("TTSWAVE2", PU_LEVEL);
-			ttswip1 = W_CachePatchName("TTSWIP1", PU_LEVEL);
-			ttsprep1 = W_CachePatchName("TTSPREP1", PU_LEVEL);
-			ttsprep2 = W_CachePatchName("TTSPREP2", PU_LEVEL);
-			ttspop1 = W_CachePatchName("TTSPOP1", PU_LEVEL);
-			ttspop2 = W_CachePatchName("TTSPOP2", PU_LEVEL);
-			ttspop3 = W_CachePatchName("TTSPOP3", PU_LEVEL);
-			ttspop4 = W_CachePatchName("TTSPOP4", PU_LEVEL);
-			ttspop5 = W_CachePatchName("TTSPOP5", PU_LEVEL);
-			ttspop6 = W_CachePatchName("TTSPOP6", PU_LEVEL);
-			ttspop7 = W_CachePatchName("TTSPOP7", PU_LEVEL);
-			break;
-
-		// don't load alacroix gfx yet; we do that upon first draw.
-		case TTMODE_ALACROIX:
-			break;
-
-		case TTMODE_USER:
-		{
-			UINT16 i;
-			lumpnum_t lumpnum;
-			char lumpname[9];
-
-			LOADTTGFX(ttuser, curttname, TTMAX_USER)
-			break;
-		}
-	}
+	F_CacheTitleScreen();
 }
 
 static void F_UnloadAlacroixGraphics(SINT8 oldttscale)
@@ -2711,6 +2731,9 @@ void F_TitleScreenDrawer(void)
 	if (modeattacking)
 		return; // We likely came here from retrying. Don't do a damn thing.
 
+	if (needpatchrecache && (curttmode != TTMODE_ALACROIX))
+		F_CacheTitleScreen();
+
 	// Draw that sky!
 	if (curbgcolor >= 0)
 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
@@ -2734,6 +2757,12 @@ void F_TitleScreenDrawer(void)
 		return;
 #endif
 
+	if (needpatchrecache && (curttmode == TTMODE_ALACROIX))
+	{
+		ttloaded[0] = ttloaded[1] = ttloaded[2] = ttloaded[3] = ttloaded[4] = ttloaded[5] = 0;
+		F_LoadAlacroixGraphics(activettscale);
+	}
+
 	switch(curttmode)
 	{
 		case TTMODE_OLD:
@@ -3681,7 +3710,7 @@ void F_ContinueDrawer(void)
 	V_DrawLevelTitle(x - (V_LevelNameWidth("Continue?")>>1), 16, 0, "Continue?");
 
 	// Two stars...
-	patch = W_CachePatchName("CONTSTAR", PU_CACHE);
+	patch = W_CachePatchName("CONTSTAR", PU_PATCH);
 	V_DrawScaledPatch(x-32, 160, 0, patch);
 	V_DrawScaledPatch(x+32, 160, 0, patch);
 
@@ -3689,14 +3718,14 @@ void F_ContinueDrawer(void)
 	if (timeleft > 9)
 	{
 		numbuf[7] = '1';
-		V_DrawScaledPatch(x - 10, 160, 0, W_CachePatchName(numbuf, PU_CACHE));
+		V_DrawScaledPatch(x - 10, 160, 0, W_CachePatchName(numbuf, PU_PATCH));
 		numbuf[7] = '0';
-		V_DrawScaledPatch(x + 10, 160, 0, W_CachePatchName(numbuf, PU_CACHE));
+		V_DrawScaledPatch(x + 10, 160, 0, W_CachePatchName(numbuf, PU_PATCH));
 	}
 	else
 	{
 		numbuf[7] = '0'+timeleft;
-		V_DrawScaledPatch(x, 160, 0, W_CachePatchName(numbuf, PU_CACHE));
+		V_DrawScaledPatch(x, 160, 0, W_CachePatchName(numbuf, PU_PATCH));
 	}
 
 	// Draw the continue markers! Show continues.
@@ -3723,7 +3752,7 @@ void F_ContinueDrawer(void)
 	}
 
 	// Spotlight
-	V_DrawScaledPatch(x, 140, 0, W_CachePatchName("CONTSPOT", PU_CACHE));
+	V_DrawScaledPatch(x, 140, 0, W_CachePatchName("CONTSPOT", PU_PATCH));
 
 	// warping laser
 	if (continuetime)
@@ -3760,7 +3789,7 @@ void F_ContinueDrawer(void)
 #define drawchar(dx, dy, n)	{\
 								sprdef = &contskins[n]->sprites[cont_spr2[n][0]];\
 								sprframe = &sprdef->spriteframes[cont_spr2[n][1]];\
-								patch = W_CachePatchNum(sprframe->lumppat[cont_spr2[n][2]], PU_CACHE);\
+								patch = W_CachePatchNum(sprframe->lumppat[cont_spr2[n][2]], PU_PATCH);\
 								V_DrawFixedPatch((dx), (dy), FRACUNIT, (sprframe->flip & (1<<cont_spr2[n][2])) ? V_FLIP : 0, patch, contcolormaps[n]);\
 							}
 
@@ -4024,10 +4053,10 @@ void F_CutsceneDrawer(void)
 	{
 		if (cutscenes[cutnum]->scene[scenenum].pichires[picnum])
 			V_DrawSmallScaledPatch(picxpos, picypos, 0,
-				W_CachePatchName(cutscenes[cutnum]->scene[scenenum].picname[picnum], PU_CACHE));
+				W_CachePatchName(cutscenes[cutnum]->scene[scenenum].picname[picnum], PU_PATCH));
 		else
 			V_DrawScaledPatch(picxpos,picypos, 0,
-				W_CachePatchName(cutscenes[cutnum]->scene[scenenum].picname[picnum], PU_CACHE));
+				W_CachePatchName(cutscenes[cutnum]->scene[scenenum].picname[picnum], PU_PATCH));
 	}
 
 	if (dofadenow && rendermode != render_none)
@@ -4513,10 +4542,10 @@ void F_TextPromptDrawer(void)
 	{
 		if (textprompts[cutnum]->page[scenenum].pichires[picnum])
 			V_DrawSmallScaledPatch(picxpos, picypos, 0,
-				W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_CACHE));
+				W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_PATCH));
 		else
 			V_DrawScaledPatch(picxpos,picypos, 0,
-				W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_CACHE));
+				W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_PATCH));
 	}
 
 	// Draw background
@@ -4526,7 +4555,7 @@ void F_TextPromptDrawer(void)
 	if (iconlump != LUMPERROR)
 	{
 		INT32 iconx, icony, scale, scaledsize;
-		patch = W_CachePatchName(textprompts[cutnum]->page[scenenum].iconname, PU_CACHE);
+		patch = W_CachePatchName(textprompts[cutnum]->page[scenenum].iconname, PU_PATCH);
 
 		// scale and center
 		if (patch->width > patch->height)
diff --git a/src/hardware/hw_bsp.c b/src/hardware/hw_bsp.c
index 9e454bcd5275dd28150e33f053fc5ee20c79ddb0..f8d4f43d9b285197c3b43743eb5bfdd83e0ce4be 100644
--- a/src/hardware/hw_bsp.c
+++ b/src/hardware/hw_bsp.c
@@ -836,8 +836,10 @@ static INT32 SolveTProblem(void)
 		return 0;
 
 	CONS_Debug(DBG_RENDER, "Solving T-joins. This may take a while. Please wait...\n");
+#ifdef HWR_LOADING_SCREEN
 	CON_Drawer(); //let the user know what we are doing
 	I_FinishUpdate(); // page flip or blit buffer
+#endif
 
 	numsplitpoly = 0;
 
@@ -964,9 +966,9 @@ void HWR_CreatePlanePolygons(INT32 bspnum)
 	CONS_Debug(DBG_RENDER, "Creating polygons, please wait...\n");
 #ifdef HWR_LOADING_SCREEN
 	ls_count = ls_percent = 0; // reset the loading status
-#endif
 	CON_Drawer(); //let the user know what we are doing
 	I_FinishUpdate(); // page flip or blit buffer
+#endif
 
 	HWR_ClearPolys();
 
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index ea16fb6d740626aa1f97a67be7d76041fa011d95..c47833187b1af03f7308d3844fbcce51baa22bd0 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -764,13 +764,12 @@ void HWR_MakePatch (const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipm
 //             CACHING HANDLING
 // =================================================
 
-static size_t gr_numtextures; // Texture count
+static size_t gr_numtextures = 0; // Texture count
 static GLTexture_t *gr_textures; // For all textures
 static GLTexture_t *gr_flats; // For all (texture) flats, as normal flats don't need to be cached
 
 void HWR_InitTextureCache(void)
 {
-	gr_numtextures = 0;
 	gr_textures = NULL;
 	gr_flats = NULL;
 }
@@ -958,6 +957,9 @@ static void HWR_CacheTextureAsFlat(GLMipmap_t *grMipmap, INT32 texturenum)
 {
 	UINT8 *flat;
 
+	if (needpatchflush)
+		W_FlushCachedPatches();
+
 	// setup the texture info
 #ifdef GLIDE_API_COMPATIBILITY
 	grMipmap->grInfo.smallLodLog2 = GR_LOD_LOG2_64;
@@ -983,6 +985,9 @@ void HWR_LiterallyGetFlat(lumpnum_t flatlumpnum)
 	if (flatlumpnum == LUMPERROR)
 		return;
 
+	if (needpatchflush)
+		W_FlushCachedPatches();
+
 	grmip = HWR_GetCachedGLPatch(flatlumpnum)->mipmap;
 	if (!grmip->downloaded && !grmip->grInfo.data)
 		HWR_CacheFlat(grmip, flatlumpnum);
@@ -1061,6 +1066,9 @@ static void HWR_LoadMappedPatch(GLMipmap_t *grmip, GLPatch_t *gpatch)
 // -----------------+
 void HWR_GetPatch(GLPatch_t *gpatch)
 {
+	if (needpatchflush)
+		W_FlushCachedPatches();
+
 	// is it in hardware cache
 	if (!gpatch->mipmap->downloaded && !gpatch->mipmap->grInfo.data)
 	{
@@ -1091,6 +1099,9 @@ void HWR_GetMappedPatch(GLPatch_t *gpatch, const UINT8 *colormap)
 {
 	GLMipmap_t *grmip, *newmip;
 
+	if (needpatchflush)
+		W_FlushCachedPatches();
+
 	if (colormap == colormaps || colormap == NULL)
 	{
 		// Load the default (green) color in doom cache (temporary?) AND hardware cache
@@ -1216,6 +1227,9 @@ GLPatch_t *HWR_GetPic(lumpnum_t lumpnum)
 {
 	GLPatch_t *grpatch;
 
+	if (needpatchflush)
+		W_FlushCachedPatches();
+
 	grpatch = HWR_GetCachedGLPatch(lumpnum);
 
 	if (!grpatch->mipmap->downloaded && !grpatch->mipmap->grInfo.data)
@@ -1419,6 +1433,9 @@ void HWR_GetFadeMask(lumpnum_t fademasklumpnum)
 {
 	GLMipmap_t *grmip;
 
+	if (needpatchflush)
+		W_FlushCachedPatches();
+
 	grmip = HWR_GetCachedGLPatch(fademasklumpnum)->mipmap;
 
 	if (!grmip->downloaded && !grmip->grInfo.data)
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index 4519e1280018d4f69015cba9e32b4df015d190bb..598a635aa47c18b4b79697efdc131e601ab97607 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -992,7 +992,7 @@ void HWR_DrawViewBorder(INT32 clearlines)
 	// top edge
 	if (clearlines > basewindowy - 8)
 	{
-		patch = W_CachePatchNum(viewborderlump[BRDR_T], PU_CACHE);
+		patch = W_CachePatchNum(viewborderlump[BRDR_T], PU_PATCH);
 		for (x = 0; x < baseviewwidth; x += 8)
 			HWR_DrawPatch(patch, basewindowx + x, basewindowy - 8,
 				0);
@@ -1001,7 +1001,7 @@ void HWR_DrawViewBorder(INT32 clearlines)
 	// bottom edge
 	if (clearlines > basewindowy + baseviewheight)
 	{
-		patch = W_CachePatchNum(viewborderlump[BRDR_B], PU_CACHE);
+		patch = W_CachePatchNum(viewborderlump[BRDR_B], PU_PATCH);
 		for (x = 0; x < baseviewwidth; x += 8)
 			HWR_DrawPatch(patch, basewindowx + x,
 				basewindowy + baseviewheight, 0);
@@ -1010,7 +1010,7 @@ void HWR_DrawViewBorder(INT32 clearlines)
 	// left edge
 	if (clearlines > basewindowy)
 	{
-		patch = W_CachePatchNum(viewborderlump[BRDR_L], PU_CACHE);
+		patch = W_CachePatchNum(viewborderlump[BRDR_L], PU_PATCH);
 		for (y = 0; y < baseviewheight && basewindowy + y < clearlines;
 			y += 8)
 		{
@@ -1022,7 +1022,7 @@ void HWR_DrawViewBorder(INT32 clearlines)
 	// right edge
 	if (clearlines > basewindowy)
 	{
-		patch = W_CachePatchNum(viewborderlump[BRDR_R], PU_CACHE);
+		patch = W_CachePatchNum(viewborderlump[BRDR_R], PU_PATCH);
 		for (y = 0; y < baseviewheight && basewindowy+y < clearlines;
 			y += 8)
 		{
@@ -1034,22 +1034,22 @@ void HWR_DrawViewBorder(INT32 clearlines)
 	// Draw beveled corners.
 	if (clearlines > basewindowy - 8)
 		HWR_DrawPatch(W_CachePatchNum(viewborderlump[BRDR_TL],
-				PU_CACHE),
+				PU_PATCH),
 			basewindowx - 8, basewindowy - 8, 0);
 
 	if (clearlines > basewindowy - 8)
 		HWR_DrawPatch(W_CachePatchNum(viewborderlump[BRDR_TR],
-				PU_CACHE),
+				PU_PATCH),
 			basewindowx + baseviewwidth, basewindowy - 8, 0);
 
 	if (clearlines > basewindowy+baseviewheight)
 		HWR_DrawPatch(W_CachePatchNum(viewborderlump[BRDR_BL],
-				PU_CACHE),
+				PU_PATCH),
 			basewindowx - 8, basewindowy + baseviewheight, 0);
 
 	if (clearlines > basewindowy + baseviewheight)
 		HWR_DrawPatch(W_CachePatchNum(viewborderlump[BRDR_BR],
-				PU_CACHE),
+				PU_PATCH),
 			basewindowx + baseviewwidth,
 			basewindowy + baseviewheight, 0);
 }
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 6ef48f222cede2019b1f7be09f261fb234e62f9a..e8d16c092b93abcc78accf9975eaada6a7f4688e 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -832,7 +832,7 @@ static void HWR_DrawSegsSplats(FSurfaceInfo * pSurf)
 		if (!M_PointInBox(segbbox,splat->v1.x,splat->v1.y) && !M_PointInBox(segbbox,splat->v2.x,splat->v2.y))
 			continue;
 
-		gpatch = W_CachePatchNum(splat->patch, PU_CACHE);
+		gpatch = W_CachePatchNum(splat->patch, PU_PATCH);
 		HWR_GetPatch(gpatch);
 
 		wallVerts[0].x = wallVerts[3].x = FIXED_TO_FLOAT(splat->v1.x);
@@ -6659,7 +6659,13 @@ void HWR_AddCommands(void)
 
 void HWR_AddSessionCommands(void)
 {
+	static boolean alreadycalled = false;
+	if (alreadycalled)
+		return;
+
 	CV_RegisterVar(&cv_granisotropicmode);
+
+	alreadycalled = true;
 }
 
 // --------------------------------------------------------------------------
@@ -6695,6 +6701,17 @@ void HWR_Startup(void)
 	startupdone = true;
 }
 
+// --------------------------------------------------------------------------
+// Called after switching to the hardware renderer
+// --------------------------------------------------------------------------
+void HWR_Switch(void)
+{
+	// Set special states from CVARs
+	HWD.pfnSetSpecialState(HWD_SET_MODEL_LIGHTING, cv_grmodellighting.value);
+	HWD.pfnSetSpecialState(HWD_SET_FOG_DENSITY, cv_grfogdensity.value);
+	HWD.pfnSetSpecialState(HWD_SET_TEXTUREFILTERMODE, cv_grfiltermode.value);
+	HWD.pfnSetSpecialState(HWD_SET_TEXTUREANISOTROPICMODE, cv_granisotropicmode.value);
+}
 
 // --------------------------------------------------------------------------
 // Free resources allocated by the hardware renderer
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index e46aa9801cdda903ad0ad830dad7fa5de7059c56..5733382263691a6f28d410d023c604b384a9d257 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -29,6 +29,7 @@
 
 // Startup & Shutdown the hardware mode renderer
 void HWR_Startup(void);
+void HWR_Switch(void);
 void HWR_Shutdown(void);
 
 void HWR_drawAMline(const fline_t *fl, INT32 color);
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 91a167a60517bfa6043a70328a291eec08a605d9..07eb5d24e95a2fe3f5d630b51510ac210adcf703 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -2117,6 +2117,9 @@ 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/i_video.h b/src/i_video.h
index d8f11fb13750d34e08acfd8295e94c860348c4ba..76b984d252d9463d43f3f66178636af1c3f885b3 100644
--- a/src/i_video.h
+++ b/src/i_video.h
@@ -44,6 +44,7 @@ extern boolean highcolor;
 /**	\brief setup video mode
 */
 void I_StartupGraphics(void);
+void I_StartupHardwareGraphics(void);
 
 /**	\brief restore old video mode
 */
@@ -84,6 +85,7 @@ INT32 VID_GetModeForSize(INT32 w, INT32 h);
 	\return	currect video mode
 */
 INT32 VID_SetMode(INT32 modenum);
+void VID_CheckRenderer(void);
 
 /**	\brief	The VID_GetModeName function
 
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index 58e720c85a35f6cf14dbb6547619f86a50267c75..837612e521f77854a72fb4cd3346fe6a1c2a1bd5 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -245,7 +245,7 @@ static int lib_comBufAddText(lua_State *L)
 		return LUA_ErrInvalid(L, "player_t");
 	if (plr != &players[consoleplayer])
 		return 0;
-	COM_BufAddText(va("%s\n", luaL_checkstring(L, 2)));
+	COM_BufAddTextEx(va("%s\n", luaL_checkstring(L, 2)), COM_SAFE);
 	return 0;
 }
 
@@ -262,7 +262,7 @@ static int lib_comBufInsertText(lua_State *L)
 		return LUA_ErrInvalid(L, "player_t");
 	if (plr != &players[consoleplayer])
 		return 0;
-	COM_BufInsertText(va("%s\n", luaL_checkstring(L, 2)));
+	COM_BufInsertTextEx(va("%s\n", luaL_checkstring(L, 2)), COM_SAFE);
 	return 0;
 }
 
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index a2f48c4ad5d2daced33e46e3f0f830e2a39b8f98..235010f2a2fe5b2316d0083a8516192602aa28bd 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -36,6 +36,11 @@ static UINT8 hud_enabled[(hud_MAX/8)+1];
 
 static UINT8 hudAvailable; // hud hooks field
 
+#ifdef LUA_PATCH_SAFETY
+static patchinfo_t *patchinfo, *patchinfohead;
+static int numluapatches;
+#endif
+
 // must match enum hud in lua_hud.h
 static const char *const hud_disable_options[] = {
 	"stagetitle",
@@ -250,12 +255,17 @@ static int colormap_get(lua_State *L)
 
 static int patch_get(lua_State *L)
 {
+#ifdef LUA_PATCH_SAFETY
 	patch_t *patch = *((patch_t **)luaL_checkudata(L, 1, META_PATCH));
+#else
+	patchinfo_t *patch = *((patchinfo_t **)luaL_checkudata(L, 1, META_PATCH));
+#endif
 	enum patch field = luaL_checkoption(L, 2, NULL, patch_opt);
 
 	// patches are CURRENTLY always valid, expected to be cached with PU_STATIC
 	// this may change in the future, so patch.valid still exists
-	I_Assert(patch != NULL);
+	if (!patch)
+		return LUA_ErrInvalid(L, "patch_t");
 
 	switch (field)
 	{
@@ -352,8 +362,59 @@ static int libd_patchExists(lua_State *L)
 
 static int libd_cachePatch(lua_State *L)
 {
+#ifdef LUA_PATCH_SAFETY
+	int i;
+	lumpnum_t lumpnum;
+	patchinfo_t *luapat;
+	patch_t *realpatch;
+
+	HUDONLY
+
+	luapat = patchinfohead;
+	lumpnum = W_CheckNumForName(luaL_checkstring(L, 1));
+	if (lumpnum == LUMPERROR)
+		lumpnum = W_GetNumForName("MISSING");
+
+	for (i = 0; i < numluapatches; i++)
+	{
+		// check if already cached
+		if (luapat->wadnum == WADFILENUM(lumpnum) && luapat->lumpnum == LUMPNUM(lumpnum))
+		{
+			LUA_PushUserdata(L, luapat, META_PATCH);
+			return 1;
+		}
+		luapat = luapat->next;
+		if (!luapat)
+			break;
+	}
+
+	if (numluapatches > 0)
+	{
+		patchinfo->next = Z_Malloc(sizeof(patchinfo_t), PU_STATIC, NULL);
+		patchinfo = patchinfo->next;
+	}
+	else
+	{
+		patchinfo = Z_Malloc(sizeof(patchinfo_t), PU_STATIC, NULL);
+		patchinfohead = patchinfo;
+	}
+
+	realpatch = W_CachePatchNum(lumpnum, PU_PATCH);
+
+	patchinfo->width = realpatch->width;
+	patchinfo->height = realpatch->height;
+	patchinfo->leftoffset = realpatch->leftoffset;
+	patchinfo->topoffset = realpatch->topoffset;
+
+	patchinfo->wadnum = WADFILENUM(lumpnum);
+	patchinfo->lumpnum = LUMPNUM(lumpnum);
+
+	LUA_PushUserdata(L, patchinfo, META_PATCH);
+	numluapatches++;
+#else
 	HUDONLY
-	LUA_PushUserdata(L, W_CachePatchName(luaL_checkstring(L, 1), PU_STATIC), META_PATCH);
+	LUA_PushUserdata(L, W_CachePatchName(luaL_checkstring(L, 1), PU_PATCH), META_PATCH);
+#endif
 	return 1;
 }
 
@@ -409,7 +470,7 @@ static int libd_getSpritePatch(lua_State *L)
 		return 0;
 
 	// push both the patch and it's "flip" value
-	LUA_PushUserdata(L, W_CachePatchNum(sprframe->lumppat[angle], PU_STATIC), META_PATCH);
+	LUA_PushUserdata(L, W_CachePatchNum(sprframe->lumppat[angle], PU_PATCH), META_PATCH);
 	lua_pushboolean(L, (sprframe->flip & (1<<angle)) != 0);
 	return 2;
 }
@@ -504,7 +565,7 @@ static int libd_getSprite2Patch(lua_State *L)
 		return 0;
 
 	// push both the patch and it's "flip" value
-	LUA_PushUserdata(L, W_CachePatchNum(sprframe->lumppat[angle], PU_STATIC), META_PATCH);
+	LUA_PushUserdata(L, W_CachePatchNum(sprframe->lumppat[angle], PU_PATCH), META_PATCH);
 	lua_pushboolean(L, (sprframe->flip & (1<<angle)) != 0);
 	return 2;
 }
@@ -513,12 +574,22 @@ static int libd_draw(lua_State *L)
 {
 	INT32 x, y, flags;
 	patch_t *patch;
+#ifdef LUA_PATCH_SAFETY
+	patchinfo_t *luapat;
+#endif
 	const UINT8 *colormap = NULL;
 
 	HUDONLY
 	x = luaL_checkinteger(L, 1);
 	y = luaL_checkinteger(L, 2);
+#ifdef LUA_PATCH_SAFETY
+	luapat = *((patchinfo_t **)luaL_checkudata(L, 3, META_PATCH));
+	patch = W_CachePatchNum((luapat->wadnum<<16)+luapat->lumpnum, PU_PATCH);
+#else
 	patch = *((patch_t **)luaL_checkudata(L, 3, META_PATCH));
+	if (!patch)
+		return LUA_ErrInvalid(L, "patch_t");
+#endif
 	flags = luaL_optinteger(L, 4, 0);
 	if (!lua_isnoneornil(L, 5))
 		colormap = *((UINT8 **)luaL_checkudata(L, 5, META_COLORMAP));
@@ -534,6 +605,9 @@ static int libd_drawScaled(lua_State *L)
 	fixed_t x, y, scale;
 	INT32 flags;
 	patch_t *patch;
+#ifdef LUA_PATCH_SAFETY
+	patchinfo_t *luapat;
+#endif
 	const UINT8 *colormap = NULL;
 
 	HUDONLY
@@ -542,7 +616,14 @@ static int libd_drawScaled(lua_State *L)
 	scale = luaL_checkinteger(L, 3);
 	if (scale < 0)
 		return luaL_error(L, "negative scale");
+#ifdef LUA_PATCH_SAFETY
+	luapat = *((patchinfo_t **)luaL_checkudata(L, 4, META_PATCH));
+	patch = W_CachePatchNum((luapat->wadnum<<16)+luapat->lumpnum, PU_PATCH);
+#else
 	patch = *((patch_t **)luaL_checkudata(L, 4, META_PATCH));
+	if (!patch)
+		return LUA_ErrInvalid(L, "patch_t");
+#endif
 	flags = luaL_optinteger(L, 5, 0);
 	if (!lua_isnoneornil(L, 6))
 		colormap = *((UINT8 **)luaL_checkudata(L, 6, META_COLORMAP));
@@ -1041,6 +1122,10 @@ int LUA_HudLib(lua_State *L)
 {
 	memset(hud_enabled, 0xff, (hud_MAX/8)+1);
 
+#ifdef LUA_PATCH_SAFETY
+	numluapatches = 0;
+#endif
+
 	lua_newtable(L); // HUD registry table
 		lua_newtable(L);
 		luaL_register(L, NULL, lib_draw);
diff --git a/src/m_menu.c b/src/m_menu.c
index 630f0c64327961b7f211bf0545f982fe18338a40..ae00c80628eaabffc2bc400efca1bad4329f15f4 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -309,6 +309,7 @@ static void M_ChangeControl(INT32 choice);
 // Video & Sound
 menu_t OP_VideoOptionsDef, OP_VideoModeDef, OP_ColorOptionsDef;
 #ifdef HWRENDER
+static void M_OpenGLOptionsMenu(void);
 menu_t OP_OpenGLOptionsDef, OP_OpenGLFogDef;
 #endif
 menu_t OP_SoundOptionsDef;
@@ -388,6 +389,7 @@ static void M_ResetCvars(void);
 
 // Consvar onchange functions
 static void Newgametype_OnChange(void);
+static void Newrenderer_OnChange(void);
 static void Dummymares_OnChange(void);
 
 // ==========================================================================
@@ -412,6 +414,9 @@ CV_PossibleValue_t gametype_cons_t[NUMGAMETYPES+1];
 
 consvar_t cv_newgametype = {"newgametype", "Co-op", CV_HIDEN|CV_CALL, gametype_cons_t, Newgametype_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
+consvar_t cv_newrenderer = {"newrenderer", "Software", CV_HIDEN|CV_CALL, cv_renderer_t, Newrenderer_OnChange, 0, NULL, NULL, 0, 0, NULL};
+static int newrenderer_set = 1;/* Software doesn't need confirmation! */
+
 static CV_PossibleValue_t serversort_cons_t[] = {
 	{0,"Ping"},
 	{1,"Modified State"},
@@ -1211,10 +1216,7 @@ static menuitem_t OP_VideoOptionsMenu[] =
 	{IT_STRING|IT_CVAR,      NULL, "Fullscreen",             &cv_fullscreen,         11},
 #endif
 	{IT_STRING | IT_CVAR, NULL, "Vertical Sync",                &cv_vidwait,         16},
-
-#ifdef HWRENDER
-	{IT_SUBMENU|IT_STRING, NULL, "OpenGL Options...", &OP_OpenGLOptionsDef,          21},
-#endif
+	{IT_STRING | IT_CVAR, NULL, "Renderer",                     &cv_newrenderer,        21},
 
 	{IT_HEADER, NULL, "Color Profile", NULL, 30},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness (F11)", &cv_globalgamma,36},
@@ -1254,6 +1256,11 @@ static menuitem_t OP_VideoOptionsMenu[] =
 	{IT_STRING | IT_CVAR, NULL, "Show FPS",                  &cv_ticrate,         190},
 	{IT_STRING | IT_CVAR, NULL, "Clear Before Redraw",       &cv_homremoval,      195},
 	{IT_STRING | IT_CVAR, NULL, "Show \"FOCUS LOST\"",       &cv_showfocuslost,   200},
+
+#ifdef HWRENDER
+	{IT_HEADER, NULL, "Renderer", NULL, 208},
+	{IT_CALL | IT_STRING, NULL, "OpenGL Options...",         M_OpenGLOptionsMenu, 214},
+#endif
 };
 
 static menuitem_t OP_VideoModeMenu[] =
@@ -2009,6 +2016,14 @@ menu_t OP_MonitorToggleDef =
 };
 
 #ifdef HWRENDER
+static void M_OpenGLOptionsMenu(void)
+{
+	if (rendermode == render_opengl)
+		M_SetupNextMenu(&OP_OpenGLOptionsDef);
+	else
+		M_StartMessage(M_GetText("You must be in OpenGL mode\nto access this menu.\n\n(Press a key)\n"), NULL, MM_NOTHING);
+}
+
 menu_t OP_OpenGLOptionsDef = DEFAULTMENUSTYLE(
 	MN_OP_MAIN + (MN_OP_VIDEO << 6) + (MN_OP_OPENGL << 12),
 	"M_VIDEO", OP_OpenGLOptionsMenu, &OP_VideoOptionsDef, 30, 30);
@@ -2214,6 +2229,44 @@ static void Newgametype_OnChange(void)
 	}
 }
 
+static void Newrenderer_AREYOUSURE(INT32 c)
+{
+	int n;
+	switch (c)
+	{
+		case 'y':
+		case KEY_ENTER:
+			n = cv_newrenderer.value;
+			newrenderer_set |= n;
+			CV_SetValue(&cv_renderer, n);
+			break;
+		default:
+			CV_StealthSetValue(&cv_newrenderer, cv_renderer.value);
+	}
+}
+
+static void Newrenderer_OnChange(void)
+{
+	/* Well this works for now because there's only two options. */
+	int n;
+	n = cv_newrenderer.value;
+	newrenderer_set |= cv_renderer.value;
+	if (( newrenderer_set & n ))
+		CV_SetValue(&cv_renderer, n);
+	else
+	{
+		M_StartMessage(
+				"The OpenGL renderer is incomplete.\n"
+				"Some visuals may fail to appear, or\n"
+				"appear incorrectly.\n"
+				"Do you still want to switch to it?\n"
+				"\n"
+				"(Press 'y' or 'n')",
+				Newrenderer_AREYOUSURE, MM_YESNO
+		);
+	}
+}
+
 void Screenshot_option_Onchange(void)
 {
 	OP_ScreenshotOptionsMenu[op_screenshot_folder].status =
@@ -3654,14 +3707,6 @@ void M_Init(void)
 	quitmsg[QUIT3MSG5] = M_GetText("You'll be back to play soon, though...\n......right?\n\n(Press 'Y' to quit)");
 	quitmsg[QUIT3MSG6] = M_GetText("Aww, is Egg Rock Zone too\ndifficult for you?\n\n(Press 'Y' to quit)");
 
-#ifdef HWRENDER
-	// Permanently hide some options based on render mode
-	if (rendermode == render_soft)
-		OP_VideoOptionsMenu[4].status = IT_DISABLED;
-	else if (rendermode == render_opengl)
-		OP_ScreenshotOptionsMenu[op_screenshot_colorprofile].status = IT_GRAYEDOUT;
-#endif
-
 	/*
 	Well the menu sucks for forcing us to have an item set
 	at all if every item just calls the same function, and
@@ -3738,19 +3783,19 @@ static void M_DrawThermo(INT32 x, INT32 y, consvar_t *cv)
 	centerlump[1] = W_GetNumForName("M_THERMM");
 	cursorlump = W_GetNumForName("M_THERMO");
 
-	V_DrawScaledPatch(xx, y, 0, p = W_CachePatchNum(leftlump,PU_CACHE));
+	V_DrawScaledPatch(xx, y, 0, p = W_CachePatchNum(leftlump,PU_PATCH));
 	xx += SHORT(p->width) - SHORT(p->leftoffset);
 	for (i = 0; i < 16; i++)
 	{
-		V_DrawScaledPatch(xx, y, V_WRAPX, W_CachePatchNum(centerlump[i & 1], PU_CACHE));
+		V_DrawScaledPatch(xx, y, V_WRAPX, W_CachePatchNum(centerlump[i & 1], PU_PATCH));
 		xx += 8;
 	}
-	V_DrawScaledPatch(xx, y, 0, W_CachePatchNum(rightlump, PU_CACHE));
+	V_DrawScaledPatch(xx, y, 0, W_CachePatchNum(rightlump, PU_PATCH));
 
 	xx = (cv->value - cv->PossibleValue[0].value) * (15*8) /
 		(cv->PossibleValue[1].value - cv->PossibleValue[0].value);
 
-	V_DrawScaledPatch((x + 8) + xx, y, 0, W_CachePatchNum(cursorlump, PU_CACHE));
+	V_DrawScaledPatch((x + 8) + xx, y, 0, W_CachePatchNum(cursorlump, PU_PATCH));
 }
 
 //  A smaller 'Thermo', with range given as percents (0-100)
@@ -3762,9 +3807,9 @@ static void M_DrawSlider(INT32 x, INT32 y, const consvar_t *cv, boolean ontop)
 
 	x = BASEVIDWIDTH - x - SLIDER_WIDTH;
 
-	V_DrawScaledPatch(x, y, 0, W_CachePatchName("M_SLIDEL", PU_CACHE));
+	V_DrawScaledPatch(x, y, 0, W_CachePatchName("M_SLIDEL", PU_PATCH));
 
-	p =  W_CachePatchName("M_SLIDEM", PU_CACHE);
+	p =  W_CachePatchName("M_SLIDEM", PU_PATCH);
 	for (i = 1; i < SLIDER_RANGE; i++)
 		V_DrawScaledPatch (x+i*8, y, 0,p);
 
@@ -3776,11 +3821,11 @@ static void M_DrawSlider(INT32 x, INT32 y, const consvar_t *cv, boolean ontop)
 			'\x1D' | V_YELLOWMAP, false);
 	}
 
-	p = W_CachePatchName("M_SLIDER", PU_CACHE);
+	p = W_CachePatchName("M_SLIDER", PU_PATCH);
 	V_DrawScaledPatch(x+i*8, y, 0, p);
 
 	// draw the slider cursor
-	p = W_CachePatchName("M_SLIDEC", PU_CACHE);
+	p = W_CachePatchName("M_SLIDEC", PU_PATCH);
 
 	for (i = 0; cv->PossibleValue[i+1].strvalue; i++);
 
@@ -3833,15 +3878,15 @@ void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines)
 	// draw left side
 	cx = x;
 	cy = y;
-	V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_TL], PU_CACHE));
+	V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_TL], PU_PATCH));
 	cy += boff;
-	p = W_CachePatchNum(viewborderlump[BRDR_L], PU_CACHE);
+	p = W_CachePatchNum(viewborderlump[BRDR_L], PU_PATCH);
 	for (n = 0; n < boxlines; n++)
 	{
 		V_DrawScaledPatch(cx, cy, V_WRAPY, p);
 		cy += step;
 	}
-	V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_BL], PU_CACHE));
+	V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_BL], PU_PATCH));
 
 	// draw middle
 	V_DrawFlatFill(x + boff, y + boff, width*step, boxlines*step, st_borderpatchnum);
@@ -3850,23 +3895,23 @@ void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines)
 	cy = y;
 	while (width > 0)
 	{
-		V_DrawScaledPatch(cx, cy, V_WRAPX, W_CachePatchNum(viewborderlump[BRDR_T], PU_CACHE));
-		V_DrawScaledPatch(cx, y + boff + boxlines*step, V_WRAPX, W_CachePatchNum(viewborderlump[BRDR_B], PU_CACHE));
+		V_DrawScaledPatch(cx, cy, V_WRAPX, W_CachePatchNum(viewborderlump[BRDR_T], PU_PATCH));
+		V_DrawScaledPatch(cx, y + boff + boxlines*step, V_WRAPX, W_CachePatchNum(viewborderlump[BRDR_B], PU_PATCH));
 		width--;
 		cx += step;
 	}
 
 	// draw right side
 	cy = y;
-	V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_TR], PU_CACHE));
+	V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_TR], PU_PATCH));
 	cy += boff;
-	p = W_CachePatchNum(viewborderlump[BRDR_R], PU_CACHE);
+	p = W_CachePatchNum(viewborderlump[BRDR_R], PU_PATCH);
 	for (n = 0; n < boxlines; n++)
 	{
 		V_DrawScaledPatch(cx, cy, V_WRAPY, p);
 		cy += step;
 	}
-	V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_BR], PU_CACHE));
+	V_DrawScaledPatch(cx, cy, 0, W_CachePatchNum(viewborderlump[BRDR_BR], PU_PATCH));
 */
 }
 
@@ -3877,7 +3922,7 @@ static void M_DrawStaticBox(fixed_t x, fixed_t y, INT32 flags, fixed_t w, fixed_
 	patch_t *patch;
 	fixed_t sw, pw;
 
-	patch = W_CachePatchName("LSSTATIC", PU_CACHE);
+	patch = W_CachePatchName("LSSTATIC", PU_PATCH);
 	pw = SHORT(patch->width) - (sw = w*2); //FixedDiv(w, scale); -- for scale FRACUNIT/2
 
 	/*if (pw > 0) -- model code for modders providing weird LSSTATIC
@@ -3906,15 +3951,15 @@ static void M_DrawSaveLoadBorder(INT32 x,INT32 y)
 {
 	INT32 i;
 
-	V_DrawScaledPatch (x-8,y+7,0,W_CachePatchName("M_LSLEFT",PU_CACHE));
+	V_DrawScaledPatch (x-8,y+7,0,W_CachePatchName("M_LSLEFT",PU_PATCH));
 
 	for (i = 0;i < 24;i++)
 	{
-		V_DrawScaledPatch (x,y+7,0,W_CachePatchName("M_LSCNTR",PU_CACHE));
+		V_DrawScaledPatch (x,y+7,0,W_CachePatchName("M_LSCNTR",PU_PATCH));
 		x += 8;
 	}
 
-	V_DrawScaledPatch (x,y+7,0,W_CachePatchName("M_LSRGHT",PU_CACHE));
+	V_DrawScaledPatch (x,y+7,0,W_CachePatchName("M_LSRGHT",PU_PATCH));
 }
 #endif
 
@@ -3957,10 +4002,10 @@ static void M_DrawMapEmblems(INT32 mapnum, INT32 x, INT32 y)
 		lasttype = curtype;
 
 		if (emblem->collected)
-			V_DrawSmallMappedPatch(x, y, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE),
+			V_DrawSmallMappedPatch(x, y, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH),
 			                       R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
 		else
-			V_DrawSmallScaledPatch(x, y, 0, W_CachePatchName("NEEDIT", PU_CACHE));
+			V_DrawSmallScaledPatch(x, y, 0, W_CachePatchName("NEEDIT", PU_PATCH));
 
 		emblem = M_GetLevelEmblems(-1);
 		x -= 12;
@@ -3971,7 +4016,7 @@ static void M_DrawMenuTitle(void)
 {
 	if (currentMenu->menutitlepic)
 	{
-		patch_t *p = W_CachePatchName(currentMenu->menutitlepic, PU_CACHE);
+		patch_t *p = W_CachePatchName(currentMenu->menutitlepic, PU_PATCH);
 
 		if (p->height > 24) // title is larger than normal
 		{
@@ -4023,13 +4068,13 @@ static void M_DrawGenericMenu(void)
 					if (currentMenu->menuitems[i].status & IT_CENTER)
 					{
 						patch_t *p;
-						p = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE);
+						p = W_CachePatchName(currentMenu->menuitems[i].patch, PU_PATCH);
 						V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, y, 0, p);
 					}
 					else
 					{
 						V_DrawScaledPatch(x, y, 0,
-							W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE));
+							W_CachePatchName(currentMenu->menuitems[i].patch, PU_PATCH));
 					}
 				}
 				/* FALLTHRU */
@@ -4098,7 +4143,7 @@ static void M_DrawGenericMenu(void)
 			case IT_GRAYPATCH:
 				if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0])
 					V_DrawMappedPatch(x, y, 0,
-						W_CachePatchName(currentMenu->menuitems[i].patch,PU_CACHE), graymap);
+						W_CachePatchName(currentMenu->menuitems[i].patch,PU_PATCH), graymap);
 				y += LINEHEIGHT;
 				break;
 			case IT_TRANSTEXT:
@@ -4132,12 +4177,12 @@ static void M_DrawGenericMenu(void)
 		|| ((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_NOTHING))
 	{
 		V_DrawScaledPatch(currentMenu->x + SKULLXOFF, cursory - 5, 0,
-			W_CachePatchName("M_CURSOR", PU_CACHE));
+			W_CachePatchName("M_CURSOR", PU_PATCH));
 	}
 	else
 	{
 		V_DrawScaledPatch(currentMenu->x - 24, cursory, 0,
-			W_CachePatchName("M_CURSOR", PU_CACHE));
+			W_CachePatchName("M_CURSOR", PU_PATCH));
 		V_DrawString(currentMenu->x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
 	}
 }
@@ -4275,7 +4320,7 @@ static void M_DrawGenericScrollMenu(void)
 
 	// DRAW THE SKULL CURSOR
 	V_DrawScaledPatch(currentMenu->x - 24, cursory, 0,
-		W_CachePatchName("M_CURSOR", PU_CACHE));
+		W_CachePatchName("M_CURSOR", PU_PATCH));
 }
 
 static void M_DrawPauseMenu(void)
@@ -4396,10 +4441,10 @@ static void M_DrawPauseMenu(void)
 				continue;
 
 			if (emblem->collected)
-				V_DrawSmallMappedPatch(40, 44 + (i*8), 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE),
+				V_DrawSmallMappedPatch(40, 44 + (i*8), 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH),
 				                       R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
 			else
-				V_DrawSmallScaledPatch(40, 44 + (i*8), 0, W_CachePatchName("NEEDIT", PU_CACHE));
+				V_DrawSmallScaledPatch(40, 44 + (i*8), 0, W_CachePatchName("NEEDIT", PU_PATCH));
 
 			switch (emblem->type)
 			{
@@ -4445,13 +4490,13 @@ static void M_DrawCenteredMenu(void)
 					if (currentMenu->menuitems[i].status & IT_CENTER)
 					{
 						patch_t *p;
-						p = W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE);
+						p = W_CachePatchName(currentMenu->menuitems[i].patch, PU_PATCH);
 						V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, y, 0, p);
 					}
 					else
 					{
 						V_DrawScaledPatch(x, y, 0,
-							W_CachePatchName(currentMenu->menuitems[i].patch, PU_CACHE));
+							W_CachePatchName(currentMenu->menuitems[i].patch, PU_PATCH));
 					}
 				}
 				/* FALLTHRU */
@@ -4526,7 +4571,7 @@ static void M_DrawCenteredMenu(void)
 			case IT_GRAYPATCH:
 				if (currentMenu->menuitems[i].patch && currentMenu->menuitems[i].patch[0])
 					V_DrawMappedPatch(x, y, 0,
-						W_CachePatchName(currentMenu->menuitems[i].patch,PU_CACHE), graymap);
+						W_CachePatchName(currentMenu->menuitems[i].patch,PU_PATCH), graymap);
 				y += LINEHEIGHT;
 				break;
 		}
@@ -4537,12 +4582,12 @@ static void M_DrawCenteredMenu(void)
 		|| ((currentMenu->menuitems[itemOn].status & IT_DISPLAY) == IT_NOTHING))
 	{
 		V_DrawScaledPatch(x + SKULLXOFF, cursory - 5, 0,
-			W_CachePatchName("M_CURSOR", PU_CACHE));
+			W_CachePatchName("M_CURSOR", PU_PATCH));
 	}
 	else
 	{
 		V_DrawScaledPatch(x - V_StringWidth(currentMenu->menuitems[itemOn].text, 0)/2 - 24, cursory, 0,
-			W_CachePatchName("M_CURSOR", PU_CACHE));
+			W_CachePatchName("M_CURSOR", PU_PATCH));
 		V_DrawCenteredString(x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
 	}
 }
@@ -4783,6 +4828,22 @@ static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
 	return rows;
 }
 
+//
+// M_CacheLevelPlatter
+//
+// Cache every patch used by the level platter.
+//
+static void M_CacheLevelPlatter(void)
+{
+	levselp[0][0] = W_CachePatchName("SLCT1LVL", PU_PATCH);
+	levselp[0][1] = W_CachePatchName("SLCT2LVL", PU_PATCH);
+	levselp[0][2] = W_CachePatchName("BLANKLVL", PU_PATCH);
+
+	levselp[1][0] = W_CachePatchName("SLCT1LVW", PU_PATCH);
+	levselp[1][1] = W_CachePatchName("SLCT2LVW", PU_PATCH);
+	levselp[1][2] = W_CachePatchName("BLANKLVW", PU_PATCH);
+}
+
 //
 // M_PrepareLevelPlatter
 //
@@ -4932,24 +4993,7 @@ static boolean M_PrepareLevelPlatter(INT32 gt, boolean nextmappick)
 	}
 #endif
 
-	if (levselp[0][0]) // never going to have some provided but not all, saves individually checking
-	{
-		W_UnlockCachedPatch(levselp[0][0]);
-		W_UnlockCachedPatch(levselp[0][1]);
-		W_UnlockCachedPatch(levselp[0][2]);
-
-		W_UnlockCachedPatch(levselp[1][0]);
-		W_UnlockCachedPatch(levselp[1][1]);
-		W_UnlockCachedPatch(levselp[1][2]);
-	}
-
-	levselp[0][0] = W_CachePatchName("SLCT1LVL", PU_STATIC);
-	levselp[0][1] = W_CachePatchName("SLCT2LVL", PU_STATIC);
-	levselp[0][2] = W_CachePatchName("BLANKLVL", PU_STATIC);
-
-	levselp[1][0] = W_CachePatchName("SLCT1LVW", PU_STATIC);
-	levselp[1][1] = W_CachePatchName("SLCT2LVW", PU_STATIC);
-	levselp[1][2] = W_CachePatchName("BLANKLVW", PU_STATIC);
+	M_CacheLevelPlatter();
 
 	return true;
 }
@@ -5170,6 +5214,9 @@ static void M_DrawLevelPlatterWideMap(UINT8 row, UINT8 col, INT32 x, INT32 y, bo
 	if (map <= 0)
 		return;
 
+	if (needpatchrecache)
+		M_CacheLevelPlatter();
+
 	//  A 564x100 image of the level as entry MAPxxW
 	if (!(levelselect.rows[row].mapavailable[col]))
 	{
@@ -5179,7 +5226,7 @@ static void M_DrawLevelPlatterWideMap(UINT8 row, UINT8 col, INT32 x, INT32 y, bo
 	else
 	{
 		if (W_CheckNumForName(va("%sW", G_BuildMapName(map))) != LUMPERROR)
-			patch = W_CachePatchName(va("%sW", G_BuildMapName(map)), PU_CACHE);
+			patch = W_CachePatchName(va("%sW", G_BuildMapName(map)), PU_PATCH);
 		else
 			patch = levselp[1][2]; // don't static to indicate that it's just a normal level
 
@@ -5201,6 +5248,9 @@ static void M_DrawLevelPlatterMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolea
 	if (map <= 0)
 		return;
 
+	if (needpatchrecache)
+		M_CacheLevelPlatter();
+
 	//  A 160x100 image of the level as entry MAPxxP
 	if (!(levelselect.rows[row].mapavailable[col]))
 	{
@@ -5210,7 +5260,7 @@ static void M_DrawLevelPlatterMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolea
 	else
 	{
 		if (W_CheckNumForName(va("%sP", G_BuildMapName(map))) != LUMPERROR)
-			patch = W_CachePatchName(va("%sP", G_BuildMapName(map)), PU_CACHE);
+			patch = W_CachePatchName(va("%sP", G_BuildMapName(map)), PU_PATCH);
 		else
 			patch = levselp[0][2]; // don't static to indicate that it's just a normal level
 
@@ -5270,8 +5320,8 @@ static void M_DrawLevelPlatterRow(UINT8 row, INT32 y)
 // new menus
 static void M_DrawRecordAttackForeground(void)
 {
-	patch_t *fg = W_CachePatchName("RECATKFG", PU_CACHE);
-	patch_t *clock = W_CachePatchName("RECCLOCK", PU_CACHE);
+	patch_t *fg = W_CachePatchName("RECATKFG", PU_PATCH);
+	patch_t *clock = W_CachePatchName("RECCLOCK", PU_PATCH);
 	angle_t fa;
 
 	INT32 i;
@@ -5309,7 +5359,7 @@ static void M_DrawNightsAttackMountains(void)
 {
 	static INT32 bgscrollx;
 	INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-	patch_t *background = W_CachePatchName(curbgname, PU_CACHE);
+	patch_t *background = W_CachePatchName(curbgname, PU_PATCH);
 	INT32 x = FixedInt(bgscrollx) % SHORT(background->width);
 	INT32 y = BASEVIDHEIGHT - SHORT(background->height)*2;
 
@@ -5332,16 +5382,16 @@ static void M_DrawNightsAttackBackground(void)
 	INT32 i;
 
 	// top
-	patch_t *backtopfg = W_CachePatchName("NTSATKT1", PU_CACHE);
-	patch_t *fronttopfg = W_CachePatchName("NTSATKT2", PU_CACHE);
+	patch_t *backtopfg = W_CachePatchName("NTSATKT1", PU_PATCH);
+	patch_t *fronttopfg = W_CachePatchName("NTSATKT2", PU_PATCH);
 	INT32 backtopwidth = SHORT(backtopfg->width);
 	//INT32 backtopheight = SHORT(backtopfg->height);
 	INT32 fronttopwidth = SHORT(fronttopfg->width);
 	//INT32 fronttopheight = SHORT(fronttopfg->height);
 
 	// bottom
-	patch_t *backbottomfg = W_CachePatchName("NTSATKB1", PU_CACHE);
-	patch_t *frontbottomfg = W_CachePatchName("NTSATKB2", PU_CACHE);
+	patch_t *backbottomfg = W_CachePatchName("NTSATKB1", PU_PATCH);
+	patch_t *frontbottomfg = W_CachePatchName("NTSATKB2", PU_PATCH);
 	INT32 backbottomwidth = SHORT(backbottomfg->width);
 	INT32 backbottomheight = SHORT(backbottomfg->height);
 	INT32 frontbottomwidth = SHORT(frontbottomfg->width);
@@ -5485,7 +5535,7 @@ static void M_DrawLevelPlatterMenu(void)
 
 #if 0
 	if (levelselect.rows[lsrow].maplist[lscol] > 0)
-		V_DrawScaledPatch(lsbasex + cursorx-17, lsbasey+50+lsoffs[0], 0, W_CachePatchName("M_CURSOR", PU_CACHE));
+		V_DrawScaledPatch(lsbasex + cursorx-17, lsbasey+50+lsoffs[0], 0, W_CachePatchName("M_CURSOR", PU_PATCH));
 #endif
 
 	// handle movement of cursor box
@@ -5755,7 +5805,7 @@ static void M_DrawImageDef(void)
 		V_DrawScaledPic(0,0,0,W_GetNumForName(currentMenu->menuitems[itemOn].text));
 	else
 	{
-		patch_t *patch = W_CachePatchName(currentMenu->menuitems[itemOn].text,PU_CACHE);
+		patch_t *patch = W_CachePatchName(currentMenu->menuitems[itemOn].text,PU_PATCH);
 		if (patch->width <= BASEVIDWIDTH)
 			V_DrawScaledPatch(0,0,0,patch);
 		else
@@ -5814,6 +5864,27 @@ static void M_AddonsOptions(INT32 choice)
 #define LOCATIONSTRING1 "Visit \x83SRB2.ORG/MODS\x80 to get & make add-ons!"
 //#define LOCATIONSTRING2 "Visit \x88SRB2.ORG/MODS\x80 to get & make add-ons!"
 
+static void M_LoadAddonsPatches(void)
+{
+	addonsp[EXT_FOLDER] = W_CachePatchName("M_FFLDR", PU_PATCH);
+	addonsp[EXT_UP] = W_CachePatchName("M_FBACK", PU_PATCH);
+	addonsp[EXT_NORESULTS] = W_CachePatchName("M_FNOPE", PU_PATCH);
+	addonsp[EXT_TXT] = W_CachePatchName("M_FTXT", PU_PATCH);
+	addonsp[EXT_CFG] = W_CachePatchName("M_FCFG", PU_PATCH);
+	addonsp[EXT_WAD] = W_CachePatchName("M_FWAD", PU_PATCH);
+#ifdef USE_KART
+	addonsp[EXT_KART] = W_CachePatchName("M_FKART", PU_PATCH);
+#endif
+	addonsp[EXT_PK3] = W_CachePatchName("M_FPK3", PU_PATCH);
+	addonsp[EXT_SOC] = W_CachePatchName("M_FSOC", PU_PATCH);
+	addonsp[EXT_LUA] = W_CachePatchName("M_FLUA", PU_PATCH);
+	addonsp[NUM_EXT] = W_CachePatchName("M_FUNKN", PU_PATCH);
+	addonsp[NUM_EXT+1] = W_CachePatchName("M_FSEL", PU_PATCH);
+	addonsp[NUM_EXT+2] = W_CachePatchName("M_FLOAD", PU_PATCH);
+	addonsp[NUM_EXT+3] = W_CachePatchName("M_FSRCH", PU_PATCH);
+	addonsp[NUM_EXT+4] = W_CachePatchName("M_FSAVE", PU_PATCH);
+}
+
 static void M_Addons(INT32 choice)
 {
 	const char *pathname = ".";
@@ -5857,30 +5928,7 @@ static void M_Addons(INT32 choice)
 	else
 		dir_on[menudepthleft] = 0;
 
-	if (addonsp[0]) // never going to have some provided but not all, saves individually checking
-	{
-		size_t i;
-		for (i = 0; i < NUM_EXT+5; i++)
-			W_UnlockCachedPatch(addonsp[i]);
-	}
-
-	addonsp[EXT_FOLDER] = W_CachePatchName("M_FFLDR", PU_STATIC);
-	addonsp[EXT_UP] = W_CachePatchName("M_FBACK", PU_STATIC);
-	addonsp[EXT_NORESULTS] = W_CachePatchName("M_FNOPE", PU_STATIC);
-	addonsp[EXT_TXT] = W_CachePatchName("M_FTXT", PU_STATIC);
-	addonsp[EXT_CFG] = W_CachePatchName("M_FCFG", PU_STATIC);
-	addonsp[EXT_WAD] = W_CachePatchName("M_FWAD", PU_STATIC);
-#ifdef USE_KART
-	addonsp[EXT_KART] = W_CachePatchName("M_FKART", PU_STATIC);
-#endif
-	addonsp[EXT_PK3] = W_CachePatchName("M_FPK3", PU_STATIC);
-	addonsp[EXT_SOC] = W_CachePatchName("M_FSOC", PU_STATIC);
-	addonsp[EXT_LUA] = W_CachePatchName("M_FLUA", PU_STATIC);
-	addonsp[NUM_EXT] = W_CachePatchName("M_FUNKN", PU_STATIC);
-	addonsp[NUM_EXT+1] = W_CachePatchName("M_FSEL", PU_STATIC);
-	addonsp[NUM_EXT+2] = W_CachePatchName("M_FLOAD", PU_STATIC);
-	addonsp[NUM_EXT+3] = W_CachePatchName("M_FSRCH", PU_STATIC);
-	addonsp[NUM_EXT+4] = W_CachePatchName("M_FSAVE", PU_STATIC);
+	M_LoadAddonsPatches();
 
 	MISC_AddonsDef.prevMenu = currentMenu;
 	M_SetupNextMenu(&MISC_AddonsDef);
@@ -6020,6 +6068,10 @@ static void M_DrawAddons(void)
 		return;
 	}
 
+	// Lactozilla: Load addons menu patches.
+	if (needpatchrecache)
+		M_LoadAddonsPatches();
+
 	if (Playing())
 		V_DrawCenteredString(BASEVIDWIDTH/2, 5, warningflags, "Adding files mid-game may cause problems.");
 	else
@@ -6955,13 +7007,13 @@ static void M_DrawEmblemHints(void)
 		if (emblem->collected)
 		{
 			collected = V_GREENMAP;
-			V_DrawMappedPatch(12, 12+(28*j), 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE),
+			V_DrawMappedPatch(12, 12+(28*j), 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH),
 				R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE));
 		}
 		else
 		{
 			collected = 0;
-			V_DrawScaledPatch(12, 12+(28*j), 0, W_CachePatchName("NEEDIT", PU_CACHE));
+			V_DrawScaledPatch(12, 12+(28*j), 0, W_CachePatchName("NEEDIT", PU_PATCH));
 		}
 
 		if (emblem->hint[0])
@@ -7015,40 +7067,42 @@ static tic_t st_time = 0;
 static patch_t* st_radio[9];
 static patch_t* st_launchpad[4];
 
-static void M_SoundTest(INT32 choice)
+static void M_CacheSoundTest(void)
 {
-	INT32 ul = skyRoomMenuTranslations[choice-1];
 	UINT8 i;
 	char buf[8];
 
-	soundtestpage = (UINT8)(unlockables[ul].variable);
-	if (!soundtestpage)
-		soundtestpage = 1;
-
-	if (!S_PrepareSoundTest())
-	{
-		M_StartMessage(M_GetText("No selectable tracks found.\n"),NULL,MM_NOTHING);
-		return;
-	}
-
 	STRBUFCPY(buf, "M_RADIOn");
 	for (i = 0; i < 9; i++)
 	{
-		if (st_radio[i])
-			W_UnlockCachedPatch(st_radio[i]);
 		buf[7] = (char)('0'+i);
-		st_radio[i] = W_CachePatchName(buf, PU_STATIC);
+		st_radio[i] = W_CachePatchName(buf, PU_PATCH);
 	}
 
 	STRBUFCPY(buf, "M_LPADn");
 	for (i = 0; i < 4; i++)
 	{
-		if (st_launchpad[i])
-			W_UnlockCachedPatch(st_launchpad[i]);
 		buf[6] = (char)('0'+i);
-		st_launchpad[i] = W_CachePatchName(buf, PU_STATIC);
+		st_launchpad[i] = W_CachePatchName(buf, PU_PATCH);
+	}
+}
+
+static void M_SoundTest(INT32 choice)
+{
+	INT32 ul = skyRoomMenuTranslations[choice-1];
+
+	soundtestpage = (UINT8)(unlockables[ul].variable);
+	if (!soundtestpage)
+		soundtestpage = 1;
+
+	if (!S_PrepareSoundTest())
+	{
+		M_StartMessage(M_GetText("No selectable tracks found.\n"),NULL,MM_NOTHING);
+		return;
 	}
 
+	M_CacheSoundTest();
+
 	curplaying = NULL;
 	st_time = 0;
 
@@ -7066,6 +7120,9 @@ static void M_DrawSoundTest(void)
 	fixed_t hscale = FRACUNIT/2, vscale = FRACUNIT/2, bounce = 0;
 	UINT8 frame[4] = {0, 0, -1, SKINCOLOR_RUBY};
 
+	if (needpatchrecache)
+		M_CacheSoundTest();
+
 	// let's handle the ticker first. ideally we'd tick this somewhere else, BUT...
 	if (curplaying)
 	{
@@ -7638,6 +7695,17 @@ static INT32 saveSlotSelected = 1;
 static INT32 loadgamescroll = 0;
 static UINT8 loadgameoffset = 0;
 
+static void M_CacheLoadGameData(void)
+{
+	savselp[0] = W_CachePatchName("SAVEBACK", PU_PATCH);
+	savselp[1] = W_CachePatchName("SAVENONE", PU_PATCH);
+	savselp[2] = W_CachePatchName("ULTIMATE", PU_PATCH);
+
+	savselp[3] = W_CachePatchName("GAMEDONE", PU_PATCH);
+	savselp[4] = W_CachePatchName("BLACXLVL", PU_PATCH);
+	savselp[5] = W_CachePatchName("BLANKLVL", PU_PATCH);
+}
+
 static void M_DrawLoadGameData(void)
 {
 	INT32 i, savetodraw, x, y, hsep = 90;
@@ -7646,6 +7714,9 @@ static void M_DrawLoadGameData(void)
 	if (vid.width != BASEVIDWIDTH*vid.dupx)
 		hsep = (hsep*vid.width)/(BASEVIDWIDTH*vid.dupx);
 
+	if (needpatchrecache)
+		M_CacheLoadGameData();
+
 	for (i = -2; i <= 2; i++)
 	{
 		savetodraw = (saveSlotSelected + i + numsaves)%numsaves;
@@ -7754,7 +7825,7 @@ static void M_DrawLoadGameData(void)
 				{
 					lumpnum_t lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName((savegameinfo[savetodraw].gamemap) & 8191)));
 					if (lumpnum != LUMPERROR)
-						patch = W_CachePatchNum(lumpnum, PU_CACHE);
+						patch = W_CachePatchNum(lumpnum, PU_PATCH);
 					else
 						patch = savselp[5];
 				}
@@ -7810,7 +7881,7 @@ static void M_DrawLoadGameData(void)
 					goto skipbot;
 				colormap = R_GetTranslationColormap(savegameinfo[savetodraw].botskin, charbotskin->prefcolor, 0);
 				sprframe = &sprdef->spriteframes[0];
-				patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+				patch = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
 
 				V_DrawFixedPatch(
 					tempx + (18<<FRACBITS),
@@ -7832,7 +7903,7 @@ skipbot:
 			if (!sprdef->numframes)
 				goto skipsign;
 			sprframe = &sprdef->spriteframes[0];
-			patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+			patch = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
 
 			V_DrawFixedPatch(
 				tempx,
@@ -7856,7 +7927,7 @@ skipsign:
 			if (!sprdef->numframes)
 				goto skiplife;
 			sprframe = &sprdef->spriteframes[0];
-			patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+			patch = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
 
 			V_DrawFixedPatch(
 				(tempx + 4)<<FRACBITS,
@@ -7867,7 +7938,7 @@ skiplife:
 			if (colormap)
 				Z_Free(colormap);
 
-			patch = W_CachePatchName("STLIVEX", PU_CACHE);
+			patch = W_CachePatchName("STLIVEX", PU_PATCH);
 
 			V_DrawScaledPatch(tempx + 9, y + 2, 0, patch);
 			tempx += 16;
@@ -7883,14 +7954,14 @@ skiplife:
 			// continues
 			if (savegameinfo[savetodraw].continues > 0)
 			{
-				V_DrawSmallScaledPatch(tempx, y, 0, W_CachePatchName("CONTSAVE", PU_CACHE));
+				V_DrawSmallScaledPatch(tempx, y, 0, W_CachePatchName("CONTSAVE", PU_PATCH));
 				V_DrawScaledPatch(tempx + 9, y + 2, 0, patch);
 				V_DrawString(tempx + 16, y, 0, va("%d", savegameinfo[savetodraw].continues));
 			}
 			else
 			{
-				V_DrawSmallScaledPatch(tempx, y, 0, W_CachePatchName("CONTNONE", PU_CACHE));
-				V_DrawScaledPatch(tempx + 9, y + 2, 0, W_CachePatchName("STNONEX", PU_CACHE));
+				V_DrawSmallScaledPatch(tempx, y, 0, W_CachePatchName("CONTNONE", PU_PATCH));
+				V_DrawScaledPatch(tempx + 9, y + 2, 0, W_CachePatchName("STNONEX", PU_PATCH));
 				V_DrawString(tempx + 16, y, V_GRAYMAP, "0");
 			}
 		}
@@ -8122,24 +8193,7 @@ static void M_ReadSaveStrings(void)
 		M_ReadSavegameInfo(i);
 	}
 
-	if (savselp[0]) // never going to have some provided but not all, saves individually checking
-	{
-		W_UnlockCachedPatch(savselp[0]);
-		W_UnlockCachedPatch(savselp[1]);
-		W_UnlockCachedPatch(savselp[2]);
-
-		W_UnlockCachedPatch(savselp[3]);
-		W_UnlockCachedPatch(savselp[4]);
-		W_UnlockCachedPatch(savselp[5]);
-	}
-
-	savselp[0] = W_CachePatchName("SAVEBACK", PU_STATIC);
-	savselp[1] = W_CachePatchName("SAVENONE", PU_STATIC);
-	savselp[2] = W_CachePatchName("ULTIMATE", PU_STATIC);
-
-	savselp[3] = W_CachePatchName("GAMEDONE", PU_STATIC);
-	savselp[4] = W_CachePatchName("BLACXLVL", PU_STATIC);
-	savselp[5] = W_CachePatchName("BLANKLVL", PU_STATIC);
+	M_CacheLoadGameData();
 }
 
 //
@@ -8319,6 +8373,43 @@ 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]))
+	{
+		if (skins[skinnum].sprites[SPR2_XTRA].numframes > XTRA_CHARSEL)
+		{
+			spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA];
+			spriteframe_t *sprframe = &sprdef->spriteframes[XTRA_CHARSEL];
+			description[i].charpic = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
+		}
+		else
+			description[i].charpic = W_CachePatchName("MISSING", PU_PATCH);
+	}
+	else
+		description[i].charpic = W_CachePatchName(description[i].picname, PU_PATCH);
+
+	if (description[i].nametag[0])
+		description[i].namepic = W_CachePatchName(description[i].nametag, PU_PATCH);
+}
+
+static void M_CacheCharacterSelect(void)
+{
+	INT32 i, skinnum;
+
+	for (i = 0; i < 32; i++)
+	{
+		if (!description[i].used)
+			continue;
+
+		// Already set in M_SetupChoosePlayer
+		skinnum = description[i].skinnum[0];
+		if ((skinnum != -1) && (R_SkinUsable(-1, skinnum)))
+			M_CacheCharacterSelectEntry(i, skinnum);
+	}
+}
+
 static void M_SetupChoosePlayer(INT32 choice)
 {
 	INT32 skinnum;
@@ -8366,27 +8457,7 @@ static void M_SetupChoosePlayer(INT32 choice)
 					if (i == char_on)
 						allowed = true;
 
-					if (!(description[i].picname[0]))
-					{
-						if (skins[skinnum].sprites[SPR2_XTRA].numframes > XTRA_CHARSEL)
-						{
-							spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA];
-							spriteframe_t *sprframe = &sprdef->spriteframes[XTRA_CHARSEL];
-							description[i].charpic = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
-						}
-						else
-							description[i].charpic = W_CachePatchName("MISSING", PU_CACHE);
-					}
-					else
-						description[i].charpic = W_CachePatchName(description[i].picname, PU_CACHE);
-
-					if (description[i].nametag[0])
-					{
-						const char *nametag = description[i].nametag;
-						description[i].namepic = NULL;
-						if (W_LumpExists(nametag))
-							description[i].namepic = W_CachePatchName(nametag, PU_CACHE);
-					}
+					M_CacheCharacterSelectEntry(i, skinnum);
 				}
 				// else -- Technically, character select icons without corresponding skins get bundled away behind this too. Sucks to be them.
 			}
@@ -8525,8 +8596,8 @@ static void M_DrawSetupChoosePlayerMenu(void)
 	UINT8 *colormap = NULL;
 	INT32 prev = -1, next = -1;
 
-	patch_t *charbg = W_CachePatchName("CHARBG", PU_CACHE);
-	patch_t *charfg = W_CachePatchName("CHARFG", PU_CACHE);
+	patch_t *charbg = W_CachePatchName("CHARBG", PU_PATCH);
+	patch_t *charfg = W_CachePatchName("CHARFG", PU_PATCH);
 	INT16 bgheight = SHORT(charbg->height);
 	INT16 fgheight = SHORT(charfg->height);
 	INT16 bgwidth = SHORT(charbg->width);
@@ -8534,6 +8605,10 @@ 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();
+
 	if (abs(char_scroll) > FRACUNIT)
 		char_scroll -= (char_scroll>>2);
 	else // close enough.
@@ -8712,7 +8787,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
 	// Alternative menu header
 #ifdef CHOOSEPLAYER_DRAWHEADER
 	{
-		patch_t *header = W_CachePatchName("M_PICKP", PU_CACHE);
+		patch_t *header = W_CachePatchName("M_PICKP", PU_PATCH);
 		INT32 xtitle = 146;
 		INT32 ytitle = (35 - SHORT(header->height))/2;
 		V_DrawFixedPatch(xtitle<<FRACBITS, ytitle<<FRACBITS, FRACUNIT/2, 0, header, NULL);
@@ -8882,10 +8957,10 @@ static void M_DrawStatsMaps(int location)
 			exemblem = &extraemblems[i];
 
 			if (exemblem->collected)
-				V_DrawSmallMappedPatch(292, y, 0, W_CachePatchName(M_GetExtraEmblemPatch(exemblem, false), PU_CACHE),
+				V_DrawSmallMappedPatch(292, y, 0, W_CachePatchName(M_GetExtraEmblemPatch(exemblem, false), PU_PATCH),
 				                       R_GetTranslationColormap(TC_DEFAULT, M_GetExtraEmblemColor(exemblem), GTC_CACHE));
 			else
-				V_DrawSmallScaledPatch(292, y, 0, W_CachePatchName("NEEDIT", PU_CACHE));
+				V_DrawSmallScaledPatch(292, y, 0, W_CachePatchName("NEEDIT", PU_PATCH));
 
 			V_DrawString(20, y, V_YELLOWMAP|V_ALLOWLOWERCASE, va("%s", exemblem->description));
 		}
@@ -8961,7 +9036,7 @@ static void M_DrawLevelStats(void)
 		V_DrawString(20, 56, V_GREENMAP, "(complete)");
 
 	V_DrawString(36, 64, 0, va("x %d/%d", M_CountEmblems(), numemblems+numextraemblems));
-	V_DrawSmallScaledPatch(20, 64, 0, W_CachePatchName("EMBLICON", PU_STATIC));
+	V_DrawSmallScaledPatch(20, 64, 0, W_CachePatchName("EMBLICON", PU_PATCH));
 
 	sprintf(beststr, "%u", bestscore);
 	V_DrawString(BASEVIDWIDTH/2, 48, V_YELLOWMAP, "SCORE:");
@@ -9093,7 +9168,7 @@ void M_DrawTimeAttackMenu(void)
 	}
 
 	// DRAW THE SKULL CURSOR
-	V_DrawScaledPatch(currentMenu->x - 24, cursory, 0, W_CachePatchName("M_CURSOR", PU_CACHE));
+	V_DrawScaledPatch(currentMenu->x - 24, cursory, 0, W_CachePatchName("M_CURSOR", PU_PATCH));
 	V_DrawString(currentMenu->x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
 
 	// Character face!
@@ -9102,10 +9177,10 @@ void M_DrawTimeAttackMenu(void)
 		{
 			spritedef_t *sprdef = &skins[cv_chooseskin.value-1].sprites[SPR2_XTRA];
 			spriteframe_t *sprframe = &sprdef->spriteframes[XTRA_CHARSEL];
-			PictureOfUrFace = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+			PictureOfUrFace = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
 		}
 		else
-			PictureOfUrFace = W_CachePatchName("MISSING", PU_CACHE);
+			PictureOfUrFace = W_CachePatchName("MISSING", PU_PATCH);
 
 		if (PictureOfUrFace->width >= 256)
 			V_DrawTinyScaledPatch(224, 120, 0, PictureOfUrFace);
@@ -9133,9 +9208,9 @@ void M_DrawTimeAttackMenu(void)
 		lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
 
 		if (lumpnum != LUMPERROR)
-			PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_CACHE);
+			PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_PATCH);
 		else
-			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
+			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_PATCH);
 
 		y = 32+lsheadingheight;
 		V_DrawSmallScaledPatch(216, y, 0, PictureOfLevel);
@@ -9180,7 +9255,7 @@ void M_DrawTimeAttackMenu(void)
 					goto skipThisOne;
 			}
 
-			empatch = W_CachePatchName(M_GetEmblemPatch(em, true), PU_CACHE);
+			empatch = W_CachePatchName(M_GetEmblemPatch(em, true), PU_PATCH);
 
 			empatx = SHORT(empatch->leftoffset)/2;
 			empaty = SHORT(empatch->topoffset)/2;
@@ -9189,7 +9264,7 @@ void M_DrawTimeAttackMenu(void)
 				V_DrawSmallMappedPatch(104+76+empatx, yHeight+lsheadingheight/2+empaty, 0, empatch,
 				                       R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
 			else
-				V_DrawSmallScaledPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName("NEEDITL", PU_CACHE));
+				V_DrawSmallScaledPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName("NEEDITL", PU_PATCH));
 
 			skipThisOne:
 			em = M_GetLevelEmblems(-1);
@@ -9369,7 +9444,7 @@ void M_DrawNightsAttackMenu(void)
 	}
 
 	// DRAW THE SKULL CURSOR
-	V_DrawScaledPatch(currentMenu->x - 24, cursory, 0, W_CachePatchName("M_CURSOR", PU_CACHE));
+	V_DrawScaledPatch(currentMenu->x - 24, cursory, 0, W_CachePatchName("M_CURSOR", PU_PATCH));
 	V_DrawString(currentMenu->x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
 
 	// Level record list
@@ -9393,9 +9468,9 @@ void M_DrawNightsAttackMenu(void)
 		lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
 
 		if (lumpnum != LUMPERROR)
-			PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_CACHE);
+			PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_PATCH);
 		else
-			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
+			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_PATCH);
 
 		y = 32+lsheadingheight;
 		V_DrawSmallScaledPatch(208, y, 0, PictureOfLevel);
@@ -9467,10 +9542,10 @@ void M_DrawNightsAttackMenu(void)
 				}
 
 				if (em->collected)
-					V_DrawSmallMappedPatch(xpos, yHeight+lsheadingheight/2, 0, W_CachePatchName(M_GetEmblemPatch(em, false), PU_CACHE),
+					V_DrawSmallMappedPatch(xpos, yHeight+lsheadingheight/2, 0, W_CachePatchName(M_GetEmblemPatch(em, false), PU_PATCH),
 																 R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
 				else
-					V_DrawSmallScaledPatch(xpos, yHeight+lsheadingheight/2, 0, W_CachePatchName("NEEDIT", PU_CACHE));
+					V_DrawSmallScaledPatch(xpos, yHeight+lsheadingheight/2, 0, W_CachePatchName("NEEDIT", PU_PATCH));
 
 				skipThisOne:
 				em = M_GetLevelEmblems(-1);
@@ -9506,8 +9581,8 @@ static void M_NightsAttack(INT32 choice)
 	// This is really just to make sure Sonic is the played character, just in case
 	M_PatchSkinNameTable();
 
-	ntssupersonic[0] = W_CachePatchName("NTSSONC1", PU_CACHE);
-	ntssupersonic[1] = W_CachePatchName("NTSSONC2", PU_CACHE);
+	ntssupersonic[0] = W_CachePatchName("NTSSONC1", PU_PATCH);
+	ntssupersonic[1] = W_CachePatchName("NTSSONC2", PU_PATCH);
 
 	G_SetGamestate(GS_TIMEATTACK); // do this before M_SetupNextMenu so that menu meta state knows that we're switching
 	titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please
@@ -10229,9 +10304,9 @@ static void M_DrawServerMenu(void)
 		lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
 
 		if (lumpnum != LUMPERROR)
-			PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_CACHE);
+			PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_PATCH);
 		else
-			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
+			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_PATCH);
 
 		V_DrawSmallScaledPatch(319 - (currentMenu->x + (SHORT(PictureOfLevel->width)/2)), currentMenu->y + imgheight, 0, PictureOfLevel);
 	}
@@ -10548,7 +10623,7 @@ static void M_DrawSetupMultiPlayerMenu(void)
 		multi_frame = 0;
 
 	sprframe = &sprdef->spriteframes[multi_frame];
-	patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+	patch = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
 	if (sprframe->flip & 1) // Only for first sprite
 		flags |= V_FLIP; // This sprite is left/right flipped!
 
@@ -10569,7 +10644,7 @@ faildraw:
 		return; // Can't render!
 
 	sprframe = &sprdef->spriteframes[0];
-	patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+	patch = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
 	if (sprframe->flip & 1) // Only for first sprite
 		flags |= V_FLIP; // This sprite is left/right flipped!
 
@@ -10640,7 +10715,7 @@ colordraw:
 		cursory = y;
 
 	V_DrawScaledPatch(x - 17, cursory, 0,
-		W_CachePatchName("M_CURSOR", PU_CACHE));
+		W_CachePatchName("M_CURSOR", PU_PATCH));
 }
 
 // Handle 1P/2P MP Setup
@@ -10966,7 +11041,7 @@ static void M_DrawJoystick(void)
 		if (i == itemOn)
 		{
 			V_DrawScaledPatch(currentMenu->x - 24, OP_JoystickSetDef.y+LINEHEIGHT*i-4, 0,
-				W_CachePatchName("M_CURSOR", PU_CACHE));
+				W_CachePatchName("M_CURSOR", PU_PATCH));
 		}
 	}
 }
@@ -11284,7 +11359,7 @@ static void M_DrawControl(void)
 	}
 
 	V_DrawScaledPatch(currentMenu->x - 20, cursory, 0,
-		W_CachePatchName("M_CURSOR", PU_CACHE));
+		W_CachePatchName("M_CURSOR", PU_PATCH));
 }
 
 #undef controlbuffer
@@ -11576,7 +11651,7 @@ static void M_DrawVideoMode(void)
 	j = OP_VideoModeDef.y + 14 + ((vidm_selected % vidm_column_size)*8);
 
 	V_DrawScaledPatch(i - 8, j, 0,
-		W_CachePatchName("M_CURSOR", PU_CACHE));
+		W_CachePatchName("M_CURSOR", PU_PATCH));
 }
 
 // Just M_DrawGenericScrollMenu but showing a backing behind the headers.
@@ -11704,7 +11779,7 @@ static void M_DrawColorMenu(void)
 
 	// DRAW THE SKULL CURSOR
 	V_DrawScaledPatch(currentMenu->x - 24, cursory, 0,
-		W_CachePatchName("M_CURSOR", PU_CACHE));
+		W_CachePatchName("M_CURSOR", PU_PATCH));
 }
 
 // special menuitem key handler for video mode list
@@ -11782,17 +11857,7 @@ static void M_HandleVideoMode(INT32 ch)
 
 static void M_DrawScreenshotMenu(void)
 {
-
 	M_DrawGenericScrollMenu();
-#ifdef HWRENDER
-	if ((rendermode == render_opengl) && (itemOn < 7)) // where it starts to go offscreen; change this number if you change the layout of the screenshot menu
-	{
-		INT32 y = currentMenu->y+currentMenu->menuitems[op_screenshot_colorprofile].alphaKey*2;
-		if (itemOn == 6)
-			y -= 10;
-		V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, y, V_REDMAP, "ON");
-	}
-#endif
 }
 
 // ===============
@@ -11877,7 +11942,7 @@ void M_QuitResponse(INT32 ch)
 		ptime = I_GetTime() + NEWTICRATE*2; // Shortened the quit time, used to be 2 seconds Tails 03-26-2001
 		while (ptime > I_GetTime())
 		{
-			V_DrawScaledPatch(0, 0, 0, W_CachePatchName("GAMEQUIT", PU_CACHE)); // Demo 3 Quit Screen Tails 06-16-2001
+			V_DrawScaledPatch(0, 0, 0, W_CachePatchName("GAMEQUIT", PU_PATCH)); // Demo 3 Quit Screen Tails 06-16-2001
 			I_FinishUpdate(); // Update the screen with the image Tails 06-19-2001
 			I_Sleep();
 		}
diff --git a/src/p_setup.c b/src/p_setup.c
index bf3493d8c2ab5c99ccf79cbceeb8393b71b29a73..42a6438a0f46996927ce5ec723bf51a06b6ff072 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -3019,23 +3019,14 @@ boolean P_SetupLevel(boolean skipprecip)
 		P_SpawnPrecipitation();
 
 #ifdef HWRENDER // not win32 only 19990829 by Kin
+	// Lactozilla: Free extrasubsectors regardless of renderer.
+	// Maybe we're not in OpenGL anymore.
+	if (extrasubsectors)
+		free(extrasubsectors);
+	extrasubsectors = NULL;
+	// stuff like HWR_CreatePlanePolygons is called there
 	if (rendermode == render_opengl)
-	{
-		// Lactozilla (December 8, 2019)
-		// Level setup used to free EVERY mipmap from memory.
-		// Even mipmaps that aren't related to level textures.
-		// Presumably, the hardware render code used to store textures as level data.
-		// Meaning, they had memory allocated and marked with the PU_LEVEL tag.
-		// Level textures are only reloaded after R_LoadTextures, which is
-		// when the texture list is loaded.
-#ifdef ALAM_LIGHTING
-		// BP: reset light between levels (we draw preview frame lights on current frame)
-		HWR_ResetLights();
-#endif
-		// Correct missing sidedefs & deep water trick
-		HWR_CorrectSWTricks();
-		HWR_CreatePlanePolygons((INT32)numnodes - 1);
-	}
+		HWR_SetupLevel();
 #endif
 
 	// oh god I hope this helps
@@ -3266,6 +3257,26 @@ boolean P_SetupLevel(boolean skipprecip)
 	return true;
 }
 
+#ifdef HWRENDER
+void HWR_SetupLevel(void)
+{
+	// Lactozilla (December 8, 2019)
+	// Level setup used to free EVERY mipmap from memory.
+	// Even mipmaps that aren't related to level textures.
+	// Presumably, the hardware render code used to store textures as level data.
+	// Meaning, they had memory allocated and marked with the PU_LEVEL tag.
+	// Level textures are only reloaded after R_LoadTextures, which is
+	// when the texture list is loaded.
+#ifdef ALAM_LIGHTING
+	// BP: reset light between levels (we draw preview frame lights on current frame)
+	HWR_ResetLights();
+#endif
+	// Correct missing sidedefs & deep water trick
+	HWR_CorrectSWTricks();
+	HWR_CreatePlanePolygons((INT32)numnodes - 1);
+}
+#endif
+
 //
 // P_RunSOC
 //
diff --git a/src/p_setup.h b/src/p_setup.h
index c95dd0f630cc516aec9509e73a921609a025a05f..ff28e36e4fac84b5edadd6542c888b0bf22688ba 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -98,6 +98,9 @@ void P_ScanThings(INT16 mapnum, INT16 wadnum, INT16 lumpnum);
 #endif
 void P_LoadThingsOnly(void);
 boolean P_SetupLevel(boolean skipprecip);
+#ifdef HWRENDER
+void HWR_SetupLevel(void);
+#endif
 boolean P_AddWadFile(const char *wadfilename);
 boolean P_RunSOC(const char *socfilename);
 void P_LoadSoundsRange(UINT16 wadnum, UINT16 first, UINT16 num);
diff --git a/src/r_data.c b/src/r_data.c
index cecd4840ccb4badf6d42434a6800ec3c7259d2bb..bfd83a62d1169e02d89084b38f60bd86a975585b 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -2095,7 +2095,6 @@ lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap)
 	/////////////////////
 	// This code creates the colormap array used by software renderer
 	/////////////////////
-	if (rendermode == render_soft)
 	{
 		double r, g, b, cbrightness;
 		int p;
@@ -2717,7 +2716,7 @@ void R_PrecacheLevel(void)
 				lump = sf->lumppat[k];
 				if (devparm)
 					spritememory += W_LumpLength(lump);
-				W_CachePatchNum(lump, PU_CACHE);
+				W_CachePatchNum(lump, PU_PATCH);
 			}
 		}
 	}
diff --git a/src/r_defs.h b/src/r_defs.h
index 3cc780545446509c55d0eaffcabaf5e77521b7bf..964956503b9e1503128b2c4ba549af5226de0e7c 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -740,9 +740,7 @@ typedef struct
 {
 	patch_t *patch[8][ROTANGLES];
 	boolean cached[8];
-#ifdef HWRENDER
 	aatree_t *hardware_patch[8];
-#endif
 } rotsprite_t;
 #endif
 
@@ -756,6 +754,24 @@ typedef enum
 	SRF_NONE        = 0xff // Initial value
 } spriterotateflags_t;     // SRF's up!
 
+// Same as a patch_t, except just the header
+// and the wadnum/lumpnum combination that points
+// to wherever the patch is in memory.
+struct patchinfo_s
+{
+	INT16 width;          // bounding box size
+	INT16 height;
+	INT16 leftoffset;     // pixels to the left of origin
+	INT16 topoffset;      // pixels below the origin
+
+	UINT16 wadnum;        // the software patch lump num for when the patch
+	UINT16 lumpnum;       // was flushed, and we need to re-create it
+
+	// next patchinfo_t in memory
+	struct patchinfo_s *next;
+};
+typedef struct patchinfo_s patchinfo_t;
+
 //
 // Sprites are patches with a special naming convention so they can be
 //  recognized by R_InitSprites.
diff --git a/src/r_main.c b/src/r_main.c
index 115db8cb5d0daf09df5eb8a7ef8675644a471693..a5f4bc1184e45a00d4c5c6c407ce3a38241117b3 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -19,6 +19,7 @@
 #include "r_local.h"
 #include "r_splats.h" // faB(21jan): testing
 #include "r_sky.h"
+#include "hu_stuff.h"
 #include "st_stuff.h"
 #include "p_local.h"
 #include "keys.h"
@@ -28,6 +29,7 @@
 #include "d_main.h"
 #include "v_video.h"
 #include "p_spec.h" // skyboxmo
+#include "p_setup.h"
 #include "z_zone.h"
 #include "m_random.h" // quake camera shake
 #include "r_portal.h"
@@ -1154,6 +1156,26 @@ void R_RenderPlayerView(player_t *player)
 	free(masks);
 }
 
+// Lactozilla: Renderer switching
+#ifdef HWRENDER
+void R_InitHardwareMode(void)
+{
+	HWR_AddSessionCommands();
+	HWR_Switch();
+	HWR_LoadTextures(numtextures);
+	if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction))
+		HWR_SetupLevel();
+}
+#endif
+
+void R_ReloadHUDGraphics(void)
+{
+	CONS_Debug(DBG_RENDER, "R_ReloadHUDGraphics()...\n");
+	ST_LoadGraphics();
+	HU_LoadGraphics();
+	ST_ReloadSkinFaceGraphics();
+}
+
 // =========================================================================
 //                    ENGINE COMMANDS & VARS
 // =========================================================================
diff --git a/src/r_main.h b/src/r_main.h
index 58c23cd8147792e4efdf890da95a27901f7dd759..143cc1de0225e978cc2d549f64878a08603f58b5 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -89,6 +89,10 @@ extern consvar_t cv_tailspickup;
 
 // Called by startup code.
 void R_Init(void);
+#ifdef HWRENDER
+void R_InitHardwareMode(void);
+#endif
+void R_ReloadHUDGraphics(void);
 
 // just sets setsizeneeded true
 extern boolean setsizeneeded;
diff --git a/src/r_patch.c b/src/r_patch.c
index d4bfa9cbda29f84a07b234d91d13324a14979875..18dfe523a9c4f80d1b39ed65414c9101e63dd14a 100644
--- a/src/r_patch.c
+++ b/src/r_patch.c
@@ -1374,4 +1374,19 @@ void R_FreeSkinRotSprite(size_t skinnum)
 		skinsprites++;
 	}
 }
+
+//
+// R_FreeAllRotSprite
+//
+// Free ALL sprite rotation data from memory.
+//
+void R_FreeAllRotSprite(void)
+{
+	INT32 i;
+	size_t s;
+	for (s = 0; s < numsprites; s++)
+		R_FreeSingleRotSprite(&sprites[s]);
+	for (i = 0; i < numskins; ++i)
+		R_FreeSkinRotSprite(i);
+}
 #endif
diff --git a/src/r_patch.h b/src/r_patch.h
index 8a8ab5602bbebaa507a84cb129b82d5a35bac53d..53d306b8870e42adfed06e2004d24d87af9f4e8f 100644
--- a/src/r_patch.h
+++ b/src/r_patch.h
@@ -69,6 +69,7 @@ void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum);
 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);
+void R_FreeAllRotSprite(void);
 extern fixed_t cosang2rad[ROTANGLES];
 extern fixed_t sinang2rad[ROTANGLES];
 #endif
diff --git a/src/r_segs.c b/src/r_segs.c
index 29120ebb89b4b7bac4ca3bdb7de6d16852dafe0a..2c8aadec88b7286fd5c5e08d1571a5a30a90108f 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -165,7 +165,7 @@ static void R_DrawWallSplats(void)
 		mfloorclip = floorclip;
 		mceilingclip = ceilingclip;
 
-		patch = W_CachePatchNum(splat->patch, PU_CACHE);
+		patch = W_CachePatchNum(splat->patch, PU_PATCH);
 
 		dc_texturemid = splat->top + (SHORT(patch->height)<<(FRACBITS-1)) - viewz;
 		if (splat->yoffset)
diff --git a/src/r_splats.c b/src/r_splats.c
index 414e93eb6f6bac225435926e7d497e939b21bd5b..f690dfa9417a4dff9a45d5605c719d6a9860208c 100644
--- a/src/r_splats.c
+++ b/src/r_splats.c
@@ -147,7 +147,7 @@ void R_AddWallSplat(line_t *wallline, INT16 sectorside, const char *patchname, f
 	splat->flags = flags;
 
 	// bad.. but will be needed for drawing anyway..
-	patch = W_CachePatchNum(splat->patch, PU_CACHE);
+	patch = W_CachePatchNum(splat->patch, PU_PATCH);
 
 	// offset needed by draw code for texture mapping
 	linelength = P_SegLength((seg_t *)wallline);
diff --git a/src/r_things.c b/src/r_things.c
index aa2a73515adfd2d75d450e18b6d9d10c9b050f34..bbe62d88ae6639e886f8c5f7ae5bd01485de724a 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -125,10 +125,7 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
 		sprtemp[frame].rotsprite.cached[r] = false;
 		for (ang = 0; ang < ROTANGLES; ang++)
 			sprtemp[frame].rotsprite.patch[r][ang] = NULL;
-#ifdef HWRENDER
-		if (rendermode == render_opengl)
-			sprtemp[frame].rotsprite.hardware_patch[r] = M_AATreeAlloc(AATREE_ZUSER);
-#endif // HWRENDER
+		sprtemp[frame].rotsprite.hardware_patch[r] = M_AATreeAlloc(AATREE_ZUSER);
 	}
 #endif
 
diff --git a/src/screen.c b/src/screen.c
index 507067705e473223272be23d22f1c28c80564fe3..f9d81f8af3fd71a0e655a76fdb0ed25607093870 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -53,6 +53,7 @@ void (*spanfuncs_npo2[SPANDRAWFUNC_MAX])(void);
 // ------------------
 viddef_t vid;
 INT32 setmodeneeded; //video mode change needed if > 0 (the mode number to set + 1)
+UINT8 setrenderneeded = 0;
 
 static CV_PossibleValue_t scr_depth_cons_t[] = {{8, "8 bits"}, {16, "16 bits"}, {24, "24 bits"}, {32, "32 bits"}, {0, NULL}};
 
@@ -62,7 +63,11 @@ consvar_t cv_scr_height = {"scr_height", "800", CV_SAVE, CV_Unsigned, NULL, 0, N
 consvar_t cv_scr_depth = {"scr_depth", "16 bits", CV_SAVE, scr_depth_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_renderview = {"renderview", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-static void SCR_ChangeFullscreen (void);
+static void SCR_ActuallyChangeRenderer(void);
+CV_PossibleValue_t cv_renderer_t[] = {{1, "Software"}, {2, "OpenGL"}, {0, NULL}};
+consvar_t cv_renderer = {"renderer", "Software", CV_SAVE|CV_NOLUA|CV_CALL, cv_renderer_t, SCR_ChangeRenderer, 0, NULL, NULL, 0, 0, NULL};
+
+static void SCR_ChangeFullscreen(void);
 
 consvar_t cv_fullscreen = {"fullscreen", "Yes", CV_SAVE|CV_CALL, CV_YesNo, SCR_ChangeFullscreen, 0, NULL, NULL, 0, 0, NULL};
 
@@ -87,19 +92,8 @@ boolean R_3DNow = false;
 boolean R_MMXExt = false;
 boolean R_SSE2 = false;
 
-
-void SCR_SetMode(void)
+void SCR_SetDrawFuncs(void)
 {
-	if (dedicated)
-		return;
-
-	if (!setmodeneeded || WipeInAction)
-		return; // should never happen and don't change it during a wipe, BAD!
-
-	VID_SetMode(--setmodeneeded);
-
-	V_SetPalette(0);
-
 	//
 	//  setup the right draw routines for either 8bpp or 16bpp
 	//
@@ -193,9 +187,37 @@ void SCR_SetMode(void)
 	if (SCR_IsAspectCorrect(vid.width, vid.height))
 		CONS_Alert(CONS_WARNING, M_GetText("Resolution is not aspect-correct!\nUse a multiple of %dx%d\n"), BASEVIDWIDTH, BASEVIDHEIGHT);
 */
+}
+
+void SCR_SetMode(void)
+{
+	if (dedicated)
+		return;
+
+	if (!(setmodeneeded || setrenderneeded) || WipeInAction)
+		return; // should never happen and don't change it during a wipe, BAD!
+
+	// Lactozilla: Renderer switching
+	if (setrenderneeded)
+	{
+		Z_PreparePatchFlush();
+		needpatchflush = true;
+		needpatchrecache = true;
+		VID_CheckRenderer();
+		if (!setmodeneeded)
+			VID_SetMode(vid.modenum);
+	}
+
+	if (setmodeneeded)
+		VID_SetMode(--setmodeneeded);
+
+	V_SetPalette(0);
+
+	SCR_SetDrawFuncs();
 
 	// set the apprpriate drawer for the sky (tall or INT16)
 	setmodeneeded = 0;
+	setrenderneeded = 0;
 }
 
 // do some initial settings for the game loading screen
@@ -383,6 +405,8 @@ void SCR_CheckDefaultMode(void)
 		// see note above
 		setmodeneeded = VID_GetModeForSize(cv_scr_width.value, cv_scr_height.value) + 1;
 	}
+
+	SCR_ActuallyChangeRenderer();
 }
 
 // sets the modenum as the new default video mode to be saved in the config file
@@ -412,6 +436,50 @@ void SCR_ChangeFullscreen(void)
 #endif
 }
 
+static int target_renderer = 0;
+
+void SCR_ActuallyChangeRenderer(void)
+{
+	setrenderneeded = target_renderer;
+	// setting the same renderer twice WILL crash your game, so let's not, please
+	if (rendermode == setrenderneeded)
+		setrenderneeded = 0;
+}
+
+// Lactozilla: Renderer switching
+void SCR_ChangeRenderer(void)
+{
+	setrenderneeded = 0;
+
+	if (con_startup)
+	{
+		target_renderer = cv_renderer.value;
+		if (M_CheckParm("-opengl"))
+			target_renderer = rendermode = render_opengl;
+		else if (M_CheckParm("-software"))
+			target_renderer = rendermode = render_soft;
+		// set cv_renderer back
+		SCR_ChangeRendererCVars(rendermode);
+		return;
+	}
+
+	if (cv_renderer.value == 1)
+		target_renderer = render_soft;
+	else if (cv_renderer.value == 2)
+		target_renderer = render_opengl;
+	SCR_ActuallyChangeRenderer();
+}
+
+void SCR_ChangeRendererCVars(INT32 mode)
+{
+	// set cv_renderer back
+	if (mode == render_soft)
+		CV_StealthSetValue(&cv_renderer, 1);
+	else if (mode == render_opengl)
+		CV_StealthSetValue(&cv_renderer, 2);
+	CV_StealthSetValue(&cv_newrenderer, cv_renderer.value);
+}
+
 boolean SCR_IsAspectCorrect(INT32 width, INT32 height)
 {
 	return
diff --git a/src/screen.h b/src/screen.h
index 470acb4b9699bac380afcc0770fa2fa99e358832..d47cdff9aa3ccf45555c79314e8430faeef63339 100644
--- a/src/screen.h
+++ b/src/screen.h
@@ -170,18 +170,27 @@ extern boolean R_SSE2;
 // ----------------
 // screen variables
 // ----------------
+
 extern viddef_t vid;
 extern INT32 setmodeneeded; // mode number to set if needed, or 0
 
+void SCR_ChangeRenderer(void);
+void SCR_ChangeRendererCVars(INT32 mode);
+extern UINT8 setrenderneeded;
+
 extern INT32 scr_bpp;
 extern UINT8 *scr_borderpatch; // patch used to fill the view borders
 
-extern consvar_t cv_scr_width, cv_scr_height, cv_scr_depth, cv_renderview, cv_fullscreen;
+extern CV_PossibleValue_t cv_renderer_t[];
+
+extern consvar_t cv_scr_width, cv_scr_height, cv_scr_depth, cv_renderview, cv_renderer, cv_fullscreen;
+extern consvar_t cv_newrenderer;
 // wait for page flipping to end or not
 extern consvar_t cv_vidwait;
 
 // Change video mode, only at the start of a refresh.
 void SCR_SetMode(void);
+void SCR_SetDrawFuncs(void);
 // Recalc screen size dependent stuff
 void SCR_Recalc(void);
 // Check parms once at startup
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index e054381eec6183cba9b46d4948dc4f0723a0b088..8065162929001c4e92de7ad75bac7dc5278a51ab 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -71,6 +71,7 @@
 #include "../i_video.h"
 #include "../console.h"
 #include "../command.h"
+#include "../r_main.h"
 #include "sdlmain.h"
 #ifdef HWRENDER
 #include "../hardware/hw_main.h"
@@ -1208,7 +1209,6 @@ void I_FinishUpdate(void)
 		SDL_RenderCopy(renderer, texture, NULL, NULL);
 		SDL_RenderPresent(renderer);
 	}
-
 #ifdef HWRENDER
 	else if (rendermode == render_opengl)
 	{
@@ -1408,37 +1408,53 @@ void VID_PrepareModeList(void)
 #endif
 }
 
-INT32 VID_SetMode(INT32 modeNum)
+static SDL_bool Impl_CreateContext(void)
 {
-	SDLdoUngrabMouse();
-
-	vid.recalc = 1;
-	vid.bpp = 1;
-
-	if (modeNum >= 0 && modeNum < MAXWINMODES)
+	// Renderer-specific stuff
+#ifdef HWRENDER
+	if (rendermode == render_opengl)
 	{
-		vid.width = windowedModes[modeNum][0];
-		vid.height = windowedModes[modeNum][1];
-		vid.modenum = modeNum;
+		if (!sdlglcontext)
+			sdlglcontext = SDL_GL_CreateContext(window);
+		if (sdlglcontext == NULL)
+		{
+			SDL_DestroyWindow(window);
+			I_Error("Failed to create a GL context: %s\n", SDL_GetError());
+		}
+		SDL_GL_MakeCurrent(window, sdlglcontext);
 	}
 	else
+#endif
+	if (rendermode == render_soft)
 	{
-		// just set the desktop resolution as a fallback
-		SDL_DisplayMode mode;
-		SDL_GetWindowDisplayMode(window, &mode);
-		if (mode.w >= 2048)
-		{
-			vid.width = 1920;
-			vid.height = 1200;
-		}
-		else
+		int flags = 0; // Use this to set SDL_RENDERER_* flags now
+		if (usesdl2soft)
+			flags |= SDL_RENDERER_SOFTWARE;
+		else if (cv_vidwait.value)
+			flags |= SDL_RENDERER_PRESENTVSYNC;
+
+		if (!renderer)
+			renderer = SDL_CreateRenderer(window, -1, flags);
+		if (renderer == NULL)
 		{
-			vid.width = mode.w;
-			vid.height = mode.h;
+			CONS_Printf(M_GetText("Couldn't create rendering context: %s\n"), SDL_GetError());
+			return SDL_FALSE;
 		}
-		vid.modenum = -1;
+		SDL_RenderSetLogicalSize(renderer, BASEVIDWIDTH, BASEVIDHEIGHT);
+	}
+	return SDL_TRUE;
+}
+
+void VID_CheckRenderer(void)
+{
+	if (dedicated)
+		return;
+
+	if (setrenderneeded)
+	{
+		rendermode = setrenderneeded;
+		Impl_CreateContext();
 	}
-	//Impl_SetWindowName("SRB2 "VERSIONSTRING);
 
 	SDLSetMode(vid.width, vid.height, USE_FULLSCREEN);
 	Impl_VideoSetupBuffer();
@@ -1450,8 +1466,39 @@ INT32 VID_SetMode(INT32 modeNum)
 			SDL_FreeSurface(bufSurface);
 			bufSurface = NULL;
 		}
+#ifdef HWRENDER
+		HWR_FreeTextureCache();
+#endif
+		SCR_SetDrawFuncs();
+	}
+#ifdef HWRENDER
+	else if (rendermode == render_opengl)
+	{
+		I_StartupHardwareGraphics();
+		R_InitHardwareMode();
+		HWR_Switch();
 	}
+#endif
+}
+
+INT32 VID_SetMode(INT32 modeNum)
+{
+	SDLdoUngrabMouse();
+
+	vid.recalc = 1;
+	vid.bpp = 1;
 
+	if (modeNum < 0)
+		modeNum = 0;
+	if (modeNum >= MAXWINMODES)
+		modeNum = MAXWINMODES-1;
+
+	vid.width = windowedModes[modeNum][0];
+	vid.height = windowedModes[modeNum][1];
+	vid.modenum = modeNum;
+
+	//Impl_SetWindowName("SRB2 "VERSIONSTRING);
+	VID_CheckRenderer();
 	return SDL_TRUE;
 }
 
@@ -1472,8 +1519,7 @@ static SDL_bool Impl_CreateWindow(SDL_bool fullscreen)
 		flags |= SDL_WINDOW_BORDERLESS;
 
 #ifdef HWRENDER
-	if (rendermode == render_opengl)
-		flags |= SDL_WINDOW_OPENGL;
+	flags |= SDL_WINDOW_OPENGL;
 #endif
 
 	// Create a window
@@ -1486,38 +1532,7 @@ static SDL_bool Impl_CreateWindow(SDL_bool fullscreen)
 		return SDL_FALSE;
 	}
 
-	// Renderer-specific stuff
-#ifdef HWRENDER
-	if (rendermode == render_opengl)
-	{
-		sdlglcontext = SDL_GL_CreateContext(window);
-		if (sdlglcontext == NULL)
-		{
-			SDL_DestroyWindow(window);
-			I_Error("Failed to create a GL context: %s\n", SDL_GetError());
-		}
-		SDL_GL_MakeCurrent(window, sdlglcontext);
-	}
-	else
-#endif
-	if (rendermode == render_soft)
-	{
-		flags = 0; // Use this to set SDL_RENDERER_* flags now
-		if (usesdl2soft)
-			flags |= SDL_RENDERER_SOFTWARE;
-		else if (cv_vidwait.value)
-			flags |= SDL_RENDERER_PRESENTVSYNC;
-
-		renderer = SDL_CreateRenderer(window, -1, flags);
-		if (renderer == NULL)
-		{
-			CONS_Printf(M_GetText("Couldn't create rendering context: %s\n"), SDL_GetError());
-			return SDL_FALSE;
-		}
-		SDL_RenderSetLogicalSize(renderer, BASEVIDWIDTH, BASEVIDHEIGHT);
-	}
-
-	return SDL_TRUE;
+	return Impl_CreateContext();
 }
 
 /*
@@ -1572,17 +1587,14 @@ static void Impl_VideoSetupSDLBuffer(void)
 static void Impl_VideoSetupBuffer(void)
 {
 	// Set up game's software render buffer
-	//if (rendermode == render_soft)
+	vid.rowbytes = vid.width * vid.bpp;
+	vid.direct = NULL;
+	if (vid.buffer)
+		free(vid.buffer);
+	vid.buffer = calloc(vid.rowbytes*vid.height, NUMSCREENS);
+	if (!vid.buffer)
 	{
-		vid.rowbytes = vid.width * vid.bpp;
-		vid.direct = NULL;
-		if (vid.buffer)
-			free(vid.buffer);
-		vid.buffer = calloc(vid.rowbytes*vid.height, NUMSCREENS);
-		if (!vid.buffer)
-		{
-			I_Error("%s", M_GetText("Not enough memory for video buffer\n"));
-		}
+		I_Error("%s", M_GetText("Not enough memory for video buffer\n"));
 	}
 }
 
@@ -1626,10 +1638,13 @@ void I_StartupGraphics(void)
 		))
 			framebuffer = SDL_TRUE;
 	}
-	if (M_CheckParm("-software"))
-	{
+
+#ifdef HWRENDER
+	if (M_CheckParm("-opengl"))
+		rendermode = render_opengl;
+	else if (M_CheckParm("-software"))
+#endif
 		rendermode = render_soft;
-	}
 
 	usesdl2soft = M_CheckParm("-softblit");
 	borderlesswindow = M_CheckParm("-borderless");
@@ -1637,44 +1652,7 @@ void I_StartupGraphics(void)
 	//SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY>>1,SDL_DEFAULT_REPEAT_INTERVAL<<2);
 	VID_Command_ModeList_f();
 #ifdef HWRENDER
-	if (M_CheckParm("-opengl") || rendermode == render_opengl)
-	{
-		rendermode = render_opengl;
-		HWD.pfnInit             = hwSym("Init",NULL);
-		HWD.pfnFinishUpdate     = NULL;
-		HWD.pfnDraw2DLine       = hwSym("Draw2DLine",NULL);
-		HWD.pfnDrawPolygon      = hwSym("DrawPolygon",NULL);
-		HWD.pfnRenderSkyDome    = hwSym("RenderSkyDome",NULL);
-		HWD.pfnSetBlend         = hwSym("SetBlend",NULL);
-		HWD.pfnClearBuffer      = hwSym("ClearBuffer",NULL);
-		HWD.pfnSetTexture       = hwSym("SetTexture",NULL);
-		HWD.pfnReadRect         = hwSym("ReadRect",NULL);
-		HWD.pfnGClipRect        = hwSym("GClipRect",NULL);
-		HWD.pfnClearMipMapCache = hwSym("ClearMipMapCache",NULL);
-		HWD.pfnSetSpecialState  = hwSym("SetSpecialState",NULL);
-		HWD.pfnSetPalette       = hwSym("SetPalette",NULL);
-		HWD.pfnGetTextureUsed   = hwSym("GetTextureUsed",NULL);
-		HWD.pfnDrawModel        = hwSym("DrawModel",NULL);
-		HWD.pfnCreateModelVBOs  = hwSym("CreateModelVBOs",NULL);
-		HWD.pfnSetTransform     = hwSym("SetTransform",NULL);
-		HWD.pfnGetRenderVersion = hwSym("GetRenderVersion",NULL);
-		HWD.pfnPostImgRedraw    = hwSym("PostImgRedraw",NULL);
-		HWD.pfnFlushScreenTextures=hwSym("FlushScreenTextures",NULL);
-		HWD.pfnStartScreenWipe  = hwSym("StartScreenWipe",NULL);
-		HWD.pfnEndScreenWipe    = hwSym("EndScreenWipe",NULL);
-		HWD.pfnDoScreenWipe     = hwSym("DoScreenWipe",NULL);
-		HWD.pfnDrawIntermissionBG=hwSym("DrawIntermissionBG",NULL);
-		HWD.pfnMakeScreenTexture= hwSym("MakeScreenTexture",NULL);
-		HWD.pfnMakeScreenFinalTexture=hwSym("MakeScreenFinalTexture",NULL);
-		HWD.pfnDrawScreenFinalTexture=hwSym("DrawScreenFinalTexture",NULL);
-		// check gl renderer lib
-		if (HWD.pfnGetRenderVersion() != VERSION)
-			I_Error("%s", M_GetText("The version of the renderer doesn't match the version of the executable\nBe sure you have installed SRB2 properly.\n"));
-		if (!HWD.pfnInit(I_Error)) // let load the OpenGL library
-		{
-			rendermode = render_soft;
-		}
-	}
+	I_StartupHardwareGraphics();
 #endif
 
 	// Fury: we do window initialization after GL setup to allow
@@ -1734,6 +1712,50 @@ void I_StartupGraphics(void)
 	graphics_started = true;
 }
 
+void I_StartupHardwareGraphics(void)
+{
+#ifdef HWRENDER
+	static boolean glstartup = false;
+	if (!glstartup)
+	{
+		HWD.pfnInit             = hwSym("Init",NULL);
+		HWD.pfnFinishUpdate     = NULL;
+		HWD.pfnDraw2DLine       = hwSym("Draw2DLine",NULL);
+		HWD.pfnDrawPolygon      = hwSym("DrawPolygon",NULL);
+		HWD.pfnRenderSkyDome    = hwSym("RenderSkyDome",NULL);
+		HWD.pfnSetBlend         = hwSym("SetBlend",NULL);
+		HWD.pfnClearBuffer      = hwSym("ClearBuffer",NULL);
+		HWD.pfnSetTexture       = hwSym("SetTexture",NULL);
+		HWD.pfnReadRect         = hwSym("ReadRect",NULL);
+		HWD.pfnGClipRect        = hwSym("GClipRect",NULL);
+		HWD.pfnClearMipMapCache = hwSym("ClearMipMapCache",NULL);
+		HWD.pfnSetSpecialState  = hwSym("SetSpecialState",NULL);
+		HWD.pfnSetPalette       = hwSym("SetPalette",NULL);
+		HWD.pfnGetTextureUsed   = hwSym("GetTextureUsed",NULL);
+		HWD.pfnDrawModel        = hwSym("DrawModel",NULL);
+		HWD.pfnCreateModelVBOs  = hwSym("CreateModelVBOs",NULL);
+		HWD.pfnSetTransform     = hwSym("SetTransform",NULL);
+		HWD.pfnGetRenderVersion = hwSym("GetRenderVersion",NULL);
+		HWD.pfnPostImgRedraw    = hwSym("PostImgRedraw",NULL);
+		HWD.pfnFlushScreenTextures=hwSym("FlushScreenTextures",NULL);
+		HWD.pfnStartScreenWipe  = hwSym("StartScreenWipe",NULL);
+		HWD.pfnEndScreenWipe    = hwSym("EndScreenWipe",NULL);
+		HWD.pfnDoScreenWipe     = hwSym("DoScreenWipe",NULL);
+		HWD.pfnDrawIntermissionBG=hwSym("DrawIntermissionBG",NULL);
+		HWD.pfnMakeScreenTexture= hwSym("MakeScreenTexture",NULL);
+		HWD.pfnMakeScreenFinalTexture=hwSym("MakeScreenFinalTexture",NULL);
+		HWD.pfnDrawScreenFinalTexture=hwSym("DrawScreenFinalTexture",NULL);
+		// check gl renderer lib
+		if (HWD.pfnGetRenderVersion() != VERSION)
+			I_Error("%s", M_GetText("The version of the renderer doesn't match the version of the executable\nBe sure you have installed SRB2 properly.\n"));
+		if (!HWD.pfnInit(I_Error)) // let load the OpenGL library
+			rendermode = render_soft;
+		else
+			glstartup = true;
+	}
+#endif
+}
+
 void I_ShutdownGraphics(void)
 {
 	const rendermode_t oldrendermode = rendermode;
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 1b8107edb89ff57751eadb7f96c869f01efb3f47..7fa612d12c944670289198f53e0a19ce5b1571bd 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -2709,6 +2709,9 @@ static void ST_overlayDrawer(void)
 
 void ST_Drawer(void)
 {
+	if (needpatchrecache)
+		R_ReloadHUDGraphics();
+
 #ifdef SEENAMES
 	if (cv_seenames.value && cv_allowseenames.value && displayplayer == consoleplayer && seenplayer && seenplayer->mo)
 	{
diff --git a/src/v_video.c b/src/v_video.c
index c91e5f213e8fb7ec0eca1ddc9d29176dbc2d8440..4785a154186c91a8421ce89027a2b9362b0c38d2 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -1045,13 +1045,13 @@ void V_DrawContinueIcon(INT32 x, INT32 y, INT32 flags, INT32 skinnum, UINT8 skin
 	{
 		spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA];
 		spriteframe_t *sprframe = &sprdef->spriteframes[XTRA_CONTINUE];
-		patch_t *patch = W_CachePatchNum(sprframe->lumppat[0], PU_LEVEL);
+		patch_t *patch = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
 		const UINT8 *colormap = R_GetTranslationColormap(skinnum, skincolor, GTC_CACHE);
 
 		V_DrawMappedPatch(x, y, flags, patch, colormap);
 	}
 	else
-		V_DrawScaledPatch(x - 10, y - 14, flags, W_CachePatchName("CONTINS", PU_CACHE));
+		V_DrawScaledPatch(x - 10, y - 14, flags, W_CachePatchName("CONTINS", PU_PATCH));
 }
 
 //
diff --git a/src/w_wad.c b/src/w_wad.c
index 86972118a8c8d55a44e7d3e23bcda86d4d2cfd4d..d8b362d4974c35ce86984e2636992c363377ce96 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -55,6 +55,7 @@
 #include "dehacked.h"
 #include "d_clisrv.h"
 #include "r_defs.h"
+#include "r_data.h"
 #include "i_system.h"
 #include "md5.h"
 #include "lua_script.h"
@@ -1375,7 +1376,6 @@ void *W_CacheLumpNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 
 void *W_CacheLumpNum(lumpnum_t lumpnum, INT32 tag)
 {
-
 	return W_CacheLumpNumPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum),tag);
 }
 
@@ -1488,12 +1488,31 @@ void *W_CacheLumpName(const char *name, INT32 tag)
 // Cache a patch into heap memory, convert the patch format as necessary
 //
 
+void W_FlushCachedPatches(void)
+{
+	if (needpatchflush)
+	{
+		Z_FreeTag(PU_CACHE);
+		Z_FreeTag(PU_PATCH);
+		Z_FreeTag(PU_HUDGFX);
+		Z_FreeTag(PU_HWRPATCHINFO);
+		Z_FreeTag(PU_HWRMODELTEXTURE);
+		Z_FreeTag(PU_HWRCACHE);
+		Z_FreeTags(PU_HWRCACHE_UNLOCKED, PU_HWRPATCHINFO_UNLOCKED);
+	}
+	needpatchflush = false;
+}
+
+// Software-only compile cache the data without conversion
 void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 {
 #ifdef HWRENDER
 	GLPatch_t *grPatch;
 #endif
 
+	if (needpatchflush)
+		W_FlushCachedPatches();
+
 	if (!TestValidLump(wad, lump))
 		return NULL;
 
diff --git a/src/w_wad.h b/src/w_wad.h
index a8be13ece29c17511a7dc5579faccdfa0789d9ad..0a0da7681c45e23084d2289aef1545ea8f5dab8b 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -177,6 +177,7 @@ void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag); // return a patch
 void *W_CachePatchNum(lumpnum_t lumpnum, INT32 tag); // return a patch_t
 
 void W_UnlockCachedPatch(void *patch);
+void W_FlushCachedPatches(void);
 
 void W_VerifyFileMD5(UINT16 wadfilenum, const char *matchmd5);
 
diff --git a/src/win32/win_vid.c b/src/win32/win_vid.c
index 39a60cd93b936ba3e524d18c4614a8834128a8a4..d0aab92b3e16a4d13fb4cb57039869a732bfaef1 100644
--- a/src/win32/win_vid.c
+++ b/src/win32/win_vid.c
@@ -239,6 +239,11 @@ void I_StartupGraphics(void)
 	if (!dedicated) graphics_started = true;
 }
 
+void I_StartupHardwareGraphics(void)
+{
+	// oh yeah woo yeah oh yeah woo yeah oh yeah woo yeah oh yeah woo yeah oh yeah woo yeah oh yeah woo yeah oh yeah woo y
+}
+
 // ------------------
 // I_ShutdownGraphics
 // Close the screen, restore previous video mode.
@@ -946,6 +951,11 @@ INT32 VID_SetMode(INT32 modenum)
 	return 1;
 }
 
+void VID_CheckRenderer(void)
+{
+	// ..............
+}
+
 // ========================================================================
 // Free the video buffer of the last video mode,
 // allocate a new buffer for the video mode to set.
diff --git a/src/y_inter.c b/src/y_inter.c
index 842bd1b287e22d7696185cdec69af5dce953aa29..5123f2f976bfd1b861911836b63edb2be73bbc4e 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -157,6 +157,7 @@ typedef struct
 
 boolean usebuffer = false;
 static boolean useinterpic;
+static boolean safetorender = true;
 static y_buffer_t *y_buffer;
 
 static INT32 intertic;
@@ -172,6 +173,7 @@ static void Y_CalculateCompetitionWinners(void);
 static void Y_CalculateTimeRaceWinners(void);
 static void Y_CalculateMatchWinners(void);
 static void Y_UnloadData(void);
+static void Y_CleanupData(void);
 
 // Stuff copy+pasted from st_stuff.c
 #define ST_DrawNumFromHud(h,n)        V_DrawTallNum(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f, n)
@@ -321,9 +323,19 @@ void Y_IntermissionDrawer(void)
 	if (intertype == int_none || rendermode == render_none)
 		return;
 
-	if (!usebuffer)
+	// Lactozilla: Renderer switching
+	if (needpatchrecache)
+	{
+		Y_CleanupData();
+		safetorender = false;
+	}
+
+	if (!usebuffer || !safetorender)
 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 
+	if (!safetorender)
+		goto dontdrawbg;
+
 	if (useinterpic)
 		V_DrawScaledPatch(0, 0, 0, interpic);
 	else if (!usetile)
@@ -358,6 +370,7 @@ void Y_IntermissionDrawer(void)
 	else
 		V_DrawPatchFill(bgtile);
 
+dontdrawbg:
 	if (intertype == int_coop)
 	{
 		INT32 bonusy;
@@ -407,14 +420,17 @@ void Y_IntermissionDrawer(void)
 
 		bonusy = 150;
 		// Total
-		V_DrawScaledPatch(152, bonusy, 0, data.coop.ptotal);
-		V_DrawTallNum(BASEVIDWIDTH - 68, bonusy + 1, 0, data.coop.total);
+		if (safetorender)
+		{
+			V_DrawScaledPatch(152, bonusy, 0, data.coop.ptotal);
+			V_DrawTallNum(BASEVIDWIDTH - 68, bonusy + 1, 0, data.coop.total);
+		}
 		bonusy -= (3*SHORT(tallnum[0]->height)/2) + 1;
 
 		// Draw bonuses
 		for (i = 3; i >= 0; --i)
 		{
-			if (data.coop.bonuses[i].display)
+			if (data.coop.bonuses[i].display && safetorender)
 			{
 				V_DrawScaledPatch(152, bonusy, 0, data.coop.bonuspatches[i]);
 				V_DrawTallNum(BASEVIDWIDTH - 68, bonusy + 1, 0, data.coop.bonuses[i].points);
@@ -643,7 +659,8 @@ void Y_IntermissionDrawer(void)
 		char strtime[10];
 
 		// draw the header
-		V_DrawScaledPatch(112, 2, 0, data.match.result);
+		if (safetorender)
+			V_DrawScaledPatch(112, 2, 0, data.match.result);
 
 		// draw the level name
 		V_DrawCenteredString(BASEVIDWIDTH/2, 20, 0, data.match.levelstring);
@@ -1169,6 +1186,8 @@ void Y_StartIntermission(void)
 		I_Error("endtic is dirty");
 #endif
 
+	safetorender = true;
+
 	if (!multiplayer)
 	{
 		timer = 0;
@@ -1222,20 +1241,20 @@ void Y_StartIntermission(void)
 			data.coop.tics = players[consoleplayer].realtime;
 
 			for (i = 0; i < 4; ++i)
-				data.coop.bonuspatches[i] = W_CachePatchName(data.coop.bonuses[i].patch, PU_STATIC);
-			data.coop.ptotal = W_CachePatchName("YB_TOTAL", PU_STATIC);
+				data.coop.bonuspatches[i] = W_CachePatchName(data.coop.bonuses[i].patch, PU_PATCH);
+			data.coop.ptotal = W_CachePatchName("YB_TOTAL", PU_PATCH);
 
 			// get act number
 			data.coop.actnum = mapheaderinfo[gamemap-1]->actnum;
 
 			// get background patches
-			widebgpatch = W_CachePatchName("INTERSCW", PU_STATIC);
-			bgpatch = W_CachePatchName("INTERSCR", PU_STATIC);
+			widebgpatch = W_CachePatchName("INTERSCW", PU_PATCH);
+			bgpatch = W_CachePatchName("INTERSCR", PU_PATCH);
 
 			// grab an interscreen if appropriate
 			if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
 			{
-				interpic = W_CachePatchName(mapheaderinfo[gamemap-1]->interscreen, PU_STATIC);
+				interpic = W_CachePatchName(mapheaderinfo[gamemap-1]->interscreen, PU_PATCH);
 				useinterpic = true;
 				usebuffer = false;
 			}
@@ -1293,18 +1312,18 @@ void Y_StartIntermission(void)
 			Y_AwardSpecialStageBonus();
 
 			for (i = 0; i < 2; ++i)
-				data.spec.bonuspatches[i] = W_CachePatchName(data.spec.bonuses[i].patch, PU_STATIC);
+				data.spec.bonuspatches[i] = W_CachePatchName(data.spec.bonuses[i].patch, PU_PATCH);
 
-			data.spec.pscore = W_CachePatchName("YB_SCORE", PU_STATIC);
-			data.spec.pcontinues = W_CachePatchName("YB_CONTI", PU_STATIC);
+			data.spec.pscore = W_CachePatchName("YB_SCORE", PU_PATCH);
+			data.spec.pcontinues = W_CachePatchName("YB_CONTI", PU_PATCH);
 
 			// get background tile
-			bgtile = W_CachePatchName("SPECTILE", PU_STATIC);
+			bgtile = W_CachePatchName("SPECTILE", PU_PATCH);
 
 			// grab an interscreen if appropriate
 			if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
 			{
-				interpic = W_CachePatchName(mapheaderinfo[gamemap-1]->interscreen, PU_STATIC);
+				interpic = W_CachePatchName(mapheaderinfo[gamemap-1]->interscreen, PU_PATCH);
 				useinterpic = true;
 			}
 			else
@@ -1316,14 +1335,14 @@ void Y_StartIntermission(void)
 			// get special stage specific patches
 /*			if (!stagefailed && ALL7EMERALDS(emeralds))
 			{
-				data.spec.cemerald = W_CachePatchName("GOTEMALL", PU_STATIC);
+				data.spec.cemerald = W_CachePatchName("GOTEMALL", PU_PATCH);
 				data.spec.headx = 70;
 				data.spec.nowsuper = players[consoleplayer].skin
-					? NULL : W_CachePatchName("NOWSUPER", PU_STATIC);
+					? NULL : W_CachePatchName("NOWSUPER", PU_PATCH);
 			}
 			else
 			{
-				data.spec.cemerald = W_CachePatchName("CEMERALD", PU_STATIC);
+				data.spec.cemerald = W_CachePatchName("CEMERALD", PU_PATCH);
 				data.spec.headx = 48;
 				data.spec.nowsuper = NULL;
 			} */
@@ -1401,9 +1420,9 @@ void Y_StartIntermission(void)
 
 			// get RESULT header
 			data.match.result =
-				W_CachePatchName("RESULT", PU_STATIC);
+				W_CachePatchName("RESULT", PU_PATCH);
 
-			bgtile = W_CachePatchName("SRB2BACK", PU_STATIC);
+			bgtile = W_CachePatchName("SRB2BACK", PU_PATCH);
 			usetile = true;
 			useinterpic = false;
 			break;
@@ -1429,9 +1448,9 @@ void Y_StartIntermission(void)
 			data.match.levelstring[sizeof data.match.levelstring - 1] = '\0';
 
 			// get RESULT header
-			data.match.result = W_CachePatchName("RESULT", PU_STATIC);
+			data.match.result = W_CachePatchName("RESULT", PU_PATCH);
 
-			bgtile = W_CachePatchName("SRB2BACK", PU_STATIC);
+			bgtile = W_CachePatchName("SRB2BACK", PU_PATCH);
 			usetile = true;
 			useinterpic = false;
 			break;
@@ -1468,7 +1487,7 @@ void Y_StartIntermission(void)
 				data.match.blueflag = bmatcico;
 			}
 
-			bgtile = W_CachePatchName("SRB2BACK", PU_STATIC);
+			bgtile = W_CachePatchName("SRB2BACK", PU_PATCH);
 			usetile = true;
 			useinterpic = false;
 			break;
@@ -1494,7 +1513,7 @@ void Y_StartIntermission(void)
 			data.competition.levelstring[sizeof data.competition.levelstring - 1] = '\0';
 
 			// get background tile
-			bgtile = W_CachePatchName("SRB2BACK", PU_STATIC);
+			bgtile = W_CachePatchName("SRB2BACK", PU_PATCH);
 			usetile = true;
 			useinterpic = false;
 			break;
@@ -2032,7 +2051,8 @@ void Y_EndIntermission(void)
 	usebuffer = false;
 }
 
-#define UNLOAD(x) Z_ChangeTag(x, PU_CACHE); x = NULL
+#define UNLOAD(x) if (x) {Z_ChangeTag(x, PU_CACHE);} x = NULL;
+#define CLEANUP(x) x = NULL;
 
 //
 // Y_UnloadData
@@ -2083,5 +2103,47 @@ static void Y_UnloadData(void)
 			//are not handled
 			break;
 	}
+}
 
+static void Y_CleanupData(void)
+{
+	// unload the background patches
+	CLEANUP(bgpatch);
+	CLEANUP(widebgpatch);
+	CLEANUP(bgtile);
+	CLEANUP(interpic);
+
+	switch (intertype)
+	{
+		case int_coop:
+			// unload the coop and single player patches
+			CLEANUP(data.coop.bonuspatches[3]);
+			CLEANUP(data.coop.bonuspatches[2]);
+			CLEANUP(data.coop.bonuspatches[1]);
+			CLEANUP(data.coop.bonuspatches[0]);
+			CLEANUP(data.coop.ptotal);
+			break;
+		case int_spec:
+			// unload the special stage patches
+			//CLEANUP(data.spec.cemerald);
+			//CLEANUP(data.spec.nowsuper);
+			CLEANUP(data.spec.bonuspatches[1]);
+			CLEANUP(data.spec.bonuspatches[0]);
+			CLEANUP(data.spec.pscore);
+			CLEANUP(data.spec.pcontinues);
+			break;
+		case int_match:
+		case int_race:
+			CLEANUP(data.match.result);
+			break;
+		case int_ctf:
+			CLEANUP(data.match.blueflag);
+			CLEANUP(data.match.redflag);
+			break;
+		default:
+			//without this default,
+			//int_none, int_tag, int_chaos, and int_classicrace
+			//are not handled
+			break;
+	}
 }
diff --git a/src/z_zone.c b/src/z_zone.c
index daad5489d32252eaf7ac116ef5c19ecd8c5bf703..5a0ff638bfd7e09512af622abf46d66565251029 100644
--- a/src/z_zone.c
+++ b/src/z_zone.c
@@ -27,6 +27,7 @@
 
 #include "doomdef.h"
 #include "doomstat.h"
+#include "r_patch.h"
 #include "i_system.h" // I_GetFreeMem
 #include "i_video.h" // rendermode
 #include "z_zone.h"
@@ -497,6 +498,35 @@ void Z_FreeTags(INT32 lowtag, INT32 hightag)
 // Utility functions
 // -----------------
 
+// for renderer switching, free a bunch of stuff
+boolean needpatchflush = false;
+boolean needpatchrecache = false;
+
+// flush all patches from memory
+// (also frees memory tagged with PU_CACHE)
+// (which are not necessarily patches but I don't care)
+void Z_FlushCachedPatches(void)
+{
+	CONS_Debug(DBG_RENDER, "Z_FlushCachedPatches()...\n");
+	Z_FreeTag(PU_CACHE);
+	Z_FreeTag(PU_PATCH);
+	Z_FreeTag(PU_HUDGFX);
+	Z_FreeTag(PU_HWRPATCHINFO);
+	Z_FreeTag(PU_HWRMODELTEXTURE);
+	Z_FreeTag(PU_HWRCACHE);
+	Z_FreeTag(PU_HWRCACHE_UNLOCKED);
+	Z_FreeTag(PU_HWRPATCHINFO_UNLOCKED);
+}
+
+// happens before a renderer switch
+void Z_PreparePatchFlush(void)
+{
+	CONS_Debug(DBG_RENDER, "Z_PreparePatchFlush()...\n");
+#ifdef ROTSPRITE
+	R_FreeAllRotSprite();
+#endif
+}
+
 // starting value of nextcleanup
 #define CLEANUPCOUNT 2000
 
diff --git a/src/z_zone.h b/src/z_zone.h
index 635c334cf78e0565b2e9bd25f4ff075c432fc115..9e5f74343aa0b009956ad13af57fa8e01be089a0 100644
--- a/src/z_zone.h
+++ b/src/z_zone.h
@@ -43,6 +43,7 @@ enum
 	PU_SOUND                 = 11, // static while playing
 	PU_MUSIC                 = 12, // static while playing
 	PU_HUDGFX                = 13, // static until WAD added
+	PU_PATCH                 = 14, // static until renderer change
 
 	PU_HWRPATCHINFO          = 21, // Hardware GLPatch_t struct for OpenGL texture cache
 	PU_HWRPATCHCOLMIPMAP     = 22, // Hardware GLMipmap_t struct colormap variation of patch
@@ -142,4 +143,10 @@ size_t Z_TagsUsage(INT32 lowtag, INT32 hightag);
 char *Z_StrDup(const char *in);
 #define Z_Unlock(p) (void)p // TODO: remove this now that NDS code has been removed
 
+// For renderer switching
+extern boolean needpatchflush;
+extern boolean needpatchrecache;
+void Z_FlushCachedPatches(void);
+void Z_PreparePatchFlush(void);
+
 #endif