diff --git a/src/gl/renderer/gl_renderbuffers.h b/src/gl/renderer/gl_renderbuffers.h
index efa61f3e2..f2f7b7cb9 100644
--- a/src/gl/renderer/gl_renderbuffers.h
+++ b/src/gl/renderer/gl_renderbuffers.h
@@ -2,7 +2,6 @@
 #define __GL_RENDERBUFFERS_H
 
 #include "gl/shaders/gl_shader.h"
-#include "gl/renderer/gl_renderer.h"
 
 class FGLBloomTextureLevel
 {
diff --git a/src/gl/renderer/gl_renderer.cpp b/src/gl/renderer/gl_renderer.cpp
index 2895341cc..20c1fb127 100644
--- a/src/gl/renderer/gl_renderer.cpp
+++ b/src/gl/renderer/gl_renderer.cpp
@@ -106,6 +106,8 @@ FGLRenderer::FGLRenderer(OpenGLFrameBuffer *fb)
 	mTonemapPalette = nullptr;
 	mBuffers = nullptr;
 	mPresentShader = nullptr;
+	mPresent3dCheckerShader = nullptr;
+	mPresent3dColumnShader = nullptr;
 	mPresent3dRowShader = nullptr;
 	mBloomExtractShader = nullptr;
 	mBloomCombineShader = nullptr;
@@ -148,6 +150,8 @@ void FGLRenderer::Initialize(int width, int height)
 	mFXAAShader = new FFXAAShader;
 	mFXAALumaShader = new FFXAALumaShader;
 	mPresentShader = new FPresentShader();
+	mPresent3dCheckerShader = new FPresent3DCheckerShader();
+	mPresent3dColumnShader = new FPresent3DColumnShader();
 	mPresent3dRowShader = new FPresent3DRowShader();
 	m2DDrawer = new F2DDrawer;
 
@@ -201,6 +205,8 @@ FGLRenderer::~FGLRenderer()
 	}
 	if (mBuffers) delete mBuffers;
 	if (mPresentShader) delete mPresentShader;
+	if (mPresent3dCheckerShader) delete mPresent3dCheckerShader;
+	if (mPresent3dColumnShader) delete mPresent3dColumnShader;
 	if (mLinearDepthShader) delete mLinearDepthShader;
 	if (mDepthBlurShader) delete mDepthBlurShader;
 	if (mSSAOShader) delete mSSAOShader;
diff --git a/src/gl/renderer/gl_renderer.h b/src/gl/renderer/gl_renderer.h
index 257919fbd..c671d7497 100644
--- a/src/gl/renderer/gl_renderer.h
+++ b/src/gl/renderer/gl_renderer.h
@@ -35,6 +35,8 @@ class FLensShader;
 class FFXAALumaShader;
 class FFXAAShader;
 class FPresentShader;
+class FPresent3DCheckerShader;
+class FPresent3DColumnShader; 
 class FPresent3DRowShader;
 class F2DDrawer;
 class FHardwareTexture;
@@ -117,6 +119,8 @@ public:
 	FFXAALumaShader *mFXAALumaShader;
 	FFXAAShader *mFXAAShader;
 	FPresentShader *mPresentShader;
+	FPresent3DCheckerShader *mPresent3dCheckerShader;
+	FPresent3DColumnShader *mPresent3dColumnShader;
 	FPresent3DRowShader *mPresent3dRowShader;
 
 	FTexture *gllight;
diff --git a/src/gl/shaders/gl_present3dRowshader.cpp b/src/gl/shaders/gl_present3dRowshader.cpp
index 607e4ede6..d8369a3b6 100644
--- a/src/gl/shaders/gl_present3dRowshader.cpp
+++ b/src/gl/shaders/gl_present3dRowshader.cpp
@@ -38,23 +38,37 @@
 #include "gl/system/gl_cvars.h"
 #include "gl/shaders/gl_present3dRowshader.h"
 
