diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 092351dce..41f057f74 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -150,6 +150,8 @@ void RasterizerOpenGL::DrawTriangles() { SyncFramebuffer(); SyncDrawState(); + SyncScissorTest(); + if (state.draw.shader_dirty) { SetShader(); state.draw.shader_dirty = false; @@ -222,6 +224,11 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { SyncDepthTest(); break; + // Scissor test + case PICA_REG_INDEX(scissor_test.mode): + state.draw.shader_dirty = true; + break; + // Logic op case PICA_REG_INDEX(output_merger.logic_op): SyncLogicOp(); @@ -499,6 +506,7 @@ void RasterizerOpenGL::SetShader() { // Update uniforms SyncAlphaTest(); SyncCombinerColor(); + SyncScissorTest(); auto& tev_stages = Pica::g_state.regs.GetTevStages(); for (int index = 0; index < tev_stages.size(); ++index) SyncTevConstColor(index, tev_stages[index]); @@ -657,6 +665,32 @@ void RasterizerOpenGL::SyncDepthTest() { state.depth.write_mask = regs.output_merger.depth_write_enable ? GL_TRUE : GL_FALSE; } +void RasterizerOpenGL::SyncScissorTest() { + const auto& regs = Pica::g_state.regs; + + GLsizei viewport_height = (GLsizei)Pica::float24::FromRawFloat24(regs.viewport_size_y).ToFloat32() * 2; + + GLsizei viewport_corner_y = -(GLsizei)static_cast(regs.viewport_corner.y) + + regs.framebuffer.GetHeight() - viewport_height; + + // OpenGL uses different y coordinates, so negate corner offset and flip origin + GLint scissor_bottom = viewport_height - (GLsizei)regs.scissor_test.bottom + viewport_corner_y; + + GLint scissor_top = viewport_height - (GLsizei)regs.scissor_test.top - 1; + + if (uniform_block_data.data.scissor_right != regs.scissor_test.right || + uniform_block_data.data.scissor_bottom != scissor_bottom || + uniform_block_data.data.scissor_left != regs.scissor_test.left + 1 || + uniform_block_data.data.scissor_top != scissor_top) { + + uniform_block_data.data.scissor_right = regs.scissor_test.right; + uniform_block_data.data.scissor_bottom = scissor_bottom; + uniform_block_data.data.scissor_left = regs.scissor_test.left + 1; + uniform_block_data.data.scissor_top = scissor_top; + uniform_block_data.dirty = true; + } +} + void RasterizerOpenGL::SyncCombinerColor() { auto combiner_color = PicaToGL::ColorRGBA8(Pica::g_state.regs.tev_combiner_buffer_color.raw); if (combiner_color != uniform_block_data.data.tev_combiner_buffer_color) { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 92b1f812e..381719eaa 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -36,6 +36,8 @@ struct PicaShaderConfig { res.alpha_test_func = regs.output_merger.alpha_test.enable ? regs.output_merger.alpha_test.func.Value() : Pica::Regs::CompareFunc::Always; + res.scissor_test_mode = regs.scissor_test.mode; + // Copy relevant TevStageConfig fields only. We're doing this manually (instead of calling // the GetTevStages() function) because BitField explicitly disables copies. @@ -87,6 +89,7 @@ struct PicaShaderConfig { }; Pica::Regs::CompareFunc alpha_test_func; + Pica::Regs::ScissorMode scissor_test_mode; std::array tev_stages = {}; u8 combiner_buffer_input; }; @@ -191,16 +194,19 @@ private: GLfloat tex_coord2[2]; }; - /// Uniform structure for the Uniform Buffer Object, all members must be 16-byte aligned + /// Uniform structure for the Uniform Buffer Object, all members must be aligned as per the OpenGL std140 layout specification struct UniformData { // A vec4 color for each of the six tev stages std::array const_color[6]; std::array tev_combiner_buffer_color; GLint alphatest_ref; - INSERT_PADDING_BYTES(12); + GLint scissor_right; + GLint scissor_bottom; + GLint scissor_left; + GLint scissor_top; }; - static_assert(sizeof(UniformData) == 0x80, "The size of the UniformData structure has changed, update the structure in the shader"); + static_assert(sizeof(UniformData) == 0x84, "The size of the UniformData structure has changed, update the structure in the shader"); static_assert(sizeof(UniformData) < 16000, "UniformData structure must be less than 16kb as per the OpenGL spec"); /// Reconfigure the OpenGL color texture to use the given format and dimensions @@ -239,6 +245,9 @@ private: /// Syncs the depth test states to match the PICA register void SyncDepthTest(); + /// Syncs the scissor test state to match the PICA register + void SyncScissorTest(); + /// Syncs the TEV constant color to match the PICA register void SyncTevConstColor(int tev_index, const Pica::Regs::TevStageConfig& tev_stage); diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 38de5d469..e2ca1867c 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -327,6 +327,7 @@ std::string GenerateFragmentShader(const PicaShaderConfig& config) { in vec4 primary_color; in vec2 texcoord[3]; +in vec4 gl_FragCoord; out vec4 color; @@ -334,6 +335,10 @@ layout (std140) uniform shader_data { vec4 const_color[NUM_TEV_STAGES]; vec4 tev_combiner_buffer_color; int alphatest_ref; + int scissor_right; + int scissor_bottom; + int scissor_left; + int scissor_top; }; uniform sampler2D tex[3]; @@ -341,6 +346,16 @@ uniform sampler2D tex[3]; void main() { )"; + // Append the scissor test + if (config.scissor_test_mode == Regs::ScissorMode::Include || config.scissor_test_mode == Regs::ScissorMode::Exclude) { + out += "if (scissor_left <= scissor_right || scissor_top >= scissor_bottom) discard;\n"; + out += "if ("; + // Negate the condition if we have to keep only the pixels outside the scissor box + if (config.scissor_test_mode == Regs::ScissorMode::Include) + out += "!"; + out += "(gl_FragCoord.x >= scissor_right && gl_FragCoord.x <= scissor_left && gl_FragCoord.y >= scissor_top && gl_FragCoord.y <= scissor_bottom)) discard;\n"; + } + // Do not do any sort of processing if it's obvious we're not going to pass the alpha test if (config.alpha_test_func == Regs::CompareFunc::Never) { out += "discard; }";