diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index cf7d0343b343d471cca00ee0b8ba040cbe3002c1..eb4e7d33ca9f9a3319b89564fbbdd70df8b0e711 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -90,7 +90,8 @@ enum patch {
 	// Methods
 	patch_getPixel,
 	patch_setPixel,
-	patch_copy
+	patch_copy,
+	patch_clear
 };
 static const char *const patch_opt[] = {
 	"valid",
@@ -103,6 +104,7 @@ static const char *const patch_opt[] = {
 	"getPixel",
 	"setPixel",
 	"copy",
+	"clear",
 	NULL};
 
 static int patch_fields_ref = LUA_NOREF;
@@ -378,8 +380,17 @@ static int lib_patch_copy(lua_State *L)
 	int src_img_width = -1;
 	int src_img_height = -1;
 
+	patch_t *src_patch = NULL;
+
 	if (!lua_istable(L, 2))
-		return luaL_typerror(L, 2, "table");
+	{
+		src_patch = *((patch_t **)luaL_checkudata(L, 2, META_PATCH));
+		if (!src_patch)
+			return LUA_ErrInvalid(L, "patch_t");
+
+		src_img_width = src_patch->width;
+		src_img_height = src_patch->height;
+	}
 	else
 	{
 		src_img_width = luaL_optinteger(L, 3, patch->width);
@@ -387,17 +398,17 @@ static int lib_patch_copy(lua_State *L)
 
 		lua_remove(L, 3);
 		lua_remove(L, 3);
-	}
 
-	if (src_img_width <= 0)
-		return luaL_error(L, "invalid source image width");
-	if (src_img_height <= 0)
-		return luaL_error(L, "invalid source image height");
+		if (src_img_width <= 0)
+			return luaL_error(L, "invalid source image width");
+		if (src_img_height <= 0)
+			return luaL_error(L, "invalid source image height");
+	}
 
 	int sx = luaL_optinteger(L, 3, 0);
 	int sy = luaL_optinteger(L, 4, 0);
-	int sw = luaL_optinteger(L, 5, patch->width);
-	int sh = luaL_optinteger(L, 6, patch->height);
+	int sw = luaL_optinteger(L, 5, src_img_width);
+	int sh = luaL_optinteger(L, 6, src_img_height);
 	int dx = luaL_optinteger(L, 7, 0);
 	int dy = luaL_optinteger(L, 8, 0);
 	boolean copy_transparent = lua_optboolean(L, 9);
@@ -407,12 +418,12 @@ static int lib_patch_copy(lua_State *L)
 	if (sh <= 0)
 		return luaL_error(L, "invalid copy rect height");
 
-	size_t size = (unsigned)(src_img_width * src_img_height);
-
-	img_prepare_buffer(size);
-
 	if (lua_istable(L, 2))
 	{
+		size_t size = (unsigned)(src_img_width * src_img_height);
+
+		img_prepare_buffer(size);
+
 		if (lua_objlen(L, 2) + 1 != size)
 			return luaL_error(L, "invalid table length");
 
@@ -422,9 +433,27 @@ static int lib_patch_copy(lua_State *L)
 		img_get_pixels_from_table(L, size);
 
 		lua_pop(L, 2);
+
+		Patch_UpdatePixels(patch, patch_update_buffer, src_img_width, src_img_height, PICFMT_FLAT16, sx, sy, sw, sh, dx, dy, copy_transparent);
+	}
+	else
+	{
+		V_DrawIntoPatch(patch, src_patch, dx << FRACBITS, dy << FRACBITS, FRACUNIT, FRACUNIT, 0, NULL, sx << FRACBITS, sy << FRACBITS, sw << FRACBITS, sh << FRACBITS, copy_transparent);
 	}
 
-	Patch_UpdatePixels(patch, patch_update_buffer, src_img_width, src_img_height, PICFMT_FLAT16, sx, sy, sw, sh, dx, dy, copy_transparent);
+	return 0;
+}
+
+static int lib_patch_clear(lua_State *L)
+{
+	patch_t *patch = *((patch_t **)luaL_checkudata(L, 1, META_PATCH));
+	if (!patch)
+		return LUA_ErrInvalid(L, "patch_t");
+
+	if (patch->type != PATCH_TYPE_DYNAMIC)
+		return luaL_error(L, "cannot modify a static patch");
+
+	Patch_Clear(patch);
 
 	return 0;
 }