+void FPresentStereoShaderBase::Init(const char * vtx_shader_name, const char * program_name)
+{
+	FPresentShaderBase::Init(vtx_shader_name, program_name);
+	LeftEyeTexture.Init(mShader, "LeftEyeTexture");
+	RightEyeTexture.Init(mShader, "RightEyeTexture");
+	WindowPositionParity.Init(mShader, "WindowPositionParity");
+}
+
+void FPresent3DCheckerShader::Bind()
+{
+	if (!mShader)
+	{
+		Init("shaders/glsl/present_checker3d.fp", "shaders/glsl/presentChecker3d");
+	}
+	mShader.Bind();
+}
+
+void FPresent3DColumnShader::Bind()
+{
+	if (!mShader)
+	{
+		Init("shaders/glsl/present_column3d.fp", "shaders/glsl/presentColumn3d");
+	}
+	mShader.Bind();
+}
+
 void FPresent3DRowShader::Bind()
 {
 	if (!mShader)
 	{
-		mShader.Compile(FShaderProgram::Vertex, "shaders/glsl/screenquadscale.vp", "", 330);
-		mShader.Compile(FShaderProgram::Fragment, "shaders/glsl/present_row3d.fp", "", 330);
-		mShader.SetFragDataLocation(0, "FragColor");
-		mShader.Link("shaders/glsl/presentRow3d");
-		mShader.SetAttribLocation(0, "PositionInProjection");
-		mShader.SetAttribLocation(1, "UV");
-		LeftEyeTexture.Init(mShader, "LeftEyeTexture");
-		RightEyeTexture.Init(mShader, "RightEyeTexture");
-		InvGamma.Init(mShader, "InvGamma");
-		Contrast.Init(mShader, "Contrast");
-		Brightness.Init(mShader, "Brightness");
-		Scale.Init(mShader, "UVScale");
-		VerticalPixelOffset.Init(mShader, "VerticalPixelOffset");
+		Init("shaders/glsl/present_row3d.fp", "shaders/glsl/presentRow3d");
 	}
 	mShader.Bind();
 }
diff --git a/src/gl/shaders/gl_present3dRowshader.h b/src/gl/shaders/gl_present3dRowshader.h
index 8e3bd772d..10419d7f5 100644
--- a/src/gl/shaders/gl_present3dRowshader.h
+++ b/src/gl/shaders/gl_present3dRowshader.h
@@ -29,22 +29,35 @@
 #define GL_PRESENT3DROWSHADER_H_
 
 #include "gl_shaderprogram.h"
+#include "gl_presentshader.h"
 
-class FPresent3DRowShader
+class FPresentStereoShaderBase : public FPresentShaderBase
 {
 public:
-	void Bind();
-
 	FBufferedUniformSampler LeftEyeTexture;
 	FBufferedUniformSampler RightEyeTexture;
-	FBufferedUniform1f InvGamma;
-	FBufferedUniform1f Contrast;
-	FBufferedUniform1f Brightness;
-	FBufferedUniform2f Scale;
-	FBufferedUniform1i VerticalPixelOffset;
+	FBufferedUniform1i WindowPositionParity;
 
-private:
-	FShaderProgram mShader;
+protected:
+	void Init(const char * vtx_shader_name, const char * program_name) override;
+};
+
+class FPresent3DCheckerShader : public FPresentStereoShaderBase
+{
+public:
+	void Bind() override;
+};
+
+class FPresent3DColumnShader : public FPresentStereoShaderBase
+{
+public:
+	void Bind() override;
+};
+
+class FPresent3DRowShader : public FPresentStereoShaderBase
+{
+public:
+	void Bind() override;
 };
 
 // GL_PRESENT3DROWSHADER_H_
diff --git a/src/gl/shaders/gl_presentshader.cpp b/src/gl/shaders/gl_presentshader.cpp
index 39d20d0fc..15acc74fb 100644
--- a/src/gl/shaders/gl_presentshader.cpp
+++ b/src/gl/shaders/gl_presentshader.cpp
@@ -36,21 +36,26 @@
 #include "gl/system/gl_cvars.h"
 #include "gl/shaders/gl_presentshader.h"
 
+void FPresentShaderBase::Init(const char * vtx_shader_name, const char * program_name)
+{
+	mShader.Compile(FShaderProgram::Vertex, "shaders/glsl/screenquadscale.vp", "", 330);
+	mShader.Compile(FShaderProgram::Fragment, vtx_shader_name, "", 330);
+	mShader.SetFragDataLocation(0, "FragColor");
+	mShader.Link(program_name);
+	mShader.SetAttribLocation(0, "PositionInProjection");
+	mShader.SetAttribLocation(1, "UV");
+	InvGamma.Init(mShader, "InvGamma");
+	Contrast.Init(mShader, "Contrast");
+	Brightness.Init(mShader, "Brightness");
+	Scale.Init(mShader, "UVScale");
+}
+
 void FPresentShader::Bind()
 {
 	if (!mShader)
 	{
-		mShader.Compile(FShaderProgram::Vertex, "shaders/glsl/screenquadscale.vp", "", 330);
-		mShader.Compile(FShaderProgram::Fragment, "shaders/glsl/present.fp", "", 330);
-		mShader.SetFragDataLocation(0, "FragColor");
-		mShader.Link("shaders/glsl/present");
-		mShader.SetAttribLocation(0, "PositionInProjection");
-		mShader.SetAttribLocation(1, "UV");
+		Init("shaders/glsl/present.fp", "shaders/glsl/present");
 		InputTexture.Init(mShader, "InputTexture");
-		InvGamma.Init(mShader, "InvGamma");
-		Contrast.Init(mShader, "Contrast");
-		Brightness.Init(mShader, "Brightness");
-		Scale.Init(mShader, "UVScale");
 	}
 	mShader.Bind();
 }
