diff --git a/plugins/GSdx/GSdx.cpp b/plugins/GSdx/GSdx.cpp
index 86c2722ae7..700330fcce 100644
--- a/plugins/GSdx/GSdx.cpp
+++ b/plugins/GSdx/GSdx.cpp
@@ -341,6 +341,7 @@ void GSdxApp::Init()
 	m_default_configuration["debug_glsl_shader"]                          = "0";
 	m_default_configuration["debug_opengl"]                               = "0";
 	m_default_configuration["disable_hw_gl_draw"]                         = "0";
+	m_default_configuration["dithering_ps2"]                              = "1";
 	m_default_configuration["dump"]                                       = "0";
 	m_default_configuration["extrathreads"]                               = "2";
 	m_default_configuration["extrathreads_height"]                        = "4";
diff --git a/plugins/GSdx/Renderers/DX11/GSDevice11.h b/plugins/GSdx/Renderers/DX11/GSDevice11.h
index 1f2682224b..93daf827c5 100644
--- a/plugins/GSdx/Renderers/DX11/GSDevice11.h
+++ b/plugins/GSdx/Renderers/DX11/GSDevice11.h
@@ -103,6 +103,7 @@ public:
 
 		GSVector4 TC_OffsetHack;
 		GSVector4 Af;
+		GSVector4 DitherMatrix[4];
 
 		PSConstantBuffer()
 		{
@@ -115,6 +116,11 @@ public:
 			ChannelShuffle = GSVector4i::zero();
 			FbMask = GSVector4i::zero();
 			Af = GSVector4::zero();
+
+			DitherMatrix[0] = GSVector4::zero();
+			DitherMatrix[1] = GSVector4::zero();
+			DitherMatrix[2] = GSVector4::zero();
+			DitherMatrix[3] = GSVector4::zero();
 		}
 
 		__forceinline bool Update(const PSConstantBuffer* cb)
@@ -123,7 +129,8 @@ public:
 			GSVector4i* b = (GSVector4i*)cb;
 
 			if(!((a[0] == b[0]) /*& (a[1] == b1)*/ & (a[2] == b[2]) & (a[3] == b[3]) & (a[4] == b[4]) & (a[5] == b[5]) &
-				(a[6] == b[6]) & (a[7] == b[7]) & (a[9] == b[9])).alltrue()) // if WH matches HalfTexel does too
+				(a[6] == b[6]) & (a[7] == b[7]) & (a[9] == b[9]) & // if WH matches HalfTexel does too
+				(a[10] == b[10]) & (a[11] == b[11]) & (a[12] == b[12]) & (a[13] == b[13])).alltrue())
 			{
 				a[0] = b[0];
 				a[1] = b[1];
@@ -135,6 +142,11 @@ public:
 				a[7] = b[7];
 				a[9] = b[9];
 
+				a[10] = b[10];
+				a[11] = b[11];
+				a[12] = b[12];
+				a[13] = b[13];
+
 				return true;
 			}
 
@@ -211,17 +223,21 @@ public:
 				uint32 fbmask:1;
 
 				// Blend and Colclip
+				uint32 hdr:1;
 				uint32 blend_a:2;
-				uint32 blend_b:2;
-				uint32 blend_c:2;
+				uint32 blend_b:2; // bit30/31
+				uint32 blend_c:2; // bit0
 				uint32 blend_d:2;
 				uint32 clr1:1;
-				uint32 hdr:1;
+
 				uint32 colclip:1;
 
 				// Others ways to fetch the texture
 				uint32 channel:3;
 
+				// Dithering
+				uint32 dither:2;
+
 				// Hack
 				uint32 tcoffsethack:1;
 				uint32 urban_chaos_hle:1;
@@ -229,7 +245,7 @@ public:
 				uint32 point_sampler:1;
 				uint32 invalid_tex0:1; // Lupin the 3rd
 
-				uint32 _free:18;
+				uint32 _free:16;
 			};
 
 			uint64 key;
diff --git a/plugins/GSdx/Renderers/DX11/GSRendererDX11.cpp b/plugins/GSdx/Renderers/DX11/GSRendererDX11.cpp
index fe9799a9a3..c79ed43edf 100644
--- a/plugins/GSdx/Renderers/DX11/GSRendererDX11.cpp
+++ b/plugins/GSdx/Renderers/DX11/GSRendererDX11.cpp
@@ -986,6 +986,16 @@ void GSRendererDX11::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sou
 	}
 
 	m_ps_sel.fba = m_context->FBA.FBA;
