diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index 2aba473c5c1191b828c217754e719f5c040fe9c1..c7c85a24af077fad63407c00d985ea7b4a5e53d1 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -262,37 +262,29 @@ static void HWR_DrawPatchInCache(GLMipmap_t *mipmap,
 	INT32 pwidth, INT32 pheight,
 	const patch_t *realpatch)
 {
-	INT32 ncols;
-	fixed_t xfrac, xfracstep;
-	fixed_t yfracstep, scale_y;
-	const column_t *patchcol;
-	UINT8 *block = mipmap->data;
-	INT32 bpp;
-	INT32 blockmodulo;
-
 	if (pwidth <= 0 || pheight <= 0)
 		return;
 
-	ncols = pwidth;
-
 	// source advance
-	xfrac = 0;
-	xfracstep = FRACUNIT;
-	yfracstep = FRACUNIT;
-	scale_y   = FRACUNIT;
+	fixed_t xfrac = 0;
+	fixed_t xfracstep = FRACUNIT;
+	fixed_t yfracstep = FRACUNIT;
+	fixed_t scale_y   = FRACUNIT;
 
-	bpp = format2bpp(mipmap->format);
+	INT32 bpp = format2bpp(mipmap->format);
 
 	if (bpp < 1 || bpp > 4)
 		I_Error("HWR_DrawPatchInCache: no drawer defined for this bpp (%d)\n",bpp);
 
 	// NOTE: should this actually be pblockwidth*bpp?
-	blockmodulo = pblockwidth*bpp;
+	INT32 blockmodulo = pblockwidth*bpp;
 
 	// Draw each column to the block cache
-	for (; ncols--; block += bpp, xfrac += xfracstep)
+	UINT8 *block = mipmap->data;
+
+	for (int x = 0; x < pwidth; x++, block += bpp, xfrac += xfracstep)
 	{
-		patchcol = &realpatch->columns[xfrac>>FRACBITS];
+		const column_t *patchcol = &realpatch->columns[xfrac>>FRACBITS];
 
 		HWR_DrawColumnInCache(patchcol, block, mipmap,
 								pblockheight, blockmodulo,
@@ -542,7 +534,6 @@ void HWR_MakePatch (const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipm
 	}
 
 	Z_Free(grMipmap->data);
-	grMipmap->data = NULL;
 
 	if (makebitmap)
 	{
@@ -576,8 +567,8 @@ void HWR_FreeTextureData(patch_t *patch)
 
 	if (vid.glstate == VID_GL_LIBRARY_LOADED)
 		HWD.pfnDeleteTexture(grPatch->mipmap);
-	if (grPatch->mipmap->data)
-		Z_Free(grPatch->mipmap->data);
+
+	Z_Free(grPatch->mipmap->data);
 }
 
 void HWR_FreeTexture(patch_t *patch)
@@ -641,8 +632,6 @@ void HWR_FreeTextureColormaps(patch_t *patch)
 			Z_Free(next->data);
 		if (next->colormap)
 			Z_Free(next->colormap);
-		next->data = NULL;
-		next->colormap = NULL;
 		HWD.pfnDeleteTexture(next);
 
 		// Free the old colormap mipmap from memory.
@@ -697,9 +686,7 @@ void HWR_InitMapTextures(void)
 static void FreeMapTexture(GLMapTexture_t *tex)
 {
 	HWD.pfnDeleteTexture(&tex->mipmap);
-	if (tex->mipmap.data)
-		Z_Free(tex->mipmap.data);
-	tex->mipmap.data = NULL;
+	Z_Free(tex->mipmap.data);
 }
 
 void HWR_FreeMapTextures(void)
@@ -897,6 +884,88 @@ static void HWR_UpdatePatchMipmap(patch_t *patch, GLMipmap_t *grMipmap)
 	Z_ChangeTag(grMipmap->data, PU_HWRCACHE_UNLOCKED);
 }
 
+static void HWR_UpdatePatchPixels(GLMipmap_t *mipmap, UINT8 *pixels, INT16 patchheight, bitarray_t *pixels_opaque, INT16 left, INT16 top, INT16 right, INT16 bottom)
+{
+	INT32 bpp = format2bpp(mipmap->format);
+	if (bpp < 1 || bpp > 4)
+		I_Error("HWR_UpdatePatchPixels: no drawer defined for this bpp (%d)\n",bpp);
+
+	UINT8 *dest_pixels = (UINT8*)mipmap->data;
+
+	for (INT16 y = top; y < bottom; y++)
+	{
+		UINT8 *dest = &dest_pixels[(y * (mipmap->width * bpp)) + (left * bpp)];
+
+		for (INT16 x = left; x < right; x++)
+		{
+			UINT8 texel = 0x00;
+			UINT8 alpha = 0x00;
+
+			size_t position = (x * patchheight) + y;
+
+			if (in_bit_array(pixels_opaque, position))
+			{
+				texel = pixels[position];
+				alpha = 0xFF;
+
+				// Make pixel transparent if chroma keyed
+				if ((mipmap->flags & TF_CHROMAKEYED) && texel == HWR_PATCHES_CHROMAKEY_COLORINDEX)
+					alpha = 0x00;
+			}
+
+			UINT16 texelu16;
+			RGBA_t colortemp;
+
+			switch (bpp)
+			{
+				case 2:
+					texelu16 = (UINT16)((alpha<<8) | texel);
+					memcpy(dest, &texelu16, sizeof(UINT16));
+					break;
+				case 3:
+					colortemp = V_GetColor(texel);
+					memcpy(dest, &colortemp, sizeof(RGBA_t)-sizeof(UINT8));
+					break;
+				case 4:
+					colortemp = V_GetColor(texel);
+					colortemp.s.alpha = alpha;
+					memcpy(dest, &colortemp, sizeof(RGBA_t));
+					break;
+				// default is 1
+				default:
+					*dest = texel;
+					break;
+			}
+
+			dest += bpp;
+		}
+	}
+}
+
+static void HWR_UpdateMipmapRegion(patch_t *patch, GLMipmap_t *grMipmap, INT16 left, INT16 top, INT16 right, INT16 bottom)
+{
+	GLPatch_t *grPatch = patch->hardware;
+
+	bitarray_t *pixels_opaque = Patch_GetOpaqueRegions(patch);
+
+	if (pixels_opaque == NULL || grMipmap->width == 0 || grMipmap->data == NULL)
+		HWR_MakePatch(patch, grPatch, grMipmap, true);
+	else
+		HWR_UpdatePatchPixels(grMipmap, patch->pixels, patch->height, pixels_opaque, left, top, right, bottom);
+
+	// If hardware does not have the texture, then call pfnSetTexture to upload it
+	// If it does have the texture, then call pfnUpdateTextureRegion to update it
+	if (!grMipmap->downloaded)
+		HWD.pfnSetTexture(grMipmap);
+	else
+		HWD.pfnUpdateTextureRegion(grMipmap, left, top, right - left, bottom - top);
+
+	HWR_SetCurrentTexture(grMipmap);
+
+	// The system-memory data can be purged now.
+	Z_ChangeTag(grMipmap->data, PU_HWRCACHE_UNLOCKED);
+}
+
 // -----------------+
 // HWR_GetPatch     : Downloads a patch to the hardware cache and make it ready for use
 // -----------------+
@@ -907,11 +976,12 @@ void HWR_GetPatch(patch_t *patch)
 	HWR_LoadPatchMipmap(patch, ((GLPatch_t *)patch->hardware)->mipmap);
 }
 
-void HWR_UpdatePatch(patch_t *patch)
+void HWR_UpdatePatchRegion(patch_t *patch, INT16 left, INT16 top, INT16 right, INT16 bottom)
 {
 	if (!patch->hardware)
 		Patch_CreateGL(patch);
-	HWR_UpdatePatchMipmap(patch, ((GLPatch_t *)patch->hardware)->mipmap);
+
+	HWR_UpdateMipmapRegion(patch, ((GLPatch_t *)patch->hardware)->mipmap, left, top, right, bottom);
 }
 
 // -------------------+
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index 1c4cd99ab03d34498fa13d132a01ef53af9e6e61..63e82f10f3708867362f1f99653f5a7ceca26eb1 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -42,6 +42,7 @@ EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags);
 EXPORT void HWRAPI(ClearBuffer) (FBOOLEAN ColorMask, FBOOLEAN DepthMask, FRGBAFloat *ClearColor);
 EXPORT void HWRAPI(SetTexture) (GLMipmap_t *TexInfo);
 EXPORT void HWRAPI(UpdateTexture) (GLMipmap_t *TexInfo);