@@ -471,6 +500,9 @@ static int patch_get(lua_State *L)
 	case patch_copy:
 		lua_pushcfunction(L, lib_patch_copy);
 		break;
+	case patch_clear:
+		lua_pushcfunction(L, lib_patch_clear);
+		break;
 	}
 	return 1;
 }
diff --git a/src/r_patch.c b/src/r_patch.c
index 4c4cdc01978c881d3426bc357398d72d3debcb01..af66ee5daa67f80d161bb3bdc3e41287d60f5fe9 100644
--- a/src/r_patch.c
+++ b/src/r_patch.c
@@ -19,7 +19,7 @@
 #include "hardware/hw_glob.h"
 #endif
 
-static void Patch_MarkDirtyRect(dynamicpatch_t *dpatch, INT16 left, INT16 top, INT16 right, INT16 bottom);
+static boolean Patch_CheckDirtyRect(dynamicpatch_t *dpatch);
 static void Patch_ClearDirtyRect(dynamicpatch_t *dpatch);
 
 patch_t *Patch_Create(INT16 width, INT16 height)
@@ -51,8 +51,15 @@ patch_t *Patch_CreateDynamic(INT16 width, INT16 height)
 	return patch;
 }
 
-static void Patch_MarkDirtyRect(dynamicpatch_t *dpatch, INT16 left, INT16 top, INT16 right, INT16 bottom)
+void Patch_MarkDirtyRect(patch_t *patch, INT16 left, INT16 top, INT16 right, INT16 bottom)
 {
+	dynamicpatch_t *dpatch = (dynamicpatch_t*)patch;
+
+	left = max(0, left);
+	right = min(right, patch->width);
+	top = max(0, top);
+	bottom = min(bottom, patch->height);
+
 	if (left < dpatch->rect_dirty[0])
 		dpatch->rect_dirty[0] = left;
 	if (top < dpatch->rect_dirty[1])
@@ -63,6 +70,37 @@ static void Patch_MarkDirtyRect(dynamicpatch_t *dpatch, INT16 left, INT16 top, I
 		dpatch->rect_dirty[3] = bottom;
 }
 
+static boolean Patch_CheckDirtyRect(dynamicpatch_t *dpatch)
+{
+	patch_t *patch = (patch_t*)dpatch;
+
+	// left
+	if (dpatch->rect_dirty[0] < 0
+	|| dpatch->rect_dirty[0] > patch->width
+	|| dpatch->rect_dirty[0] >= dpatch->rect_dirty[2]) // right
+		return false;
+
+	// top
+	if (dpatch->rect_dirty[1] < 0
+	|| dpatch->rect_dirty[1] > patch->height
+	|| dpatch->rect_dirty[1] >= dpatch->rect_dirty[3]) // bottom
+		return false;
+
+	// right
+	if (dpatch->rect_dirty[2] > patch->width
+	|| dpatch->rect_dirty[2] < 0
+	|| dpatch->rect_dirty[2] <= dpatch->rect_dirty[0]) // left
+		return false;
+
+	// bottom
+	if (dpatch->rect_dirty[3] > patch->height
+	|| dpatch->rect_dirty[3] < 0
+	|| dpatch->rect_dirty[3] <= dpatch->rect_dirty[1]) // top
+		return false;
+
+	return true;
+}
+
 static void Patch_ClearDirtyRect(dynamicpatch_t *dpatch)
 {
 	dpatch->rect_dirty[0] = INT16_MAX;
@@ -83,6 +121,53 @@ static void Patch_InitDynamicColumns(patch_t *patch)
 	}
 }
 