+	m_ps_sel.dither = m_dithering > 0 && m_ps_sel.dfmt == 2 && m_env.DTHE.DTHE;
+
+	if(m_ps_sel.dither)
+	{
+		m_ps_sel.dither = m_dithering;
+		ps_cb.DitherMatrix[0] = GSVector4(m_env.DIMX.DM00, m_env.DIMX.DM10, m_env.DIMX.DM20, m_env.DIMX.DM30);
+		ps_cb.DitherMatrix[1] = GSVector4(m_env.DIMX.DM01, m_env.DIMX.DM11, m_env.DIMX.DM21, m_env.DIMX.DM31);
+		ps_cb.DitherMatrix[2] = GSVector4(m_env.DIMX.DM02, m_env.DIMX.DM12, m_env.DIMX.DM22, m_env.DIMX.DM32);
+		ps_cb.DitherMatrix[3] = GSVector4(m_env.DIMX.DM03, m_env.DIMX.DM13, m_env.DIMX.DM23, m_env.DIMX.DM33);
+	}
 
 	if (PRIM->FGE)
 	{
diff --git a/plugins/GSdx/Renderers/DX11/GSTextureFX11.cpp b/plugins/GSdx/Renderers/DX11/GSTextureFX11.cpp
index bd64b48359..c6ffb19458 100644
--- a/plugins/GSdx/Renderers/DX11/GSTextureFX11.cpp
+++ b/plugins/GSdx/Renderers/DX11/GSTextureFX11.cpp
@@ -219,6 +219,7 @@ void GSDevice11::SetupPS(PSSelector sel, const PSConstantBuffer* cb, PSSamplerSe
 		sm.AddMacro("PS_BLEND_B", sel.blend_b);
 		sm.AddMacro("PS_BLEND_C", sel.blend_c);
 		sm.AddMacro("PS_BLEND_D", sel.blend_d);
+		sm.AddMacro("PS_DITHER", sel.dither);
 
 		CComPtr<ID3D11PixelShader> ps;
 
diff --git a/plugins/GSdx/Renderers/HW/GSRendererHW.cpp b/plugins/GSdx/Renderers/HW/GSRendererHW.cpp
index 26b18944a2..30809f5b53 100644
--- a/plugins/GSdx/Renderers/HW/GSRendererHW.cpp
+++ b/plugins/GSdx/Renderers/HW/GSRendererHW.cpp
@@ -28,7 +28,6 @@ GSRendererHW::GSRendererHW(GSTextureCache* tc)
 	, m_custom_width(1024)
 	, m_custom_height(1024)
 	, m_reset(false)
-	, m_upscale_multiplier(1)
 	, m_userhacks_ts_half_bottom(-1)
 	, m_tc(tc)
 	, m_src(nullptr)
@@ -42,6 +41,7 @@ GSRendererHW::GSRendererHW(GSTextureCache* tc)
 	m_upscale_multiplier = theApp.GetConfigI("upscale_multiplier");
 	m_large_framebuffer  = theApp.GetConfigB("large_framebuffer");
 	m_accurate_date = theApp.GetConfigI("accurate_date");
+	m_dithering = theApp.GetConfigI("dithering_ps2"); // 0 off, 1 auto, 2 auto no scale
 
 	if (theApp.GetConfigB("UserHacks")) {
 		m_userhacks_enabled_gs_mem_clear = !theApp.GetConfigB("UserHacks_Disable_Safe_Features");
diff --git a/plugins/GSdx/Renderers/HW/GSRendererHW.h b/plugins/GSdx/Renderers/HW/GSRendererHW.h
index ab88dad249..4c9ac3cd7d 100644
--- a/plugins/GSdx/Renderers/HW/GSRendererHW.h
+++ b/plugins/GSdx/Renderers/HW/GSRendererHW.h
@@ -158,6 +158,7 @@ protected:
 
 	int m_accurate_date;
 	int m_sw_blending;
+	int m_dithering;
 
 	bool m_channel_shuffle;
 
diff --git a/plugins/GSdx/Renderers/OpenGL/GSDeviceOGL.cpp b/plugins/GSdx/Renderers/OpenGL/GSDeviceOGL.cpp
index ba20e646d3..ad9b29dfc3 100644
--- a/plugins/GSdx/Renderers/OpenGL/GSDeviceOGL.cpp
+++ b/plugins/GSdx/Renderers/OpenGL/GSDeviceOGL.cpp
@@ -982,6 +982,7 @@ GLuint GSDeviceOGL::CompilePS(PSSelector sel)
 		+ format("#define PS_WRITE_RG %d\n", sel.write_rg)
 		+ format("#define PS_FBMASK %d\n", sel.fbmask)
 		+ format("#define PS_HDR %d\n", sel.hdr)
+		+ format("#define PS_DITHER %d\n", sel.dither)
 		// + format("#define PS_PABE %d\n", sel.pabe)
 	;
 
diff --git a/plugins/GSdx/Renderers/OpenGL/GSDeviceOGL.h b/plugins/GSdx/Renderers/OpenGL/GSDeviceOGL.h
index d2c65e91b4..f0a0b1ba4d 100644
--- a/plugins/GSdx/Renderers/OpenGL/GSDeviceOGL.h
+++ b/plugins/GSdx/Renderers/OpenGL/GSDeviceOGL.h
@@ -197,6 +197,7 @@ public:
 		GSVector4 HalfTexel;
 		GSVector4 MinMax;
 		GSVector4 TC_OH_TS;
+		GSVector4 DitherMatrix[4];
 
 		PSConstantBuffer()
 		{
@@ -208,6 +209,11 @@ public:
 			MskFix        = GSVector4i::zero();
 			TC_OH_TS      = GSVector4::zero();
 			FbMask        = GSVector4i::zero();
+
+			DitherMatrix[0] = GSVector4::zero();
+			DitherMatrix[1] = GSVector4::zero();
+			DitherMatrix[2] = GSVector4::zero();
+			DitherMatrix[3] = GSVector4::zero();
 		}
 
 		__forceinline bool Update(const PSConstantBuffer* cb)
@@ -217,7 +223,8 @@ public:
 
 			// if WH matches both HalfTexel and TC_OH_TS do too
 			// MinMax depends on WH and MskFix so no need to check it too
-			if(!((a[0] == b[0]) & (a[1] == b[1]) & (a[2] == b[2]) & (a[3] == b[3]) & (a[4] == b[4])).alltrue())
+			if(!((a[0] == b[0]) & (a[1] == b[1]) & (a[2] == b[2]) & (a[3] == b[3]) & (a[4] == b[4])
+				& (a[8] == b[8]) & (a[9] == b[9]) & (a[10] == b[10]) & (a[11] == b[11])).alltrue())
 			{
 				// Note previous check uses SSE already, a plain copy will be faster than any memcpy
 				a[0] = b[0];
@@ -227,6 +234,11 @@ public:
 				a[4] = b[4];
 				a[5] = b[5];
 
+				a[8] = b[8];
+				a[9] = b[9];
+				a[10] = b[10];
+				a[11] = b[11];
+
 				return true;
 			}
 
@@ -287,6 +299,9 @@ public:
 				// Others ways to fetch the texture
 				uint32 channel:3;
 
+				// Dithering
+				uint32 dither:2;
+
 				// Hack
 				uint32 tcoffsethack:1;
 				uint32 urban_chaos_hle:1;
@@ -297,7 +312,7 @@ public:
 				uint32 point_sampler:1;
 				uint32 invalid_tex0:1; // Lupin the 3rd
 
-				uint32 _free2:10;
+				uint32 _free2:8;
 			};
 
 			uint64 key;
diff --git a/plugins/GSdx/Renderers/OpenGL/GSRendererOGL.cpp b/plugins/GSdx/Renderers/OpenGL/GSRendererOGL.cpp
index e0f419156a..cc0aeb3235 100644
--- a/plugins/GSdx/Renderers/OpenGL/GSRendererOGL.cpp
+++ b/plugins/GSdx/Renderers/OpenGL/GSRendererOGL.cpp
@@ -1217,6 +1217,18 @@ void GSRendererOGL::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sour
 	}
 
 	m_ps_sel.fba = m_context->FBA.FBA;
+	m_ps_sel.dither = m_dithering > 0 && m_ps_sel.dfmt == 2 && m_env.DTHE.DTHE;
+
+	if (m_ps_sel.dither)
+	{
+		GL_INS("DITHERING mode ENABLED (%d)", m_dithering);
+
+		m_ps_sel.dither = m_dithering;
+		ps_cb.DitherMatrix[0] = GSVector4(m_env.DIMX.DM00, m_env.DIMX.DM01, m_env.DIMX.DM02, m_env.DIMX.DM03);
+		ps_cb.DitherMatrix[1] = GSVector4(m_env.DIMX.DM10, m_env.DIMX.DM11, m_env.DIMX.DM12, m_env.DIMX.DM13);
+		ps_cb.DitherMatrix[2] = GSVector4(m_env.DIMX.DM20, m_env.DIMX.DM21, m_env.DIMX.DM22, m_env.DIMX.DM23);
+		ps_cb.DitherMatrix[3] = GSVector4(m_env.DIMX.DM30, m_env.DIMX.DM31, m_env.DIMX.DM32, m_env.DIMX.DM33);
+	}
 
 	if (PRIM->FGE)
 	{
diff --git a/plugins/GSdx/res/glsl/common_header.glsl b/plugins/GSdx/res/glsl/common_header.glsl
index 2673ec848a..a712a9cb27 100644
--- a/plugins/GSdx/res/glsl/common_header.glsl
+++ b/plugins/GSdx/res/glsl/common_header.glsl
@@ -94,6 +94,8 @@ layout(std140, binding = 21) uniform cb21
 
     vec2 TextureScale;
     vec2 TC_OffsetHack;
+
+    mat4 DitherMatrix;
 };
 #endif
 
diff --git a/plugins/GSdx/res/glsl/tfx_fs.glsl b/plugins/GSdx/res/glsl/tfx_fs.glsl
index 0ce52f7502..80ff1483a2 100644
--- a/plugins/GSdx/res/glsl/tfx_fs.glsl
+++ b/plugins/GSdx/res/glsl/tfx_fs.glsl
@@ -637,6 +637,18 @@ void ps_fbmask(inout vec4 C)
 #endif
 }
 