+EXPORT void HWRAPI(UpdateTextureRegion) (GLMipmap_t *TexInfo, INT32 x, INT32 y, INT32 width, INT32 height);
 EXPORT void HWRAPI(DeleteTexture) (GLMipmap_t *TexInfo);
 EXPORT void HWRAPI(ReadRect) (INT32 x, INT32 y, INT32 width, INT32 height, INT32 dst_stride, UINT16 *dst_data);
 EXPORT void HWRAPI(GClipRect) (INT32 minx, INT32 miny, INT32 maxx, INT32 maxy, float nearclip);
@@ -95,6 +96,7 @@ struct hwdriver_s
 	ClearBuffer         pfnClearBuffer;
 	SetTexture          pfnSetTexture;
 	UpdateTexture       pfnUpdateTexture;
+	UpdateTextureRegion pfnUpdateTextureRegion;
 	DeleteTexture       pfnDeleteTexture;
 	ReadRect            pfnReadRect;
 	GClipRect           pfnGClipRect;
diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h
index 352ff1e962a82ca35ae55fe5b65c19dd4687ca7d..0bd84a473a5bf95cb62a39da9367e5a091d35750 100644
--- a/src/hardware/hw_glob.h
+++ b/src/hardware/hw_glob.h
@@ -117,7 +117,7 @@ patch_t *HWR_GetCachedGLPatchPwad(UINT16 wad, UINT16 lump);
 patch_t *HWR_GetCachedGLPatch(lumpnum_t lumpnum);
 
 void HWR_GetPatch(patch_t *patch);
-void HWR_UpdatePatch(patch_t *patch);
+void HWR_UpdatePatchRegion(patch_t *patch, INT16 left, INT16 top, INT16 right, INT16 bottom);
 void HWR_GetMappedPatch(patch_t *patch, const UINT8 *colormap);
 void HWR_GetFadeMask(lumpnum_t fademasklumpnum);
 patch_t *HWR_GetPic(lumpnum_t lumpnum);
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 71cb5ca70375629ee06c7943e0c5be881555a0c1..f6cfab3ea0224688052b115c0890d53c8c1c9100 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -1333,18 +1333,15 @@ void Flush(void)
 	while (TexCacheHead)
 	{
 		FTextureInfo *pTexInfo = TexCacheHead;
-		GLMipmap_t *texture = pTexInfo->texture;
+		TexCacheHead = pTexInfo->next;
 
 		if (pTexInfo->downloaded)
-		{
 			pglDeleteTextures(1, (GLuint *)&pTexInfo->downloaded);
-			pTexInfo->downloaded = 0;
-		}
 
+		GLMipmap_t *texture = pTexInfo->texture;
 		if (texture)
 			texture->downloaded = 0;
 
-		TexCacheHead = pTexInfo->next;
 		free(pTexInfo);
 	}
 
@@ -1746,9 +1743,9 @@ EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags)
 	CurrentPolyFlags = PolyFlags;
 }
 
-static void AllocTextureBuffer(GLMipmap_t *pTexInfo)
+static RGBA_t *AllocTextureBuffer(unsigned width, unsigned height)
 {
-	size_t size = pTexInfo->width * pTexInfo->height;
+	size_t size = width * height;
 	if (size > textureBufferSize)
 	{
 		textureBuffer = realloc(textureBuffer, size * sizeof(RGBA_t));
@@ -1756,116 +1753,143 @@ static void AllocTextureBuffer(GLMipmap_t *pTexInfo)
 			I_Error("AllocTextureBuffer: out of memory allocating %s bytes", sizeu1(size * sizeof(RGBA_t)));
 		textureBufferSize = size;
 	}
+	return textureBuffer;
 }
 
 // -----------------+
 // UpdateTexture    : Updates texture data.
 // -----------------+
