diff --git a/src/deh_tables.c b/src/deh_tables.c
index cfc98f631e9d475f403e104425eae189949157bf..e1f4936a8737245c6ad6fa7c08d224b7eb20925c 100644
--- a/src/deh_tables.c
+++ b/src/deh_tables.c
@@ -4921,6 +4921,7 @@ struct int_const_s const INT_CONST[] = {
 	{"RF_SHADOWDRAW",RF_SHADOWDRAW},
 	{"RF_SHADOWEFFECTS",RF_SHADOWEFFECTS},
 	{"RF_DROPSHADOW",RF_DROPSHADOW},
+	{"RF_PORTALSPRITE",RF_PORTALSPRITE},
 
 	// Level flags
 	{"LF_SCRIPTISFILE",LF_SCRIPTISFILE},
diff --git a/src/doomdef.h b/src/doomdef.h
index 46bd5df159090564c53c3182adb2deb8d6e8f39a..bc06f79bc76d9521c978b89e90e9e48a1bdba1ec 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -540,9 +540,6 @@ INT32 I_GetKey(void);
 // Max gamepad/joysticks that can be detected/used.
 #define MAX_JOYSTICKS 4
 
-// Software multithreading
-#define MAX_RENDER_CONTEXTS 8
-
 #ifndef M_PIl
 #define M_PIl 3.1415926535897932384626433832795029L
 #endif
diff --git a/src/r_context.h b/src/r_context.h
index edf01f2bdcea8c82f1773f5f2e702cfed7d86ac8..cd8389d667d6e7ae55f4d070d7e3de43417cce7a 100644
--- a/src/r_context.h
+++ b/src/r_context.h
@@ -168,13 +168,13 @@ typedef struct wallcontext_s
 
 typedef struct rendercontext_s
 {
-	// Setup
 	vbuffer_t buffer;
-	INT32 num;
 
 	INT32 begincolumn;
 	INT32 endcolumn;
 
+	INT16 *sprtopclip, *sprbotclip;
+
 	viewcontext_t viewcontext;
 	bspcontext_t bspcontext;
 	planecontext_t planecontext;
diff --git a/src/r_defs.h b/src/r_defs.h
index e3aa6f5f2e6eb365f4f6c35d920a41e5d86e9568..865c646d2aea44ed26ca70f3c0c9b9c580d79081 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -30,6 +30,8 @@
 
 #include "taglist.h"
 
+#define MAX_PORTAL_RECURSION 12
+
 //
 // ClipWallSegment
 // Clips the given range of columns
@@ -61,7 +63,10 @@ typedef UINT8 lighttable_t;
 
 typedef struct vbuffer_s
 {
+	UINT8 numscreens;
+	UINT8 *viewbuf, *waterbuf;
 	UINT8 *screens[5];
+	boolean isbasebuffer;
 } vbuffer_t;
 
 typedef struct {
@@ -757,6 +762,8 @@ typedef enum
 	RF_SHADOWDRAW       = 0x00004000,  // Stretches and skews the sprite like a shadow.
 	RF_SHADOWEFFECTS    = 0x00008000,  // Scales and becomes transparent like a shadow.
 	RF_DROPSHADOW       = (RF_SHADOWDRAW | RF_SHADOWEFFECTS | RF_FULLDARK),
+
+	RF_PORTALSPRITE     = 0x00010000
 } renderflags_t;
 
 typedef enum
diff --git a/src/r_draw.h b/src/r_draw.h
index c7355ad9207cb122198f701e77489609923d8557..991302d943e47bbe30ef9a09f814f738e7aa29b0 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -133,9 +133,6 @@ typedef struct colcontext_s
 	INT32 texheight;
 
 	// vars for R_DrawMaskedColumn
-	// Rum and Raisin put these on the sprite context.
-	// I put them on the column context because it felt
-	// more appropriate (for [REDACTED] anyway)
 	INT16 *mfloorclip;
 	INT16 *mceilingclip;
 
diff --git a/src/r_draw8.c b/src/r_draw8.c
index 104d207fd3360581e56ff4bd8238f0596f67c27a..de3cb997e0679ae96c41c9df67763c0c1e1b7ce6 100644
--- a/src/r_draw8.c
+++ b/src/r_draw8.c
@@ -40,11 +40,7 @@ void R_DrawColumn_8(colcontext_t *dc)
 #endif
 
 	// Framebuffer destination address.
-	// Use ylookup LUT to avoid multiply with ScreenWidth.
-	// Use columnofs LUT for subwindows?
-
-	//dest = ylookup[dc->yl] + columnofs[dc->x];
-	dest = &topleft[dc->yl*vid.width + dc->x];
+	dest = &dc->dest->viewbuf[dc->yl*vid.width + dc->x];
 
 	count++;
 
@@ -123,11 +119,7 @@ void R_Draw2sMultiPatchColumn_8(colcontext_t *dc)
 #endif
 
 	// Framebuffer destination address.
-	// Use ylookup LUT to avoid multiply with ScreenWidth.
-	// Use columnofs LUT for subwindows?
-
-	//dest = ylookup[dc->yl] + columnofs[dc->x];
-	dest = &topleft[dc->yl*vid.width + dc->x];
+	dest = &dc->dest->viewbuf[dc->yl*vid.width + dc->x];
 
 	count++;
 
@@ -219,11 +211,7 @@ void R_Draw2sMultiPatchTranslucentColumn_8(colcontext_t *dc)
 #endif
 
 	// Framebuffer destination address.
-	// Use ylookup LUT to avoid multiply with ScreenWidth.
-	// Use columnofs LUT for subwindows?
-
-	//dest = ylookup[dc->yl] + columnofs[dc->x];
-	dest = &topleft[dc->yl*vid.width + dc->x];
+	dest = &dc->dest->viewbuf[dc->yl*vid.width + dc->x];
 
 	count++;
 
@@ -321,12 +309,10 @@ void R_DrawShadeColumn_8(colcontext_t *dc)
 #endif
 
 	// FIXME. As above.
-	//dest = ylookup[dc->yl] + columnofs[dc->x];
-	dest = &topleft[dc->yl*vid.width + dc->x];
+	dest = &dc->dest->viewbuf[dc->yl*vid.width + dc->x];
 
 	// Looks familiar.
 	fracstep = dc->iscale;
-	//frac = dc->texturemid + (dc->yl - centery)*fracstep;
 	frac = (dc->texturemid + FixedMul((dc->yl << FRACBITS) - centeryfrac, fracstep));
 
 	// Here we do an additional index re-mapping.
@@ -360,8 +346,7 @@ void R_DrawTranslucentColumn_8(colcontext_t *dc)
 #endif
 
 	// FIXME. As above.
-	//dest = ylookup[dc->yl] + columnofs[dc->x];
-	dest = &topleft[dc->yl*vid.width + dc->x];
+	dest = &dc->dest->viewbuf[dc->yl*vid.width + dc->x];
 
 	// Looks familiar.
 	fracstep = dc->iscale;
@@ -431,7 +416,7 @@ void R_DrawDropShadowColumn_8(colcontext_t *dc)
 	if (count <= 0) // Zero length, column does not exceed a pixel.
 		return;
 
-	dest = &topleft[dc->yl*vid.width + dc->x];
+	dest = &dc->dest->viewbuf[dc->yl*vid.width + dc->x];
 
 	{
 #define DSCOLOR 31 // palette index for the color of the shadow
@@ -465,8 +450,7 @@ void R_DrawTranslatedTranslucentColumn_8(colcontext_t *dc)
 		return;
 
 	// FIXME. As above.
-	//dest = ylookup[dc->yl] + columnofs[dc->x];
-	dest = &topleft[dc->yl*vid.width + dc->x];
+	dest = &dc->dest->viewbuf[dc->yl*vid.width + dc->x];
 
 	// Looks familiar.
 	fracstep = dc->iscale;
@@ -541,8 +525,7 @@ void R_DrawTranslatedColumn_8(colcontext_t *dc)
 #endif
 
 	// FIXME. As above.
-	//dest = ylookup[dc->yl] + columnofs[dc->x];
-	dest = &topleft[dc->yl*vid.width + dc->x];
+	dest = &dc->dest->viewbuf[dc->yl*vid.width + dc->x];
 
 	// Looks familiar.
 	fracstep = dc->iscale;
@@ -584,7 +567,7 @@ void R_DrawSpan_8(spancontext_t *ds)
 	UINT8 *source;
 	UINT8 *colormap;
 	UINT8 *dest;
-	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+	const UINT8 *deststop = ds->dest->viewbuf + vid.rowbytes * vid.height;
 
 	size_t count = (ds->x2 - ds->x1 + 1);
 
@@ -603,7 +586,7 @@ void R_DrawSpan_8(spancontext_t *ds)
 
 	source = ds->source;
 	colormap = ds->colormap;
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 
 	if (dest+8 > deststop)
 		return;
@@ -683,7 +666,7 @@ void R_DrawTiltedSpan_8(spancontext_t *ds)
 	uz = ds->sup->z + ds->sup->y*(centery-ds->y) + ds->sup->x*(ds->x1-centerx);
 	vz = ds->svp->z + ds->svp->y*(centery-ds->y) + ds->svp->x*(ds->x1-centerx);
 
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 	source = ds->source;
 	//colormap = ds->colormap;
 
@@ -806,7 +789,7 @@ void R_DrawTiltedTranslucentSpan_8(spancontext_t *ds)
 	uz = ds->sup->z + ds->sup->y*(centery-ds->y) + ds->sup->x*(ds->x1-centerx);
 	vz = ds->svp->z + ds->svp->y*(centery-ds->y) + ds->svp->x*(ds->x1-centerx);
 
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 	source = ds->source;
 	//colormap = ds->colormap;
 
@@ -929,8 +912,8 @@ void R_DrawTiltedTranslucentWaterSpan_8(spancontext_t *ds)
 	uz = ds->sup->z + ds->sup->y*(centery-ds->y) + ds->sup->x*(ds->x1-centerx);
 	vz = ds->svp->z + ds->svp->y*(centery-ds->y) + ds->svp->x*(ds->x1-centerx);
 
-	dest = ylookup[ds->y] + columnofs[ds->x1];
-	dsrc = screens[1] + (ds->y+ds->bgofs)*vid.width + ds->x1;
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
+	dsrc = ds->dest->waterbuf + (ds->y+ds->bgofs)*vid.width + ds->x1;
 	source = ds->source;
 	//colormap = ds->colormap;
 
@@ -1051,7 +1034,7 @@ void R_DrawTiltedSplat_8(spancontext_t *ds)
 	uz = ds->sup->z + ds->sup->y*(centery-ds->y) + ds->sup->x*(ds->x1-centerx);
 	vz = ds->svp->z + ds->svp->y*(centery-ds->y) + ds->svp->x*(ds->x1-centerx);
 
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 	source = ds->source;
 	//colormap = ds->colormap;
 
@@ -1168,7 +1151,7 @@ void R_DrawSplat_8(spancontext_t *ds)
 	UINT8 *source;
 	UINT8 *colormap;
 	UINT8 *dest;
-	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+	const UINT8 *deststop = ds->dest->viewbuf + vid.rowbytes * vid.height;
 
 	size_t count = (ds->x2 - ds->x1 + 1);
 	UINT32 val;
@@ -1188,7 +1171,7 @@ void R_DrawSplat_8(spancontext_t *ds)
 
 	source = ds->source;
 	colormap = ds->colormap;
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 
 	while (count >= 8)
 	{
@@ -1288,7 +1271,7 @@ void R_DrawTranslucentSplat_8(spancontext_t *ds)
 	UINT8 *source;
 	UINT8 *colormap;
 	UINT8 *dest;
-	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+	const UINT8 *deststop = ds->dest->viewbuf + vid.rowbytes * vid.height;
 
 	size_t count = (ds->x2 - ds->x1 + 1);
 	UINT32 val;
@@ -1308,7 +1291,7 @@ void R_DrawTranslucentSplat_8(spancontext_t *ds)
 
 	source = ds->source;
 	colormap = ds->colormap;
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 
 	while (count >= 8)
 	{
@@ -1390,7 +1373,7 @@ void R_DrawFloorSprite_8(spancontext_t *ds)
 	UINT8 *colormap;
 	UINT8 *translation;
 	UINT8 *dest;
-	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+	const UINT8 *deststop = ds->dest->viewbuf + vid.rowbytes * vid.height;
 
 	size_t count = (ds->x2 - ds->x1 + 1);
 	UINT32 val;
@@ -1411,7 +1394,7 @@ void R_DrawFloorSprite_8(spancontext_t *ds)
 	source = (UINT16 *)ds->source;
 	colormap = ds->colormap;
 	translation = ds->translation;
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 
 	while (count >= 8)
 	{
@@ -1501,7 +1484,7 @@ void R_DrawTranslucentFloorSprite_8(spancontext_t *ds)
 	UINT8 *colormap;
 	UINT8 *translation;
 	UINT8 *dest;
-	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+	const UINT8 *deststop = ds->dest->viewbuf + vid.rowbytes * vid.height;
 
 	size_t count = (ds->x2 - ds->x1 + 1);
 	UINT32 val;
@@ -1522,7 +1505,7 @@ void R_DrawTranslucentFloorSprite_8(spancontext_t *ds)
 	source = (UINT16 *)ds->source;
 	colormap = ds->colormap;
 	translation = ds->translation;
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 
 	while (count >= 8)
 	{
@@ -1617,7 +1600,7 @@ void R_DrawTiltedFloorSprite_8(spancontext_t *ds)
 	uz = ds->sup->z + ds->sup->y*(centery-ds->y) + ds->sup->x*(ds->x1-centerx);
 	vz = ds->svp->z + ds->svp->y*(centery-ds->y) + ds->svp->x*(ds->x1-centerx);
 
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 	source = (UINT16 *)ds->source;
 	colormap = ds->colormap;
 	translation = ds->translation;
@@ -1726,7 +1709,7 @@ void R_DrawTiltedTranslucentFloorSprite_8(spancontext_t *ds)
 	uz = ds->sup->z + ds->sup->y*(centery-ds->y) + ds->sup->x*(ds->x1-centerx);
 	vz = ds->svp->z + ds->svp->y*(centery-ds->y) + ds->svp->x*(ds->x1-centerx);
 
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 	source = (UINT16 *)ds->source;
 	colormap = ds->colormap;
 	translation = ds->translation;
@@ -1821,7 +1804,7 @@ void R_DrawTranslucentSpan_8(spancontext_t *ds)
 	UINT8 *source;
 	UINT8 *colormap;
 	UINT8 *dest;
-	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+	const UINT8 *deststop = ds->dest->viewbuf + vid.rowbytes * vid.height;
 
 	size_t count = (ds->x2 - ds->x1 + 1);
 	UINT32 val;
@@ -1841,7 +1824,7 @@ void R_DrawTranslucentSpan_8(spancontext_t *ds)
 
 	source = ds->source;
 	colormap = ds->colormap;
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 
 	while (count >= 8)
 	{
@@ -1918,8 +1901,8 @@ void R_DrawTranslucentWaterSpan_8(spancontext_t *ds)
 
 	source = ds->source;
 	colormap = ds->colormap;
-	dest = ylookup[ds->y] + columnofs[ds->x1];
-	dsrc = screens[1] + (ds->y+ds->bgofs)*vid.width + ds->x1;
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
+	dsrc = ds->dest->waterbuf + (ds->y+ds->bgofs)*vid.width + ds->x1;
 	count = ds->x2 - ds->x1 + 1;
 
 	while (count >= 8)
@@ -1981,8 +1964,7 @@ void R_DrawFogSpan_8(spancontext_t *ds)
 	size_t count;
 
 	colormap = ds->colormap;
-	//dest = ylookup[ds->y] + columnofs[ds->x1];
-	dest = &topleft[ds->y *vid.width + ds->x1];
+	dest = &ds->dest->viewbuf[ds->y *vid.width + ds->x1];
 
 	count = ds->x2 - ds->x1 + 1;
 
@@ -2024,10 +2006,7 @@ void R_DrawFogColumn_8(colcontext_t *dc)
 #endif
 
 	// Framebuffer destination address.
-	// Use ylookup LUT to avoid multiply with ScreenWidth.
-	// Use columnofs LUT for subwindows?
-	//dest = ylookup[dc->yl] + columnofs[dc->x];
-	dest = &topleft[dc->yl*vid.width + dc->x];
+	dest = &dc->dest->viewbuf[dc->yl*vid.width + dc->x];
 
 	// Determine scaling, which is the only mapping to be done.
 	do
diff --git a/src/r_draw8_npo2.c b/src/r_draw8_npo2.c
index 70ac467f3e19a88b98a7ca6d856e220e65929989..c203b7323e796ac89994436e3a114fb6dc421b03 100644
--- a/src/r_draw8_npo2.c
+++ b/src/r_draw8_npo2.c
@@ -32,7 +32,7 @@ void R_DrawSpan_NPO2_8(spancontext_t *ds)
 	UINT8 *source;
 	UINT8 *colormap;
 	UINT8 *dest;
-	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+	const UINT8 *deststop = ds->dest->viewbuf + vid.rowbytes * vid.height;
 
 	size_t count = (ds->x2 - ds->x1 + 1);
 
@@ -41,7 +41,7 @@ void R_DrawSpan_NPO2_8(spancontext_t *ds)
 
 	source = ds->source;
 	colormap = ds->colormap;
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 
 	if (dest+8 > deststop)
 		return;
@@ -116,7 +116,7 @@ void R_DrawTiltedSpan_NPO2_8(spancontext_t *ds)
 	uz = ds->sup->z + ds->sup->y*(centery-ds->y) + ds->sup->x*(ds->x1-centerx);
 	vz = ds->svp->z + ds->svp->y*(centery-ds->y) + ds->svp->x*(ds->x1-centerx);
 
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 	source = ds->source;
 	//colormap = ds->colormap;
 
@@ -306,7 +306,7 @@ void R_DrawTiltedTranslucentSpan_NPO2_8(spancontext_t *ds)
 	uz = ds->sup->z + ds->sup->y*(centery-ds->y) + ds->sup->x*(ds->x1-centerx);
 	vz = ds->svp->z + ds->svp->y*(centery-ds->y) + ds->svp->x*(ds->x1-centerx);
 
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 	source = ds->source;
 	//colormap = ds->colormap;
 
@@ -494,7 +494,7 @@ void R_DrawTiltedSplat_NPO2_8(spancontext_t *ds)
 	uz = ds->sup->z + ds->sup->y*(centery-ds->y) + ds->sup->x*(ds->x1-centerx);
 	vz = ds->svp->z + ds->svp->y*(centery-ds->y) + ds->svp->x*(ds->x1-centerx);
 
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 	source = ds->source;
 	//colormap = ds->colormap;
 
@@ -678,7 +678,7 @@ void R_DrawSplat_NPO2_8(spancontext_t *ds)
 	UINT8 *source;
 	UINT8 *colormap;
 	UINT8 *dest;
-	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+	const UINT8 *deststop = ds->dest->viewbuf + vid.rowbytes * vid.height;
 
 	size_t count = (ds->x2 - ds->x1 + 1);
 	UINT32 val;
@@ -688,7 +688,7 @@ void R_DrawSplat_NPO2_8(spancontext_t *ds)
 
 	source = ds->source;
 	colormap = ds->colormap;
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 
 	fixedwidth = ds->flatwidth << FRACBITS;
 	fixedheight = ds->flatheight << FRACBITS;
@@ -746,7 +746,7 @@ void R_DrawTranslucentSplat_NPO2_8(spancontext_t *ds)
 	UINT8 *source;
 	UINT8 *colormap;
 	UINT8 *dest;
-	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+	const UINT8 *deststop = ds->dest->viewbuf + vid.rowbytes * vid.height;
 
 	size_t count = (ds->x2 - ds->x1 + 1);
 	UINT32 val;
@@ -756,7 +756,7 @@ void R_DrawTranslucentSplat_NPO2_8(spancontext_t *ds)
 
 	source = ds->source;
 	colormap = ds->colormap;
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 
 	fixedwidth = ds->flatwidth << FRACBITS;
 	fixedheight = ds->flatheight << FRACBITS;
@@ -815,7 +815,7 @@ void R_DrawFloorSprite_NPO2_8(spancontext_t *ds)
 	UINT8 *translation;
 	UINT8 *colormap;
 	UINT8 *dest;
-	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+	const UINT8 *deststop = ds->dest->viewbuf + vid.rowbytes * vid.height;
 
 	size_t count = (ds->x2 - ds->x1 + 1);
 	UINT32 val;
@@ -826,7 +826,7 @@ void R_DrawFloorSprite_NPO2_8(spancontext_t *ds)
 	source = (UINT16 *)ds->source;
 	colormap = ds->colormap;
 	translation = ds->translation;
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 
 	fixedwidth = ds->flatwidth << FRACBITS;
 	fixedheight = ds->flatheight << FRACBITS;
@@ -885,7 +885,7 @@ void R_DrawTranslucentFloorSprite_NPO2_8(spancontext_t *ds)
 	UINT8 *translation;
 	UINT8 *colormap;
 	UINT8 *dest;
-	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+	const UINT8 *deststop = ds->dest->viewbuf + vid.rowbytes * vid.height;
 
 	size_t count = (ds->x2 - ds->x1 + 1);
 	UINT32 val;
@@ -896,7 +896,7 @@ void R_DrawTranslucentFloorSprite_NPO2_8(spancontext_t *ds)
 	source = (UINT16 *)ds->source;
 	colormap = ds->colormap;
 	translation = ds->translation;
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 
 	fixedwidth = ds->flatwidth << FRACBITS;
 	fixedheight = ds->flatheight << FRACBITS;
@@ -969,7 +969,7 @@ void R_DrawTiltedFloorSprite_NPO2_8(spancontext_t *ds)
 	uz = ds->sup->z + ds->sup->y*(centery-ds->y) + ds->sup->x*(ds->x1-centerx);
 	vz = ds->svp->z + ds->svp->y*(centery-ds->y) + ds->svp->x*(ds->x1-centerx);
 
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 	source = (UINT16 *)ds->source;
 	colormap = ds->colormap;
 	translation = ds->translation;
@@ -1125,7 +1125,7 @@ void R_DrawTiltedTranslucentFloorSprite_NPO2_8(spancontext_t *ds)
 	uz = ds->sup->z + ds->sup->y*(centery-ds->y) + ds->sup->x*(ds->x1-centerx);
 	vz = ds->svp->z + ds->svp->y*(centery-ds->y) + ds->svp->x*(ds->x1-centerx);
 
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 	source = (UINT16 *)ds->source;
 	colormap = ds->colormap;
 	translation = ds->translation;
@@ -1266,7 +1266,7 @@ void R_DrawTranslucentSpan_NPO2_8(spancontext_t *ds)
 	UINT8 *source;
 	UINT8 *colormap;
 	UINT8 *dest;
-	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+	const UINT8 *deststop = ds->dest->viewbuf + vid.rowbytes * vid.height;
 
 	size_t count = (ds->x2 - ds->x1 + 1);
 	UINT32 val;
@@ -1276,7 +1276,7 @@ void R_DrawTranslucentSpan_NPO2_8(spancontext_t *ds)
 
 	source = ds->source;
 	colormap = ds->colormap;
-	dest = ylookup[ds->y] + columnofs[ds->x1];
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
 
 	fixedwidth = ds->flatwidth << FRACBITS;
 	fixedheight = ds->flatheight << FRACBITS;
@@ -1331,7 +1331,7 @@ void R_DrawTranslucentWaterSpan_NPO2_8(spancontext_t *ds)
 	UINT8 *colormap;
 	UINT8 *dest;
 	UINT8 *dsrc;
-	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+	const UINT8 *deststop = ds->dest->viewbuf + vid.rowbytes * vid.height;
 
 	size_t count = (ds->x2 - ds->x1 + 1);
 
@@ -1340,8 +1340,8 @@ void R_DrawTranslucentWaterSpan_NPO2_8(spancontext_t *ds)
 
 	source = ds->source;
 	colormap = ds->colormap;
-	dest = ylookup[ds->y] + columnofs[ds->x1];
-	dsrc = screens[1] + (ds->y+ds->bgofs)*vid.width + ds->x1;
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
+	dsrc = ds->dest->waterbuf + (ds->y+ds->bgofs)*vid.width + ds->x1;
 
 	fixedwidth = ds->flatwidth << FRACBITS;
 	fixedheight = ds->flatheight << FRACBITS;
@@ -1413,8 +1413,8 @@ void R_DrawTiltedTranslucentWaterSpan_NPO2_8(spancontext_t *ds)
 	uz = ds->sup->z + ds->sup->y*(centery-ds->y) + ds->sup->x*(ds->x1-centerx);
 	vz = ds->svp->z + ds->svp->y*(centery-ds->y) + ds->svp->x*(ds->x1-centerx);
 
-	dest = ylookup[ds->y] + columnofs[ds->x1];
-	dsrc = screens[1] + (ds->y+ds->bgofs)*vid.width + ds->x1;
+	dest = &ds->dest->viewbuf[ds->y*vid.width + ds->x1];
+	dsrc = ds->dest->waterbuf + (ds->y+ds->bgofs)*vid.width + ds->x1;
 	source = ds->source;
 	//colormap = ds->colormap;
 
diff --git a/src/r_main.c b/src/r_main.c
index 99f9b0a277dcdf1d495db33e7e0347894b6f842f..50e60a3d75e46f8682a269f3d7b22cf42691fa97 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -40,20 +40,9 @@
 #include "hardware/hw_main.h"
 #endif
 
-//profile stuff ---------------------------------------------------------
-//#define TIMING
-#ifdef TIMING
-#include "p5prof.h"
-INT64 mycount;
-INT64 mytotal = 0;
-//unsigned long  nombre = 100000;
-#endif
-//profile stuff ---------------------------------------------------------
-
-// Fineangles in the SCREENWIDTH wide window.
-#define FIELDOFVIEW 2048
-
+#define MAX_RENDER_CONTEXTS MAX_PORTAL_RECURSION
 static rendercontext_t r_contexts[MAX_RENDER_CONTEXTS];
+UINT8 r_curcontext = 0;
 
 // increment every time a check is made
 size_t validcount = 1;
@@ -135,7 +124,7 @@ static CV_PossibleValue_t drawdist_precip_cons_t[] = {
 
 static CV_PossibleValue_t fov_cons_t[] = {{60*FRACUNIT, "MIN"}, {179*FRACUNIT, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t translucenthud_cons_t[] = {{0, "MIN"}, {10, "MAX"}, {0, NULL}};
-static CV_PossibleValue_t maxportals_cons_t[] = {{0, "MIN"}, {12, "MAX"}, {0, NULL}};
+static CV_PossibleValue_t maxportals_cons_t[] = {{0, "MIN"}, {MAX_PORTAL_RECURSION, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t homremoval_cons_t[] = {{0, "No"}, {1, "Yes"}, {2, "Flash"}, {0, NULL}};
 
 static void Fov_OnChange(void);
@@ -454,9 +443,9 @@ boolean R_DoCulling(line_t *cullheight, line_t *viewcullheight, fixed_t vz, fixe
 	return false;
 }
 
-//
-// R_InitTextureMapping
-//
+// Fineangles in the SCREENWIDTH wide window.
+#define FIELDOFVIEW 2048
+
 static void R_InitTextureMapping(void)
 {
 	INT32 i;
@@ -1010,6 +999,11 @@ static void R_ResetContext(rendercontext_t *context, INT32 leftclip, INT32 right
 		context->bspcontext.portalclipstart = leftclip;
 		context->bspcontext.portalclipend = rightclip;
 
+		for (INT32 i = 0; i < leftclip; i++)
+			context->planecontext.ceilingclip[i] = viewheight;
+		for (INT32 i = rightclip + 1; i < viewwidth; i++)
+			context->planecontext.floorclip[i] = -1;
+
 		R_ClearClipSegs(&context->bspcontext, leftclip, rightclip);
 	}
 
@@ -1038,6 +1032,68 @@ static void R_InitContexts(void)
 	}
 }
 
+static void R_PrepareViewContext(viewcontext_t *context)
+{
+	context->x = viewx;
+	context->y = viewy;
+	context->z = viewz;
+
+	context->angle = viewangle;
+	context->sin = viewsin;
+	context->cos = viewcos;
+
+	context->sector = viewsector;
+	context->player = viewplayer;
+	context->mobj = r_viewmobj;
+}
+
+static void R_InitRenderContext(rendercontext_t *context, INT32 begincolumn, INT32 endcolumn)
+{
+	context->buffer.viewbuf = topleft;
+	context->buffer.waterbuf = screens[1];
+
+	for (unsigned i = 0; i < NUMSCREENS; i++)
+		context->buffer.screens[i] = screens[i];
+
+	context->buffer.isbasebuffer = true;
+	context->buffer.numscreens = NUMSCREENS;
+
+	context->colcontext.dest = &context->buffer;
+	context->spancontext.dest = &context->buffer;
+
+	context->begincolumn = begincolumn;
+	context->endcolumn = endcolumn;
+
+	R_ResetContext(context, context->begincolumn, context->endcolumn);
+
+	if (context->spritecontext.sectorvisited == NULL)
+	{
+		context->spritecontext.sectorvisited = Z_Malloc(
+			sizeof(boolean) * numsectors,
+			PU_LEVEL, &context->spritecontext.sectorvisited);
+	}
+
+	memset(context->spritecontext.sectorvisited, 0, sizeof(boolean) * numsectors);
+}
+
+static void R_PrepareContext(rendercontext_t *context)
+{
+	R_InitRenderContext(context, 0, viewwidth);
+	R_PrepareViewContext(&context->viewcontext);
+}
+
+static void R_FreeContextSlopeData(rendercontext_t *context)
+{
+	spancontext_t *ds = &context->spancontext;
+
+	Z_Free(ds->su);
+	Z_Free(ds->sv);
+	Z_Free(ds->sz);
+
+	ds->su = ds->sv = ds->sz = NULL;
+	ds->sup = ds->svp = ds->szp = NULL;
+}
+
 //
 // R_SetViewSize
 // Do not really change anything here,
@@ -1114,17 +1170,7 @@ void R_ExecuteSetViewSize(void)
 			yslopetab[i] = FixedDiv(centerx*FRACUNIT, dy);
 		}
 
-		for (i = 0; i < MAX_RENDER_CONTEXTS; i++)
-		{
-			spancontext_t *ds = &r_contexts[i].spancontext;
-
-			Z_Free(ds->su);
-			Z_Free(ds->sv);
-			Z_Free(ds->sz);
-
-			ds->su = ds->sv = ds->sz = NULL;
-			ds->sup = ds->svp = ds->szp = NULL;
-		}
+		R_FreeContextSlopeData(&r_contexts[0]);
 	}
 
 	memset(scalelight, 0xFF, sizeof(scalelight));
@@ -1554,58 +1600,185 @@ boolean R_IsViewpointThirdPerson(player_t *player, boolean skybox)
 // R_RenderView
 // ================
 
-static void R_PrepareViewContext(viewcontext_t *context)
+static void R_ClearPlayerView(rendercontext_t *context)
 {
-	context->x = viewx;
-	context->y = viewy;
-	context->z = viewz;
+	if (cv_homremoval.value && context->viewcontext.player == &players[displayplayer]) // if this is display player 1
+	{
+		if (cv_homremoval.value == 1)
+			memset(context->buffer.viewbuf, 31, vid.width * vid.height * vid.bpp); // No HOM effect!
+		else //'development' HOM removal -- makes it blindingly obvious if HOM is spotted.
+			memset(context->buffer.viewbuf, 32+(timeinmap&15), vid.width * vid.height * vid.bpp);
+	}
+}
 
-	context->angle = viewangle;
-	context->sin = viewsin;
-	context->cos = viewcos;
+void R_RenderPlayerView(player_t *player)
+{
+	R_SetupFrame(player);
+	framecount++;
+	validcount++;
 
-	context->sector = viewsector;
-	context->player = viewplayer;
-	context->mobj = r_viewmobj;
+	R_PrepareContext(&r_contexts[0]);
+
+	// check for new console commands.
+	NetUpdate();
+
+	R_ClearPlayerView(&r_contexts[0]);
+	R_RenderViewContext(&r_contexts[0]);
+
+	while (r_curcontext)
+		R_FinishAltView();
 }
 
-static void R_PrepareContext(rendercontext_t *context)
+static void R_CopyContextClip(
+	size_t start, size_t length,
+	INT16 *topdest, INT16 *botdest,
+	INT16 *cliptop, INT16 *clipbot)
 {
-	context->buffer.screens[0] = topleft;
-	for (unsigned i = 1; i < NUMSCREENS; i++)
-		context->buffer.screens[i] = screens[i];
+	M_Memcpy(topdest, cliptop + start, length * sizeof(INT16));
+	M_Memcpy(botdest, clipbot + start, length * sizeof(INT16));
+}
 
-	context->begincolumn = 0;
-	context->endcolumn = viewwidth;
+static void R_AllocVBuffer(rendercontext_t *context, UINT8 numscreens)
+{
+	context->buffer.numscreens = numscreens;
+	context->buffer.isbasebuffer = false;
 
-	R_ResetContext(context, context->begincolumn, context->endcolumn);
-	R_PrepareViewContext(&context->viewcontext);
+	for (UINT8 i = 0; i < numscreens; i++)
+	{
+		void *screen = malloc(vid.rowbytes*vid.height);
+		if (!screen)
+			I_Error("R_AllocVBuffer: Out of memory for buffer %d", i);
 
-	if (context->spritecontext.sectorvisited == NULL)
-		context->spritecontext.sectorvisited = Z_Calloc(sizeof(boolean) * numsectors, PU_LEVEL, NULL);
-	memset(context->spritecontext.sectorvisited, 0, sizeof(boolean) * numsectors);
+		context->buffer.screens[i] = screen;
+	}
+
+	context->buffer.viewbuf = context->buffer.screens[0] + viewwindowy*vid.width + viewwindowx;
+	context->buffer.waterbuf = context->buffer.screens[1];
 }
 
-void R_RenderPlayerView(player_t *player)
+static void R_CopyViewContext(viewcontext_t *thisvctx, viewcontext_t *viewctx)
 {
-	if (cv_homremoval.value && player == &players[displayplayer]) // if this is display player 1
+	thisvctx->x = viewctx->x;
+	thisvctx->y = viewctx->y;
+	thisvctx->z = viewctx->z;
+
+	thisvctx->angle = viewctx->angle;
+	thisvctx->sin = viewctx->sin;
+	thisvctx->cos = viewctx->cos;
+
+	thisvctx->sector = viewctx->sector;
+	thisvctx->player = viewctx->player;
+	thisvctx->mobj = viewctx->mobj;
+}
+
+boolean R_NewAltView(INT32 left, INT32 right, INT16 *cliptop, INT16 *clipbot, viewcontext_t *viewctx, boolean allocvbuf)
+{
+	if (r_curcontext == min(MAX_RENDER_CONTEXTS, cv_maxportals.value)-1)
+		return false;
+
+	left = max(0, left);
+	right = min(right, viewwidth);
+
+	unsigned length = right - left;
+
+	// Nahhhh bruh fr? On God??
+	if (!length || left >= right)
+		return false;
+
+	r_curcontext++;
+	rendercontext_t *context = &r_contexts[r_curcontext];
+
+	R_InitRenderContext(context, left, right);
+	R_CopyViewContext(&context->viewcontext, viewctx);
+
+	if (allocvbuf)
 	{
-		if (cv_homremoval.value == 1)
-			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); // No HOM effect!
-		else //'development' HOM removal -- makes it blindingly obvious if HOM is spotted.
-			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 32+(timeinmap&15));
+		R_AllocVBuffer(context, 2);
+		R_ClearPlayerView(context);
 	}
 
-	R_SetupFrame(player);
-	framecount++;
-	validcount++;
+	R_CopyContextClip(left, length,
+		context->planecontext.ceilingclip + left,
+		context->planecontext.floorclip + left,
+		cliptop, clipbot);
 
-	R_PrepareContext(&r_contexts[0]);
+	context->sprtopclip = malloc(length * sizeof(INT16));
+	context->sprbotclip = malloc(length * sizeof(INT16));
 
-	// check for new console commands.
-	NetUpdate();
+	if (!context->sprtopclip)
+		I_Error("R_NewAltView: Out of memory for sprtopclip");
+	if (!context->sprbotclip)
+		I_Error("R_NewAltView: Out of memory for sprbotclip");
 
-	R_RenderViewContext(&r_contexts[0]);
+	R_CopyContextClip(left, length,
+		context->sprtopclip, context->sprbotclip,
+		cliptop, clipbot);
+
+	return true;
+}
+
+void R_RenderAltView(void)
+{
+	R_RenderViewContext(&r_contexts[r_curcontext]);
+}
+
+static void R_DrawAltViewColumn(rendercontext_t *context, UINT8 *transmap, UINT8 *srcbuf, INT32 x, INT16 top, INT16 bottom)
+{
+	if (top < 0)
+		top = 0;
+	if (bottom >= vid.height)
+		bottom = vid.height - 1;
+
+	INT32 count = bottom - top + 1;
+	if (count <= 0)
+		return;
+
+	UINT8 *dest = &context->buffer.viewbuf[top*vid.width + x];
+	UINT8 *source = &srcbuf[top*vid.width + x];
+
+	while (count--)
+	{
+		*dest = *(transmap + ((*source)<<8) + (*dest));
+		dest += vid.width;
+		source += vid.width;
+	}
+}
+
+void R_BlitAltView(rendercontext_t *curctx, UINT8 *transmap)
+{
+	rendercontext_t *altctx = &r_contexts[r_curcontext];
+
+	INT32 start = altctx->begincolumn;
+	INT32 left = max(start, curctx->begincolumn);
+	INT32 right = min(altctx->endcolumn, curctx->endcolumn);
+
+	for (INT32 x = left; x < right; x++)
+	{
+		R_DrawAltViewColumn(curctx, transmap, altctx->buffer.viewbuf, x,
+			altctx->sprtopclip[x - start]+1, altctx->sprbotclip[x - start]-1);
+	}
+}
+
+void R_FinishAltView(void)
+{
+	if (r_curcontext == 0)
+		return;
+
+	rendercontext_t *context = &r_contexts[r_curcontext];
+
+	R_FreeContextSlopeData(context);
+	Z_Free(context->spritecontext.sectorvisited);
+
+	free(context->sprtopclip);
+	free(context->sprbotclip);
+
+	if (!context->buffer.isbasebuffer)
+	{
+		for (UINT8 i = 0; i < context->buffer.numscreens; i++)
+			free(context->buffer.screens[i]);
+	}
+
+	r_curcontext--;
 }
 
 // =========================================================================
diff --git a/src/r_main.h b/src/r_main.h
index e183a75f4cc5a18d605ec1d49c97b71a1225cf5f..81ddba16d999737437e744bb53782287032f3ef1 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -16,6 +16,7 @@
 
 #include "d_player.h"
 #include "r_data.h"
+#include "r_context.h"
 #include "r_textures.h"
 #include "m_perfstats.h" // ps_metric_t
 
@@ -135,6 +136,11 @@ boolean R_IsViewpointThirdPerson(player_t *player, boolean skybox);
 // Called by D_Display.
 void R_RenderPlayerView(player_t *player);
 
+boolean R_NewAltView(INT32 left, INT32 right, INT16 *cliptop, INT16 *clipbot, viewcontext_t *viewctx, boolean allocvbuf);
+void R_BlitAltView(rendercontext_t *context, UINT8 *transmap);
+void R_RenderAltView(void);
+void R_FinishAltView(void);
+
 // add commands related to engine, at game startup
 void R_RegisterEngineStuff(void);
 #endif
diff --git a/src/r_plane.c b/src/r_plane.c
index c85ab8a8ff50ccc9b5d78075a5e688ac7bd32355..dc932750f9fd8122019e432263ba4e3d264de8b7 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -853,8 +853,12 @@ void R_DrawSinglePlane(rendercontext_t *context, visplane_t *pl)
 					if (bottom > vid.height)
 						bottom = vid.height;
 
+					UINT8 *src = (splitscreen && context->viewcontext.player == &players[secondarydisplayplayer])
+						? context->buffer.screens[0] + (top+(vid.height>>1))*vid.width
+						: context->buffer.screens[0] + ((top)*vid.width);
+
 					// Only copy the part of the screen we need
-					VID_BlitLinearScreen((splitscreen && context->viewcontext.player == &players[secondarydisplayplayer]) ? screens[0] + (top+(vid.height>>1))*vid.width : screens[0]+((top)*vid.width), screens[1]+((top)*vid.width),
+					VID_BlitLinearScreen(src, context->buffer.waterbuf+((top)*vid.width),
 										 vid.width, bottom-top,
 										 vid.width, vid.width);
 				}
diff --git a/src/r_portal.h b/src/r_portal.h
index 9b99de2effbb6c25f74e54b67e186c9ba27f69ff..e244e29f4abaab2c5a5ae485d9ea40a5eb9cb086 100644
--- a/src/r_portal.h
+++ b/src/r_portal.h
@@ -56,4 +56,5 @@ void Portal_ClipRange (struct planecontext_s *planecontext, portal_t* portal);
 void Portal_ClipApply (struct planecontext_s *planecontext, const portal_t* portal);
 
 void Portal_AddSkyboxPortals (struct rendercontext_s *context);
+
 #endif
diff --git a/src/r_segs.c b/src/r_segs.c
index 719229292e1cb1eacb28123a48ed28e762735c55..d9ca5d048602e5e36583ba2ec713528b75415fad 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -1305,6 +1305,10 @@ static void R_RenderSegLoop(rendercontext_t *context, wallcontext_t *wallcontext
 
 		planecontext->frontscale[currx] = wallcontext->scale;
 
+		// FIXME: Why is this < 0 sometimes?
+		if (yl < 0)
+			yl = 0;
+
 		// draw the wall tiers
 		if (segcontext->midtexture)
 		{
@@ -1386,6 +1390,10 @@ static void R_RenderSegLoop(rendercontext_t *context, wallcontext_t *wallcontext
 				if (mid <= planecontext->ceilingclip[currx])
 					mid = planecontext->ceilingclip[currx]+1;
 
+				// FIXME: Why is this < 0 sometimes?
+				if (mid < 0)
+					mid = 0;
+
 				if (mid <= yh) // back floor higher than front floor ?
 				{
 					if (yh < 0) // entirely off top of screen
@@ -1521,8 +1529,8 @@ void R_StoreWallRange(rendercontext_t *context, wallcontext_t *wallcontext, INT3
 	sector_t *frontsector = bspcontext->frontsector;
 	sector_t *backsector = bspcontext->backsector;
 
-	INT32 worldtop, worldbottom, worldhigh, worldlow;
-	INT32 worldtopslope, worldbottomslope, worldhighslope, worldlowslope; // worldtop/bottom at end of slope
+	INT32 worldtop, worldbottom, worldhigh = 0, worldlow = 0;
+	INT32 worldtopslope, worldbottomslope, worldhighslope = 0, worldlowslope = 0; // worldtop/bottom at end of slope
 
 	drawseg_t *ds_p;
 	segloopcontext_t loopcontext;
diff --git a/src/r_state.h b/src/r_state.h
index 519421ff9f736881296c2bd84fe6c3d1e43af1de..4d1fe7e008872cae9ae4200e93c4bab14a6aee16 100644
--- a/src/r_state.h
+++ b/src/r_state.h
@@ -99,4 +99,6 @@ extern angle_t doubleclipangle;
 extern INT32 viewangletox[FINEANGLES/2];
 extern angle_t xtoviewangle[MAXVIDWIDTH+1];
 
+extern UINT8 r_curcontext;
+
 #endif
diff --git a/src/r_things.c b/src/r_things.c
index a84325f10a20c9fd842af21cd7b0411c2b7f94de..44fef3a1268b430f006e538358956e6b86838cbc 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -964,6 +964,325 @@ static void R_DrawVisSprite(rendercontext_t *context, vissprite_t *vis)
 	vis->x2 = x2;
 }
 
+static void R_ClipPortalSpriteColumn(colcontext_t *dc, column_t *column, INT32 x, INT32 *prevdelta, INT16 *top, INT16 *bot)
+{
+	INT32 topdelta = column->topdelta;
+	if (topdelta <= *prevdelta)
+		topdelta += *prevdelta;
+	*prevdelta = topdelta;
+
+	INT32 topscreen = dc->sprtopscreen + dc->spryscale*topdelta;
+	INT32 bottomscreen = topscreen + dc->spryscale*column->length;
+
+	INT32 yl = (topscreen+FRACUNIT-1)>>FRACBITS;
+	INT32 yh = (bottomscreen-1)>>FRACBITS;
+
+	if (yh >= dc->mfloorclip[x])
+		yh = dc->mfloorclip[x]-1;
+	if (yl <= dc->mceilingclip[x])
+		yl = dc->mceilingclip[x]+1;
+	if (yl < 0)
+		yl = 0;
+	if (yh >= vid.height) // yl must be < vid.height, so reduces number of checks in tight loop
+		yh = vid.height - 1;
+
+	if (yl <= yh && yh > 0)
+	{
+		if (top)
+			*top = yl;
+		if (bot)
+			*bot = yh;
+	}
+	else
+	{
+		if (top)
+			*top = -1;
+		if (bot)
+			*bot = -1;
+	}
+}
+
+static void R_ClipPortalSpriteColumnFlipped(colcontext_t *dc, column_t *column, INT32 x, INT32 *prevdelta, INT16 *top, INT16 *bot)
+{
+	INT32 topdelta = column->topdelta;
+	if (topdelta <= *prevdelta)
+		topdelta += *prevdelta;
+	*prevdelta = topdelta;
+	topdelta = dc->lengthcol-column->length-topdelta;
+
+	INT32 topscreen = dc->sprtopscreen + dc->spryscale*topdelta;
+	INT32 bottomscreen = topscreen + dc->spryscale*column->length;
+
+	INT32 yl = (topscreen+FRACUNIT-1)>>FRACBITS;
+	INT32 yh = (bottomscreen-1)>>FRACBITS;
+
+	if (yh >= dc->mfloorclip[x])
+		yh = dc->mfloorclip[x]-1;
+	if (yl <= dc->mceilingclip[x])
+		yl = dc->mceilingclip[x]+1;
+	if (yl < 0)
+		yl = 0;
+	if (yh >= vid.height) // yl must be < vid.height, so reduces number of checks in tight loop
+		yh = vid.height - 1;
+
+	if (yl <= yh && yh > 0)
+	{
+		if (top)
+			*top = yl;
+		if (bot)
+			*bot = yh;
+	}
+	else
+	{
+		if (top)
+			*top = -1;
+		if (bot)
+			*bot = -1;
+	}
+}
+
+static void R_AddPortalSpriteColumn(colcontext_t *dc, column_t *column, INT32 x, INT16 *cliptop, INT16 *clipbot,
+	void (*clipfunc)(colcontext_t *, column_t *, INT32, INT32 *, INT16 *, INT16 *))
+{
+	INT32 prevdelta = 0;
+	INT16 firstrow = -1, lastrow = -1;
+
+	column_t *first = column;
+
+	for (;;)
+	{
+		if (first->topdelta == 0xff)
+		{
+			cliptop[x] = vid.height;
+			clipbot[x] = -1;
+			return;
+		}
+
+		clipfunc(dc, first, x, &prevdelta, &firstrow, &lastrow);
+
+		if (firstrow == -1)
+			first = (column_t *)((UINT8 *)first + first->length + 4);
+		else
+			break;
+	}
+
+	column = first;
+
+	for (;;)
+	{
+		column_t *next = (column_t *)((UINT8 *)column + column->length + 4);
+
+		if (next->topdelta == 0xff)
+		{
+			if (column != first)
+				clipfunc(dc, column, x, &prevdelta, NULL, &lastrow);
+			break;
+		}
+
+		column = next;
+	}
+
+	if (lastrow == -1)
+		firstrow = vid.height + 1;
+
+	cliptop[x] = firstrow - 1;
+	clipbot[x] = lastrow + 1;
+}
+
+static void R_RenderPortalSprite(rendercontext_t *context, vissprite_t *vis)
+{
+	colcontext_t *dc = &context->colcontext;
+	column_t *column;
+	void (*localclipfunc)(colcontext_t *, column_t *, INT32, INT32 *, INT16 *, INT16 *);
+	INT32 texturecolumn;
+	INT32 pwidth;
+	fixed_t frac;
+	patch_t *patch = vis->patch;
+	fixed_t this_scale = vis->thingscale;
+	fixed_t texturemid = vis->texturemid;
+	INT32 x, x1, x2;
+	INT64 overflow_test;
+
+	if (!patch)
+		return;
+
+	// Check for overflow
+	overflow_test = (INT64)centeryfrac - (((INT64)vis->texturemid*vis->scale)>>FRACBITS);
+	if (overflow_test < 0) overflow_test = -overflow_test;
+	if ((UINT64)overflow_test&0xFFFFFFFF80000000ULL) return; // fixed point mult would overflow
+
+	if (vis->scalestep) // handles right edge too
+	{
+		overflow_test = (INT64)centeryfrac - (((INT64)vis->texturemid*(vis->scale + (vis->scalestep*(vis->x2 - vis->x1))))>>FRACBITS);
+		if (overflow_test < 0) overflow_test = -overflow_test;
+		if ((UINT64)overflow_test&0xFFFFFFFF80000000ULL) return; // ditto
+	}
+
+	frac = vis->startfrac;
+
+	if (vis->mobj->skin && ((skin_t *)vis->mobj->skin)->flags & SF_HIRES)
+		this_scale = FixedMul(this_scale, ((skin_t *)vis->mobj->skin)->highresscale);
+	if (this_scale <= 0)
+		this_scale = 1;
+	if (this_scale != FRACUNIT)
+	{
+		if (!(vis->cut & SC_ISSCALED))
+		{
+			vis->scale = FixedMul(vis->scale, this_scale);
+			vis->scalestep = FixedMul(vis->scalestep, this_scale);
+			vis->xiscale = FixedDiv(vis->xiscale,this_scale);
+			vis->cut |= SC_ISSCALED;
+		}
+		texturemid = FixedDiv(texturemid,this_scale);
+	}
+
+	dc->spryscale = vis->scale;
+
+	if (!(vis->scalestep))
+	{
+		dc->sprtopscreen = centeryfrac - FixedMul(texturemid, dc->spryscale);
+		dc->sprtopscreen += vis->shear.tan * vis->shear.offset;
+	}
+
+	x1 = vis->x1;
+	x2 = vis->x2;
+
+	if (vis->x1 < 0)
+	{
+		dc->spryscale += vis->scalestep*(-vis->x1);
+		vis->x1 = 0;
+	}
+
+	if (vis->x2 >= vid.width)
+		vis->x2 = vid.width-1;
+
+	localclipfunc = (vis->cut & SC_VFLIP) ? R_ClipPortalSpriteColumnFlipped : R_ClipPortalSpriteColumn;
+	dc->lengthcol = patch->height;
+
+	INT16 cliptop[MAXVIDWIDTH], clipbot[MAXVIDWIDTH];
+
+	// Split drawing loops for paper and non-paper to reduce conditional checks per sprite
+	if (vis->scalestep)
+	{
+		fixed_t horzscale = FixedMul(vis->spritexscale, this_scale);
+		fixed_t scalestep = FixedMul(vis->scalestep, vis->spriteyscale);
+
+		pwidth = patch->width;
+
+		// Papersprite drawing loop
+		for (x = vis->x1; x <= vis->x2; x++, dc->spryscale += scalestep)
+		{
+			angle_t angle = ((vis->centerangle + xtoviewangle[x]) >> ANGLETOFINESHIFT) & 0xFFF;
+			texturecolumn = (vis->paperoffset - FixedMul(FINETANGENT(angle), vis->paperdistance)) / horzscale;
+
+			if (texturecolumn < 0 || texturecolumn >= pwidth)
+			{
+				cliptop[x] = vid.height;
+				clipbot[x] = -1;
+				continue;
+			}
+
+			if (vis->xiscale < 0) // Flipped sprite
+				texturecolumn = pwidth - 1 - texturecolumn;
+
+			dc->sprtopscreen = (centeryfrac - FixedMul(texturemid, dc->spryscale));
+			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn]));
+
+			R_AddPortalSpriteColumn(dc, column, x, cliptop, clipbot, localclipfunc);
+		}
+	}
+	else if (vis->cut & SC_SHEAR)
+	{
+#ifdef RANGECHECK
+		pwidth = patch->width;
+#endif
+
+		// Vertically sheared sprite
+		for (x = vis->x1; x <= vis->x2; x++, frac += vis->xiscale, texturemid -= vis->shear.tan)
+		{
+#ifdef RANGECHECK
+			texturecolumn = frac>>FRACBITS;
+			if (texturecolumn < 0 || texturecolumn >= pwidth)
+				I_Error("R_RenderPortalSprite: bad texturecolumn at %d from end", vis->x2 - x);
+			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn]));
+#else
+			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[frac>>FRACBITS]));
+#endif
+
+			dc->sprtopscreen = (centeryfrac - FixedMul(texturemid, dc->spryscale));
+			R_AddPortalSpriteColumn(dc, column, x, cliptop, clipbot, localclipfunc);
+		}
+	}
+	else
+	{
+#ifdef RANGECHECK
+		pwidth = patch->width;
+#endif
+
+		// Non-paper drawing loop
+		for (x = vis->x1; x <= vis->x2; x++, frac += vis->xiscale, dc->sprtopscreen += vis->shear.tan)
+		{
+#ifdef RANGECHECK
+			texturecolumn = frac>>FRACBITS;
+			if (texturecolumn < 0 || texturecolumn >= pwidth)
+				I_Error("R_RenderPortalSprite: bad texturecolumn at %d from end", vis->x2 - x);
+			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn]));
+#else
+			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[frac>>FRACBITS]));
+#endif
+			R_AddPortalSpriteColumn(dc, column, x, cliptop, clipbot, localclipfunc);
+		}
+	}
+
+	mobj_t *source = vis->mobj;
+	mobj_t *target = source->target;
+	boolean altview = false;
+
+	if (context->bspcontext.portalrender < cv_maxportals.value
+		&& vis->x1 <= vis->x2
+		&& target != NULL && !P_MobjWasRemoved(target) && target != source)
+	{
+		angle_t dangle = target->angle - source->angle;
+
+		fixed_t disttopoint = R_PointToDist2(source->x, source->y, context->viewcontext.x, context->viewcontext.y);
+		angle_t angtopoint = R_PointToAngle2(source->x, source->y, context->viewcontext.x, context->viewcontext.y);
+		angtopoint += dangle;
+
+		viewcontext_t viewctx;
+
+		viewctx.x = target->x + FixedMul(FINECOSINE(angtopoint>>ANGLETOFINESHIFT), disttopoint);
+		viewctx.y = target->y + FixedMul(FINESINE(angtopoint>>ANGLETOFINESHIFT), disttopoint);
+		viewctx.z = context->viewcontext.z + target->z - source->z;
+		viewctx.angle = context->viewcontext.angle + dangle;
+		viewctx.sin = FINESINE(viewctx.angle>>ANGLETOFINESHIFT);
+		viewctx.cos = FINECOSINE(viewctx.angle>>ANGLETOFINESHIFT);
+		viewctx.sector = R_PointInSubsector(viewctx.x, viewctx.y)->sector;
+		viewctx.player = context->viewcontext.player;
+		viewctx.mobj = target;
+
+		UINT8 *transmap = vis->transmap;
+
+		if (R_NewAltView(x1, x2+1, cliptop, clipbot, &viewctx, transmap != NULL))
+		{
+			context->bspcontext.portalrender++;
+
+			R_RenderAltView();
+			if (transmap)
+				R_BlitAltView(context, transmap);
+			R_FinishAltView();
+
+			context->bspcontext.portalrender--;
+			altview = true;
+		}
+	}
+
+	vis->x1 = x1;
+	vis->x2 = x2;
+
+	if (!altview)
+		R_DrawVisSprite(context, vis);
+}
+
 // Special precipitation drawer Tails 08-18-2002
 static void R_DrawPrecipitationVisSprite(rendercontext_t *context, vissprite_t *vis)
 {
@@ -1216,7 +1535,7 @@ static void R_ProjectDropShadow(spritecontext_t *spritecontext, viewcontext_t *v
 
 	groundz = R_GetShadowZ(thing, &groundslope);
 
-	if (abs(groundz-viewz)/tz > 4) return; // Prevent stretchy shadows and possible crashes
+	if (abs(groundz-viewcontext->z)/tz > 4) return; // Prevent stretchy shadows and possible crashes
 
 	floordiff = abs((isflipped ? thing->height : 0) + thing->z - groundz);
 
@@ -1229,7 +1548,7 @@ static void R_ProjectDropShadow(spritecontext_t *spritecontext, viewcontext_t *v
 	xscale = FixedDiv(projection, tz);
 	yscale = FixedDiv(projectiony, tz);
 	shadowxscale = FixedMul(thing->radius*2, scalemul);
-	shadowyscale = FixedMul(FixedMul(thing->radius*2, scalemul), FixedDiv(abs(groundz - viewz), tz));
+	shadowyscale = FixedMul(FixedMul(thing->radius*2, scalemul), FixedDiv(abs(groundz - viewcontext->z), tz));
 	shadowyscale = min(shadowyscale, shadowxscale) / patch->height;
 	shadowxscale /= patch->width;
 	shadowskew = 0;
@@ -1262,7 +1581,7 @@ static void R_ProjectDropShadow(spritecontext_t *spritecontext, viewcontext_t *v
 	shadow->gy = thing->y;
 	shadow->gzt = (isflipped ? shadow->pzt : shadow->pz) + patch->height * shadowyscale / 2;
 	shadow->gz = shadow->gzt - patch->height * shadowyscale;
-	shadow->texturemid = FixedMul(thing->scale, FixedDiv(shadow->gzt - viewz, shadowyscale));
+	shadow->texturemid = FixedMul(thing->scale, FixedDiv(shadow->gzt - viewcontext->z, shadowyscale));
 	if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES)
 		shadow->texturemid = FixedMul(shadow->texturemid, ((skin_t *)thing->skin)->highresscale);
 	shadow->scalestep = 0;
@@ -1277,8 +1596,8 @@ static void R_ProjectDropShadow(spritecontext_t *spritecontext, viewcontext_t *v
 	shadow->scale = FixedMul(yscale, shadowyscale);
 	shadow->thingscale = thing->scale;
 	shadow->sector = vis->sector;
-	shadow->szt = (INT16)((centeryfrac - FixedMul(shadow->gzt - viewz, yscale))>>FRACBITS);
-	shadow->sz = (INT16)((centeryfrac - FixedMul(shadow->gz - viewz, yscale))>>FRACBITS);
+	shadow->szt = (INT16)((centeryfrac - FixedMul(shadow->gzt - viewcontext->z, yscale))>>FRACBITS);
+	shadow->sz = (INT16)((centeryfrac - FixedMul(shadow->gz - viewcontext->z, yscale))>>FRACBITS);
 	shadow->cut = SC_ISSCALED|SC_SHADOW; //check this
 
 	shadow->startfrac = 0;
@@ -1879,7 +2198,7 @@ static void R_ProjectSprite(rendercontext_t *context, mobj_t *thing)
 	}
 
 	heightsec = thing->subsector->sector->heightsec;
-	if (view_plr->mo && view_plr->mo->subsector)
+	if (view_plr && view_plr->mo && view_plr->mo->subsector)
 		phs = view_plr->mo->subsector->sector->heightsec;
 	else
 		phs = -1;
@@ -2017,6 +2336,8 @@ static void R_ProjectSprite(rendercontext_t *context, mobj_t *thing)
 		vis->cut |= SC_VFLIP;
 	if (splat)
 		vis->cut |= SC_SPLAT; // I like ya cut g
+	if (vis->renderflags & RF_PORTALSPRITE && context->bspcontext.portalrender < cv_maxportals.value)
+		vis->cut |= SC_PORTAL;
 
 	vis->patch = patch;
 
@@ -2777,7 +3098,9 @@ static void R_DrawSprite(rendercontext_t *context, vissprite_t *spr)
 	context->colcontext.mfloorclip = spr->clipbot;
 	context->colcontext.mceilingclip = spr->cliptop;
 
-	if (spr->cut & SC_SPLAT)
+	if (spr->cut & SC_PORTAL)
+		R_RenderPortalSprite(context, spr);
+	else if (spr->cut & SC_SPLAT)
 		R_DrawFloorSplat(context, spr);
 	else
 		R_DrawVisSprite(context, spr);
@@ -2792,9 +3115,9 @@ static void R_DrawPrecipitationSprite(rendercontext_t *context, vissprite_t *spr
 	R_DrawPrecipitationVisSprite(context, spr);
 }
 
-// R_ClipVisSprite
+// R_ClipSingleSprite
 // Clips vissprites without drawing, so that portals can work. -Red
-static void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, rendercontext_t *context, drawseg_t *dsstart, portal_t *portal)
+static void R_ClipSingleSprite(vissprite_t *spr, INT32 x1, INT32 x2, rendercontext_t *context, drawseg_t *dsstart, portal_t *portal)
 {
 	drawseg_t *ds;
 	INT32		x;
@@ -2991,6 +3314,21 @@ static void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, rendercontext_
 			spr->cliptop[x] = -1;
 		}
 	}
+
+	if (r_curcontext)
+	{
+		INT32 begin = context->begincolumn;
+		INT32 start_index = max(begin, x1);
+		INT32 end_index = min(begin + context->endcolumn - begin, x2);
+
+		for (x = start_index; x <= end_index; x++)
+		{
+			if (spr->clipbot[x] > context->sprbotclip[x - begin])
+				spr->clipbot[x] = context->sprbotclip[x - begin];
+			if (spr->cliptop[x] < context->sprtopclip[x - begin])
+				spr->cliptop[x] = context->sprtopclip[x - begin];
+		}
+	}
 }
 
 void R_ClipSprites(rendercontext_t *context, drawseg_t *dsstart, portal_t *portal)
@@ -3002,7 +3340,7 @@ void R_ClipSprites(rendercontext_t *context, drawseg_t *dsstart, portal_t *porta
 		vissprite_t *spr = R_GetVisSprite(spritecontext, spritecontext->clippedvissprites);
 		INT32 x1 = (spr->cut & SC_SPLAT) ? 0 : spr->x1;
 		INT32 x2 = (spr->cut & SC_SPLAT) ? viewwidth-1 : spr->x2;
-		R_ClipVisSprite(spr, x1, x2, context, dsstart, portal);
+		R_ClipSingleSprite(spr, x1, x2, context, dsstart, portal);
 	}
 }
 
diff --git a/src/r_things.h b/src/r_things.h
index 04725dfca011799c729c599a7f89c81620b3de78..2f08ab83fca52aca7ab5d8d84411fc512ebb13e4 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -127,6 +127,7 @@ typedef enum
 	SC_SHADOW     = 1<<10,
 	SC_SHEAR      = 1<<11,
 	SC_SPLAT      = 1<<12,
+	SC_PORTAL     = 1<<13,
 	// masks
 	SC_CUTMASK    = SC_TOP|SC_BOTTOM,
 	SC_FLAGMASK   = ~SC_CUTMASK