+void ps_dither(inout vec4 C)
+{
+#if PS_DITHER
+    #if PS_DITHER == 2
+    ivec2 fpos = ivec2(gl_FragCoord.xy);
+    #else
+    ivec2 fpos = ivec2(gl_FragCoord.xy / ScalingFactor.x);
+    #endif
+    C.rgb += DitherMatrix[fpos.y&3][fpos.x&3];
+#endif
+}
+
 void ps_blend(inout vec4 Color, float As)
 {
 #if SW_BLEND
@@ -692,7 +704,8 @@ void ps_blend(inout vec4 Color, float As)
     Color.rgb = trunc((A - B) * C + D);
 #endif
 
-    // FIXME dithering
+    // Dithering
+    ps_dither(Color);
 
     // Correct the Color value based on the output format
 #if PS_COLCLIP == 0 && PS_HDR == 0
@@ -842,6 +855,13 @@ void ps_main()
     return;
 #endif
 
+#if !SW_BLEND && PS_DITHER
+    ps_dither(C);
+    // Dither matrix range is [-4,3] but positive values can cause issues when
+    // software blending is not used or is unavailable.
+    C.rgb -= 3.0;
+#endif
+
     ps_blend(C, alpha_blend);
 
     ps_fbmask(C);
diff --git a/plugins/GSdx/res/tfx.fx b/plugins/GSdx/res/tfx.fx
index d293c1d808..6bc4c8b27c 100644
--- a/plugins/GSdx/res/tfx.fx
+++ b/plugins/GSdx/res/tfx.fx
@@ -49,6 +49,7 @@
 #define PS_BLEND_B 0
 #define PS_BLEND_C 0
 #define PS_BLEND_D 0
+#define PS_DITHER 0
 #endif
 
 #define SW_BLEND (PS_BLEND_A || PS_BLEND_B || PS_BLEND_D)
@@ -116,6 +117,7 @@ cbuffer cb1
 	float4 TC_OffsetHack;
 	float Af;
 	float3 _pad;
+	float4x4 DitherMatrix;
 };
 
 cbuffer cb2