-EXPORT void HWRAPI(UpdateTexture) (GLMipmap_t *pTexInfo)
+EXPORT void HWRAPI(UpdateTextureRegion) (GLMipmap_t *pTexInfo, INT32 x, INT32 y, INT32 width, INT32 height)
 {
-	// Upload a texture
-	GLuint num = pTexInfo->downloaded;
 	boolean update = true;
 
-	INT32 w = pTexInfo->width, h = pTexInfo->height;
-	INT32 i, j;
+	INT32 i, j, dy;
 
 	const GLubyte *pImgData = (const GLubyte *)pTexInfo->data;
 	const GLvoid *ptex = NULL;
-	RGBA_t *tex = NULL;
 
-	// Generate a new texture name.
-	if (!num)
+	// Generate a new texture ID if there is none
+	if (pTexInfo->downloaded == 0)
 	{
-		pglGenTextures(1, &num);
-		pTexInfo->downloaded = num;
+		GLuint id = 0;
+		pglGenTextures(1, &id);
+		pTexInfo->downloaded = id;
 		update = false;
 	}
 
-	//GL_DBG_Printf("UpdateTexture %d %x\n", (INT32)num, pImgData);
-
-	if ((pTexInfo->format == GL_TEXFMT_P_8) || (pTexInfo->format == GL_TEXFMT_AP_88))
+	if (pTexInfo->format == GL_TEXFMT_P_8 || pTexInfo->format == GL_TEXFMT_AP_88)
 	{
-		AllocTextureBuffer(pTexInfo);
-		ptex = tex = textureBuffer;
+		ptex = AllocTextureBuffer(width, height);
 
-		for (j = 0; j < h; j++)
+		for (j = y, dy = 0; j < y + height; j++, dy++)
 		{
-			for (i = 0; i < w; i++)
+			const GLubyte *src = &pImgData[pTexInfo->width*j+x];
+			RGBA_t *tex = &textureBuffer[width*dy];
+
+			for (i = x; i < x + width; i++)
 			{
-				if ((*pImgData == HWR_PATCHES_CHROMAKEY_COLORINDEX) &&
+				if ((*src == HWR_PATCHES_CHROMAKEY_COLORINDEX) &&
 					(pTexInfo->flags & TF_CHROMAKEYED))
 				{
-					tex[w*j+i].s.red   = 0;
-					tex[w*j+i].s.green = 0;
-					tex[w*j+i].s.blue  = 0;
-					tex[w*j+i].s.alpha = 0;
+					tex->s.red   = 0;
+					tex->s.green = 0;
+					tex->s.blue  = 0;
+					tex->s.alpha = 0;
 					pTexInfo->flags |= TF_TRANSPARENT; // there is a hole in it
 				}
 				else
 				{
-					tex[w*j+i].s.red   = myPaletteData[*pImgData].s.red;
-					tex[w*j+i].s.green = myPaletteData[*pImgData].s.green;
-					tex[w*j+i].s.blue  = myPaletteData[*pImgData].s.blue;
-					tex[w*j+i].s.alpha = myPaletteData[*pImgData].s.alpha;
+					tex->s.red   = myPaletteData[*src].s.red;
+					tex->s.green = myPaletteData[*src].s.green;
+					tex->s.blue  = myPaletteData[*src].s.blue;
+					tex->s.alpha = myPaletteData[*src].s.alpha;
 				}
 
-				pImgData++;
+				src++;
 
 				if (pTexInfo->format == GL_TEXFMT_AP_88)
 				{
 					if (!(pTexInfo->flags & TF_CHROMAKEYED))
-						tex[w*j+i].s.alpha = *pImgData;
-					pImgData++;
+						tex->s.alpha = *src;
+					src++;
 				}
+
+				tex++;
 			}
 		}
 	}
 	else if (pTexInfo->format == GL_TEXFMT_RGBA)
 	{
-		// Directly upload the texture data without any kind of conversion.
-		ptex = pImgData;
+		if (x == 0 && y == 0 && width == pTexInfo->width && height == pTexInfo->height)
+		{
+			// Directly upload the texture data without any kind of conversion.
+			ptex = pImgData;
+		}
+		else
+		{
+			RGBA_t *src_pixels = (RGBA_t *)pTexInfo->data;
+
+			ptex = AllocTextureBuffer(width, height);
+
+			for (j = y, dy = 0; j < y + height; j++, dy++)
+			{
+				RGBA_t *src = &src_pixels[pTexInfo->width*j+x];
+				RGBA_t *tex = &textureBuffer[width*dy];
+
+				for (i = x; i < x + width; i++)
+				{
+					*tex = *src;
+					tex++;
+					src++;
+				}
+			}
+		}
 	}
 	else if (pTexInfo->format == GL_TEXFMT_ALPHA_INTENSITY_88)
 	{
-		AllocTextureBuffer(pTexInfo);
-		ptex = tex = textureBuffer;
+		ptex = AllocTextureBuffer(width, height);
 
-		for (j = 0; j < h; j++)
+		for (j = y, dy = 0; j < y + height; j++, dy++)
 		{
-			for (i = 0; i < w; i++)
+			const GLubyte *src = &pImgData[pTexInfo->width*j+x];
+			RGBA_t *tex = &textureBuffer[width*dy];
+			for (i = x; i < x + width; i++)
 			{
-				tex[w*j+i].s.red   = *pImgData;
-				tex[w*j+i].s.green = *pImgData;
-				tex[w*j+i].s.blue  = *pImgData;
-				pImgData++;
-				tex[w*j+i].s.alpha = *pImgData;
-				pImgData++;
+				tex->s.red   = *src;
+				tex->s.green = *src;
+				tex->s.blue  = *src;
+				src++;
+				tex->s.alpha = *src;
+				src++;
+				tex++;
 			}
 		}
 	}
 	else if (pTexInfo->format == GL_TEXFMT_ALPHA_8) // Used for fade masks
 	{
-		AllocTextureBuffer(pTexInfo);
-		ptex = tex = textureBuffer;
+		ptex = AllocTextureBuffer(width, height);
 
-		for (j = 0; j < h; j++)
+		for (j = y, dy = 0; j < y + height; j++, dy++)
 		{
-			for (i = 0; i < w; i++)
+			const GLubyte *src = &pImgData[width*j+x];
+			RGBA_t *tex = &textureBuffer[width*dy];
+			for (i = x; i < x + width; i++)
 			{
-				tex[w*j+i].s.red   = 255; // 255 because the fade mask is modulated with the screen texture, so alpha affects it while the colours don't
-				tex[w*j+i].s.green = 255;
-				tex[w*j+i].s.blue  = 255;
-				tex[w*j+i].s.alpha = *pImgData;
-				pImgData++;
+				// 255 because the fade mask is modulated with the screen texture, so alpha affects it while the colours don't
+				tex->s.red   = 255;
+				tex->s.green = 255;
+				tex->s.blue  = 255;
+				tex->s.alpha = *src;
+				src++;
+				tex++;
 			}
 		}
 	}
 	else
 		GL_MSG_Warning("UpdateTexture: bad format %d\n", pTexInfo->format);
 
-	pglBindTexture(GL_TEXTURE_2D, num);
-	tex_downloaded = num;
+	pglBindTexture(GL_TEXTURE_2D, pTexInfo->downloaded);
+	tex_downloaded = pTexInfo->downloaded;
 
 	// disable texture filtering on any texture that has holes so there's no dumb borders or blending issues
 	if (pTexInfo->flags & TF_TRANSPARENT)
@@ -1881,64 +1905,60 @@ EXPORT void HWRAPI(UpdateTexture) (GLMipmap_t *pTexInfo)
 
 	if (pTexInfo->format == GL_TEXFMT_ALPHA_INTENSITY_88)
 	{
-		//pglTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 		if (MipMap)
 		{
-			pgluBuild2DMipmaps(GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, w, h, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
+			pgluBuild2DMipmaps(GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, pTexInfo->width, pTexInfo->height, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0);
 			if (pTexInfo->flags & TF_TRANSPARENT)
-				pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0); // No mippmaps on transparent stuff
+				pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0); // No mipmaps on transparent stuff
 			else
 				pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 4);
-			//pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_LINEAR_MIPMAP_LINEAR);
 		}
 		else
 		{
 			if (update)
-				pglTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
+				pglTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 			else
-				pglTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
+				pglTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, pTexInfo->width, pTexInfo->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 		}
 	}
 	else if (pTexInfo->format == GL_TEXFMT_ALPHA_8)
 	{
-		//pglTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 		if (MipMap)
 		{
-			pgluBuild2DMipmaps(GL_TEXTURE_2D, GL_ALPHA, w, h, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
+			pgluBuild2DMipmaps(GL_TEXTURE_2D, GL_ALPHA, pTexInfo->width, pTexInfo->height, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0);
 			if (pTexInfo->flags & TF_TRANSPARENT)
-				pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0); // No mippmaps on transparent stuff
+				pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0); // No mipmaps on transparent stuff
 			else
 				pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 4);