diff --git a/src/gl/shaders/gl_presentshader.h b/src/gl/shaders/gl_presentshader.h
index 6f4e899bb..6f9e1bfd7 100644
--- a/src/gl/shaders/gl_presentshader.h
+++ b/src/gl/shaders/gl_presentshader.h
@@ -3,19 +3,27 @@
 
 #include "gl_shaderprogram.h"
 
-class FPresentShader
+class FPresentShaderBase
 {
 public:
-	void Bind();
+	virtual void Bind() = 0;
 
-	FBufferedUniformSampler InputTexture;
 	FBufferedUniform1f InvGamma;
 	FBufferedUniform1f Contrast;
 	FBufferedUniform1f Brightness;
 	FBufferedUniform2f Scale;
 
-private:
+protected:
+	virtual void Init(const char * vtx_shader_name, const char * program_name);
 	FShaderProgram mShader;
 };
 
+class FPresentShader : public FPresentShaderBase
+{
+public:
+	void Bind() override;
+
+	FBufferedUniformSampler InputTexture;
+};
+
 #endif
\ No newline at end of file
diff --git a/src/gl/stereo3d/gl_interleaved3d.cpp b/src/gl/stereo3d/gl_interleaved3d.cpp
index 036a84b0b..029d233e4 100644
--- a/src/gl/stereo3d/gl_interleaved3d.cpp
+++ b/src/gl/stereo3d/gl_interleaved3d.cpp
@@ -43,10 +43,25 @@
 EXTERN_CVAR(Float, vid_brightness)
 EXTERN_CVAR(Float, vid_contrast)
 EXTERN_CVAR(Bool, fullscreen)