@@ -665,6 +667,18 @@ void ps_fbmask(inout float4 C, float2 pos_xy)
 	}
 }
 
+void ps_dither(inout float3 C, float2 pos_xy)
+{
+#if PS_DITHER
+	#if PS_DITHER == 2
+	int2 fpos = int2(pos_xy);
+	#else
+	int2 fpos = int2(pos_xy / (float)PS_SCALE_FACTOR);
+	#endif
+	C += DitherMatrix[fpos.y&3][fpos.x&3];
+#endif
+}
+
 void ps_blend(inout float4 Color, float As, float2 pos_xy)
 {
 	if (SW_BLEND)
@@ -684,6 +698,9 @@ void ps_blend(inout float4 Color, float As, float2 pos_xy)
 
 		Cv = (PS_BLEND_A == PS_BLEND_B) ? D : trunc(((A - B) * C) + D);
 
+		// Dithering
+		ps_dither(Cv, pos_xy);
+
 		// Standard Clamp
 		if (PS_COLCLIP == 0 && PS_HDR == 0)
 			Cv = clamp(Cv, (float3)0.0f, (float3)255.0f);
@@ -746,6 +763,13 @@ PS_OUTPUT ps_main(PS_INPUT input)
 		if (C.a < A_one) C.a += A_one;
 	}
 
+#if !SW_BLEND && PS_DITHER
+	ps_dither(C.rgb, input.p.xy);
+	// Dither matrix range is [-4,3] but positive values can cause issues when
+	// software blending is not used or is unavailable.
+	C.rgb -= 3.0;
+#endif
+	
 	ps_blend(C, alpha_blend, input.p.xy);
 
 	ps_fbmask(C, input.p.xy);