+void Patch_Clear(patch_t *patch)
+{
+	if (patch->type != PATCH_TYPE_DYNAMIC)
+		return;
+
+	size_t total_pixels = patch->width * patch->height;
+
+	memset(patch->pixels, 0, total_pixels * sizeof(UINT8));
+
+	for (INT32 x = 0; x < patch->width; x++)
+		patch->columns[x].num_posts = 0;
+
+	dynamicpatch_t *dpatch = (dynamicpatch_t*)patch;
+
+	memset(dpatch->pixels_opaque, 0, BIT_ARRAY_SIZE(total_pixels));
+
+	dpatch->is_dirty = dpatch->update_columns = true;
+}
+
+void Patch_ClearRect(patch_t *patch, INT16 x, INT16 y, INT16 width, INT16 height)
+{
+	if (patch->type != PATCH_TYPE_DYNAMIC)
+		return;
+
+	dynamicpatch_t *dpatch = (dynamicpatch_t*)patch;
+
+	for (INT16 dy = y; dy < y + height; dy++)
+	{
+		if (dy < 0)
+			continue;
+		else if (dy >= patch->height)
+			break;
+
+		for (INT16 dx = x; dx < x + width; dx++)
+		{
+			if (dx < 0)
+				continue;
+			else if (dx >= patch->width)
+				break;
+
+			size_t position = (dx * patch->height) + dy;
+			unset_bit_array(dpatch->pixels_opaque, position);
+			dpatch->is_dirty = dpatch->update_columns = true;
+		}
+	}
+}
+
 patch_t *Patch_CreateFromDoomPatch(softwarepatch_t *source)
 {
 	patch_t *patch = (patch_t*)Patch_Create(0, 0);
@@ -337,16 +422,18 @@ void Patch_SetPixel(patch_t *patch, void *pixel, pictureformat_t informat, INT32
 	{
 		// No pixel in this position, so columns need to be rebuilt
 		if (!in_bit_array(dpatch->pixels_opaque, position))
+		{
+			set_bit_array(dpatch->pixels_opaque, position);
 			dpatch->update_columns = true;
+		}
 
-		set_bit_array(dpatch->pixels_opaque, position);
 		writePixelFunc(&patch->pixels[position], pixel);
 
 		dpatch->is_dirty = true;
 	}
 
 	if (dpatch->is_dirty)
-		Patch_MarkDirtyRect(dpatch, x, y, x + 1, y + 1);
+		Patch_MarkDirtyRect(patch, x, y, x + 1, y + 1);
 }
 
 void Patch_UpdatePixels(patch_t *patch,
@@ -409,12 +496,7 @@ void Patch_UpdatePixels(patch_t *patch,
 
 	dynamicpatch_t *dpatch = (dynamicpatch_t *)patch;
 
-	INT32 dest_x1 = max(0, dx);
-	INT32 dest_x2 = min(dx + sw, patch->width);
-	INT32 dest_y1 = max(0, dy);
-	INT32 dest_y2 = min(dy + sh, patch->height);
-
-	Patch_MarkDirtyRect(dpatch, dest_x1, dest_y1, dest_x2, dest_y2);
+	Patch_MarkDirtyRect(patch, dx, dy, dx + sw, dy + sh);
 
 	for (INT32 x = dx; x < dx + sw; x++, sx++)
 	{
@@ -453,9 +535,15 @@ void Patch_UpdatePixels(patch_t *patch,
 			}
 			else
 			{
-				set_bit_array(dpatch->pixels_opaque, position);
+				if (!in_bit_array(dpatch->pixels_opaque, position))
+				{
+					set_bit_array(dpatch->pixels_opaque, position);
+					dpatch->update_columns = true;
+				}
+
 				writePixelFunc(&patch->pixels[position], input);
-				dpatch->update_columns = dpatch->is_dirty = true;
+
+				dpatch->is_dirty = true;
 			}
 		}
 	}
@@ -482,18 +570,21 @@ void Patch_DoDynamicUpdate(patch_t *patch)
 	if (patch->columns == NULL)
 		Patch_InitDynamicColumns(patch);
 
-	if (dpatch->update_columns)
+	if (Patch_CheckDirtyRect(dpatch))
 	{
-		for (INT32 x = dpatch->rect_dirty[0]; x < dpatch->rect_dirty[2]; x++)
-			Patch_RebuildColumn(&patch->columns[x], x, patch->height, dpatch->pixels_opaque);
-	}
-
-	Patch_FreeMiscData(patch);
+		if (dpatch->update_columns)
+		{
+			for (INT32 x = dpatch->rect_dirty[0]; x < dpatch->rect_dirty[2]; x++)
+				Patch_RebuildColumn(&patch->columns[x], x, patch->height, dpatch->pixels_opaque);
+		}
 
 #ifdef HWRENDER
-	if (patch->hardware)
-		HWR_UpdatePatchRegion(patch, dpatch->rect_dirty[0], dpatch->rect_dirty[1], dpatch->rect_dirty[2], dpatch->rect_dirty[3]);
+		if (patch->hardware)
+			HWR_UpdatePatchRegion(patch, dpatch->rect_dirty[0], dpatch->rect_dirty[1], dpatch->rect_dirty[2], dpatch->rect_dirty[3]);
 #endif
+	}
+
+	Patch_FreeMiscData(patch);
 
 	dpatch->is_dirty = false;
 	dpatch->update_columns = false;
diff --git a/src/r_patch.h b/src/r_patch.h
index 9ee5892fad59c319601c20cfb3770aacd540c398..0a5c1c0bbc72a65cdb0c3584806f469d6b8ba3ac 100644
--- a/src/r_patch.h
+++ b/src/r_patch.h
@@ -37,6 +37,10 @@ void Patch_UpdatePixels(patch_t *patch,
 
 boolean Patch_NeedsUpdate(patch_t *patch);
 void Patch_DoDynamicUpdate(patch_t *patch);
+void Patch_MarkDirtyRect(patch_t *dpatch, INT16 left, INT16 top, INT16 right, INT16 bottom);
+
+void Patch_Clear(patch_t *patch);
+void Patch_ClearRect(patch_t *patch, INT16 x, INT16 y, INT16 width, INT16 height);
 
 bitarray_t *Patch_GetOpaqueRegions(patch_t *patch);
 
diff --git a/src/v_video.c b/src/v_video.c
index 150bd0d338754b574fbd44b60193933b1b6a5d0a..176fd28883afc52aee65657241348779456f68ec 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -1064,6 +1064,152 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, IN
 	}
 }
 