-EXTERN_CVAR(Int, win_y) // pixel position of top of display window
+EXTERN_CVAR(Int, win_x) // screen pixel position of left of display window
+EXTERN_CVAR(Int, win_y) // screen pixel position of top of display window
 
 namespace s3d {
 
+/* static */
+const CheckerInterleaved3D& CheckerInterleaved3D::getInstance(float ipd)
+{
+	static CheckerInterleaved3D instance(ipd);
+	return instance;
+}
+
+/* static */
+const ColumnInterleaved3D& ColumnInterleaved3D::getInstance(float ipd)
+{
+	static ColumnInterleaved3D instance(ipd);
+	return instance;
+}
+
 /* static */
 const RowInterleaved3D& RowInterleaved3D::getInstance(float ipd)
 {
@@ -54,11 +69,7 @@ const RowInterleaved3D& RowInterleaved3D::getInstance(float ipd)
 	return instance;
 }
 
-RowInterleaved3D::RowInterleaved3D(double ipdMeters) 
-	: TopBottom3D(ipdMeters)
-{}
-
-void RowInterleaved3D::Present() const
+static void prepareInterleavedPresent(FPresentStereoShaderBase& shader)
 {
 	GLRenderer->mBuffers->BindOutputFB();
 	GLRenderer->ClearBorders();
@@ -79,27 +90,102 @@ void RowInterleaved3D::Present() const
 	const GL_IRECT& box = GLRenderer->mOutputLetterbox;
 	glViewport(box.left, box.top, box.width, box.height);
 
-	bool applyGamma = true;
+	shader.Bind();
+	shader.LeftEyeTexture.Set(0);
+	shader.RightEyeTexture.Set(1);
 
-	GLRenderer->mPresent3dRowShader->Bind();
-	GLRenderer->mPresent3dRowShader->LeftEyeTexture.Set(0);
-	GLRenderer->mPresent3dRowShader->RightEyeTexture.Set(1);
-
-	if (!applyGamma || GLRenderer->framebuffer->IsHWGammaActive())
+	if ( GLRenderer->framebuffer->IsHWGammaActive() )
 	{
-		GLRenderer->mPresent3dRowShader->InvGamma.Set(1.0f);
-		GLRenderer->mPresent3dRowShader->Contrast.Set(1.0f);
-		GLRenderer->mPresent3dRowShader->Brightness.Set(0.0f);
+		shader.InvGamma.Set(1.0f);
+		shader.Contrast.Set(1.0f);
+		shader.Brightness.Set(0.0f);
 	}
 	else
 	{
-		GLRenderer->mPresent3dRowShader->InvGamma.Set(1.0f / clamp<float>(Gamma, 0.1f, 4.f));
-		GLRenderer->mPresent3dRowShader->Contrast.Set(clamp<float>(vid_contrast, 0.1f, 3.f));
-		GLRenderer->mPresent3dRowShader->Brightness.Set(clamp<float>(vid_brightness, -0.8f, 0.8f));
+		shader.InvGamma.Set(1.0f / clamp<float>(Gamma, 0.1f, 4.f));
+		shader.Contrast.Set(clamp<float>(vid_contrast, 0.1f, 3.f));
+		shader.Brightness.Set(clamp<float>(vid_brightness, -0.8f, 0.8f));
 	}
-	GLRenderer->mPresent3dRowShader->Scale.Set(
+	shader.Scale.Set(
 		GLRenderer->mScreenViewport.width / (float)GLRenderer->mBuffers->GetWidth(),
 		GLRenderer->mScreenViewport.height / (float)GLRenderer->mBuffers->GetHeight());
+}
+
+// fixme: I don't know how to get absolute window position on Mac and Linux
+// fixme: I don't know how to get window border decoration size anywhere
+// So for now I'll hard code the border effect on my test machine.
+// Workaround for others is to fuss with vr_swap_eyes CVAR until it looks right.
+// Presumably the top/left window border on my test machine has an odd number of pixels
+//  in the horizontal direction, and an even number in the vertical direction.
+#define WINDOW_BORDER_HORIZONTAL_PARITY 1
+#define WINDOW_BORDER_VERTICAL_PARITY 0
+
+void CheckerInterleaved3D::Present() const
+{
+	prepareInterleavedPresent(*GLRenderer->mPresent3dCheckerShader);
+
+	// Compute absolute offset from top of screen to top of current display window
+	// because we need screen-relative, not window-relative, scan line parity
+	int windowVOffset = 0;
+	int windowHOffset = 0;
+
+#ifdef _WIN32
+	if (!fullscreen) {
+		I_SaveWindowedPos(); // update win_y CVAR
+		windowHOffset = (win_x + WINDOW_BORDER_HORIZONTAL_PARITY) % 2;
+		windowVOffset = (win_y + WINDOW_BORDER_VERTICAL_PARITY) % 2;
+	}
+#endif // _WIN32
+
+	GLRenderer->mPresent3dCheckerShader->WindowPositionParity.Set(
+		(windowVOffset
+			+ windowHOffset
+			+ GLRenderer->mOutputLetterbox.height + 1 // +1 because of origin at bottom
+		) % 2 // because we want the top pixel offset, but gl_FragCoord.y is the bottom pixel offset
+	);
+
+	GLRenderer->RenderScreenQuad();
+}
+
+void s3d::CheckerInterleaved3D::AdjustViewports() const
+{
+	// decrease the total pixel count by 2, but keep the same aspect ratio
+	const float sqrt2 = 1.41421356237f;
+	// Change size of renderbuffer, and align to screen
+	GLRenderer->mSceneViewport.height /= sqrt2;
+	GLRenderer->mSceneViewport.top /= sqrt2;
+	GLRenderer->mSceneViewport.width /= sqrt2;
+	GLRenderer->mSceneViewport.left /= sqrt2;
+
+	GLRenderer->mScreenViewport.height /= sqrt2;
+	GLRenderer->mScreenViewport.top /= sqrt2;
+	GLRenderer->mScreenViewport.width /= sqrt2;
+	GLRenderer->mScreenViewport.left /= sqrt2;
+}
+
+void ColumnInterleaved3D::Present() const
+{
+	prepareInterleavedPresent(*GLRenderer->mPresent3dColumnShader);
+
+	// Compute absolute offset from top of screen to top of current display window
+	// because we need screen-relative, not window-relative, scan line parity
+	int windowHOffset = 0;
+
+#ifdef _WIN32
+	if (!fullscreen) {
+		I_SaveWindowedPos(); // update win_y CVAR
+		windowHOffset = (win_x + WINDOW_BORDER_HORIZONTAL_PARITY) % 2;
+	}
+#endif // _WIN32
+
+	GLRenderer->mPresent3dColumnShader->WindowPositionParity.Set(windowHOffset);
+
+	GLRenderer->RenderScreenQuad();
+}
+
+void RowInterleaved3D::Present() const
+{
+	prepareInterleavedPresent(*GLRenderer->mPresent3dRowShader);
 
 	// Compute absolute offset from top of screen to top of current display window
 	// because we need screen-relative, not window-relative, scan line parity
@@ -108,13 +194,14 @@ void RowInterleaved3D::Present() const
 #ifdef _WIN32
 	if (! fullscreen) {
 		I_SaveWindowedPos(); // update win_y CVAR
-		windowVOffset = win_y;
+		windowVOffset = (win_y + WINDOW_BORDER_VERTICAL_PARITY) % 2;
 	}
 #endif // _WIN32
 
-	GLRenderer->mPresent3dRowShader->VerticalPixelOffset.Set(
-		windowVOffset // fixme: vary with window location
-		+ box.height % 2 // because we want the top pixel offset, but gl_FragCoord.y is the bottom pixel offset
+	GLRenderer->mPresent3dRowShader->WindowPositionParity.Set(
+		(windowVOffset
+			+ GLRenderer->mOutputLetterbox.height + 1 // +1 because of origin at bottom
+		) % 2
 	);
 
 	GLRenderer->RenderScreenQuad();
diff --git a/src/gl/stereo3d/gl_interleaved3d.h b/src/gl/stereo3d/gl_interleaved3d.h
index 30aef7d5a..78bd19483 100644
--- a/src/gl/stereo3d/gl_interleaved3d.h
+++ b/src/gl/stereo3d/gl_interleaved3d.h
@@ -42,15 +42,30 @@
 #include "gl/system/gl_system.h"
 #include "gl/renderer/gl_renderstate.h"
 
-class FPresent3DRowShader;
-
 namespace s3d {
 
+class CheckerInterleaved3D : public SideBySideSquished
+{
+public:
+	static const CheckerInterleaved3D& getInstance(float ipd);
+	CheckerInterleaved3D(double ipdMeters) : SideBySideSquished(ipdMeters) {}
+	void Present() const override;
+	void AdjustViewports() const override;
+};
+
+class ColumnInterleaved3D : public SideBySideSquished
+{
+public:
+	static const ColumnInterleaved3D& getInstance(float ipd);
+	ColumnInterleaved3D(double ipdMeters) : SideBySideSquished(ipdMeters) {}
+	void Present() const override;
+};
+
 class RowInterleaved3D : public TopBottom3D
 {
 public:
 	static const RowInterleaved3D& getInstance(float ipd);
-	RowInterleaved3D(double ipdMeters);
+	RowInterleaved3D(double ipdMeters) : TopBottom3D(ipdMeters) {}
 	void Present() const override;
 };
 
diff --git a/src/gl/stereo3d/gl_stereo_cvars.cpp b/src/gl/stereo3d/gl_stereo_cvars.cpp
index 16296393d..fe5ae3261 100644
--- a/src/gl/stereo3d/gl_stereo_cvars.cpp
+++ b/src/gl/stereo3d/gl_stereo_cvars.cpp
@@ -107,6 +107,12 @@ const Stereo3DMode& Stereo3DMode::getCurrentMode()
 	case 12:
 		setCurrentMode(RowInterleaved3D::getInstance(vr_ipd));
 		break;
+	case 13:
+		setCurrentMode(ColumnInterleaved3D::getInstance(vr_ipd));
+		break;
+	case 14:
+		setCurrentMode(CheckerInterleaved3D::getInstance(vr_ipd));
+		break;
 	case 0:
 	default:
 		setCurrentMode(MonoView::getInstance());
diff --git a/src/p_acs.cpp b/src/p_acs.cpp
index 0a2ccddac..71ef65f22 100644
--- a/src/p_acs.cpp
+++ b/src/p_acs.cpp
@@ -5821,43 +5821,54 @@ doplaysound:			if (funcIndex == ACSF_PlayActorSound)
 		// [ZK] A_Warp in ACS
 		case ACSF_Warp:
 		{
-			int tid_dest = args[0];
-			int xofs = args[1];
-			int yofs = args[2];
-			int zofs = args[3];
-			int angle = args[4];
-			int flags = args[5];
-			const char *statename = argCount > 6 ? FBehavior::StaticLookupString(args[6]) : "";
-			bool exact = argCount > 7 ? !!args[7] : false;
-			int heightoffset = argCount > 8 ? args[8] : 0;
-			int radiusoffset = argCount > 9 ? args[9] : 0;
-			int pitch = argCount > 10 ? args[10] : 0;
-
-			FState *state = argCount > 6 ? activator->GetClass()->FindStateByString(statename, exact) : 0;
-
-			AActor *reference;
-			if((flags & WARPF_USEPTR) && tid_dest != AAPTR_DEFAULT)
+			if (nullptr == activator)
 			{
-				reference = COPY_AAPTR(activator, tid_dest);
-			}
-			else
-			{
-				reference = SingleActorFromTID(tid_dest, activator);
-			}
-
-			// If there is no activator or actor to warp to, fail.
-			if (activator == NULL || !reference)
 				return false;
-
-			if (P_Thing_Warp(activator, reference, ACSToDouble(xofs), ACSToDouble(yofs), ACSToDouble(zofs), ACSToAngle(angle), flags, ACSToDouble(heightoffset), ACSToDouble(radiusoffset), ACSToAngle(pitch)))
-			{
-				if (state && argCount > 6)
-				{
-					activator->SetState(state);
-				}
-				return true;
 			}
-			return false;
+			
+			const int dest = args[0];
+			const int flags = args[5];
+			
+			AActor* const reference = ((flags & WARPF_USEPTR) && (AAPTR_DEFAULT != dest))
+				? COPY_AAPTR(activator, dest)
+				: SingleActorFromTID(dest, activator);
+
+			if (nullptr == reference)
+			{
+				// there is no actor to warp to
+				return false;
+			}
+			
+			const double xofs = ACSToDouble(args[1]);
+			const double yofs = ACSToDouble(args[2]);
+			const double zofs = ACSToDouble(args[3]);
+			const DAngle angle = ACSToAngle(args[4]);
+			const double heightoffset = argCount > 8 ? ACSToDouble(args[8]) : 0.0;
+			const double radiusoffset = argCount > 9 ? ACSToDouble(args[9]) : 0.0;
+			const DAngle pitch = ACSToAngle(argCount > 10 ? args[10] : 0);
+
+			if (!P_Thing_Warp(activator, reference, xofs, yofs, zofs, angle, flags, heightoffset, radiusoffset, pitch))
+			{
+				return false;
+			}
+			
+			if (argCount > 6)
+			{
+				const char* const statename = FBehavior::StaticLookupString(args[6]);
+				
+				if (nullptr != statename)
+				{
+					const bool exact = argCount > 7 && !!args[7];
+					FState* const state = activator->GetClass()->FindStateByString(statename, exact);
+					
+					if (nullptr != state)
+					{
+						activator->SetState(state);
+					}
+				}
+			}
+			
+			return true;
 		}
 		case ACSF_GetMaxInventory:
 			actor = SingleActorFromTID(args[0], activator);
diff --git a/src/p_pspr.cpp b/src/p_pspr.cpp
index fab7e38d8..e5b076acd 100644
--- a/src/p_pspr.cpp
+++ b/src/p_pspr.cpp
@@ -1006,7 +1006,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_OverlayOffset)
 	PARAM_FLOAT_OPT(wx)		{ wx = 0.; }
 	PARAM_FLOAT_OPT(wy)		{ wy = 32.; }
 	PARAM_INT_OPT(flags)	{ flags = 0; }
-	A_OverlayOffset(self, layer, wx, wy, flags);
+	A_OverlayOffset(self, ((layer != 0) ? layer : stateinfo->mPSPIndex), wx, wy, flags);
 	return 0;
 }
 
@@ -1033,10 +1033,10 @@ DEFINE_ACTION_FUNCTION(AActor, A_OverlayFlags)
 	PARAM_INT(flags);
 	PARAM_BOOL(set);
 
-	if (self->player == nullptr)
+	if (!ACTION_CALL_FROM_PSPRITE())
 		return 0;
 
-	DPSprite *pspr = self->player->FindPSprite(layer);
+	DPSprite *pspr = self->player->FindPSprite(((layer != 0) ? layer : stateinfo->mPSPIndex));
 
 	if (pspr == nullptr)
 		return 0;
@@ -1049,6 +1049,52 @@ DEFINE_ACTION_FUNCTION(AActor, A_OverlayFlags)
 	return 0;
 }
 
+//---------------------------------------------------------------------------
+//
+// PROC OverlayX/Y
+// Action function to return the X/Y of an overlay.
+//---------------------------------------------------------------------------
+
+static double GetOverlayPosition(AActor *self, int layer, bool gety)
+{
+	if (layer)
+	{
+		DPSprite *pspr = self->player->FindPSprite(layer);
+
+		if (pspr != nullptr)
+		{
+			return gety ? (pspr->y) : (pspr->x);
+		}
+	}
+	return 0.;
+}
+
+DEFINE_ACTION_FUNCTION(AActor, OverlayX)
+{
+	PARAM_ACTION_PROLOGUE;
+	PARAM_INT_OPT(layer) { layer = 0; }
+
+	if (ACTION_CALL_FROM_PSPRITE())
+	{
+		double res = GetOverlayPosition(self, ((layer != 0) ? layer : stateinfo->mPSPIndex), false);
+		ACTION_RETURN_FLOAT(res);	
+	}
+	ACTION_RETURN_FLOAT(0.);
+}
+
+DEFINE_ACTION_FUNCTION(AActor, OverlayY)
+{
+	PARAM_ACTION_PROLOGUE;
+	PARAM_INT_OPT(layer) { layer = 0; }
+
+	if (ACTION_CALL_FROM_PSPRITE())
+	{
+		double res = GetOverlayPosition(self, ((layer != 0) ? layer : stateinfo->mPSPIndex), true);
+		ACTION_RETURN_FLOAT(res);
+	}
+	ACTION_RETURN_FLOAT(0.);
+}
+
 //---------------------------------------------------------------------------
 //
 // PROC OverlayID
diff --git a/src/posix/cocoa/i_main.mm b/src/posix/cocoa/i_main.mm
index bbe344809..d8f0e74cf 100644
--- a/src/posix/cocoa/i_main.mm
+++ b/src/posix/cocoa/i_main.mm
@@ -130,6 +130,53 @@ void Mac_I_FatalError(const char* const message)
 }
 
 
+static void I_DetectOS()
+{
+	SInt32 majorVersion = 0;
+	Gestalt(gestaltSystemVersionMajor, &majorVersion);
+	
+	SInt32 minorVersion = 0;
+	Gestalt(gestaltSystemVersionMinor, &minorVersion);
+	
+	SInt32 bugFixVersion = 0;
+	Gestalt(gestaltSystemVersionBugFix, &bugFixVersion);
+	
+	const char* name = "Unknown version";
+	
+	if (10 == majorVersion) switch (minorVersion)
+	{
+		case  4: name = "Mac OS X Tiger";        break;
+		case  5: name = "Mac OS X Leopard";      break;
+		case  6: name = "Mac OS X Snow Leopard"; break;
+		case  7: name = "Mac OS X Lion";         break;
+		case  8: name = "OS X Mountain Lion";    break;
+		case  9: name = "OS X Mavericks";        break;
+		case 10: name = "OS X Yosemite";         break;
+		case 11: name = "OS X El Capitan";       break;
+		case 12: name = "macOS Sierra";          break;
+	}
+
+	char release[16] = {};
+	size_t size = sizeof release - 1;
+	sysctlbyname("kern.osversion", release, &size, nullptr, 0);
+	
+	const char* const architecture =
+#ifdef __i386__
+		"32-bit Intel";
+#elif defined __x86_64__
+		"64-bit Intel";
+#elif defined __ppc__
+		"32-bit PowerPC";
+#elif defined __ppc64__
+		"64-bit PowerPC";
+#else
+		"Unknown";
+#endif
+	
+	Printf("OS: %s %d.%d.%d (%s) %s\n", name, majorVersion, minorVersion, bugFixVersion, release, architecture);
+}
+
+
 DArgs* Args; // command line arguments
 
 
@@ -165,6 +212,8 @@ void OriginalMainTry(int argc, char** argv)
 	progdir += "/";
 
 	C_InitConsole(80 * 8, 25 * 8, false);
+	
+	I_DetectOS();
 	D_DoomMain();
 }
 
diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt
index 44b801f51..fb6b4c832 100644
--- a/wadsrc/static/actors/actor.txt
+++ b/wadsrc/static/actors/actor.txt
@@ -58,6 +58,8 @@ ACTOR Actor native //: Thinker
 	native float GetSpriteRotation(int ptr = AAPTR_DEFAULT);
 	native int GetMissileDamage(int mask, int add, int ptr = AAPTR_DEFAULT);
 	action native int OverlayID();
+	action native float OverlayX(int layer = 0);
+	action native float OverlayY(int layer = 0);
 
 	// Action functions
 	// Meh, MBF redundant functions. Only for DeHackEd support.
diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu
index 079fd0b89..a662b719e 100644
--- a/wadsrc/static/language.enu
+++ b/wadsrc/static/language.enu
@@ -2712,6 +2712,8 @@ OPTVAL_SBSFULL		    	= "Side-by-side Full";
 OPTVAL_SBSNARROW			= "Side-by-side Narrow";
 OPTVAL_TOPBOTTOM			= "Top/Bottom";
 OPTVAL_ROWINTERLEAVED		= "Row Interleaved";
+OPTVAL_COLUMNINTERLEAVED	= "Column Interleaved";
+OPTVAL_CHECKERBOARD			= "Checkerboard";
 OPTVAL_QUADBUFFERED 		= "Quad-buffered";
 OPTVAL_UNCHARTED2			= "Uncharted 2";
 OPTVAL_HEJLDAWSON			= "Hejl Dawson";
