diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index c8af1c6b67..0e09a7ee55 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -643,8 +643,10 @@ public:
                 u32 d3d_cull_mode;
 
                 ComparisonOp depth_test_func;
+                float alpha_test_ref;
+                ComparisonOp alpha_test_func;
 
-                INSERT_PADDING_WORDS(0xB);
+                INSERT_PADDING_WORDS(0x9);
 
                 struct {
                     u32 separate_alpha;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 3daccf82f0..be51c5215f 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -570,10 +570,11 @@ void RasterizerOpenGL::DrawArrays() {
     SyncBlendState();
     SyncLogicOpState();
     SyncCullMode();
-    SyncAlphaTest();
     SyncScissorTest();
+    // Alpha Testing is synced on shaders.
     SyncTransformFeedback();
     SyncPointState();
+    CheckAlphaTests();
 
     // TODO(bunnei): Sync framebuffer_scale uniform here
     // TODO(bunnei): Sync scissorbox uniform(s) here
@@ -1007,17 +1008,6 @@ void RasterizerOpenGL::SyncLogicOpState() {
     state.logic_op.operation = MaxwellToGL::LogicOp(regs.logic_op.operation);
 }
 
-void RasterizerOpenGL::SyncAlphaTest() {
-    const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
-
-    // TODO(Rodrigo): Alpha testing is a legacy OpenGL feature, but it can be
-    // implemented with a test+discard in fragment shaders.
-    if (regs.alpha_test_enabled != 0) {
-        LOG_CRITICAL(Render_OpenGL, "Alpha testing is not implemented");
-        UNREACHABLE();
-    }
-}
-
 void RasterizerOpenGL::SyncScissorTest() {
     const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
 
@@ -1052,4 +1042,15 @@ void RasterizerOpenGL::SyncPointState() {
     state.point.size = regs.point_size == 0 ? 1 : regs.point_size;
 }
 
+void RasterizerOpenGL::CheckAlphaTests() {
+    const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+
+    if (regs.alpha_test_enabled != 0 && regs.rt_control.count > 1) {
+        LOG_CRITICAL(
+            Render_OpenGL,
+            "Alpha Testing is enabled with Multiple Render Targets, this behavior is undefined.");
+        UNREACHABLE();
+    }
+}
+
 } // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index b1f7ccc7e7..0e90a31f5e 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -162,9 +162,6 @@ private:
     /// Syncs the LogicOp state to match the guest state
     void SyncLogicOpState();
 
-    /// Syncs the alpha test state to match the guest state
-    void SyncAlphaTest();
-
     /// Syncs the scissor test state to match the guest state
     void SyncScissorTest();
 
@@ -174,6 +171,9 @@ private:
     /// Syncs the point state to match the guest state
     void SyncPointState();
 
+    /// Check asserts for alpha testing.
+    void CheckAlphaTests();
+
     bool has_ARB_direct_state_access = false;
     bool has_ARB_multi_bind = false;
     bool has_ARB_separate_shader_objects = false;
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index d36f190b7d..7a019fc860 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -1266,9 +1266,29 @@ private:
 
         ASSERT_MSG(header.ps.omap.sample_mask == 0, "Samplemask write is unimplemented");
 
+        shader.AddLine("if (alpha_test[0] != 0) {");
+        ++shader.scope;
+        // We start on the register containing the alpha value in the first RT.
+        u32 current_reg = 3;
+        for (u32 render_target = 0; render_target < Maxwell3D::Regs::NumRenderTargets;
+             ++render_target) {
+            // TODO(Blinkhawk): verify the behavior of alpha testing on hardware when
+            // multiple render targets are used.
+            if (header.ps.IsColorComponentOutputEnabled(render_target, 0) ||
+                header.ps.IsColorComponentOutputEnabled(render_target, 1) ||
+                header.ps.IsColorComponentOutputEnabled(render_target, 2) ||
+                header.ps.IsColorComponentOutputEnabled(render_target, 3)) {
+                shader.AddLine(fmt::format("if (!AlphaFunc({})) discard;",
+                                           regs.GetRegisterAsFloat(current_reg)));
+                current_reg += 4;
+            }
+        }
+        --shader.scope;
+        shader.AddLine('}');
+
         // Write the color outputs using the data in the shader registers, disabled
         // rendertargets/components are skipped in the register assignment.
-        u32 current_reg = 0;
+        current_reg = 0;
         for (u32 render_target = 0; render_target < Maxwell3D::Regs::NumRenderTargets;
              ++render_target) {
             // TODO(Subv): Figure out how dual-source blending is configured in the Switch.
@@ -3516,7 +3536,7 @@ private:
 
     // Declarations
     std::set<std::string> declr_predicates;
-}; // namespace Decompiler
+}; // namespace OpenGL::GLShader::Decompiler
 
 std::string GetCommonDeclarations() {
     return fmt::format("#define MAX_CONSTBUFFER_ELEMENTS {}\n",
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index ecbc9d8ed3..e883ffb1df 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -29,6 +29,7 @@ layout(std140) uniform vs_config {
     vec4 viewport_flip;
     uvec4 instance_id;
     uvec4 flip_stage;
+    uvec4 alpha_test;
 };
 )";
 
@@ -105,6 +106,7 @@ layout (std140) uniform gs_config {
     vec4 viewport_flip;
     uvec4 instance_id;
     uvec4 flip_stage;
+    uvec4 alpha_test;
 };
 
 void main() {
@@ -142,8 +144,33 @@ layout (std140) uniform fs_config {
     vec4 viewport_flip;
     uvec4 instance_id;
     uvec4 flip_stage;
+    uvec4 alpha_test;
 };
 
+bool AlphaFunc(in float value) {
+    float ref = uintBitsToFloat(alpha_test[2]);
+    switch (alpha_test[1]) {
+        case 1:
+            return false;
+        case 2:
+            return value < ref;
+        case 3:
+            return value == ref;
+        case 4:
+            return value <= ref;
+        case 5:
+            return value > ref;
+        case 6:
+            return value != ref;
+        case 7:
+            return value >= ref;
+        case 8:
+            return true;
+        default:
+            return false;
+    }
+}
+
 void main() {
     exec_fragment();
 }
@@ -152,4 +179,4 @@ void main() {
     out += program.first;
     return {out, program.second};
 }
-} // namespace OpenGL::GLShader
\ No newline at end of file
+} // namespace OpenGL::GLShader
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp
index 010857ec62..8b8869ecbf 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp
@@ -16,6 +16,17 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& sh
     viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0f : 1.0f;
     viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0f : 1.0f;
 
+    u32 func = static_cast<u32>(regs.alpha_test_func);
+    // Normalize the gl variants of opCompare to be the same as the normal variants
+    u32 op_gl_variant_base = static_cast<u32>(Tegra::Engines::Maxwell3D::Regs::ComparisonOp::Never);
+    if (func >= op_gl_variant_base) {
+        func = func - op_gl_variant_base + 1U;
+    }
+
+    alpha_test.enabled = regs.alpha_test_enabled;
+    alpha_test.func = func;
+    alpha_test.ref = regs.alpha_test_ref;
+
     // We only assign the instance to the first component of the vector, the rest is just padding.
     instance_id[0] = state.current_instance;
 
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h
index b3a191cf2c..36fe1f04ce 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.h
+++ b/src/video_core/renderer_opengl/gl_shader_manager.h
@@ -22,8 +22,14 @@ struct MaxwellUniformData {
     alignas(16) GLvec4 viewport_flip;
     alignas(16) GLuvec4 instance_id;
     alignas(16) GLuvec4 flip_stage;
+    struct alignas(16) {
+        GLuint enabled;
+        GLuint func;
+        GLfloat ref;
+        GLuint padding;
+    } alpha_test;
 };
-static_assert(sizeof(MaxwellUniformData) == 48, "MaxwellUniformData structure size is incorrect");
+static_assert(sizeof(MaxwellUniformData) == 64, "MaxwellUniformData structure size is incorrect");
 static_assert(sizeof(MaxwellUniformData) < 16384,
               "MaxwellUniformData structure must be less than 16kb as per the OpenGL spec");