+void V_DrawIntoPatch(patch_t *dest_patch, patch_t *src_patch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 flags, const UINT8 *colormap, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h, boolean copy_transparent)
+{
+	if (dest_patch->type != PATCH_TYPE_DYNAMIC)
+		return;
+
+	dynamicpatch_t *dpatch = (dynamicpatch_t *)dest_patch;
+
+	if (Patch_NeedsUpdate(src_patch))
+		Patch_DoDynamicUpdate(src_patch);
+
+	if (src_patch->columns == NULL)
+		return;
+
+	if (sx < 0)
+	{
+		w += sx;
+		sx = 0;
+	}
+	if (sy < 0)
+	{
+		h += sy;
+		sy = 0;
+	}
+
+	if (sx >= src_patch->width << FRACBITS || w <= 0)
+		return;
+	if (sy >= src_patch->height << FRACBITS || h <= 0)
+		return;
+
+	UINT8 (*patchdrawfunc)(const UINT8*, const UINT8*, fixed_t) = standardpdraw;
+
+	v_translevel = NULL;
+	v_colormap = NULL;
+
+	UINT32 alphalevel = ((flags & V_ALPHAMASK) >> V_ALPHASHIFT);
+	UINT32 blendmode = ((flags & V_BLENDMASK) >> V_BLENDSHIFT);
+
+	if (alphalevel || blendmode)
+	{
+		if (alphalevel >= 10)
+			return; // invis
+
+		if (alphalevel || blendmode)
+		{
+			v_translevel = R_GetBlendTable(blendmode+1, alphalevel);
+			patchdrawfunc = translucentpdraw;
+		}
+	}
+
+	if (colormap)
+	{
+		v_colormap = colormap;
+		patchdrawfunc = v_translevel ? transmappedpdraw : mappedpdraw;
+	}
+
+	fixed_t fdup = pscale;
+	fixed_t vdup = vscale;
+	fixed_t colfrac = FixedDiv(FRACUNIT, fdup);
+	fixed_t rowfrac = FixedDiv(FRACUNIT, vdup);
+
+	fixed_t offsetx = 0, offsety = 0;
+
+	// left offset
+	if (flags & V_FLIP)
+		offsetx = FixedMul((src_patch->width - src_patch->leftoffset)<<FRACBITS, pscale) + 1;
+	else
+		offsetx = FixedMul(src_patch->leftoffset<<FRACBITS, pscale);
+
+	// top offset
+	offsety = FixedMul(src_patch->topoffset<<FRACBITS, vscale);
+
+	// Subtract the offsets from x/y positions
+	x -= offsetx;
+	y -= offsety;
+
+	x >>= FRACBITS;
+	y >>= FRACBITS;
+
+	fixed_t pwidth = src_patch->width;
+	if (pscale != FRACUNIT)
+		pwidth = (pwidth * pscale) >> FRACBITS;
+
+	int dest_x = x;
+
+	Patch_MarkDirtyRect(dest_patch, x, y, x + (w >> FRACBITS), y + (h >> FRACBITS));
+
+	if (copy_transparent)
+		Patch_ClearRect(dest_patch, x, y, w >> FRACBITS, h >> FRACBITS);
+
+	if (dest_patch->pixels == NULL)
+		dest_patch->pixels = Z_Calloc(dest_patch->width * dest_patch->height, PU_PATCH_DATA, NULL);
+
+	for (fixed_t col = sx; (col>>FRACBITS) < src_patch->width && (col - sx) < w; col += colfrac, dest_x++)
+	{
+		if (dest_x < 0)
+			continue;
+		else if (dest_x >= dest_patch->width)
+			break;
+
+		unsigned texturecolumn = col >> FRACBITS;
+		if (flags & V_FLIP)
+			texturecolumn = src_patch->width - 1 - texturecolumn;
+
+		column_t *column = &src_patch->columns[texturecolumn];
+
+		for (unsigned i = 0; i < column->num_posts; i++)
+		{
+			post_t *post = &column->posts[i];
+			INT32 topdelta = post->topdelta << FRACBITS;
+			UINT8 *source = column->pixels + post->data_offset;
+
+			int dest_y = y;
+
+			fixed_t ofs;
+			if (topdelta - sy > 0)
+			{
+				dest_y = y + FixedInt(FixedMul(topdelta - sy, vdup));
+				ofs = 0;
+			}
+			else
+				ofs = sy - topdelta;
+
+			for (; dest_y < dest_patch->height && (size_t)(ofs>>FRACBITS) < post->length && ((ofs - sy) + topdelta) < h; ofs += rowfrac, dest_y++)
+			{
+				if (dest_y < 0)
+					continue;
+
+				size_t position = (dest_x * dest_patch->height) + dest_y;
+				if (!in_bit_array(dpatch->pixels_opaque, position))
+				{
+					set_bit_array(dpatch->pixels_opaque, position);
+					dpatch->update_columns = true;
+				}
+
+				UINT8 *dest = &dest_patch->pixels[position];
+				*dest = patchdrawfunc(dest, source, ofs);
+
+				dpatch->is_dirty = true;
+			}
+
+			if (dest_y >= dest_patch->height)
+				break;
+		}
+	}
+}
+
 //
 // V_DrawContinueIcon
 // Draw a mini player!  If we can, that is.  Otherwise we draw a star.
diff --git a/src/v_video.h b/src/v_video.h
index ff03836b597bec40f8712f534afef32bd46a2db8..34031d8da63460c869c4a366b97759f50f32138f 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -172,6 +172,7 @@ void V_CubeApply(UINT8 *red, UINT8 *green, UINT8 *blue);
 #define V_DrawFixedPatch(x,y,sc,s,p,c) V_DrawStretchyFixedPatch(x,y,sc,sc,s,p,c)
 void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap);
 void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h);
+void V_DrawIntoPatch(patch_t *dest_patch, patch_t *src_patch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 flags, const UINT8 *colormap, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h, boolean copy_transparent);
 
 void V_DrawContinueIcon(INT32 x, INT32 y, INT32 flags, INT32 skinnum, UINT16 skincolor);