-			//pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_LINEAR_MIPMAP_LINEAR);
 		}
 		else
 		{
 			if (update)
-				pglTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
+				pglTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 			else
-				pglTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
+				pglTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, pTexInfo->width, pTexInfo->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 		}
 	}
 	else
 	{
 		if (MipMap)
 		{
-			pgluBuild2DMipmaps(GL_TEXTURE_2D, textureformatGL, w, h, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
+			pgluBuild2DMipmaps(GL_TEXTURE_2D, textureformatGL, pTexInfo->width, pTexInfo->height, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 			// Control the mipmap level of detail
-			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0); // the lower the number, the higer the detail
+			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0); // the lower the number, the higher the detail
 			if (pTexInfo->flags & TF_TRANSPARENT)
-				pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0); // No mippmaps on transparent stuff
+				pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0); // No mipmaps on transparent stuff
 			else
 				pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 5);
 		}
 		else
 		{
 			if (update)
-				pglTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
+				pglTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 			else
-				pglTexImage2D(GL_TEXTURE_2D, 0, textureformatGL, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
+				pglTexImage2D(GL_TEXTURE_2D, 0, textureformatGL, pTexInfo->width, pTexInfo->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 		}
 	}
 
@@ -1956,6 +1976,11 @@ EXPORT void HWRAPI(UpdateTexture) (GLMipmap_t *pTexInfo)
 		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropic_filter);
 }
 
+EXPORT void HWRAPI(UpdateTexture) (GLMipmap_t *pTexInfo)
+{
+	UpdateTextureRegion(pTexInfo, 0, 0, pTexInfo->width, pTexInfo->height);
+}
+
 // -----------------+
 // SetTexture       : The mipmap becomes the current texture source
 // -----------------+
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index de5cc77537ba0f7f275a9e9d33f07bce1505ef1d..cf7d0343b343d471cca00ee0b8ba040cbe3002c1 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -424,7 +424,7 @@ static int lib_patch_copy(lua_State *L)
 		lua_pop(L, 2);
 	}
 
-	Patch_Update(patch, patch_update_buffer, src_img_width, src_img_height, PICFMT_FLAT16, sx, sy, sw, sh, dx, dy, 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;
 }
diff --git a/src/r_defs.h b/src/r_defs.h
index 66f699624d53969c72453df87c323c278cebadfe..598408e659530689afe6234280e52b9c46947f37 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -809,15 +809,14 @@ typedef struct
 //
 typedef struct
 {
+	UINT8 type;
+
 	INT16 width, height;
 	INT16 leftoffset, topoffset;
 	INT32 width_mask;
 
 	UINT8 *pixels;
 	column_t *columns;
-	post_t *posts;
-
-	UINT8 type;
 
 	void *hardware; // OpenGL patch, allocated whenever necessary
 	void *flats[4]; // The patch as flats
@@ -827,6 +826,25 @@ typedef struct
 #endif
 } patch_t;
 
+typedef struct
+{
+	patch_t patch;
+
+	post_t *posts;
+} staticpatch_t;
+
+typedef struct
+{
+	patch_t patch;
+
+	boolean is_dirty;
+	boolean update_columns;
+
+	INT16 rect_dirty[4]; // left, top, right, bottom
+
+	bitarray_t *pixels_opaque;
+} dynamicpatch_t;
+
 #if defined(_MSC_VER)
 #pragma pack(1)
 #endif
diff --git a/src/r_patch.c b/src/r_patch.c
index 1257deb73d14343abd10fb409085a11ae4e7f1dc..dab600ed10844b48c9c9ee79d999b66a232b749b 100644
--- a/src/r_patch.c
+++ b/src/r_patch.c
@@ -19,13 +19,12 @@
 #include "hardware/hw_glob.h"
 #endif
 
