diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index 77d7133b67112ec6498ebcf79d3c6432952d38c3..5295da653e7170cc6bc0ef5fa3e17d95f7420158 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -292,6 +292,7 @@ enum hwdsetspecialstate
 	HWD_SET_TEXTUREFILTERMODE,
 	HWD_SET_TEXTUREANISOTROPICMODE,
 	HWD_SET_WIREFRAME,
+	HWD_SET_MSAA,
 	HWD_NUMSTATE
 };
 
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 69198a648a27d54ff5571aa6c1be918dccb403c9..faba050335be8d02e3e39b74b04b92c3ea63798a 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -44,6 +44,7 @@
 #include "../r_translation.h"
 #include "../d_main.h"
 #include "../p_slopes.h"
+#include "../m_argv.h" // parm functions for msaa
 
 // ==========================================================================
 // the hardware driver object
@@ -5916,6 +5917,14 @@ void HWR_Startup(void)
 		HWR_SetShaderState();
 		HWR_LoadAllCustomShaders();
 		HWR_TogglePaletteRendering();
+
+		if (M_CheckParm("-msaa"))
+		{
+			if (M_CheckParm("-a2c"))
+				HWD.pfnSetSpecialState(HWD_SET_MSAA, 2);
+			else
+				HWD.pfnSetSpecialState(HWD_SET_MSAA, 1);
+		}
 	}
 
 	gl_init = true;
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index f2267dfacd839d7f89491504b24bdb9531a87937..11dec6f90af3dd06cf458b6c5de4ffee01a60f84 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -2277,6 +2277,15 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
 			if (maximumAnisotropy)
 				Flush(); //??? if we want to change filter mode by texture, remove this
 			break;
+			
+		case HWD_SET_MSAA:
+			if (Value)
+			{
+				pglEnable(GL_MULTISAMPLE);
+				if (Value == 2)
+					pglEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
+			}
+			break;
 
 		case HWD_SET_WIREFRAME:
 			pglPolygonMode(GL_FRONT_AND_BACK, Value ? GL_LINE : GL_FILL);
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 3e8b3a0b6bc45ed2874ad33c3c0346c6ed02038e..981069c03b7055d06d05de15f949d3c19d27c567 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -98,6 +98,10 @@ consvar_t cv_vidwait = CVAR_INIT ("vid_wait", "On", "Synchronize framerate with
 static consvar_t cv_stretch = CVAR_INIT ("stretch", "Off", NULL, CV_SAVE|CV_NOSHOWHELP, CV_OnOff, NULL);
 static consvar_t cv_alwaysgrabmouse = CVAR_INIT ("alwaysgrabmouse", "Off", "If on, the game will never let go of the mouse, even when unused", CV_SAVE, CV_OnOff, NULL);
 
+// these cant be used since config is read after window creation, so need to use command line parameter instead
+//static CV_PossibleValue_t msaa_cons_t[] = {{0, "Off"}, {2, "2X"}, {4, "4X"}, {8, "8X"}, {16, "16X"}, {0, NULL}};
+//consvar_t cv_msaa = CVAR_INIT("msaa", "Off", "If on, MSAA antialiasing will be used, reducing jagged lines at the cost of performance", CV_SAVE, msaa_cons_t, NULL);
+
 UINT8 graphics_started = 0; // Is used in console.c and screen.c
 
 // To disable fullscreen at startup
@@ -1591,6 +1595,17 @@ static SDL_bool Impl_CreateWindow(SDL_bool fullscreen)
 #ifdef HWRENDER
 	flags |= SDL_WINDOW_OPENGL;
 
+	if (M_CheckParm("-msaa") && M_IsNextParm())
+	{
+		unsigned int value;
+		const char* str = M_GetNextParm();
+		if (sscanf(str, "%u", &value))
+		{
+			SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
+			SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, value);
+		}
+	}
+
 	// Without a 24-bit depth buffer many visuals are ruined by z-fighting.
 	// Some GPU drivers may give us a 16-bit depth buffer since the
 	// default value for SDL_GL_DEPTH_SIZE is 16.