From 145ae369639af6f91a59b8217c5e9ba42b0c5f0b Mon Sep 17 00:00:00 2001
From: Rodolfo Bogado <rodolfoosvaldobogado@gmail.com>
Date: Fri, 2 Nov 2018 00:21:25 -0300
Subject: [PATCH] Implement multi-target viewports and blending

---
 src/video_core/engines/maxwell_3d.cpp         |  16 ++
 src/video_core/engines/maxwell_3d.h           |  14 +-
 .../renderer_opengl/gl_rasterizer.cpp         |  93 ++++----
 .../renderer_opengl/gl_rasterizer.h           |   5 +-
 src/video_core/renderer_opengl/gl_state.cpp   | 205 ++++++++++++------
 src/video_core/renderer_opengl/gl_state.h     |  52 +++--
 6 files changed, 258 insertions(+), 127 deletions(-)

diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index d79c50919..2cd595f26 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -37,6 +37,22 @@ void Maxwell3D::InitializeRegisterDefaults() {
         regs.viewport[viewport].depth_range_near = 0.0f;
         regs.viewport[viewport].depth_range_far = 1.0f;
     }
+    // Doom and Bomberman seems to use the uninitialized registers and just enable blend
+    // so initialize blend registers with sane values
+    regs.blend.equation_rgb = Regs::Blend::Equation::Add;
+    regs.blend.factor_source_rgb = Regs::Blend::Factor::One;
+    regs.blend.factor_dest_rgb = Regs::Blend::Factor::Zero;
+    regs.blend.equation_a = Regs::Blend::Equation::Add;
+    regs.blend.factor_source_a = Regs::Blend::Factor::One;
+    regs.blend.factor_dest_a = Regs::Blend::Factor::Zero;
+    for (std::size_t blend_index = 0; blend_index < Regs::NumRenderTargets; blend_index++) {
+        regs.independent_blend[blend_index].equation_rgb = Regs::Blend::Equation::Add;
+        regs.independent_blend[blend_index].factor_source_rgb = Regs::Blend::Factor::One;
+        regs.independent_blend[blend_index].factor_dest_rgb = Regs::Blend::Factor::Zero;
+        regs.independent_blend[blend_index].equation_a = Regs::Blend::Equation::Add;
+        regs.independent_blend[blend_index].factor_source_a = Regs::Blend::Factor::One;
+        regs.independent_blend[blend_index].factor_dest_a = Regs::Blend::Factor::Zero;
+    }
 }
 
 void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) {
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 50873813e..33eb57360 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -646,8 +646,14 @@ public:
                 ComparisonOp depth_test_func;
                 float alpha_test_ref;
                 ComparisonOp alpha_test_func;
-
-                INSERT_PADDING_WORDS(0x9);
+                u32 draw_tfb_stride;
+                struct {
+                    float r;
+                    float g;
+                    float b;
+                    float a;
+                } blend_color;
+                INSERT_PADDING_WORDS(0x4);
 
                 struct {
                     u32 separate_alpha;
@@ -1087,6 +1093,10 @@ ASSERT_REG_POSITION(depth_write_enabled, 0x4BA);
 ASSERT_REG_POSITION(alpha_test_enabled, 0x4BB);
 ASSERT_REG_POSITION(d3d_cull_mode, 0x4C2);
 ASSERT_REG_POSITION(depth_test_func, 0x4C3);
+ASSERT_REG_POSITION(alpha_test_ref, 0x4C4);
+ASSERT_REG_POSITION(alpha_test_func, 0x4C5);
+ASSERT_REG_POSITION(draw_tfb_stride, 0x4C6);
+ASSERT_REG_POSITION(blend_color, 0x4C7);
 ASSERT_REG_POSITION(blend, 0x4CF);
 ASSERT_REG_POSITION(stencil_enable, 0x4E0);
 ASSERT_REG_POSITION(stencil_front_op_fail, 0x4E1);
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index a0527fe57..73770ff69 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -580,7 +580,6 @@ void RasterizerOpenGL::DrawArrays() {
     SyncLogicOpState();
     SyncCullMode();
     SyncPrimitiveRestart();
-    SyncDepthRange();
     SyncScissorTest();
     // Alpha Testing is synced on shaders.
     SyncTransformFeedback();
@@ -899,12 +898,16 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
 
 void RasterizerOpenGL::SyncViewport() {
     const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
-    const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()};
-
-    state.viewport.x = viewport_rect.left;
-    state.viewport.y = viewport_rect.bottom;
-    state.viewport.width = static_cast<GLsizei>(viewport_rect.GetWidth());
-    state.viewport.height = static_cast<GLsizei>(viewport_rect.GetHeight());
+    for (size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) {
+        const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[i].GetRect()};
+        auto& viewport = state.viewports[i];
+        viewport.x = viewport_rect.left;
+        viewport.y = viewport_rect.bottom;
+        viewport.width = static_cast<GLsizei>(viewport_rect.GetWidth());
+        viewport.height = static_cast<GLsizei>(viewport_rect.GetHeight());
+        viewport.depth_range_far = regs.viewport[i].depth_range_far;
+        viewport.depth_range_near = regs.viewport[i].depth_range_near;
+    }
 }
 
 void RasterizerOpenGL::SyncClipEnabled() {
@@ -946,13 +949,6 @@ void RasterizerOpenGL::SyncPrimitiveRestart() {
     state.primitive_restart.index = regs.primitive_restart.index;
 }
 
-void RasterizerOpenGL::SyncDepthRange() {
-    const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
-
-    state.depth.depth_range_near = regs.viewport->depth_range_near;
-    state.depth.depth_range_far = regs.viewport->depth_range_far;
-}
-
 void RasterizerOpenGL::SyncDepthTestState() {
     const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
 
@@ -996,23 +992,44 @@ void RasterizerOpenGL::SyncStencilTestState() {
 void RasterizerOpenGL::SyncBlendState() {
     const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
 
-    // TODO(Subv): Support more than just render target 0.
-    state.blend.enabled = regs.blend.enable[0] != 0;
+    state.blend_color.red = regs.blend_color.r;
+    state.blend_color.green = regs.blend_color.g;
+    state.blend_color.blue = regs.blend_color.b;
+    state.blend_color.alpha = regs.blend_color.a;
 
-    if (!state.blend.enabled)
+    state.independant_blend.enabled = regs.independent_blend_enable;
+    if (!state.independant_blend.enabled) {
+        auto& blend = state.blend[0];
+        blend.separate_alpha = regs.blend.separate_alpha;
+        blend.rgb_equation = MaxwellToGL::BlendEquation(regs.blend.equation_rgb);
+        blend.src_rgb_func = MaxwellToGL::BlendFunc(regs.blend.factor_source_rgb);
+        blend.dst_rgb_func = MaxwellToGL::BlendFunc(regs.blend.factor_dest_rgb);
+        if (blend.separate_alpha) {
+            blend.a_equation = MaxwellToGL::BlendEquation(regs.blend.equation_a);
+            blend.src_a_func = MaxwellToGL::BlendFunc(regs.blend.factor_source_a);
+            blend.dst_a_func = MaxwellToGL::BlendFunc(regs.blend.factor_dest_a);
+        }
+        for (size_t i = 1; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) {
+            state.blend[i].enabled = false;
+        }
         return;
+    }
 
-    ASSERT_MSG(regs.logic_op.enable == 0,
-               "Blending and logic op can't be enabled at the same time.");
-
-    ASSERT_MSG(regs.independent_blend_enable == 1, "Only independent blending is implemented");
-    ASSERT_MSG(!regs.independent_blend[0].separate_alpha, "Unimplemented");
-    state.blend.rgb_equation = MaxwellToGL::BlendEquation(regs.independent_blend[0].equation_rgb);
-    state.blend.src_rgb_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_source_rgb);
-    state.blend.dst_rgb_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_dest_rgb);
-    state.blend.a_equation = MaxwellToGL::BlendEquation(regs.independent_blend[0].equation_a);
-    state.blend.src_a_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_source_a);
-    state.blend.dst_a_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_dest_a);
+    for (size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) {
+        auto& blend = state.blend[i];
+        blend.enabled = regs.blend.enable[i] != 0;
+        if (!blend.enabled)
+            continue;
+        blend.separate_alpha = regs.independent_blend[i].separate_alpha;
+        blend.rgb_equation = MaxwellToGL::BlendEquation(regs.independent_blend[i].equation_rgb);
+        blend.src_rgb_func = MaxwellToGL::BlendFunc(regs.independent_blend[i].factor_source_rgb);
+        blend.dst_rgb_func = MaxwellToGL::BlendFunc(regs.independent_blend[i].factor_dest_rgb);
+        if (blend.separate_alpha) {
+            blend.a_equation = MaxwellToGL::BlendEquation(regs.independent_blend[i].equation_a);
+            blend.src_a_func = MaxwellToGL::BlendFunc(regs.independent_blend[i].factor_source_a);
+            blend.dst_a_func = MaxwellToGL::BlendFunc(regs.independent_blend[i].factor_dest_a);
+        }
+    }
 }
 
 void RasterizerOpenGL::SyncLogicOpState() {
@@ -1031,19 +1048,19 @@ void RasterizerOpenGL::SyncLogicOpState() {
 }
 
 void RasterizerOpenGL::SyncScissorTest() {
+    // TODO: what is the correct behavior here, a single scissor for all targets
+    // or scissor disabled for the rest of the targets?
     const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
-
     state.scissor.enabled = (regs.scissor_test.enable != 0);
-    // TODO(Blinkhawk): Figure if the hardware supports scissor testing per viewport and how it's
-    // implemented.
-    if (regs.scissor_test.enable != 0) {
-        const u32 width = regs.scissor_test.max_x - regs.scissor_test.min_x;
-        const u32 height = regs.scissor_test.max_y - regs.scissor_test.min_y;
-        state.scissor.x = regs.scissor_test.min_x;
-        state.scissor.y = regs.scissor_test.min_y;
-        state.scissor.width = width;
-        state.scissor.height = height;
+    if (regs.scissor_test.enable == 0) {
+        return;
     }
+    const u32 width = regs.scissor_test.max_x - regs.scissor_test.min_x;
+    const u32 height = regs.scissor_test.max_y - regs.scissor_test.min_y;
+    state.scissor.x = regs.scissor_test.min_x;
+    state.scissor.y = regs.scissor_test.min_y;
+    state.scissor.width = width;
+    state.scissor.height = height;
 }
 
 void RasterizerOpenGL::SyncTransformFeedback() {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 47097c569..8ec22df8d 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -133,7 +133,7 @@ private:
     u32 SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader,
                       GLenum primitive_mode, u32 current_unit);
 
-    /// Syncs the viewport to match the guest state
+    /// Syncs the viewport and depth range to match the guest state
     void SyncViewport();
 
     /// Syncs the clip enabled status to match the guest state
@@ -148,9 +148,6 @@ private:
     /// Syncs the primitve restart to match the guest state
     void SyncPrimitiveRestart();
 
-    /// Syncs the depth range to match the guest state
-    void SyncDepthRange();
-
     /// Syncs the depth test state to match the guest state
     void SyncDepthTestState();
 
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index b6b426f34..2e1f81e26 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -22,8 +22,6 @@ OpenGLState::OpenGLState() {
     depth.test_enabled = false;
     depth.test_func = GL_LESS;
     depth.write_mask = GL_TRUE;
-    depth.depth_range_near = 0.0f;
-    depth.depth_range_far = 1.0f;
 
     primitive_restart.enabled = false;
     primitive_restart.index = 0;
@@ -45,19 +43,33 @@ OpenGLState::OpenGLState() {
     };
     reset_stencil(stencil.front);
     reset_stencil(stencil.back);
-
-    blend.enabled = true;
-    blend.rgb_equation = GL_FUNC_ADD;
-    blend.a_equation = GL_FUNC_ADD;
-    blend.src_rgb_func = GL_ONE;
-    blend.dst_rgb_func = GL_ZERO;
-    blend.src_a_func = GL_ONE;
-    blend.dst_a_func = GL_ZERO;
-    blend.color.red = 0.0f;
-    blend.color.green = 0.0f;
-    blend.color.blue = 0.0f;
-    blend.color.alpha = 0.0f;
-
+    for (auto& item : viewports) {
+        item.x = 0;
+        item.y = 0;
+        item.width = 0;
+        item.height = 0;
+        item.depth_range_near = 0.0f;
+        item.depth_range_far = 1.0f;
+    }
+    scissor.enabled = false;
+    scissor.x = 0;
+    scissor.y = 0;
+    scissor.width = 0;
+    scissor.height = 0;
+    for (auto& item : blend) {
+        item.enabled = true;
+        item.rgb_equation = GL_FUNC_ADD;
+        item.a_equation = GL_FUNC_ADD;
+        item.src_rgb_func = GL_ONE;
+        item.dst_rgb_func = GL_ZERO;
+        item.src_a_func = GL_ONE;
+        item.dst_a_func = GL_ZERO;
+    }
+    independant_blend.enabled = false;
+    blend_color.red = 0.0f;
+    blend_color.green = 0.0f;
+    blend_color.blue = 0.0f;
+    blend_color.alpha = 0.0f;
     logic_op.enabled = false;
     logic_op.operation = GL_COPY;
 
@@ -73,17 +85,6 @@ OpenGLState::OpenGLState() {
     draw.shader_program = 0;
     draw.program_pipeline = 0;
 
-    scissor.enabled = false;
-    scissor.x = 0;
-    scissor.y = 0;
-    scissor.width = 0;
-    scissor.height = 0;
-
-    viewport.x = 0;
-    viewport.y = 0;
-    viewport.width = 0;
-    viewport.height = 0;
-
     clip_distance = {};
 
     point.size = 1;
@@ -152,11 +153,6 @@ void OpenGLState::ApplyDepth() const {
     if (depth.write_mask != cur_state.depth.write_mask) {
         glDepthMask(depth.write_mask);
     }
-    // Depth range
-    if (depth.depth_range_near != cur_state.depth.depth_range_near ||
-        depth.depth_range_far != cur_state.depth.depth_range_far) {
-        glDepthRange(depth.depth_range_near, depth.depth_range_far);
-    }
 }
 
 void OpenGLState::ApplyPrimitiveRestart() const {
@@ -208,7 +204,7 @@ void OpenGLState::ApplyStencilTest() const {
     }
 }
 
-void OpenGLState::ApplyScissorTest() const {
+void OpenGLState::ApplyScissor() const {
     const bool scissor_changed = scissor.enabled != cur_state.scissor.enabled;
     if (scissor_changed) {
         if (scissor.enabled) {
@@ -217,51 +213,141 @@ void OpenGLState::ApplyScissorTest() const {
             glDisable(GL_SCISSOR_TEST);
         }
     }
-    if (scissor_changed || scissor_changed || scissor.x != cur_state.scissor.x ||
-        scissor.y != cur_state.scissor.y || scissor.width != cur_state.scissor.width ||
-        scissor.height != cur_state.scissor.height) {
+    if (scissor.enabled &&
+        (scissor_changed || scissor.x != cur_state.scissor.x || scissor.y != cur_state.scissor.y ||
+         scissor.width != cur_state.scissor.width || scissor.height != cur_state.scissor.height)) {
         glScissor(scissor.x, scissor.y, scissor.width, scissor.height);
     }
 }
 
-void OpenGLState::ApplyBlending() const {
-    const bool blend_changed = blend.enabled != cur_state.blend.enabled;
+void OpenGLState::ApplyViewport() const {
+    if (GLAD_GL_ARB_viewport_array) {
+        for (GLuint i = 0;
+             i < static_cast<GLuint>(Tegra::Engines::Maxwell3D::Regs::NumRenderTargets); i++) {
+            const auto& current = cur_state.viewports[i];
+            const auto& updated = viewports[i];
+            if (updated.x != current.x || updated.y != current.y ||
+                updated.width != current.width || updated.height != current.height) {
+                glViewportIndexedf(i, updated.x, updated.y, updated.width, updated.height);
+            }
+            if (updated.depth_range_near != current.depth_range_near ||
+                updated.depth_range_far != current.depth_range_far) {
+                glDepthRangeIndexed(i, updated.depth_range_near, updated.depth_range_far);
+            }
+        }
+    } else {
+        const auto& current = cur_state.viewports[0];
+        const auto& updated = viewports[0];
+        if (updated.x != current.x || updated.y != current.y || updated.width != current.width ||
+            updated.height != current.height) {
+            glViewport(updated.x, updated.y, updated.width, updated.height);
+        }
+        if (updated.depth_range_near != current.depth_range_near ||
+            updated.depth_range_far != current.depth_range_far) {
+            glDepthRange(updated.depth_range_near, updated.depth_range_far);
+        }
+    }
+}
+
+void OpenGLState::ApplyGlobalBlending() const {
+    const Blend& current = cur_state.blend[0];
+    const Blend& updated = blend[0];
+    const bool blend_changed = updated.enabled != current.enabled;
     if (blend_changed) {
-        if (blend.enabled) {
-            ASSERT(!logic_op.enabled);
+        if (updated.enabled) {
             glEnable(GL_BLEND);
         } else {
             glDisable(GL_BLEND);
         }
     }
-    if (blend.enabled) {
-        if (blend_changed || blend.color.red != cur_state.blend.color.red ||
-            blend.color.green != cur_state.blend.color.green ||
-            blend.color.blue != cur_state.blend.color.blue ||
-            blend.color.alpha != cur_state.blend.color.alpha) {
-            glBlendColor(blend.color.red, blend.color.green, blend.color.blue, blend.color.alpha);
+    if (!updated.enabled) {
+        return;
+    }
+    if (updated.separate_alpha) {
+        if (blend_changed || updated.src_rgb_func != current.src_rgb_func ||
+            updated.dst_rgb_func != current.dst_rgb_func ||
+            updated.src_a_func != current.src_a_func || updated.dst_a_func != current.dst_a_func) {
+            glBlendFuncSeparate(updated.src_rgb_func, updated.dst_rgb_func, updated.src_a_func,
+                                updated.dst_a_func);
         }
 
-        if (blend_changed || blend.src_rgb_func != cur_state.blend.src_rgb_func ||
-            blend.dst_rgb_func != cur_state.blend.dst_rgb_func ||
-            blend.src_a_func != cur_state.blend.src_a_func ||
-            blend.dst_a_func != cur_state.blend.dst_a_func) {
-            glBlendFuncSeparate(blend.src_rgb_func, blend.dst_rgb_func, blend.src_a_func,
-                                blend.dst_a_func);
+        if (blend_changed || updated.rgb_equation != current.rgb_equation ||
+            updated.a_equation != current.a_equation) {
+            glBlendEquationSeparate(updated.rgb_equation, updated.a_equation);
+        }
+    } else {
+        if (blend_changed || updated.src_rgb_func != current.src_rgb_func ||
+            updated.dst_rgb_func != current.dst_rgb_func) {
+            glBlendFunc(updated.src_rgb_func, updated.dst_rgb_func);
         }
 
-        if (blend_changed || blend.rgb_equation != cur_state.blend.rgb_equation ||
-            blend.a_equation != cur_state.blend.a_equation) {
-            glBlendEquationSeparate(blend.rgb_equation, blend.a_equation);
+        if (blend_changed || updated.rgb_equation != current.rgb_equation) {
+            glBlendEquation(updated.rgb_equation);
         }
     }
 }
 
+void OpenGLState::ApplyTargetBlending(int target, bool force) const {
+    const Blend& updated = blend[target];
+    const Blend& current = cur_state.blend[target];
+    const bool blend_changed = updated.enabled != current.enabled || force;
+    if (blend_changed) {
+        if (updated.enabled) {
+            glEnablei(GL_BLEND, static_cast<GLuint>(target));
+        } else {
+            glDisablei(GL_BLEND, static_cast<GLuint>(target));
+        }
+    }
+    if (!updated.enabled) {
+        return;
+    }
+    if (updated.separate_alpha) {
+        if (blend_changed || updated.src_rgb_func != current.src_rgb_func ||
+            updated.dst_rgb_func != current.dst_rgb_func ||
+            updated.src_a_func != current.src_a_func || updated.dst_a_func != current.dst_a_func) {
+            glBlendFuncSeparateiARB(static_cast<GLuint>(target), updated.src_rgb_func,
+                                    updated.dst_rgb_func, updated.src_a_func, updated.dst_a_func);
+        }
+
+        if (blend_changed || updated.rgb_equation != current.rgb_equation ||
+            updated.a_equation != current.a_equation) {
+            glBlendEquationSeparateiARB(static_cast<GLuint>(target), updated.rgb_equation,
+                                        updated.a_equation);
+        }
+    } else {
+        if (blend_changed || updated.src_rgb_func != current.src_rgb_func ||
+            updated.dst_rgb_func != current.dst_rgb_func) {
+            glBlendFunciARB(static_cast<GLuint>(target), updated.src_rgb_func,
+                            updated.dst_rgb_func);
+        }
+
+        if (blend_changed || updated.rgb_equation != current.rgb_equation) {
+            glBlendEquationiARB(static_cast<GLuint>(target), updated.rgb_equation);
+        }
+    }
+}
+
+void OpenGLState::ApplyBlending() const {
+    if (independant_blend.enabled) {
+        for (size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) {
+            ApplyTargetBlending(i,
+                                independant_blend.enabled != cur_state.independant_blend.enabled);
+        }
+    } else {
+        ApplyGlobalBlending();
+    }
+    if (blend_color.red != cur_state.blend_color.red ||
+        blend_color.green != cur_state.blend_color.green ||
+        blend_color.blue != cur_state.blend_color.blue ||
+        blend_color.alpha != cur_state.blend_color.alpha) {
+        glBlendColor(blend_color.red, blend_color.green, blend_color.blue, blend_color.alpha);
+    }
+}
+
 void OpenGLState::ApplyLogicOp() const {
     const bool logic_op_changed = logic_op.enabled != cur_state.logic_op.enabled;
     if (logic_op_changed) {
         if (logic_op.enabled) {
-            ASSERT(!blend.enabled);
             glEnable(GL_COLOR_LOGIC_OP);
         } else {
             glDisable(GL_COLOR_LOGIC_OP);
@@ -348,12 +434,6 @@ void OpenGLState::Apply() const {
     if (draw.program_pipeline != cur_state.draw.program_pipeline) {
         glBindProgramPipeline(draw.program_pipeline);
     }
-    // Viewport
-    if (viewport.x != cur_state.viewport.x || viewport.y != cur_state.viewport.y ||
-        viewport.width != cur_state.viewport.width ||
-        viewport.height != cur_state.viewport.height) {
-        glViewport(viewport.x, viewport.y, viewport.width, viewport.height);
-    }
     // Clip distance
     for (std::size_t i = 0; i < clip_distance.size(); ++i) {
         if (clip_distance[i] != cur_state.clip_distance[i]) {
@@ -376,7 +456,8 @@ void OpenGLState::Apply() const {
     if (point.size != cur_state.point.size) {
         glPointSize(point.size);
     }
-    ApplyScissorTest();
+    ApplyViewport();
+    ApplyScissor();
     ApplyStencilTest();
     ApplySRgb();
     ApplyCulling();
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index fe648aff6..a027ca33c 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -46,11 +46,9 @@ public:
     } cull;
 
     struct {
-        bool test_enabled;        // GL_DEPTH_TEST
-        GLenum test_func;         // GL_DEPTH_FUNC
-        GLboolean write_mask;     // GL_DEPTH_WRITEMASK
-        GLfloat depth_range_near; // GL_DEPTH_RANGE
-        GLfloat depth_range_far;  // GL_DEPTH_RANGE
+        bool test_enabled;    // GL_DEPTH_TEST
+        GLenum test_func;     // GL_DEPTH_FUNC
+        GLboolean write_mask; // GL_DEPTH_WRITEMASK
     } depth;
 
     struct {
@@ -78,22 +76,28 @@ public:
         } front, back;
     } stencil;
 
-    struct {
+    struct Blend {
         bool enabled;        // GL_BLEND
+        bool separate_alpha; // Independent blend enabled
         GLenum rgb_equation; // GL_BLEND_EQUATION_RGB
         GLenum a_equation;   // GL_BLEND_EQUATION_ALPHA
         GLenum src_rgb_func; // GL_BLEND_SRC_RGB
         GLenum dst_rgb_func; // GL_BLEND_DST_RGB
         GLenum src_a_func;   // GL_BLEND_SRC_ALPHA
         GLenum dst_a_func;   // GL_BLEND_DST_ALPHA
+    };
+    std::array<Blend, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> blend;
 
-        struct {
-            GLclampf red;
-            GLclampf green;
-            GLclampf blue;
-            GLclampf alpha;
-        } color; // GL_BLEND_COLOR
-    } blend;
+    struct {
+        bool enabled;
+    } independant_blend;
+
+    struct {
+        GLclampf red;
+        GLclampf green;
+        GLclampf blue;
+        GLclampf alpha;
+    } blend_color; // GL_BLEND_COLOR
 
     struct {
         bool enabled; // GL_LOGIC_OP_MODE
@@ -138,6 +142,16 @@ public:
         GLuint program_pipeline; // GL_PROGRAM_PIPELINE_BINDING
     } draw;
 
+    struct viewport {
+        GLfloat x;
+        GLfloat y;
+        GLfloat width;
+        GLfloat height;
+        GLfloat depth_range_near; // GL_DEPTH_RANGE
+        GLfloat depth_range_far;  // GL_DEPTH_RANGE
+    };
+    std::array<viewport, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> viewports;
+
     struct {
         bool enabled; // GL_SCISSOR_TEST
         GLint x;
@@ -146,13 +160,6 @@ public:
         GLsizei height;
     } scissor;
 
-    struct {
-        GLint x;
-        GLint y;
-        GLsizei width;
-        GLsizei height;
-    } viewport;
-
     struct {
         float size; // GL_POINT_SIZE
     } point;
@@ -194,11 +201,14 @@ private:
     void ApplyDepth() const;
     void ApplyPrimitiveRestart() const;
     void ApplyStencilTest() const;
-    void ApplyScissorTest() const;
+    void ApplyViewport() const;
+    void ApplyTargetBlending(int target, bool force) const;
+    void ApplyGlobalBlending() const;
     void ApplyBlending() const;
     void ApplyLogicOp() const;
     void ApplyTextures() const;
     void ApplySamplers() const;
+    void ApplyScissor() const;
 };
 
 } // namespace OpenGL