-//
-// Creates a patch.
-//
+static void Patch_MarkDirtyRect(dynamicpatch_t *dpatch, INT16 left, INT16 top, INT16 right, INT16 bottom);
+static void Patch_ClearDirtyRect(dynamicpatch_t *dpatch);
 
 patch_t *Patch_Create(INT16 width, INT16 height)
 {
-	patch_t *patch = Z_Calloc(sizeof(patch_t), PU_PATCH, NULL);
+	patch_t *patch = Z_Calloc(sizeof(staticpatch_t), PU_PATCH, NULL);
 
 	patch->width = width;
 	patch->height = height;
@@ -36,18 +35,57 @@ patch_t *Patch_Create(INT16 width, INT16 height)
 
 patch_t *Patch_CreateDynamic(INT16 width, INT16 height)
 {
-	patch_t *patch = Z_Calloc(sizeof(patch_t), PU_PATCH, NULL);
+	patch_t *patch = Z_Calloc(sizeof(dynamicpatch_t), PU_PATCH, NULL);
 
 	patch->width = width;
 	patch->height = height;
 	patch->type = PATCH_TYPE_DYNAMIC;
 
+	dynamicpatch_t *dpatch = (dynamicpatch_t*)patch;
+	dpatch->pixels_opaque = Z_Calloc(BIT_ARRAY_SIZE(width * height), PU_PATCH_DATA, NULL);
+	dpatch->is_dirty = false;
+	dpatch->update_columns = false;
+
+	Patch_ClearDirtyRect(dpatch);
+
 	return patch;
 }
 
+static void Patch_MarkDirtyRect(dynamicpatch_t *dpatch, INT16 left, INT16 top, INT16 right, INT16 bottom)
+{
+	if (left < dpatch->rect_dirty[0])
+		dpatch->rect_dirty[0] = left;
+	if (top < dpatch->rect_dirty[1])
+		dpatch->rect_dirty[1] = top;
+	if (right > dpatch->rect_dirty[2])
+		dpatch->rect_dirty[2] = right;
+	if (bottom > dpatch->rect_dirty[3])
+		dpatch->rect_dirty[3] = bottom;
+}
+
+static void Patch_ClearDirtyRect(dynamicpatch_t *dpatch)
+{
+	dpatch->rect_dirty[0] = INT16_MAX;
+	dpatch->rect_dirty[1] = INT16_MAX;
+	dpatch->rect_dirty[2] = INT16_MIN;
+	dpatch->rect_dirty[3] = INT16_MIN;
+}
+
+static void Patch_InitDynamicColumns(patch_t *patch)
+{
+	if (patch->columns == NULL)
+		patch->columns = Z_Calloc(sizeof(column_t) * patch->width, PU_PATCH_DATA, NULL);
+
+	for (INT32 x = 0; x < patch->width; x++)
+	{
+		column_t *column = &patch->columns[x];
+		column->pixels = &patch->pixels[patch->height * x];
+	}
+}
+
 patch_t *Patch_CreateFromDoomPatch(softwarepatch_t *source)
 {
-	patch_t *patch = Patch_Create(0, 0);
+	patch_t *patch = (patch_t*)Patch_Create(0, 0);
 	if (!source)
 		return patch;
 
@@ -67,10 +105,12 @@ patch_t *Patch_CreateFromDoomPatch(softwarepatch_t *source)
 	patch->width_mask = width_po2 - 1;
 
 	patch->columns = Z_Calloc(sizeof(column_t) * patch->width, PU_PATCH_DATA, NULL);
-	patch->posts = Z_Calloc(sizeof(post_t) * total_posts, PU_PATCH_DATA, NULL);
 	patch->pixels = Z_Calloc(sizeof(UINT8) * total_pixels, PU_PATCH_DATA, NULL);
 
-	Patch_MakeColumns(source, patch->width, patch->width, patch->pixels, patch->columns, patch->posts, false);
+	staticpatch_t *spatch = (staticpatch_t*)patch;
+	spatch->posts = Z_Calloc(sizeof(post_t) * total_posts, PU_PATCH_DATA, NULL);
+
+	Patch_MakeColumns(source, patch->width, patch->width, patch->pixels, patch->columns, spatch->posts, false);
 
 	return patch;
 }
@@ -141,9 +181,9 @@ void Patch_MakeColumns(softwarepatch_t *source, size_t num_columns, INT16 width,
 // Other functions
 //
 
-static void Patch_RebuildColumn(column_t *column, INT16 height, UINT8 *is_opaque)
+static void Patch_RebuildColumn(column_t *column, INT32 x, INT16 height, bitarray_t *is_opaque)
 {
-	post_t *post;
+	post_t *post = NULL;
 	boolean was_opaque = false;
 
 	unsigned post_count = column->num_posts;
@@ -152,7 +192,7 @@ static void Patch_RebuildColumn(column_t *column, INT16 height, UINT8 *is_opaque
 	for (INT32 y = 0; y < height; y++)
 	{
 		// End span if we have a transparent pixel
-		if (!is_opaque[y])
+		if (!in_bit_array(is_opaque, (x * height) + y))
 		{
 			was_opaque = false;
 			continue;
@@ -195,7 +235,13 @@ column_t *Patch_GetColumn(patch_t *patch, unsigned column)
 
 void *Patch_GetPixel(patch_t *patch, INT32 x, INT32 y)
 {
-	if (x < 0 || x >= patch->width || patch->columns == NULL)
+	if (x < 0 || x >= patch->width)
+		return NULL;
+
+	if (Patch_NeedsUpdate(patch))
+		Patch_DoDynamicUpdate(patch);
+
+	if (patch->columns == NULL)
 		return NULL;
 
 	column_t *column = &patch->columns[x];
@@ -249,12 +295,8 @@ void Patch_SetPixel(patch_t *patch, void *pixel, pictureformat_t informat, INT32
 	else if (inbpp == PICDEPTH_8BPP)
 		writePixelFunc = PicFmt_WritePixel_i8o8;
 
-	column_t *column = NULL;
-
-	boolean did_update_column = false;
-
 	// If the patch is empty
-	if (!patch->columns)
+	if (!patch->pixels)
 	{
 		if (!pixel_is_opaque)
 		{
@@ -264,167 +306,39 @@ void Patch_SetPixel(patch_t *patch, void *pixel, pictureformat_t informat, INT32
 
 		if (patch->pixels == NULL)
 			patch->pixels = Z_Calloc(patch->width * patch->height, PU_PATCH_DATA, NULL);
-		if (patch->columns == NULL)
-			patch->columns = Z_Calloc(sizeof(column_t) * patch->width, PU_PATCH_DATA, NULL);
-
-		column = &patch->columns[x];
-		column->pixels = &patch->pixels[patch->height * x];
 	}
-	else
-	{
-		column = &patch->columns[x];
-		column->pixels = &patch->pixels[patch->height * x];
 
-		for (unsigned i = 0; i < column->num_posts; i++)
-		{
-			post_t *post = &column->posts[i];
-
-			int this_topdelta = (int)post->topdelta;
-			int next_topdelta;
-			if (i < column->num_posts - 1)
-				next_topdelta = column->posts[i + 1].topdelta;
-			else
-				next_topdelta = patch->height;
+	dynamicpatch_t *dpatch = (dynamicpatch_t *)patch;
 
-			// Handle the case where this is the first post, and the pixel is before it
-			if (i == 0 && y < this_topdelta)
-			{
-				if (!pixel_is_opaque)
-				{
-					// If the pixel is transparent, ignore
-					continue;
-				}
-
-				column->posts = Z_Realloc(column->posts, sizeof(post_t) * (column->num_posts + 1), PU_PATCH_DATA, NULL);
-
-				memmove(&column->posts[1], &column->posts[0], column->num_posts * sizeof(post_t));
-
-				column->num_posts++;
-
-				post_t *dest_post = &column->posts[0];
-				dest_post->topdelta = (unsigned)y;
-				dest_post->length = 1;
-				dest_post->data_offset = dest_post->topdelta;
-
-				writePixelFunc(&column->pixels[dest_post->data_offset], pixel);
-
-				did_update_column = true;
-
-				break;
-			}
-			// Pixel is inside a post
-			else if (y >= this_topdelta && y < this_topdelta + (signed)post->length)
-			{
-				// Handle the case where the pixel needs to be removed
-				if (!pixel_is_opaque)
-				{
-					if (!transparent_overwrite)
-						continue;
-
-					int resulting_post_length = y - post->topdelta;
-					if ((resulting_post_length == (signed)post->length - 1 && i == column->num_posts - 1)
-					|| (resulting_post_length == 0))
-					{
-						if (resulting_post_length == 0)
-							post->topdelta++;
-						post->length--;
-						if (post->length == 0)
-						{
-							if (column->num_posts > 1 && i < column->num_posts - 1)
-								memmove(&column->posts[i], &column->posts[i + 1], (column->num_posts - i) * sizeof(post_t));
-							column->num_posts--;
-						}
-					}
-					else
-					{
-						column->num_posts++;
-						column->posts = Z_Realloc(column->posts, sizeof(post_t) * column->num_posts, PU_PATCH_DATA, NULL);
-
-						if (i != column->num_posts)
-							memmove(&column->posts[i + 1], &column->posts[i], ((column->num_posts - 1) - i) * sizeof(post_t));
-
-						column->posts[i + 1].length = column->posts[i].length - resulting_post_length - 1;
-						column->posts[i + 1].topdelta = y + 1;
-						column->posts[i + 1].data_offset = column->posts[i + 1].topdelta;
-						column->posts[i].length = resulting_post_length;
-					}
-				}
-				else
-					writePixelFunc(&column->pixels[post->data_offset + (y - post->topdelta)], pixel);
-
-				did_update_column = true;
-
-				break;
-			}
-			// After this post, but before the next one
-			else if (y >= this_topdelta + (signed)post->length && y < next_topdelta)
-			{
-				if (!pixel_is_opaque)
-				{
-					// If the pixel is transparent, ignore
-					continue;
-				}
-
-				post_t *dest_post = NULL;
-
-				column->posts = Z_Realloc(column->posts, sizeof(post_t) * (column->num_posts + 1), PU_PATCH_DATA, NULL);
-
-				if (i == column->num_posts - 1)
-					dest_post = &column->posts[column->num_posts];
-				else
-				{
-					memmove(&column->posts[i + 1], &column->posts[i], (column->num_posts - i) * sizeof(post_t));
-					dest_post = &column->posts[i];
-				}
-
-				column->num_posts++;
-
-				dest_post->topdelta = (unsigned)y;
-				dest_post->length = 1;
-				dest_post->data_offset = dest_post->topdelta;
-
-				writePixelFunc(&column->pixels[dest_post->data_offset], pixel);
-
-				did_update_column = true;
-
-				break;
-			}
-		}
-	}
+	size_t position = (x * patch->height) + y;
 
-	if (!did_update_column && column && column->num_posts == 0)
+	if (!pixel_is_opaque)
 	{
-		if (!pixel_is_opaque)
+		if (transparent_overwrite)
 		{
-			// If the pixel is transparent, do nothing
-			return;
+			// No longer a pixel in this position, so columns need to be rebuilt
+			unset_bit_array(dpatch->pixels_opaque, position);
+			dpatch->update_columns = true;
+			dpatch->is_dirty = true;
 		}
-
-		column->num_posts = 1;
-		column->posts = Z_Realloc(column->posts, sizeof(post_t) * column->num_posts, PU_PATCH_DATA, NULL);
-
-		post_t *post = &column->posts[0];
-		post->topdelta = (unsigned)y;
-		post->length = 1;
-		post->data_offset = post->topdelta;
-
-		writePixelFunc(&column->pixels[post->data_offset], pixel);
-
-		did_update_column = true;
 	}
-
-	if (did_update_column)
+	else
 	{
-		Patch_FreeMiscData(patch);
+		// No pixel in this position, so columns need to be rebuilt
+		if (!in_bit_array(dpatch->pixels_opaque, position))
+			dpatch->update_columns = true;
 
-#ifdef HWRENDER
-		if (patch->hardware)
-			HWR_UpdatePatch(patch);
-#endif
+		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);
 }
 
-void Patch_Update(patch_t *patch,
+void Patch_UpdatePixels(patch_t *patch,
 	void *pixels, INT32 src_img_width, INT32 src_img_height,
 	pictureformat_t informat,
 	INT32 sx, INT32 sy, INT32 sw, INT32 sh, INT32 dx, INT32 dy,
@@ -436,10 +350,6 @@ void Patch_Update(patch_t *patch,
 	if (src_img_width <= 0 || src_img_height <= 0 || sw <= 0 || sh <= 0)
 		return;
 
-	boolean did_update = false;
-
-	if (patch->columns == NULL)
-		patch->columns = Z_Calloc(sizeof(column_t) * patch->width, PU_PATCH_DATA, NULL);
 	if (patch->pixels == NULL)
 		patch->pixels = Z_Calloc(patch->width * patch->height, PU_PATCH_DATA, NULL);
 
@@ -467,35 +377,34 @@ void Patch_Update(patch_t *patch,
 		}
 	}
 
-	if (readPixelFunc == NULL)
-		I_Error("Patch_Update: unsupported input format");
-
-	INT32 inbpp = Picture_FormatBPP(informat);
-
-	if (inbpp == PICDEPTH_32BPP)
+	switch (Picture_FormatBPP(informat))
 	{
+	case PICDEPTH_32BPP:
 		getAlphaFunc = PicFmt_GetAlpha_32bpp;
 		writePixelFunc = PicFmt_WritePixel_i32o8;
-	}
-	else if (inbpp == PICDEPTH_16BPP)
-	{
+		break;
+	case PICDEPTH_16BPP:
 		getAlphaFunc = PicFmt_GetAlpha_16bpp;
 		writePixelFunc = PicFmt_WritePixel_i16o8;
-	}
-	else if (inbpp == PICDEPTH_8BPP)
-	{
+		break;
+	case PICDEPTH_8BPP:
 		getAlphaFunc = PicFmt_GetAlpha_8bpp;
 		writePixelFunc = PicFmt_WritePixel_i8o8;
+		break;
 	}
 
-	UINT8 *is_opaque = malloc(patch->height * sizeof(UINT8));
-	if (!is_opaque)
-	{
-		abort();
-		return;
-	}
+	if (readPixelFunc == NULL || writePixelFunc == NULL || getAlphaFunc == NULL)
+		I_Error("Patch_UpdatePixels: unsupported input format");
+
+	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);
 
-	// Write columns
 	for (INT32 x = dx; x < dx + sw; x++, sx++)
 	{
 		if (x < 0 || sx < 0)
@@ -503,19 +412,6 @@ void Patch_Update(patch_t *patch,
 		else if (x >= patch->width || sx >= src_img_width)
 			break;
 
-		column_t *column = &patch->columns[x];
-		column->pixels = &patch->pixels[patch->height * x];
-
-		memset(is_opaque, 0, patch->height * sizeof(UINT8));
-
-		for (unsigned i = 0; i < column->num_posts; i++)
-		{
-			post_t *post = &column->posts[i];
-			memset(&is_opaque[post->topdelta], 1, post->length);
-		}
-
-		boolean did_update_column = false;
-
 		INT32 src_y = sy;
 
 		for (INT32 y = dy; y < dy + sh; y++, src_y++)
@@ -534,39 +430,73 @@ void Patch_Update(patch_t *patch,
 			if (input != NULL)
 				opaque = getAlphaFunc(input, 0) > 0;
 
+			size_t position = (x * patch->height) + y;
+
 			if (!opaque)
 			{
 				if (transparent_overwrite)
 				{
-					is_opaque[y] = false;
-					did_update = did_update_column = true;
+					unset_bit_array(dpatch->pixels_opaque, position);
+					dpatch->update_columns = dpatch->is_dirty = true;
 				}
 			}
 			else
 			{
-				did_update = did_update_column = true;
-				is_opaque[y] = true;
-				writePixelFunc(&column->pixels[y], input);
+				set_bit_array(dpatch->pixels_opaque, position);
+				writePixelFunc(&patch->pixels[position], input);
+				dpatch->update_columns = dpatch->is_dirty = true;
 			}
 		}
+	}
+}
 
-		if (!did_update_column)
-			continue;
+boolean Patch_NeedsUpdate(patch_t *patch)
+{
+	if (patch->type != PATCH_TYPE_DYNAMIC)
+		return false;
 
-		Patch_RebuildColumn(column, patch->height, is_opaque);
-	}
+	dynamicpatch_t *dpatch = (dynamicpatch_t *)patch;
+	return dpatch->is_dirty;
+}
 
-	if (did_update)
+void Patch_DoDynamicUpdate(patch_t *patch)
+{
+	if (patch->type != PATCH_TYPE_DYNAMIC)
+		return;
+
+	dynamicpatch_t *dpatch = (dynamicpatch_t *)patch;
+	if (!dpatch->is_dirty)
+		return;
+
+	if (patch->columns == NULL)
+		Patch_InitDynamicColumns(patch);
+
+	if (dpatch->update_columns)
 	{
-		Patch_FreeMiscData(patch);
+		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);
 
 #ifdef HWRENDER
-		if (patch->hardware)
-			HWR_UpdatePatch(patch);
+	if (patch->hardware)
+		HWR_UpdatePatchRegion(patch, dpatch->rect_dirty[0], dpatch->rect_dirty[1], dpatch->rect_dirty[2], dpatch->rect_dirty[3]);
 #endif
-	}
 
-	free(is_opaque);
+	dpatch->is_dirty = false;
+	dpatch->update_columns = false;
+
+	Patch_ClearDirtyRect(dpatch);
+}
+
+bitarray_t *Patch_GetOpaqueRegions(patch_t *patch)
+{
+	if (patch->type != PATCH_TYPE_DYNAMIC)
+		return NULL;
+
+	dynamicpatch_t *dpatch = (dynamicpatch_t *)patch;
+	return dpatch->pixels_opaque;
 }
 
 //
@@ -606,18 +536,21 @@ static void Patch_FreeData(patch_t *patch)
 
 	Patch_FreeMiscData(patch);
 
-	if (patch->type == PATCH_TYPE_DYNAMIC)
+	if (patch->type == PATCH_TYPE_STATIC)
+	{
+		staticpatch_t *spatch = (staticpatch_t*)patch;
+		Z_Free(spatch->posts);
+	}
+	else if (patch->type == PATCH_TYPE_DYNAMIC)
 	{
-		for (INT32 x = 0; x < patch->width; x++)
-			Z_Free(patch->columns[x].posts);
+		dynamicpatch_t *dpatch = (dynamicpatch_t*)patch;
+		for (INT32 x = 0; x < dpatch->patch.width; x++)
+			Z_Free(dpatch->patch.columns[x].posts);
+		Z_Free(dpatch->pixels_opaque);
 	}
 
-	if (patch->pixels)
-		Z_Free(patch->pixels);
-	if (patch->columns)
-		Z_Free(patch->columns);
-	if (patch->posts)
-		Z_Free(patch->posts);
+	Z_Free(patch->pixels);
+	Z_Free(patch->columns);
 }
 
 void Patch_Free(patch_t *patch)
diff --git a/src/r_patch.h b/src/r_patch.h
index 1b8fd8f7060150f6da4ddf862390e526d75a381a..9ee5892fad59c319601c20cfb3770aacd540c398 100644
--- a/src/r_patch.h
+++ b/src/r_patch.h
@@ -29,12 +29,17 @@ patch_t *Patch_CreateDynamic(INT16 width, INT16 height);
 
 void *Patch_GetPixel(patch_t *patch, INT32 x, INT32 y);
 void Patch_SetPixel(patch_t *patch, void *pixel, pictureformat_t informat, INT32 x, INT32 y, boolean transparent_overwrite);
-void Patch_Update(patch_t *patch,
+void Patch_UpdatePixels(patch_t *patch,
 	void *pixels, INT32 src_img_width, INT32 src_img_height,
 	pictureformat_t informat,
 	INT32 sx, INT32 sy, INT32 sw, INT32 sh, INT32 dx, INT32 dy,
 	boolean transparent_overwrite);
 
+boolean Patch_NeedsUpdate(patch_t *patch);
+void Patch_DoDynamicUpdate(patch_t *patch);
+
+bitarray_t *Patch_GetOpaqueRegions(patch_t *patch);
+
 void Patch_Free(patch_t *patch);
 void Patch_FreeMiscData(patch_t *patch);
 
diff --git a/src/r_picformats.c b/src/r_picformats.c
index c2eb16355643987cbf4c44bbd4f4d92b3cb5c7de..ba63ce95bdaa71942859600fcd77c6405b5ffcf8 100644
--- a/src/r_picformats.c
+++ b/src/r_picformats.c
@@ -364,7 +364,6 @@ void *Picture_PatchConvert(
 
 	out->columns = Z_Calloc(sizeof(column_t) * out->width, PU_PATCH_DATA, NULL);
 	out->pixels = Z_Calloc(max_pixels * (outbpp / 8), PU_PATCH_DATA, NULL);
-	out->posts = NULL;
 
 	UINT8 *imgptr = out->pixels;
 
@@ -407,8 +406,9 @@ void *Picture_PatchConvert(
 			{
 				num_posts++;
 
-				out->posts = Z_Realloc(out->posts, sizeof(post_t) * num_posts, PU_PATCH_DATA, NULL);
-				post = &out->posts[num_posts - 1];
+				staticpatch_t *spatch = (staticpatch_t*)out;
+				spatch->posts = Z_Realloc(spatch->posts, sizeof(post_t) * num_posts, PU_PATCH_DATA, NULL);
+				post = &spatch->posts[num_posts - 1];
 				post->topdelta = (unsigned)y;
 				post->length = 0;
 				post->data_offset = post_data_offset;
@@ -437,7 +437,10 @@ void *Picture_PatchConvert(
 	{
 		column_t *column = &out->columns[x];
 		if (column->num_posts > 0)
-			column->posts = &out->posts[column_posts[x]];
+		{
+			staticpatch_t *spatch = (staticpatch_t*)out;
+			column->posts = &spatch->posts[column_posts[x]];
+		}
 		if (old_pixels != out->pixels)
 			column->pixels = out->pixels + (column->pixels - old_pixels);
 	}
diff --git a/src/sdl/hwsym_sdl.c b/src/sdl/hwsym_sdl.c
index 96e3d7d6926ef23771c8dcf489b4d8d2a16c0a1c..40f88a22c379bb91a5fadd2cbb2fafbb6fb4a9ae 100644
--- a/src/sdl/hwsym_sdl.c
+++ b/src/sdl/hwsym_sdl.c
@@ -86,6 +86,7 @@ void *hwSym(const char *funcName,void *handle)
 	GETFUNC(ClearBuffer);
 	GETFUNC(SetTexture);
 	GETFUNC(UpdateTexture);
+	GETFUNC(UpdateTextureRegion);
 	GETFUNC(DeleteTexture);
 	GETFUNC(ReadRect);
 	GETFUNC(GClipRect);
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 590d7d142a7a536678bfb96d3891c78264a19fba..d33c2b0d09f57c50f868f1dd90c0130944b855e6 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1934,6 +1934,7 @@ void VID_StartupOpenGL(void)
 		HWD.pfnClearBuffer      = hwSym("ClearBuffer",NULL);
 		HWD.pfnSetTexture       = hwSym("SetTexture",NULL);
 		HWD.pfnUpdateTexture    = hwSym("UpdateTexture",NULL);
+		HWD.pfnUpdateTextureRegion = hwSym("UpdateTextureRegion", NULL);
 		HWD.pfnDeleteTexture    = hwSym("DeleteTexture",NULL);
 		HWD.pfnReadRect         = hwSym("ReadRect",NULL);
 		HWD.pfnGClipRect        = hwSym("GClipRect",NULL);
diff --git a/src/v_video.c b/src/v_video.c
index 4b2cb5dfb2907d0eb408910a3059d6b916493571..150bd0d338754b574fbd44b60193933b1b6a5d0a 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -514,6 +514,9 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 
 	UINT8 perplayershuffle = 0;
 
+	if (Patch_NeedsUpdate(patch))
+		Patch_DoDynamicUpdate(patch);
+
 	if (patch->columns == NULL || rendermode == render_none)
 		return;
 
@@ -798,6 +801,9 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, IN
 
 	UINT8 perplayershuffle = 0;
 
+	if (Patch_NeedsUpdate(patch))
+		Patch_DoDynamicUpdate(patch);
+
 	if (patch->columns == NULL || rendermode == render_none)
 		return;