diff --git a/src/common/math_util.h b/src/common/math_util.h index 575b27b06..7b5513788 100644 --- a/src/common/math_util.h +++ b/src/common/math_util.h @@ -1,4 +1,8 @@ -// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// Copyright 2013 Dolphin Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -23,6 +27,12 @@ struct Rectangle { constexpr Rectangle(T left, T top, T right, T bottom) : left(left), top(top), right(right), bottom(bottom) {} + template + operator Rectangle() const { + return Rectangle{static_cast(left), static_cast(top), static_cast(right), + static_cast(bottom)}; + } + [[nodiscard]] constexpr bool operator==(const Rectangle& rhs) const { return (left == rhs.left) && (top == rhs.top) && (right == rhs.right) && (bottom == rhs.bottom); @@ -55,6 +65,9 @@ struct Rectangle { return Rectangle{left, top, static_cast(left + GetWidth() * s), static_cast(top + GetHeight() * s)}; } + [[nodiscard]] Rectangle VerticalMirror(T ref_height) const { + return Rectangle{left, ref_height - bottom, right, ref_height - top}; + } }; template diff --git a/src/video_core/pica/regs_framebuffer.h b/src/video_core/pica/regs_framebuffer.h index 0ab6b8fd6..5a647dca9 100644 --- a/src/video_core/pica/regs_framebuffer.h +++ b/src/video_core/pica/regs_framebuffer.h @@ -1,4 +1,4 @@ -// Copyright 2017 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -233,6 +233,7 @@ struct FramebufferRegs { // GetWidth() and GetHeight() instead. BitField<0, 11, u32> width; BitField<12, 10, u32> height; + BitField<24, 1, u32> flip; }; INSERT_PADDING_WORDS(0x1); @@ -251,6 +252,10 @@ struct FramebufferRegs { inline u32 GetHeight() const { return height + 1; } + + inline bool IsFlipped() const { + return !flip.Value(); + } } framebuffer; // Returns the number of bytes in the specified depth format diff --git a/src/video_core/pica/regs_rasterizer.h b/src/video_core/pica/regs_rasterizer.h index 5b4785da5..be4f965dc 100644 --- a/src/video_core/pica/regs_rasterizer.h +++ b/src/video_core/pica/regs_rasterizer.h @@ -1,4 +1,4 @@ -// Copyright 2017 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -29,11 +29,13 @@ struct RasterizerRegs { BitField<0, 24, u32> viewport_size_x; - INSERT_PADDING_WORDS(0x1); + BitField<1, 31, u32> viewport_size_x_inv; BitField<0, 24, u32> viewport_size_y; - INSERT_PADDING_WORDS(0x3); + BitField<1, 31, u32> viewport_size_y_inv; + + INSERT_PADDING_WORDS(0x2); BitField<0, 1, u32> clip_enable; BitField<0, 24, u32> clip_coef[4]; // float24 diff --git a/src/video_core/pica_types.h b/src/video_core/pica_types.h index 02641ba69..cf8742154 100644 --- a/src/video_core/pica_types.h +++ b/src/video_core/pica_types.h @@ -1,4 +1,4 @@ -// Copyright 2015 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -138,7 +138,7 @@ public: } private: - static constexpr u32 MASK = (1 << (M + E + 1)) - 1; + static constexpr u32 MASK = static_cast(1 << (M + E + 1)) - 1; static constexpr u32 MANTISSA_MASK = (1 << M) - 1; static constexpr u32 EXPONENT_MASK = (1 << E) - 1; @@ -153,6 +153,7 @@ private: } }; +using f31 = Pica::Float<23, 7>; using f24 = Pica::Float<16, 7>; using f20 = Pica::Float<12, 7>; using f16 = Pica::Float<10, 5>; diff --git a/src/video_core/rasterizer_accelerated.cpp b/src/video_core/rasterizer_accelerated.cpp index d44c23014..ca966441e 100644 --- a/src/video_core/rasterizer_accelerated.cpp +++ b/src/video_core/rasterizer_accelerated.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -846,7 +846,7 @@ void RasterizerAccelerated::SyncTextureBorderColor(int tex_index) { } void RasterizerAccelerated::SyncClipPlane() { - const u32 enable_clip1 = regs.rasterizer.clip_enable != 0; + const bool enable_clip1 = regs.rasterizer.clip_enable != 0; const auto raw_clip_coef = regs.rasterizer.GetClipCoef(); const Common::Vec4f new_clip_coef = {raw_clip_coef.x.ToFloat32(), raw_clip_coef.y.ToFloat32(), raw_clip_coef.z.ToFloat32(), raw_clip_coef.w.ToFloat32()}; diff --git a/src/video_core/rasterizer_cache/framebuffer_base.h b/src/video_core/rasterizer_cache/framebuffer_base.h index 9a1123270..2f2545849 100644 --- a/src/video_core/rasterizer_cache/framebuffer_base.h +++ b/src/video_core/rasterizer_cache/framebuffer_base.h @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -65,13 +65,18 @@ template class FramebufferHelper { public: explicit FramebufferHelper(RasterizerCache* res_cache_, typename T::Framebuffer* fb_, - const Pica::RasterizerRegs& regs, + bool flip_rect, const Pica::RasterizerRegs& regs, Common::Rectangle surfaces_rect) : res_cache{res_cache_}, fb{fb_} { const u32 res_scale = fb->Scale(); + const u32 height = surfaces_rect.GetHeight() / res_scale; // Determine the draw rectangle (render area + scissor) - const Common::Rectangle viewport_rect = regs.GetViewportRect(); + Common::Rectangle viewport_rect = regs.GetViewportRect(); + if (flip_rect) { + viewport_rect = viewport_rect.VerticalMirror(height); + } + draw_rect.left = std::clamp(static_cast(surfaces_rect.left) + viewport_rect.left * res_scale, surfaces_rect.left, surfaces_rect.right); @@ -103,6 +108,10 @@ public: static_cast(surfaces_rect.left + (regs.scissor_test.x2 + 1) * res_scale); scissor_rect.top = static_cast(surfaces_rect.bottom + (regs.scissor_test.y2 + 1) * res_scale); + + if (flip_rect) { + scissor_rect = scissor_rect.VerticalMirror(height); + } } ~FramebufferHelper() { diff --git a/src/video_core/rasterizer_cache/rasterizer_cache.h b/src/video_core/rasterizer_cache/rasterizer_cache.h index 60126826e..8b830a13b 100644 --- a/src/video_core/rasterizer_cache/rasterizer_cache.h +++ b/src/video_core/rasterizer_cache/rasterizer_cache.h @@ -1,4 +1,4 @@ -// Copyright Citra Emulator Project / Lime3DS Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -765,7 +765,8 @@ FramebufferHelper RasterizerCache::GetFramebufferSurfaces(bool using_color it->second = slot_framebuffers.insert(runtime, fb_params, color_surface, depth_surface); } - return FramebufferHelper{this, &slot_framebuffers[it->second], regs.rasterizer, fb_rect}; + return FramebufferHelper{this, &slot_framebuffers[it->second], + regs.framebuffer.framebuffer.IsFlipped(), regs.rasterizer, fb_rect}; } template diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 65e080994..2337741c1 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -409,6 +409,12 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) { state.viewport.width = static_cast(viewport.width); state.viewport.height = static_cast(viewport.height); + // If the framebuffer is flipped, request vertex shader to flip vertex y + const bool is_flipped = regs.framebuffer.framebuffer.IsFlipped(); + vs_uniform_block_data.dirty |= vs_uniform_block_data.data.flip_viewport != is_flipped; + vs_uniform_block_data.data.flip_viewport = is_flipped; + state.cull.mode = is_flipped && state.cull.enabled ? GL_FRONT : GL_BACK; + // Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect. // Enable scissor test to prevent drawing outside of the framebuffer region const auto draw_rect = fb_helper.DrawRect(); diff --git a/src/video_core/renderer_vulkan/pica_to_vk.h b/src/video_core/renderer_vulkan/pica_to_vk.h index b0dfe891c..43f16c60c 100644 --- a/src/video_core/renderer_vulkan/pica_to_vk.h +++ b/src/video_core/renderer_vulkan/pica_to_vk.h @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -177,13 +177,13 @@ inline vk::PrimitiveTopology PrimitiveTopology(Pica::PipelineRegs::TriangleTopol return vk::PrimitiveTopology::eTriangleList; } -inline vk::CullModeFlags CullMode(Pica::RasterizerRegs::CullMode mode) { +inline vk::CullModeFlags CullMode(Pica::RasterizerRegs::CullMode mode, bool flip_viewport) { switch (mode) { case Pica::RasterizerRegs::CullMode::KeepAll: return vk::CullModeFlagBits::eNone; case Pica::RasterizerRegs::CullMode::KeepClockWise: case Pica::RasterizerRegs::CullMode::KeepCounterClockWise: - return vk::CullModeFlagBits::eBack; + return flip_viewport ? vk::CullModeFlagBits::eFront : vk::CullModeFlagBits::eBack; default: UNREACHABLE_MSG("Unknown cull mode {}", mode); } diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 9fadaa83f..b0ba424bd 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -147,7 +147,8 @@ bool GraphicsPipeline::Build(bool fail_on_compile_required) { const vk::PipelineRasterizationStateCreateInfo raster_state = { .depthClampEnable = false, .rasterizerDiscardEnable = false, - .cullMode = PicaToVK::CullMode(info.rasterization.cull_mode), + .cullMode = + PicaToVK::CullMode(info.rasterization.cull_mode, info.rasterization.flip_viewport), .frontFace = PicaToVK::FrontFace(info.rasterization.cull_mode), .depthBiasEnable = false, .lineWidth = 1.0f, diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index 57f4bc54f..ab85771d7 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -54,6 +54,7 @@ union RasterizationState { u8 value = 0; BitField<0, 2, Pica::PipelineRegs::TriangleTopology> topology; BitField<4, 2, Pica::RasterizerRegs::CullMode> cull_mode; + BitField<6, 1, u8> flip_viewport; }; union DepthStencilState { diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 98f68abfb..da784aa98 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -271,8 +271,12 @@ bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) { } if (instance.IsExtendedDynamicStateSupported()) { - if (rasterization.cull_mode != current_rasterization.cull_mode || is_dirty) { - cmdbuf.setCullModeEXT(PicaToVK::CullMode(rasterization.cull_mode)); + const bool needs_flip = + rasterization.flip_viewport != current_rasterization.flip_viewport; + if (rasterization.cull_mode != current_rasterization.cull_mode || needs_flip || + is_dirty) { + cmdbuf.setCullModeEXT( + PicaToVK::CullMode(rasterization.cull_mode, rasterization.flip_viewport)); cmdbuf.setFrontFaceEXT(PicaToVK::FrontFace(rasterization.cull_mode)); } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 91c800437..5b6b93ea3 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -509,6 +509,12 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) { shader_dirty = false; } + // If the framebuffer is flipped, request to also flip vulkan viewport + const bool is_flipped = regs.framebuffer.framebuffer.IsFlipped(); + vs_uniform_block_data.dirty |= vs_uniform_block_data.data.flip_viewport != is_flipped; + vs_uniform_block_data.data.flip_viewport = is_flipped; + pipeline_info.rasterization.flip_viewport.Assign(is_flipped); + // Sync the LUTs within the texture buffer SyncAndUploadLUTs(); SyncAndUploadLUTsLF(); diff --git a/src/video_core/shader/generator/glsl_shader_gen.cpp b/src/video_core/shader/generator/glsl_shader_gen.cpp index c91d63131..64747a50f 100644 --- a/src/video_core/shader/generator/glsl_shader_gen.cpp +++ b/src/video_core/shader/generator/glsl_shader_gen.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -37,6 +37,7 @@ layout (set = 0, binding = 1, std140) uniform vs_data { layout (binding = 1, std140) uniform vs_data { #endif bool enable_clip1; + bool flip_viewport; vec4 clip_coef; }; @@ -123,6 +124,9 @@ void main() { normquat = vert_normquat; view = vert_view; vec4 vtx_pos = SanitizeVertex(vert_position); + if (flip_viewport) { + vtx_pos.y = -vtx_pos.y; + } gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w); )"; if (use_clip_planes) { @@ -238,6 +242,9 @@ std::string GenerateVertexShader(const ShaderSetup& setup, const PicaVSConfig& c semantic(VSOutputAttributes::POSITION_Z) + ", " + semantic(VSOutputAttributes::POSITION_W) + ");\n"; out += " vtx_pos = SanitizeVertex(vtx_pos);\n"; + out += " if (flip_viewport) {\n"; + out += " vtx_pos.y = -vtx_pos.y;\n"; + out += " }\n"; out += " gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w);\n"; if (config.state.use_clip_planes) { out += " gl_ClipDistance[0] = -vtx_pos.z;\n"; // fixed PICA clipping plane z <= 0 @@ -332,6 +339,9 @@ struct Vertex { semantic(VSOutputAttributes::POSITION_Z) + ", " + semantic(VSOutputAttributes::POSITION_W) + ");\n"; out += " vtx_pos = SanitizeVertex(vtx_pos);\n"; + out += " if (flip_viewport) {\n"; + out += " vtx_pos.y = -vtx_pos.y;\n"; + out += " }\n"; out += " gl_Position = vec4(vtx_pos.x, vtx_pos.y, -vtx_pos.z, vtx_pos.w);\n"; if (state.use_clip_planes) { out += " gl_ClipDistance[0] = -vtx_pos.z;\n"; // fixed PICA clipping plane z <= 0 diff --git a/src/video_core/shader/generator/shader_uniforms.h b/src/video_core/shader/generator/shader_uniforms.h index 6c79180e7..95ef409ca 100644 --- a/src/video_core/shader/generator/shader_uniforms.h +++ b/src/video_core/shader/generator/shader_uniforms.h @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -86,7 +86,8 @@ struct PicaUniformsData { }; struct VSUniformData { - u32 enable_clip1; + alignas(4) bool enable_clip1; + alignas(4) bool flip_viewport; alignas(16) Common::Vec4f clip_coef; }; static_assert(sizeof(VSUniformData) == 32,