diff --git a/wadsrc/static/menudef.zz b/wadsrc/static/menudef.zz
index d5c2bbc5c..c06862bc8 100644
--- a/wadsrc/static/menudef.zz
+++ b/wadsrc/static/menudef.zz
@@ -188,6 +188,8 @@ OptionValue VRMode
 	4, "$OPTVAL_SBSNARROW"
 	11, "$OPTVAL_TOPBOTTOM"
 	12, "$OPTVAL_ROWINTERLEAVED"
+	13, "$OPTVAL_COLUMNINTERLEAVED"
+	14, "$OPTVAL_CHECKERBOARD"
 	5, "$OPTVAL_LEFTEYE"
 	6, "$OPTVAL_RIGHTEYE"
 	7, "$OPTVAL_QUADBUFFERED"
diff --git a/wadsrc/static/shaders/glsl/present_checker3d.fp b/wadsrc/static/shaders/glsl/present_checker3d.fp
new file mode 100644
index 000000000..5247261e0
--- /dev/null
+++ b/wadsrc/static/shaders/glsl/present_checker3d.fp
@@ -0,0 +1,37 @@
+
+in vec2 TexCoord;
+out vec4 FragColor;
+
+uniform sampler2D LeftEyeTexture;
+uniform sampler2D RightEyeTexture;
+uniform float InvGamma;
+uniform float Contrast;
+uniform float Brightness;
+uniform int WindowPositionParity; // top-of-window might not be top-of-screen
+
+vec4 ApplyGamma(vec4 c)
+{
+	vec3 val = c.rgb * Contrast - (Contrast - 1.0) * 0.5;
+	val += Brightness * 0.5;
+	val = pow(max(val, vec3(0.0)), vec3(InvGamma));
+	return vec4(val, c.a);
+}
+
+void main()
+{
+	int thisVerticalPixel = int(gl_FragCoord.y); // Bottom row is typically the right eye, when WindowHeight is even
+	int thisHorizontalPixel = int(gl_FragCoord.x); // column
+	bool isLeftEye = (thisVerticalPixel // because we want to alternate eye view on each row
+			+ thisHorizontalPixel // and each column
+			+ WindowPositionParity // because the window might not be aligned to the screen
+		) % 2 == 0;
+	vec4 inputColor;
+	if (isLeftEye) {
+		inputColor = texture(LeftEyeTexture, TexCoord);
+	}
+	else {
+		// inputColor = vec4(0, 1, 0, 1);
+		inputColor = texture(RightEyeTexture, TexCoord);
+	}
+	FragColor = ApplyGamma(inputColor);
+}
diff --git a/wadsrc/static/shaders/glsl/present_column3d.fp b/wadsrc/static/shaders/glsl/present_column3d.fp
new file mode 100644
index 000000000..b46246cf0
--- /dev/null
+++ b/wadsrc/static/shaders/glsl/present_column3d.fp
@@ -0,0 +1,35 @@
+
+in vec2 TexCoord;
+out vec4 FragColor;
+
+uniform sampler2D LeftEyeTexture;
+uniform sampler2D RightEyeTexture;
+uniform float InvGamma;
+uniform float Contrast;
+uniform float Brightness;
+uniform int WindowPositionParity; // top-of-window might not be top-of-screen
+
+vec4 ApplyGamma(vec4 c)
+{
+	vec3 val = c.rgb * Contrast - (Contrast - 1.0) * 0.5;
+	val += Brightness * 0.5;
+	val = pow(max(val, vec3(0.0)), vec3(InvGamma));
+	return vec4(val, c.a);
+}
+
+void main()
+{
+	int thisHorizontalPixel = int(gl_FragCoord.x); // zero-based column index from left
+	bool isLeftEye = (thisHorizontalPixel // because we want to alternate eye view on each column
+		 + WindowPositionParity // because the window might not be aligned to the screen
+		) % 2 == 0;
+	vec4 inputColor;
+	if (isLeftEye) {
+		inputColor = texture(LeftEyeTexture, TexCoord);
+	}
+	else {
+		// inputColor = vec4(0, 1, 0, 1);
+		inputColor = texture(RightEyeTexture, TexCoord);
+	}
+	FragColor = ApplyGamma(inputColor);
+}
diff --git a/wadsrc/static/shaders/glsl/present_row3d.fp b/wadsrc/static/shaders/glsl/present_row3d.fp
index 8ae72d1e0..50b27ac26 100644
--- a/wadsrc/static/shaders/glsl/present_row3d.fp
+++ b/wadsrc/static/shaders/glsl/present_row3d.fp
@@ -7,7 +7,7 @@ uniform sampler2D RightEyeTexture;
 uniform float InvGamma;
 uniform float Contrast;
 uniform float Brightness;
-uniform int VerticalPixelOffset; // top-of-window might not be top-of-screen
+uniform int WindowPositionParity; // top-of-window might not be top-of-screen
 
 vec4 ApplyGamma(vec4 c)
 {
@@ -19,9 +19,9 @@ vec4 ApplyGamma(vec4 c)
 
 void main()
 {
-	int thisVerticalPixel = int(gl_FragCoord.y + 1.0); // Bottom row is typically the right eye, when WindowHeight is even
+	int thisVerticalPixel = int(gl_FragCoord.y); // Bottom row is typically the right eye, when WindowHeight is even
 	bool isLeftEye = (thisVerticalPixel // because we want to alternate eye view on each row
-		 + VerticalPixelOffset // because the window might not be aligned to the screen
+		 + WindowPositionParity // because the window might not be aligned to the screen
 		) % 2 == 0;
 	vec4 inputColor;
 	if (isLeftEye) {