From b85b35d5ea6af0dbd7ea74b47e2564a805d9bfe5 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 27 Nov 2021 17:07:02 -0800 Subject: [PATCH 1/7] VideoCommon: Create dedicated structs for scissor pos/offset --- Source/Core/VideoCommon/BPMemory.h | 64 ++++++++++++++++++++++----- Source/Core/VideoCommon/BPStructs.cpp | 18 ++------ 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/Source/Core/VideoCommon/BPMemory.h b/Source/Core/VideoCommon/BPMemory.h index 0d21166094..4729e06be5 100644 --- a/Source/Core/VideoCommon/BPMemory.h +++ b/Source/Core/VideoCommon/BPMemory.h @@ -1212,24 +1212,64 @@ struct fmt::formatter } }; -union X12Y12 +union ScissorPos { - BitField<0, 12, u32> y; - BitField<12, 12, u32> x; + // The top bit is ignored, and not included in the mask used by GX SDK functions + // (though libogc includes it for the bottom coordinate (only) for some reason) + // x_full and y_full include that bit for the FIFO analyzer, though it is usually unset. + // The SDK also adds 342 to these values. + BitField<0, 11, u32> y; + BitField<0, 12, u32> y_full; + BitField<12, 11, u32> x; + BitField<12, 12, u32> x_full; u32 hex; }; +template <> +struct fmt::formatter +{ + constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); } + template + auto format(const ScissorPos& pos, FormatContext& ctx) + { + return format_to(ctx.out(), + "X: {} (raw: {})\n" + "Y: {} (raw: {})", + pos.x - 342, pos.x_full, pos.y - 342, pos.y_full); + } +}; + +union ScissorOffset +{ + // The scissor offset ignores the top bit (though it isn't masked off by the GX SDK). + // Each value is also divided by 2 (so 0-511 map to 0-1022). + // x_full and y_full include that top bit for the FIFO analyzer, though it is usually unset. + // The SDK also adds 342 to each value (before dividing it). + BitField<0, 9, u32> x; + BitField<0, 10, u32> x_full; + BitField<10, 9, u32> y; + BitField<10, 10, u32> y_full; + u32 hex; +}; +template <> +struct fmt::formatter +{ + constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); } + template + auto format(const ScissorOffset& off, FormatContext& ctx) + { + return format_to(ctx.out(), + "X: {} (raw: {})\n" + "Y: {} (raw: {})", + (off.x << 1) - 342, off.x_full, (off.y << 1) - 342, off.y_full); + } +}; + union X10Y10 { BitField<0, 10, u32> x; BitField<10, 10, u32> y; u32 hex; }; -union S32X10Y10 -{ - BitField<0, 10, s32> x; - BitField<10, 10, s32> y; - u32 hex; -}; // Framebuffer/pixel stuff (incl fog) enum class SrcBlendFactor : u32 @@ -2309,8 +2349,8 @@ struct BPMemory IND_MTX indmtx[3]; // 06-0e GXSetIndTexMtx, 2x3 matrices IND_IMASK imask; // 0f TevStageIndirect tevind[16]; // 10 GXSetTevIndirect - X12Y12 scissorTL; // 20 - X12Y12 scissorBR; // 21 + ScissorPos scissorTL; // 20 + ScissorPos scissorBR; // 21 LPSize lineptwidth; // 22 line and point width u32 sucounter; // 23 u32 rascounter; // 24 @@ -2344,7 +2384,7 @@ struct BPMemory u32 boundbox0; // 55 u32 boundbox1; // 56 u32 unknown7[2]; // 57,58 - S32X10Y10 scissorOffset; // 59 + ScissorOffset scissorOffset; // 59 u32 unknown8[6]; // 5a,5b,5c,5d, 5e,5f BPS_TmemConfig tmem_config; // 60-66 u32 metric; // 67 diff --git a/Source/Core/VideoCommon/BPStructs.cpp b/Source/Core/VideoCommon/BPStructs.cpp index 503ef6154f..7e5ecf1689 100644 --- a/Source/Core/VideoCommon/BPStructs.cpp +++ b/Source/Core/VideoCommon/BPStructs.cpp @@ -815,19 +815,10 @@ std::pair GetBPRegInfo(u8 cmd, u32 cmddata) fmt::to_string(TevStageIndirect{.fullhex = cmddata})); case BPMEM_SCISSORTL: // 0x20 - { - const X12Y12 top_left{.hex = cmddata}; - return std::make_pair(RegName(BPMEM_SCISSORTL), - fmt::format("Scissor Top: {}\nScissor Left: {}", top_left.y, top_left.x)); - } + return std::make_pair(RegName(BPMEM_SCISSORTL), fmt::to_string(ScissorPos{.hex = cmddata})); case BPMEM_SCISSORBR: // 0x21 - { - const X12Y12 bottom_right{.hex = cmddata}; - return std::make_pair( - RegName(BPMEM_SCISSORBR), - fmt::format("Scissor Bottom: {}\nScissor Right: {}", bottom_right.y, bottom_right.x)); - } + return std::make_pair(RegName(BPMEM_SCISSORBR), fmt::to_string(ScissorPos{.hex = cmddata})); case BPMEM_LINEPTWIDTH: // 0x22 return std::make_pair(RegName(BPMEM_LINEPTWIDTH), fmt::to_string(LPSize{.hex = cmddata})); @@ -1002,11 +993,8 @@ std::pair GetBPRegInfo(u8 cmd, u32 cmddata) // TODO: Description case BPMEM_SCISSOROFFSET: // 0x59 - { - const S32X10Y10 xy{.hex = cmddata}; return std::make_pair(RegName(BPMEM_SCISSOROFFSET), - fmt::format("Scissor X offset: {}\nScissor Y offset: {}", xy.x, xy.y)); - } + fmt::to_string(ScissorOffset{.hex = cmddata})); case BPMEM_PRELOAD_ADDR: // 0x60 return DescriptionlessReg(BPMEM_PRELOAD_ADDR); From 4595b89ad80ce2384d188de802ee03a183eb5905 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Fri, 12 Nov 2021 14:32:17 -0800 Subject: [PATCH 2/7] VideoCommon: Remove bSupportsOversizedViewports I think this is a relic of D3D9. D3D11 and D3D12 seem to work fine without it. Plus, ViewportCorrectionMatrix just didn't work correctly (at least with the viewports being generated by the new scissor code). --- Source/Core/Core/DolphinAnalytics.cpp | 1 - Source/Core/VideoBackends/D3D/D3DMain.cpp | 1 - .../Core/VideoBackends/D3D12/VideoBackend.cpp | 1 - .../Core/VideoBackends/Null/NullBackend.cpp | 1 - Source/Core/VideoBackends/OGL/OGLMain.cpp | 1 - Source/Core/VideoBackends/Software/SWmain.cpp | 1 - .../VideoBackends/Vulkan/VulkanContext.cpp | 1 - Source/Core/VideoCommon/BPFunctions.cpp | 11 ---- .../Core/VideoCommon/VertexShaderManager.cpp | 56 ------------------- Source/Core/VideoCommon/VideoConfig.h | 1 - 10 files changed, 75 deletions(-) diff --git a/Source/Core/Core/DolphinAnalytics.cpp b/Source/Core/Core/DolphinAnalytics.cpp index d55a5fba5e..5b67fd2c04 100644 --- a/Source/Core/Core/DolphinAnalytics.cpp +++ b/Source/Core/Core/DolphinAnalytics.cpp @@ -406,7 +406,6 @@ void DolphinAnalytics::MakePerGameBuilder() g_Config.backend_info.bSupportsExclusiveFullscreen); builder.AddData("gpu-has-dual-source-blend", g_Config.backend_info.bSupportsDualSourceBlend); builder.AddData("gpu-has-primitive-restart", g_Config.backend_info.bSupportsPrimitiveRestart); - builder.AddData("gpu-has-oversized-viewports", g_Config.backend_info.bSupportsOversizedViewports); builder.AddData("gpu-has-geometry-shaders", g_Config.backend_info.bSupportsGeometryShaders); builder.AddData("gpu-has-3d-vision", g_Config.backend_info.bSupports3DVision); builder.AddData("gpu-has-early-z", g_Config.backend_info.bSupportsEarlyZ); diff --git a/Source/Core/VideoBackends/D3D/D3DMain.cpp b/Source/Core/VideoBackends/D3D/D3DMain.cpp index ea11c33676..6fc2c285a1 100644 --- a/Source/Core/VideoBackends/D3D/D3DMain.cpp +++ b/Source/Core/VideoBackends/D3D/D3DMain.cpp @@ -79,7 +79,6 @@ void VideoBackend::FillBackendInfo() g_Config.backend_info.bSupportsExclusiveFullscreen = true; g_Config.backend_info.bSupportsDualSourceBlend = true; g_Config.backend_info.bSupportsPrimitiveRestart = true; - g_Config.backend_info.bSupportsOversizedViewports = false; g_Config.backend_info.bSupportsGeometryShaders = true; g_Config.backend_info.bSupportsComputeShaders = false; g_Config.backend_info.bSupports3DVision = true; diff --git a/Source/Core/VideoBackends/D3D12/VideoBackend.cpp b/Source/Core/VideoBackends/D3D12/VideoBackend.cpp index 2ddec74236..197d309332 100644 --- a/Source/Core/VideoBackends/D3D12/VideoBackend.cpp +++ b/Source/Core/VideoBackends/D3D12/VideoBackend.cpp @@ -51,7 +51,6 @@ void VideoBackend::FillBackendInfo() g_Config.backend_info.bSupportsExclusiveFullscreen = true; g_Config.backend_info.bSupportsDualSourceBlend = true; g_Config.backend_info.bSupportsPrimitiveRestart = true; - g_Config.backend_info.bSupportsOversizedViewports = false; g_Config.backend_info.bSupportsGeometryShaders = true; g_Config.backend_info.bSupports3DVision = false; g_Config.backend_info.bSupportsEarlyZ = true; diff --git a/Source/Core/VideoBackends/Null/NullBackend.cpp b/Source/Core/VideoBackends/Null/NullBackend.cpp index 0a400fc2fa..5d04a5adac 100644 --- a/Source/Core/VideoBackends/Null/NullBackend.cpp +++ b/Source/Core/VideoBackends/Null/NullBackend.cpp @@ -30,7 +30,6 @@ void VideoBackend::InitBackendInfo() g_Config.backend_info.bSupportsExclusiveFullscreen = true; g_Config.backend_info.bSupportsDualSourceBlend = true; g_Config.backend_info.bSupportsPrimitiveRestart = true; - g_Config.backend_info.bSupportsOversizedViewports = true; g_Config.backend_info.bSupportsGeometryShaders = true; g_Config.backend_info.bSupportsComputeShaders = false; g_Config.backend_info.bSupports3DVision = false; diff --git a/Source/Core/VideoBackends/OGL/OGLMain.cpp b/Source/Core/VideoBackends/OGL/OGLMain.cpp index 3e16b86ae8..a286625b34 100644 --- a/Source/Core/VideoBackends/OGL/OGLMain.cpp +++ b/Source/Core/VideoBackends/OGL/OGLMain.cpp @@ -78,7 +78,6 @@ void VideoBackend::InitBackendInfo() g_Config.backend_info.MaxTextureSize = 16384; g_Config.backend_info.bUsesLowerLeftOrigin = true; g_Config.backend_info.bSupportsExclusiveFullscreen = false; - g_Config.backend_info.bSupportsOversizedViewports = true; g_Config.backend_info.bSupportsGeometryShaders = true; g_Config.backend_info.bSupportsComputeShaders = false; g_Config.backend_info.bSupports3DVision = false; diff --git a/Source/Core/VideoBackends/Software/SWmain.cpp b/Source/Core/VideoBackends/Software/SWmain.cpp index 753f869c8e..a74b2eb1c9 100644 --- a/Source/Core/VideoBackends/Software/SWmain.cpp +++ b/Source/Core/VideoBackends/Software/SWmain.cpp @@ -68,7 +68,6 @@ void VideoSoftware::InitBackendInfo() g_Config.backend_info.bSupports3DVision = false; g_Config.backend_info.bSupportsDualSourceBlend = true; g_Config.backend_info.bSupportsEarlyZ = true; - g_Config.backend_info.bSupportsOversizedViewports = true; g_Config.backend_info.bSupportsPrimitiveRestart = false; g_Config.backend_info.bSupportsMultithreading = false; g_Config.backend_info.bSupportsComputeShaders = false; diff --git a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp index 8e0f0ed513..677bedf9d9 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp +++ b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp @@ -260,7 +260,6 @@ void VulkanContext::PopulateBackendInfo(VideoConfig* config) { config->backend_info.api_type = APIType::Vulkan; config->backend_info.bSupports3DVision = false; // D3D-exclusive. - config->backend_info.bSupportsOversizedViewports = true; // Assumed support. config->backend_info.bSupportsEarlyZ = true; // Assumed support. config->backend_info.bSupportsPrimitiveRestart = true; // Assumed support. config->backend_info.bSupportsBindingLayout = false; // Assumed support. diff --git a/Source/Core/VideoCommon/BPFunctions.cpp b/Source/Core/VideoCommon/BPFunctions.cpp index fe3867c29c..9bc26fb9d2 100644 --- a/Source/Core/VideoCommon/BPFunctions.cpp +++ b/Source/Core/VideoCommon/BPFunctions.cpp @@ -153,17 +153,6 @@ void SetViewport() far_depth = 1.0f - min_depth; } - // Clamp to size if oversized not supported. Required for D3D. - if (!g_ActiveConfig.backend_info.bSupportsOversizedViewports) - { - const float max_width = static_cast(g_renderer->GetCurrentFramebuffer()->GetWidth()); - const float max_height = static_cast(g_renderer->GetCurrentFramebuffer()->GetHeight()); - x = std::clamp(x, 0.0f, max_width - 1.0f); - y = std::clamp(y, 0.0f, max_height - 1.0f); - width = std::clamp(width, 1.0f, max_width - x); - height = std::clamp(height, 1.0f, max_height - y); - } - // Lower-left flip. if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin) y = static_cast(g_renderer->GetCurrentFramebuffer()->GetHeight()) - y - height; diff --git a/Source/Core/VideoCommon/VertexShaderManager.cpp b/Source/Core/VideoCommon/VertexShaderManager.cpp index 23e94776be..6ba7144345 100644 --- a/Source/Core/VideoCommon/VertexShaderManager.cpp +++ b/Source/Core/VideoCommon/VertexShaderManager.cpp @@ -49,55 +49,6 @@ static Common::Matrix44 s_viewportCorrection; VertexShaderConstants VertexShaderManager::constants; bool VertexShaderManager::dirty; -// Viewport correction: -// In D3D, the viewport rectangle must fit within the render target. -// Say you want a viewport at (ix, iy) with size (iw, ih), -// but your viewport must be clamped at (ax, ay) with size (aw, ah). -// Just multiply the projection matrix with the following to get the same -// effect: -// [ (iw/aw) 0 0 ((iw - 2*(ax-ix)) / aw - 1) ] -// [ 0 (ih/ah) 0 ((-ih + 2*(ay-iy)) / ah + 1) ] -// [ 0 0 1 0 ] -// [ 0 0 0 1 ] -static void ViewportCorrectionMatrix(Common::Matrix44& result) -{ - int scissorXOff = bpmem.scissorOffset.x * 2; - int scissorYOff = bpmem.scissorOffset.y * 2; - - // TODO: ceil, floor or just cast to int? - // TODO: Directly use the floats instead of rounding them? - float intendedX = xfmem.viewport.xOrig - xfmem.viewport.wd - scissorXOff; - float intendedY = xfmem.viewport.yOrig + xfmem.viewport.ht - scissorYOff; - float intendedWd = 2.0f * xfmem.viewport.wd; - float intendedHt = -2.0f * xfmem.viewport.ht; - - if (intendedWd < 0.f) - { - intendedX += intendedWd; - intendedWd = -intendedWd; - } - if (intendedHt < 0.f) - { - intendedY += intendedHt; - intendedHt = -intendedHt; - } - - // fit to EFB size - float X = (intendedX >= 0.f) ? intendedX : 0.f; - float Y = (intendedY >= 0.f) ? intendedY : 0.f; - float Wd = (X + intendedWd <= EFB_WIDTH) ? intendedWd : (EFB_WIDTH - X); - float Ht = (Y + intendedHt <= EFB_HEIGHT) ? intendedHt : (EFB_HEIGHT - Y); - - result = Common::Matrix44::Identity(); - if (Wd == 0 || Ht == 0) - return; - - result.data[4 * 0 + 0] = intendedWd / Wd; - result.data[4 * 0 + 3] = (intendedWd - 2.f * (X - intendedX)) / Wd - 1.f; - result.data[4 * 1 + 1] = intendedHt / Ht; - result.data[4 * 1 + 3] = (-intendedHt + 2.f * (Y - intendedY)) / Ht + 1.f; -} - void VertexShaderManager::Init() { // Initialize state tracking variables @@ -348,13 +299,6 @@ void VertexShaderManager::SetConstants() dirty = true; BPFunctions::SetViewport(); - - // Update projection if the viewport isn't 1:1 useable - if (!g_ActiveConfig.backend_info.bSupportsOversizedViewports) - { - ViewportCorrectionMatrix(s_viewportCorrection); - bProjectionChanged = true; - } } if (bProjectionChanged || g_freelook_camera.GetController()->IsDirty()) diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index def1435b7f..2ff4d502af 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -199,7 +199,6 @@ struct VideoConfig final bool bSupportsExclusiveFullscreen = false; bool bSupportsDualSourceBlend = false; bool bSupportsPrimitiveRestart = false; - bool bSupportsOversizedViewports = false; bool bSupportsGeometryShaders = false; bool bSupportsComputeShaders = false; bool bSupports3DVision = false; From 076392a0f65fa11b8c3475b762b8b9794057ae11 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Fri, 12 Nov 2021 11:48:26 -0800 Subject: [PATCH 3/7] VideoCommon: Rework scissor handling This increases accuracy, fixing the white rendering in Major Minor's Majestic March. However, the hardware backends can only have one viewport and scissor rectangle at a time, while sometimes multiple are needed to accurately emulate what is happening. If possible, this will need to be fixed later. --- Source/Core/VideoBackends/OGL/OGLRender.cpp | 2 +- Source/Core/VideoCommon/BPFunctions.cpp | 193 +++++++++++++++--- Source/Core/VideoCommon/BPFunctions.h | 123 ++++++++++- Source/Core/VideoCommon/BPStructs.cpp | 5 +- Source/Core/VideoCommon/RenderBase.cpp | 6 +- .../Core/VideoCommon/VertexShaderManager.cpp | 2 +- 6 files changed, 283 insertions(+), 48 deletions(-) diff --git a/Source/Core/VideoBackends/OGL/OGLRender.cpp b/Source/Core/VideoBackends/OGL/OGLRender.cpp index fae0d7a0ad..4c686941fe 100644 --- a/Source/Core/VideoBackends/OGL/OGLRender.cpp +++ b/Source/Core/VideoBackends/OGL/OGLRender.cpp @@ -958,7 +958,7 @@ void Renderer::ClearScreen(const MathUtil::Rectangle& rc, bool colorEnable, glDepthMask(m_current_depth_state.updateenable); // Scissor rect must be restored. - BPFunctions::SetScissor(); + BPFunctions::SetScissorAndViewport(); } void Renderer::RenderXFBToScreen(const MathUtil::Rectangle& target_rc, diff --git a/Source/Core/VideoCommon/BPFunctions.cpp b/Source/Core/VideoCommon/BPFunctions.cpp index 9bc26fb9d2..c065f1cc5f 100644 --- a/Source/Core/VideoCommon/BPFunctions.cpp +++ b/Source/Core/VideoCommon/BPFunctions.cpp @@ -7,6 +7,7 @@ #include #include +#include "Common/Assert.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" @@ -37,48 +38,172 @@ void SetGenerationMode() g_vertex_manager->SetRasterizationStateChanged(); } -void SetScissor() +int ScissorRect::GetArea() const { - /* NOTE: the minimum value here for the scissor rect is -342. - * GX SDK functions internally add an offset of 342 to scissor coords to - * ensure that the register was always unsigned. - * - * The code that was here before tried to "undo" this offset, but - * since we always take the difference, the +342 added to both - * sides cancels out. */ + return rect.GetWidth() * rect.GetHeight(); +} - /* NOTE: With a positive scissor offset, the scissor rect is shifted left and/or up; - * With a negative scissor offset, the scissor rect is shifted right and/or down. - * - * GX SDK functions internally add an offset of 342 to scissor offset. - * The scissor offset is always even, so to save space, the scissor offset register - * is scaled down by 2. So, if somebody calls GX_SetScissorBoxOffset(20, 20); - * the registers will be set to ((20 + 342) / 2 = 181, 181). - * - * The scissor offset register is 10bit signed [-512, 511]. - * e.g. In Super Mario Galaxy 1 and 2, during the "Boss roar effect", - * for a scissor offset of (0, -464), the scissor offset register will be set to - * (171, (-464 + 342) / 2 = -61). - */ - s32 xoff = bpmem.scissorOffset.x * 2; - s32 yoff = bpmem.scissorOffset.y * 2; +int ScissorResult::GetViewportArea(const ScissorRect& rect) const +{ + int x0 = std::clamp(rect.rect.left + rect.x_off, viewport_left, viewport_right); + int x1 = std::clamp(rect.rect.right + rect.x_off, viewport_left, viewport_right); - MathUtil::Rectangle native_rc(bpmem.scissorTL.x - xoff, bpmem.scissorTL.y - yoff, - bpmem.scissorBR.x - xoff + 1, bpmem.scissorBR.y - yoff + 1); - native_rc.ClampUL(0, 0, EFB_WIDTH, EFB_HEIGHT); + int y0 = std::clamp(rect.rect.top + rect.y_off, viewport_top, viewport_bottom); + int y1 = std::clamp(rect.rect.bottom + rect.y_off, viewport_top, viewport_bottom); - auto target_rc = g_renderer->ConvertEFBRectangle(native_rc); + return (x1 - x0) * (y1 - y0); +} + +// Compare so that a sorted collection of rectangles has the best one last, so that if they're drawn +// in order, the best one is the one that is drawn last (and thus over the rest). +// The exact iteration order on hardware hasn't been tested, but silly things can happen where a +// polygon can intersect with itself; this only applies outside of the viewport region (in areas +// that would normally be affected by clipping). No game is known to care about this. +bool ScissorResult::IsWorse(const ScissorRect& lhs, const ScissorRect& rhs) const +{ + // First, penalize any rect that is not in the viewport + int lhs_area = GetViewportArea(lhs); + int rhs_area = GetViewportArea(rhs); + + if (lhs_area != rhs_area) + return lhs_area < rhs_area; + + // Now compare on total areas, without regard for the viewport + return lhs.GetArea() < rhs.GetArea(); +} + +namespace +{ +// Dynamically sized small array of ScissorRanges (used as an heap-less alternative to std::vector +// to reduce allocation overhead) +struct RangeList +{ + static constexpr u32 MAX_RANGES = 9; + + u32 m_num_ranges = 0; + std::array m_ranges{}; + + void AddRange(int offset, int start, int end) + { + DEBUG_ASSERT(m_num_ranges < MAX_RANGES); + m_ranges[m_num_ranges] = ScissorRange(offset, start, end); + m_num_ranges++; + } + auto begin() const { return m_ranges.begin(); } + auto end() const { return m_ranges.begin() + m_num_ranges; } + + u32 size() { return m_num_ranges; } +}; + +static RangeList ComputeScissorRanges(int start, int end, int offset, int efb_dim) +{ + RangeList ranges; + + for (int extra_off = -4096; extra_off <= 4096; extra_off += 1024) + { + int new_off = offset + extra_off; + int new_start = std::clamp(start - new_off, 0, efb_dim); + int new_end = std::clamp(end - new_off + 1, 0, efb_dim); + if (new_start < new_end) + { + ranges.AddRange(new_off, new_start, new_end); + } + } + + return ranges; +} +} // namespace + +ScissorResult::ScissorResult(const BPMemory& bpmemory, const XFMemory& xfmemory) + : ScissorResult(bpmemory, + std::minmax(xfmemory.viewport.xOrig - xfmemory.viewport.wd, + xfmemory.viewport.xOrig + xfmemory.viewport.wd), + std::minmax(xfmemory.viewport.yOrig - xfmemory.viewport.ht, + xfmemory.viewport.yOrig + xfmemory.viewport.ht)) +{ +} +ScissorResult::ScissorResult(const BPMemory& bpmemory, std::pair viewport_x, + std::pair viewport_y) + : scissor_tl{.hex = bpmemory.scissorTL.hex}, scissor_br{.hex = bpmemory.scissorBR.hex}, + scissor_off{.hex = bpmemory.scissorOffset.hex}, viewport_left(viewport_x.first), + viewport_right(viewport_x.second), viewport_top(viewport_y.first), + viewport_bottom(viewport_y.second) +{ + // Range is [left, right] and [top, bottom] (closed intervals) + const int left = scissor_tl.x; + const int right = scissor_br.x; + const int top = scissor_tl.y; + const int bottom = scissor_br.y; + // When left > right or top > bottom, nothing renders (even with wrapping from the offsets) + if (left > right || top > bottom) + return; + + // Note that both the offsets and the coordinates have 342 added to them internally by GX + // functions (for the offsets, this is before they are divided by 2/right shifted). This code + // could undo both sets of offsets, but it doesn't need to since they cancel out when subtracting + // (and those offsets actually matter for the left > right and top > bottom checks). + const int x_off = scissor_off.x << 1; + const int y_off = scissor_off.y << 1; + + RangeList x_ranges = ComputeScissorRanges(left, right, x_off, EFB_WIDTH); + RangeList y_ranges = ComputeScissorRanges(top, bottom, y_off, EFB_HEIGHT); + + m_result.reserve(x_ranges.size() * y_ranges.size()); + + // Now we need to form actual rectangles from the x and y ranges, + // which is a simple Cartesian product of x_ranges_clamped and y_ranges_clamped. + // Each rectangle is also a Cartesian product of x_range and y_range, with + // the rectangles being half-open (of the form [x0, x1) X [y0, y1)). + for (const auto& x_range : x_ranges) + { + DEBUG_ASSERT(x_range.start < x_range.end); + DEBUG_ASSERT(x_range.end <= EFB_WIDTH); + for (const auto& y_range : y_ranges) + { + DEBUG_ASSERT(y_range.start < y_range.end); + DEBUG_ASSERT(y_range.end <= EFB_HEIGHT); + m_result.emplace_back(x_range, y_range); + } + } + + auto cmp = [&](const ScissorRect& lhs, const ScissorRect& rhs) { return IsWorse(lhs, rhs); }; + std::sort(m_result.begin(), m_result.end(), cmp); +} + +ScissorRect ScissorResult::Best() const +{ + // For now, simply choose the best rectangle (see ScissorResult::IsWorse). + // This does mean we calculate all rectangles and only choose one, which is not optimal, but this + // is called infrequently. Eventually, all backends will support multiple scissor rects. + if (!m_result.empty()) + { + return m_result.back(); + } + else + { + // But if we have no rectangles, use a bogus one that's out of bounds. + // Ideally, all backends will support multiple scissor rects, in which case this won't be + // needed. + return ScissorRect(ScissorRange{0, 1000, 1001}, ScissorRange{0, 1000, 1001}); + } +} + +ScissorResult ComputeScissorRects() +{ + return ScissorResult{bpmem, xfmem}; +} + +void SetScissorAndViewport() +{ + auto native_rc = ComputeScissorRects().Best(); + + auto target_rc = g_renderer->ConvertEFBRectangle(native_rc.rect); auto converted_rc = g_renderer->ConvertFramebufferRectangle(target_rc, g_renderer->GetCurrentFramebuffer()); g_renderer->SetScissorRect(converted_rc); -} -void SetViewport() -{ - const s32 xoff = bpmem.scissorOffset.x * 2; - const s32 yoff = bpmem.scissorOffset.y * 2; - float raw_x = xfmem.viewport.xOrig - xfmem.viewport.wd - xoff; - float raw_y = xfmem.viewport.yOrig + xfmem.viewport.ht - yoff; + float raw_x = (xfmem.viewport.xOrig - native_rc.x_off) - xfmem.viewport.wd; + float raw_y = (xfmem.viewport.yOrig - native_rc.y_off) + xfmem.viewport.ht; float raw_width = 2.0f * xfmem.viewport.wd; float raw_height = -2.0f * xfmem.viewport.ht; if (g_ActiveConfig.UseVertexRounding()) diff --git a/Source/Core/VideoCommon/BPFunctions.h b/Source/Core/VideoCommon/BPFunctions.h index 22bbea6487..eb0d67edf2 100644 --- a/Source/Core/VideoCommon/BPFunctions.h +++ b/Source/Core/VideoCommon/BPFunctions.h @@ -7,16 +7,131 @@ #pragma once -#include "Common/MathUtil.h" +#include +#include -struct BPCmd; +#include "Common/MathUtil.h" +#include "VideoCommon/BPMemory.h" +struct XFMemory; namespace BPFunctions { +struct ScissorRange +{ + constexpr ScissorRange() = default; + constexpr ScissorRange(int offset, int start, int end) : offset(offset), start(start), end(end) {} + int offset = 0; + int start = 0; + int end = 0; +}; + +struct ScissorRect +{ + constexpr ScissorRect(ScissorRange x_range, ScissorRange y_range) + : // Rectangle ctor takes x0, y0, x1, y1. + rect(x_range.start, y_range.start, x_range.end, y_range.end), x_off(x_range.offset), + y_off(y_range.offset) + { + } + + MathUtil::Rectangle rect; + int x_off; + int y_off; + + int GetArea() const; +}; + +// Although the GameCube/Wii have only one scissor configuration and only one viewport +// configuration, some values can result in multiple parts of the screen being updated. +// This can happen if the scissor offset combined with the bottom or right coordinate ends up +// exceeding 1024; then, both sides of the screen will be drawn to, while the middle is not. +// Major Minor's Majestic March causes this to happen during loading screens and other scrolling +// effects, though it draws on top of one of them. +// This can also happen if the scissor rectangle is particularly large, but this will usually +// involve drawing content outside of the viewport, which Dolphin does not currently handle. +// +// The hardware backends can currently only use one viewport and scissor rectangle, so we need to +// pick the "best" rectangle based on how much of the viewport would be rendered to the screen. +// If we choose the wrong one, then content might not actually show up when the game is expecting it +// to. This does happen on Major Minor's Majestic March for the final few frames of the horizontal +// scrolling animation, but it isn't that important. Note that the assumption that a "best" +// rectangle exists is based on games only wanting to draw one rectangle, and accidentally +// configuring the scissor offset and size of the scissor rectangle such that multiple show up; +// there are no known games where this is not the case. +struct ScissorResult +{ + ScissorResult(const BPMemory& bpmem, const XFMemory& xfmem); + ~ScissorResult() = default; + ScissorResult(const ScissorResult& other) + : scissor_tl{.hex = other.scissor_tl.hex}, scissor_br{.hex = other.scissor_br.hex}, + scissor_off{.hex = other.scissor_off.hex}, viewport_left{other.viewport_left}, + viewport_right{other.viewport_right}, viewport_top{other.viewport_top}, + viewport_bottom{other.viewport_bottom}, m_result{other.m_result} + { + } + ScissorResult& operator=(const ScissorResult& other) + { + if (this == &other) + return *this; + scissor_tl.hex = other.scissor_tl.hex; + scissor_br.hex = other.scissor_br.hex; + scissor_off.hex = other.scissor_off.hex; + viewport_left = other.viewport_left; + viewport_right = other.viewport_right; + viewport_top = other.viewport_top; + viewport_bottom = other.viewport_bottom; + m_result = other.m_result; + return *this; + } + ScissorResult(ScissorResult&& other) + : scissor_tl{.hex = other.scissor_tl.hex}, scissor_br{.hex = other.scissor_br.hex}, + scissor_off{.hex = other.scissor_off.hex}, viewport_left{other.viewport_left}, + viewport_right{other.viewport_right}, viewport_top{other.viewport_top}, + viewport_bottom{other.viewport_bottom}, m_result{std::move(other.m_result)} + { + } + ScissorResult& operator=(ScissorResult&& other) + { + if (this == &other) + return *this; + scissor_tl.hex = other.scissor_tl.hex; + scissor_br.hex = other.scissor_br.hex; + scissor_off.hex = other.scissor_off.hex; + viewport_left = other.viewport_left; + viewport_right = other.viewport_right; + viewport_top = other.viewport_top; + viewport_bottom = other.viewport_bottom; + m_result = std::move(other.m_result); + return *this; + } + + // Input values, for use in statistics + ScissorPos scissor_tl; + ScissorPos scissor_br; + ScissorOffset scissor_off; + float viewport_left; + float viewport_right; + float viewport_top; + float viewport_bottom; + + // Actual result + std::vector m_result; + + ScissorRect Best() const; + +private: + ScissorResult(const BPMemory& bpmem, std::pair viewport_x, + std::pair viewport_y); + + int GetViewportArea(const ScissorRect& rect) const; + bool IsWorse(const ScissorRect& lhs, const ScissorRect& rhs) const; +}; + +ScissorResult ComputeScissorRects(); + void FlushPipeline(); void SetGenerationMode(); -void SetScissor(); -void SetViewport(); +void SetScissorAndViewport(); void SetDepthMode(); void SetBlendMode(); void ClearScreen(const MathUtil::Rectangle& rc); diff --git a/Source/Core/VideoCommon/BPStructs.cpp b/Source/Core/VideoCommon/BPStructs.cpp index 7e5ecf1689..c6746d444a 100644 --- a/Source/Core/VideoCommon/BPStructs.cpp +++ b/Source/Core/VideoCommon/BPStructs.cpp @@ -131,8 +131,6 @@ static void BPWritten(const BPCmd& bp, int cycles_into_future) case BPMEM_SCISSORTL: // Scissor Rectable Top, Left case BPMEM_SCISSORBR: // Scissor Rectable Bottom, Right case BPMEM_SCISSOROFFSET: // Scissor Offset - SetScissor(); - SetViewport(); VertexShaderManager::SetViewportChanged(); GeometryShaderManager::SetViewportChanged(); return; @@ -1272,8 +1270,7 @@ void BPReload() // let's not risk actually replaying any writes. // note that PixelShaderManager is already covered since it has its own DoState. SetGenerationMode(); - SetScissor(); - SetViewport(); + SetScissorAndViewport(); SetDepthMode(); SetBlendMode(); OnPixelFormatChange(); diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index 9d3cfedbd3..6c369621b3 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -160,8 +160,7 @@ void Renderer::EndUtilityDrawing() { // Reset framebuffer/scissor/viewport. Pipeline will be reset at next draw. g_framebuffer_manager->BindEFBFramebuffer(); - BPFunctions::SetScissor(); - BPFunctions::SetViewport(); + BPFunctions::SetScissorAndViewport(); } void Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer) @@ -543,8 +542,7 @@ void Renderer::CheckForConfigChanges() // Viewport and scissor rect have to be reset since they will be scaled differently. if (changed_bits & CONFIG_CHANGE_BIT_TARGET_SIZE) { - BPFunctions::SetViewport(); - BPFunctions::SetScissor(); + BPFunctions::SetScissorAndViewport(); } // Stereo mode change requires recompiling our post processing pipeline and imgui pipelines for diff --git a/Source/Core/VideoCommon/VertexShaderManager.cpp b/Source/Core/VideoCommon/VertexShaderManager.cpp index 6ba7144345..fc8b9e69f8 100644 --- a/Source/Core/VideoCommon/VertexShaderManager.cpp +++ b/Source/Core/VideoCommon/VertexShaderManager.cpp @@ -298,7 +298,7 @@ void VertexShaderManager::SetConstants() } dirty = true; - BPFunctions::SetViewport(); + BPFunctions::SetScissorAndViewport(); } if (bProjectionChanged || g_freelook_camera.GetController()->IsDirty()) From 925ceab82f874dcd9eeab5ca9520b5d6f0157ed1 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Tue, 9 Nov 2021 18:38:24 -0800 Subject: [PATCH 4/7] Software: Use new scissor logic Unlike the hardware backends, the software renderer can use multiple scissor rectangles (though this will result in extra rasterization). --- .../Core/VideoBackends/Software/Clipper.cpp | 10 +-- .../VideoBackends/Software/Rasterizer.cpp | 89 ++++++++++--------- .../Core/VideoBackends/Software/Rasterizer.h | 3 +- .../VideoBackends/Software/SWRenderer.cpp | 10 +++ .../Core/VideoBackends/Software/SWRenderer.h | 2 + 5 files changed, 66 insertions(+), 48 deletions(-) diff --git a/Source/Core/VideoBackends/Software/Clipper.cpp b/Source/Core/VideoBackends/Software/Clipper.cpp index f13ebb90b7..edae393350 100644 --- a/Source/Core/VideoBackends/Software/Clipper.cpp +++ b/Source/Core/VideoBackends/Software/Clipper.cpp @@ -307,7 +307,7 @@ void ProcessTriangle(OutputVertexData* v0, OutputVertexData* v1, OutputVertexDat PerspectiveDivide(v0); PerspectiveDivide(v1); PerspectiveDivide(v2); - Rasterizer::UpdateZSlope(v0, v1, v2); + Rasterizer::UpdateZSlope(v0, v1, v2, bpmem.scissorOffset.x * 2, bpmem.scissorOffset.y * 2); INCSTAT(g_stats.this_frame.num_triangles_culled) return; } @@ -320,7 +320,7 @@ void ProcessTriangle(OutputVertexData* v0, OutputVertexData* v1, OutputVertexDat PerspectiveDivide(v0); PerspectiveDivide(v2); PerspectiveDivide(v1); - Rasterizer::UpdateZSlope(v0, v2, v1); + Rasterizer::UpdateZSlope(v0, v2, v1, bpmem.scissorOffset.x * 2, bpmem.scissorOffset.y * 2); INCSTAT(g_stats.this_frame.num_triangles_culled) return; } @@ -533,10 +533,8 @@ void PerspectiveDivide(OutputVertexData* vertex) Vec3& screen = vertex->screenPosition; float wInverse = 1.0f / projected.w; - screen.x = - projected.x * wInverse * xfmem.viewport.wd + xfmem.viewport.xOrig - bpmem.scissorOffset.x * 2; - screen.y = - projected.y * wInverse * xfmem.viewport.ht + xfmem.viewport.yOrig - bpmem.scissorOffset.y * 2; + screen.x = projected.x * wInverse * xfmem.viewport.wd + xfmem.viewport.xOrig; + screen.y = projected.y * wInverse * xfmem.viewport.ht + xfmem.viewport.yOrig; screen.z = projected.z * wInverse * xfmem.viewport.zRange + xfmem.viewport.farZ; } } // namespace Clipper diff --git a/Source/Core/VideoBackends/Software/Rasterizer.cpp b/Source/Core/VideoBackends/Software/Rasterizer.cpp index 726692138c..d23b6fc558 100644 --- a/Source/Core/VideoBackends/Software/Rasterizer.cpp +++ b/Source/Core/VideoBackends/Software/Rasterizer.cpp @@ -5,11 +5,16 @@ #include #include +#include +#include "Common/Assert.h" #include "Common/CommonTypes.h" + #include "VideoBackends/Software/EfbInterface.h" #include "VideoBackends/Software/NativeVertexFormat.h" #include "VideoBackends/Software/Tev.h" +#include "VideoCommon/BPFunctions.h" +#include "VideoCommon/BPMemory.h" #include "VideoCommon/PerfQueryBase.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/VideoCommon.h" @@ -23,14 +28,14 @@ static constexpr int BLOCK_SIZE = 2; struct SlopeContext { SlopeContext(const OutputVertexData* v0, const OutputVertexData* v1, const OutputVertexData* v2, - s32 x0, s32 y0) + s32 x0, s32 y0, s32 x_off, s32 y_off) : x0(x0), y0(y0) { // adjust a little less than 0.5 const float adjust = 0.495f; - xOff = ((float)x0 - v0->screenPosition.x) + adjust; - yOff = ((float)y0 - v0->screenPosition.y) + adjust; + xOff = ((float)x0 - (v0->screenPosition.x - x_off)) + adjust; + yOff = ((float)y0 - (v0->screenPosition.y - y_off)) + adjust; dx10 = v1->screenPosition.x - v0->screenPosition.x; dx20 = v2->screenPosition.x - v0->screenPosition.x; @@ -99,6 +104,8 @@ static Slope TexSlopes[8][3]; static Tev tev; static RasterBlock rasterBlock; +static std::vector scissors; + void Init() { tev.Init(); @@ -108,6 +115,11 @@ void Init() ZSlope = Slope(); } +void ScissorChanged() +{ + scissors = std::move(BPFunctions::ComputeScissorRects().m_result); +} + // Returns approximation of log2(f) in s28.4 // results are close enough to use for LOD static s32 FixedLog2(float f) @@ -302,37 +314,36 @@ static void BuildBlock(s32 blockX, s32 blockY) } void UpdateZSlope(const OutputVertexData* v0, const OutputVertexData* v1, - const OutputVertexData* v2) + const OutputVertexData* v2, s32 x_off, s32 y_off) { if (!bpmem.genMode.zfreeze) { - const s32 X1 = iround(16.0f * v0->screenPosition[0]) - 9; - const s32 Y1 = iround(16.0f * v0->screenPosition[1]) - 9; - const SlopeContext ctx(v0, v1, v2, (X1 + 0xF) >> 4, (Y1 + 0xF) >> 4); + const s32 X1 = iround(16.0f * (v0->screenPosition.x - x_off)) - 9; + const s32 Y1 = iround(16.0f * (v0->screenPosition.y - y_off)) - 9; + const SlopeContext ctx(v0, v1, v2, (X1 + 0xF) >> 4, (Y1 + 0xF) >> 4, x_off, y_off); ZSlope = Slope(v0->screenPosition.z, v1->screenPosition.z, v2->screenPosition.z, ctx); } } -void DrawTriangleFrontFace(const OutputVertexData* v0, const OutputVertexData* v1, - const OutputVertexData* v2) +static void DrawTriangleFrontFace(const OutputVertexData* v0, const OutputVertexData* v1, + const OutputVertexData* v2, + const BPFunctions::ScissorRect& scissor) { - INCSTAT(g_stats.this_frame.num_triangles_drawn); - // The zslope should be updated now, even if the triangle is rejected by the scissor test, as // zfreeze depends on it - UpdateZSlope(v0, v1, v2); + UpdateZSlope(v0, v1, v2, scissor.x_off, scissor.y_off); // adapted from http://devmaster.net/posts/6145/advanced-rasterization // 28.4 fixed-pou32 coordinates. rounded to nearest and adjusted to match hardware output // could also take floor and adjust -8 - const s32 Y1 = iround(16.0f * v0->screenPosition[1]) - 9; - const s32 Y2 = iround(16.0f * v1->screenPosition[1]) - 9; - const s32 Y3 = iround(16.0f * v2->screenPosition[1]) - 9; + const s32 Y1 = iround(16.0f * (v0->screenPosition.y - scissor.y_off)) - 9; + const s32 Y2 = iround(16.0f * (v1->screenPosition.y - scissor.y_off)) - 9; + const s32 Y3 = iround(16.0f * (v2->screenPosition.y - scissor.y_off)) - 9; - const s32 X1 = iround(16.0f * v0->screenPosition[0]) - 9; - const s32 X2 = iround(16.0f * v1->screenPosition[0]) - 9; - const s32 X3 = iround(16.0f * v2->screenPosition[0]) - 9; + const s32 X1 = iround(16.0f * (v0->screenPosition.x - scissor.x_off)) - 9; + const s32 X2 = iround(16.0f * (v1->screenPosition.x - scissor.x_off)) - 9; + const s32 X3 = iround(16.0f * (v2->screenPosition.x - scissor.x_off)) - 9; // Deltas const s32 DX12 = X1 - X2; @@ -359,35 +370,22 @@ void DrawTriangleFrontFace(const OutputVertexData* v0, const OutputVertexData* v s32 maxy = (std::max(std::max(Y1, Y2), Y3) + 0xF) >> 4; // scissor - s32 xoff = bpmem.scissorOffset.x * 2; - s32 yoff = bpmem.scissorOffset.y * 2; + ASSERT(scissor.rect.left >= 0); + ASSERT(scissor.rect.right <= EFB_WIDTH); + ASSERT(scissor.rect.top >= 0); + ASSERT(scissor.rect.bottom <= EFB_HEIGHT); - s32 scissorLeft = bpmem.scissorTL.x - xoff; - if (scissorLeft < 0) - scissorLeft = 0; - - s32 scissorTop = bpmem.scissorTL.y - yoff; - if (scissorTop < 0) - scissorTop = 0; - - s32 scissorRight = bpmem.scissorBR.x - xoff + 1; - if (scissorRight > s32(EFB_WIDTH)) - scissorRight = EFB_WIDTH; - - s32 scissorBottom = bpmem.scissorBR.y - yoff + 1; - if (scissorBottom > s32(EFB_HEIGHT)) - scissorBottom = EFB_HEIGHT; - - minx = std::max(minx, scissorLeft); - maxx = std::min(maxx, scissorRight); - miny = std::max(miny, scissorTop); - maxy = std::min(maxy, scissorBottom); + minx = std::max(minx, scissor.rect.left); + maxx = std::min(maxx, scissor.rect.right); + miny = std::max(miny, scissor.rect.top); + maxy = std::min(maxy, scissor.rect.bottom); if (minx >= maxx || miny >= maxy) return; // Set up the remaining slopes - const SlopeContext ctx(v0, v1, v2, (X1 + 0xF) >> 4, (Y1 + 0xF) >> 4); + const SlopeContext ctx(v0, v1, v2, (X1 + 0xF) >> 4, (Y1 + 0xF) >> 4, scissor.x_off, + scissor.y_off); float w[3] = {1.0f / v0->projectedPosition.w, 1.0f / v1->projectedPosition.w, 1.0f / v2->projectedPosition.w}; @@ -504,4 +502,13 @@ void DrawTriangleFrontFace(const OutputVertexData* v0, const OutputVertexData* v } } } + +void DrawTriangleFrontFace(const OutputVertexData* v0, const OutputVertexData* v1, + const OutputVertexData* v2) +{ + INCSTAT(g_stats.this_frame.num_triangles_drawn); + + for (const auto& scissor : scissors) + DrawTriangleFrontFace(v0, v1, v2, scissor); +} } // namespace Rasterizer diff --git a/Source/Core/VideoBackends/Software/Rasterizer.h b/Source/Core/VideoBackends/Software/Rasterizer.h index c278809966..5edd8f76af 100644 --- a/Source/Core/VideoBackends/Software/Rasterizer.h +++ b/Source/Core/VideoBackends/Software/Rasterizer.h @@ -10,9 +10,10 @@ struct OutputVertexData; namespace Rasterizer { void Init(); +void ScissorChanged(); void UpdateZSlope(const OutputVertexData* v0, const OutputVertexData* v1, - const OutputVertexData* v2); + const OutputVertexData* v2, s32 x_off, s32 y_off); void DrawTriangleFrontFace(const OutputVertexData* v0, const OutputVertexData* v1, const OutputVertexData* v2); diff --git a/Source/Core/VideoBackends/Software/SWRenderer.cpp b/Source/Core/VideoBackends/Software/SWRenderer.cpp index e5b0f08987..76eea93068 100644 --- a/Source/Core/VideoBackends/Software/SWRenderer.cpp +++ b/Source/Core/VideoBackends/Software/SWRenderer.cpp @@ -12,6 +12,7 @@ #include "VideoBackends/Software/EfbCopy.h" #include "VideoBackends/Software/EfbInterface.h" +#include "VideoBackends/Software/Rasterizer.h" #include "VideoBackends/Software/SWBoundingBox.h" #include "VideoBackends/Software/SWOGLWindow.h" #include "VideoBackends/Software/SWTexture.h" @@ -179,4 +180,13 @@ SWRenderer::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) { return std::make_unique(vtx_decl); } + +void SWRenderer::SetScissorRect(const MathUtil::Rectangle& rc) +{ + // BPFunctions calls SetScissorRect with the "best" scissor rect whenever the viewport or scissor + // changes. However, the software renderer is actually able to use multiple scissor rects (which + // is necessary in a few renderering edge cases, such as with Major Minor's Majestic March). + // Thus, we use this as a signal to update the list of scissor rects, but ignore the parameter. + Rasterizer::ScissorChanged(); +} } // namespace SW diff --git a/Source/Core/VideoBackends/Software/SWRenderer.h b/Source/Core/VideoBackends/Software/SWRenderer.h index dcedc1934d..8aa9aa4af5 100644 --- a/Source/Core/VideoBackends/Software/SWRenderer.h +++ b/Source/Core/VideoBackends/Software/SWRenderer.h @@ -58,6 +58,8 @@ public: const AbstractTexture* src_texture, const MathUtil::Rectangle& src_rect) override; + void SetScissorRect(const MathUtil::Rectangle& rc) override; + protected: std::unique_ptr CreateBoundingBox() const override; From 8745d84949b34748e736d417f657b7758f1c944f Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Thu, 18 Nov 2021 16:52:43 -0800 Subject: [PATCH 5/7] Software: Disable clipping based on xfmem This fixes https://bugs.dolphin-emu.org/issues/12562, and is also needed for a hardware test of mine. --- .../Core/VideoBackends/Software/Clipper.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Source/Core/VideoBackends/Software/Clipper.cpp b/Source/Core/VideoBackends/Software/Clipper.cpp index edae393350..ae6b60f1ee 100644 --- a/Source/Core/VideoBackends/Software/Clipper.cpp +++ b/Source/Core/VideoBackends/Software/Clipper.cpp @@ -345,7 +345,24 @@ void ProcessTriangle(OutputVertexData* v0, OutputVertexData* v1, OutputVertexDat Vertices[2] = v2; } - ClipTriangle(indices, &numIndices); + // TODO: behavior when disable_clipping_detection is set doesn't quite match actual hardware; + // there does still seem to be a maximum size after which things are clipped. Also, currently + // when clipping is enabled triangles are clipped to exactly the viewport, but on hardware there + // is a guardband (and with certain scissor configurations, things can show up in it) + // Mario Party 8 in widescreen breaks without this: https://bugs.dolphin-emu.org/issues/12769 + bool skip_clipping = false; + if (xfmem.clipDisable.disable_clipping_detection) + { + // If any w coordinate is negative, then the perspective divide will flip coordinates, breaking + // various assumptions (including backface). So, we still need to do clipping in that case. + // This isn't the actual condition hardware uses. + if (Vertices[0]->projectedPosition.w >= 0 && Vertices[1]->projectedPosition.w >= 0 && + Vertices[2]->projectedPosition.w >= 0) + skip_clipping = true; + } + + if (!skip_clipping) + ClipTriangle(indices, &numIndices); for (int i = 0; i + 3 <= numIndices; i += 3) { From 4d1e1db3c55c1d51a325e46274a1f8587805d529 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 20 Nov 2021 15:21:43 -0800 Subject: [PATCH 6/7] Software: Fix scissor rectangle always being block-aligned --- .../VideoBackends/Software/Rasterizer.cpp | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Source/Core/VideoBackends/Software/Rasterizer.cpp b/Source/Core/VideoBackends/Software/Rasterizer.cpp index d23b6fc558..aaa5255fe3 100644 --- a/Source/Core/VideoBackends/Software/Rasterizer.cpp +++ b/Source/Core/VideoBackends/Software/Rasterizer.cpp @@ -419,20 +419,23 @@ static void DrawTriangleFrontFace(const OutputVertexData* v0, const OutputVertex if (DY31 < 0 || (DY31 == 0 && DX31 > 0)) C3++; - // Start in corner of 8x8 block - minx &= ~(BLOCK_SIZE - 1); - miny &= ~(BLOCK_SIZE - 1); + // Start in corner of 2x2 block + s32 block_minx = minx & ~(BLOCK_SIZE - 1); + s32 block_miny = miny & ~(BLOCK_SIZE - 1); // Loop through blocks - for (s32 y = miny; y < maxy; y += BLOCK_SIZE) + for (s32 y = block_miny & ~(BLOCK_SIZE - 1); y < maxy; y += BLOCK_SIZE) { - for (s32 x = minx; x < maxx; x += BLOCK_SIZE) + for (s32 x = block_minx; x < maxx; x += BLOCK_SIZE) { + s32 x1_ = (x + BLOCK_SIZE - 1); + s32 y1_ = (y + BLOCK_SIZE - 1); + // Corners of block s32 x0 = x << 4; - s32 x1 = (x + BLOCK_SIZE - 1) << 4; + s32 x1 = x1_ << 4; s32 y0 = y << 4; - s32 y1 = (y + BLOCK_SIZE - 1) << 4; + s32 y1 = y1_ << 4; // Evaluate half-space functions bool a00 = C1 + DX12 * y0 - DY12 * x0 > 0; @@ -460,7 +463,8 @@ static void DrawTriangleFrontFace(const OutputVertexData* v0, const OutputVertex BuildBlock(x, y); // Accept whole block when totally covered - if (a == 0xF && b == 0xF && c == 0xF) + // We still need to check min/max x/y because of the scissor + if (a == 0xF && b == 0xF && c == 0xF && x >= minx && x1_ < maxx && y >= miny && y1_ < maxy) { for (s32 iy = 0; iy < BLOCK_SIZE; iy++) { @@ -486,7 +490,10 @@ static void DrawTriangleFrontFace(const OutputVertexData* v0, const OutputVertex { if (CX1 > 0 && CX2 > 0 && CX3 > 0) { - Draw(x + ix, y + iy, ix, iy); + // This check enforces the scissor rectangle, since it might not be aligned with the + // blocks + if (x + ix >= minx && x + ix < maxx && y + iy >= miny && y + iy < maxy) + Draw(x + ix, y + iy, ix, iy); } CX1 -= FDY12; From f3eff70e2fda8ad0894f34336584f331ab989b68 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 27 Nov 2021 17:09:55 -0800 Subject: [PATCH 7/7] Implement ImGui window for scissor rectangles This is mainly for debugging, and is only exposed by manually editing the configuration. --- Source/Core/Core/Config/GraphicsSettings.cpp | 1 + Source/Core/Core/Config/GraphicsSettings.h | 1 + Source/Core/VideoCommon/BPFunctions.h | 23 ++ Source/Core/VideoCommon/RenderBase.cpp | 3 + Source/Core/VideoCommon/Statistics.cpp | 360 ++++++++++++++++++ Source/Core/VideoCommon/Statistics.h | 15 + .../Core/VideoCommon/VertexShaderManager.cpp | 1 + Source/Core/VideoCommon/VideoConfig.cpp | 1 + Source/Core/VideoCommon/VideoConfig.h | 1 + 9 files changed, 406 insertions(+) diff --git a/Source/Core/Core/Config/GraphicsSettings.cpp b/Source/Core/Core/Config/GraphicsSettings.cpp index a0e704c0b8..b5b4f369f8 100644 --- a/Source/Core/Core/Config/GraphicsSettings.cpp +++ b/Source/Core/Core/Config/GraphicsSettings.cpp @@ -33,6 +33,7 @@ const Info GFX_LOG_RENDER_TIME_TO_FILE{{System::GFX, "Settings", "LogRende false}; const Info GFX_OVERLAY_STATS{{System::GFX, "Settings", "OverlayStats"}, false}; const Info GFX_OVERLAY_PROJ_STATS{{System::GFX, "Settings", "OverlayProjStats"}, false}; +const Info GFX_OVERLAY_SCISSOR_STATS{{System::GFX, "Settings", "OverlayScissorStats"}, false}; const Info GFX_DUMP_TEXTURES{{System::GFX, "Settings", "DumpTextures"}, false}; const Info GFX_DUMP_MIP_TEXTURES{{System::GFX, "Settings", "DumpMipTextures"}, true}; const Info GFX_DUMP_BASE_TEXTURES{{System::GFX, "Settings", "DumpBaseTextures"}, true}; diff --git a/Source/Core/Core/Config/GraphicsSettings.h b/Source/Core/Core/Config/GraphicsSettings.h index 798f27b3ac..6895501cb0 100644 --- a/Source/Core/Core/Config/GraphicsSettings.h +++ b/Source/Core/Core/Config/GraphicsSettings.h @@ -34,6 +34,7 @@ extern const Info GFX_SHOW_NETPLAY_MESSAGES; extern const Info GFX_LOG_RENDER_TIME_TO_FILE; extern const Info GFX_OVERLAY_STATS; extern const Info GFX_OVERLAY_PROJ_STATS; +extern const Info GFX_OVERLAY_SCISSOR_STATS; extern const Info GFX_DUMP_TEXTURES; extern const Info GFX_DUMP_MIP_TEXTURES; extern const Info GFX_DUMP_BASE_TEXTURES; diff --git a/Source/Core/VideoCommon/BPFunctions.h b/Source/Core/VideoCommon/BPFunctions.h index eb0d67edf2..6bcc003fe1 100644 --- a/Source/Core/VideoCommon/BPFunctions.h +++ b/Source/Core/VideoCommon/BPFunctions.h @@ -58,6 +58,10 @@ struct ScissorRect // rectangle exists is based on games only wanting to draw one rectangle, and accidentally // configuring the scissor offset and size of the scissor rectangle such that multiple show up; // there are no known games where this is not the case. +// +// An ImGui overlay that displays the scissor rectangle configuration as well as the generated +// rectangles is available by setting OverlayScissorStats (GFX_OVERLAY_SCISSOR_STATS) +// under [Settings] to True in GFX.ini. struct ScissorResult { ScissorResult(const BPMemory& bpmem, const XFMemory& xfmem); @@ -119,6 +123,25 @@ struct ScissorResult ScissorRect Best() const; + bool ScissorMatches(const ScissorResult& other) const + { + return scissor_tl.hex == other.scissor_tl.hex && scissor_br.hex == other.scissor_br.hex && + scissor_off.hex == other.scissor_off.hex; + } + bool ViewportMatches(const ScissorResult& other) const + { + return viewport_left == other.viewport_left && viewport_right == other.viewport_right && + viewport_top == other.viewport_top && viewport_bottom == other.viewport_bottom; + } + bool Matches(const ScissorResult& other, bool compare_scissor, bool compare_viewport) const + { + if (compare_scissor && !ScissorMatches(other)) + return false; + if (compare_viewport && !ViewportMatches(other)) + return false; + return true; + } + private: ScissorResult(const BPMemory& bpmem, std::pair viewport_x, std::pair viewport_y); diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index 6c369621b3..d895d648ed 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -627,6 +627,9 @@ void Renderer::DrawDebugText() if (g_ActiveConfig.bOverlayProjStats) g_stats.DisplayProj(); + if (g_ActiveConfig.bOverlayScissorStats) + g_stats.DisplayScissor(); + const std::string profile_output = Common::Profiler::ToString(); if (!profile_output.empty()) ImGui::TextUnformatted(profile_output.c_str()); diff --git a/Source/Core/VideoCommon/Statistics.cpp b/Source/Core/VideoCommon/Statistics.cpp index 010a733ce2..bd0bf0f54c 100644 --- a/Source/Core/VideoCommon/Statistics.cpp +++ b/Source/Core/VideoCommon/Statistics.cpp @@ -3,18 +3,26 @@ #include "VideoCommon/Statistics.h" +#include #include #include +#include "VideoCommon/BPFunctions.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" Statistics g_stats; +static bool clear_scissors; void Statistics::ResetFrame() { this_frame = {}; + clear_scissors = true; + if (scissors.size() > 1) + { + scissors.erase(scissors.begin(), scissors.end() - 1); + } } void Statistics::SwapDL() @@ -121,3 +129,355 @@ void Statistics::DisplayProj() const ImGui::End(); } + +void Statistics::AddScissorRect() +{ + if (clear_scissors) + { + scissors.clear(); + clear_scissors = false; + } + + BPFunctions::ScissorResult scissor = BPFunctions::ComputeScissorRects(); + bool add; + if (scissors.empty()) + { + add = true; + } + else + { + if (allow_duplicate_scissors) + { + // Only check the last entry + add = !scissors.back().Matches(scissor, show_scissors, show_viewports); + } + else + { + add = std::find_if(scissors.begin(), scissors.end(), [&](auto& s) { + return s.Matches(scissor, show_scissors, show_viewports); + }) == scissors.end(); + } + } + if (add) + scissors.push_back(std::move(scissor)); +} + +void Statistics::DisplayScissor() +{ + // TODO: This is the same position as the regular statistics text + const float scale = ImGui::GetIO().DisplayFramebufferScale.x; + ImGui::SetNextWindowPos(ImVec2(10.0f * scale, 10.0f * scale), ImGuiCond_FirstUseEver); + + if (!ImGui::Begin("Scissor Rectangles", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::End(); + return; + } + + if (ImGui::TreeNode("Options")) + { + ImGui::Checkbox("Allow Duplicates", &allow_duplicate_scissors); + ImGui::Checkbox("Show Scissors", &show_scissors); + ImGui::BeginDisabled(!show_scissors); + ImGui::Checkbox("Show Raw Values", &show_raw_scissors); + ImGui::EndDisabled(); + ImGui::Checkbox("Show Viewports", &show_viewports); + ImGui::Checkbox("Show Text", &show_text); + ImGui::DragInt("Scale", &scissor_scale, .2f, 1, 16); + ImGui::DragInt("Expected Scissor Count", &scissor_expected_count, .2f, 0, 16); + ImGui::TreePop(); + } + + ImGui::BeginDisabled(current_scissor == 0); + if (ImGui::ArrowButton("##left", ImGuiDir_Left)) + { + current_scissor--; + } + ImGui::EndDisabled(); + ImGui::SameLine(); + ImGui::BeginDisabled(current_scissor >= scissors.size()); + if (ImGui::ArrowButton("##right", ImGuiDir_Right)) + { + current_scissor++; + if (current_scissor > scissors.size()) + { + current_scissor = scissors.size(); + } + } + ImGui::EndDisabled(); + ImGui::SameLine(); + if (current_scissor == 0) + ImGui::Text("Displaying all %zu rectangle(s)", scissors.size()); + else if (current_scissor <= scissors.size()) + ImGui::Text("Displaying rectangle %zu / %zu", current_scissor, scissors.size()); + else + ImGui::Text("Displaying rectangle %zu / %zu (OoB)", current_scissor, scissors.size()); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 p = ImGui::GetCursorScreenPos(); + ImGui::Dummy(ImVec2(1024 * 3 / scissor_scale, 1024 * 3 / scissor_scale)); + + constexpr int DRAW_START = -1024; + constexpr int DRAW_END = DRAW_START + 3 * 1024; + + const auto vec = [&](int x, int y, int xoff = 0, int yoff = 0) { + return ImVec2(p.x + int(float(x - DRAW_START) / scissor_scale) + xoff, + p.y + int(float(y - DRAW_START) / scissor_scale) + yoff); + }; + + const auto light_grey = ImGui::GetColorU32(ImVec4(.5f, .5f, .5f, 1.f)); + + // Draw gridlines + for (int x = DRAW_START; x <= DRAW_END; x += 1024) + draw_list->AddLine(vec(x, DRAW_START), vec(x, DRAW_END), light_grey); + for (int y = DRAW_START; y <= DRAW_END; y += 1024) + draw_list->AddLine(vec(DRAW_START, y), vec(DRAW_END, y), light_grey); + + const auto draw_x = [&](int x, int y, int size, ImU32 col) { + // Add an extra offset on the second parameter as otherwise ImGui seems to give results that are + // too small on one side + draw_list->AddLine(vec(x, y, -size, -size), vec(x, y, +size + 1, +size + 1), col); + draw_list->AddLine(vec(x, y, -size, +size), vec(x, y, +size + 1, -size - 1), col); + }; + const auto draw_rect = [&](int x0, int y0, int x1, int y1, ImU32 col, bool show_oob = true) { + x0 = std::clamp(x0, DRAW_START, DRAW_END); + y0 = std::clamp(y0, DRAW_START, DRAW_END); + x1 = std::clamp(x1, DRAW_START, DRAW_END); + y1 = std::clamp(y1, DRAW_START, DRAW_END); + if (x0 < x1 && y0 < y1) + { + draw_list->AddRect(vec(x0, y0), vec(x1, y1), col); + } + else if (show_oob) + { + // Markers at the two corners, for when they don't form a valid rectangle. + draw_list->AddLine(vec(x0, y0), vec(x0, y0, 8, 0), col); + draw_list->AddLine(vec(x0, y0), vec(x0, y0, 0, 8), col); + draw_list->AddLine(vec(x1, y1), vec(x1, y1, -8, 0), col); + draw_list->AddLine(vec(x1, y1), vec(x1, y1, 0, -8), col); + } + }; + static std::array COLORS = { + ImVec4(1, 0, 0, 1), ImVec4(1, 1, 0, 1), ImVec4(0, 1, 0, 1), + ImVec4(0, 1, 1, 1), ImVec4(0, 0, 1, 1), ImVec4(1, 0, 1, 1), + }; + const auto draw_scissor = [&](size_t index) { + const auto& info = scissors[index]; + const ImU32 col = ImGui::GetColorU32(COLORS[index % COLORS.size()]); + int x_off = info.scissor_off.x << 1; + int y_off = info.scissor_off.y << 1; + // Subtract 2048 instead of 1024, because when x_off is large enough we need to show two + // rectangles in the upper sections + for (int y = y_off - 2048; y < DRAW_END; y += 1024) + { + for (int x = x_off - 2048; x < DRAW_END; x += 1024) + { + draw_rect(x, y, x + EFB_WIDTH, y + EFB_HEIGHT, col, false); + } + } + // Use the full offset here so that ones that have the extra bit set show up distinctly + draw_x(info.scissor_off.x_full << 1, info.scissor_off.y_full << 1, 4, col); + + if (show_scissors) + { + draw_rect(info.scissor_tl.x, info.scissor_tl.y, info.scissor_br.x + 1, info.scissor_br.y + 1, + col); + } + if (show_viewports) + { + draw_rect(info.viewport_left, info.viewport_top, info.viewport_right, info.viewport_bottom, + col); + } + for (size_t i = 0; i < info.m_result.size(); i++) + { + // The last entry in the sorted list of results is the one that is used by hardware backends + const u8 new_alpha = (i == info.m_result.size() - 1) ? 0x40 : 0x80; + const ImU32 new_col = (col & ~IM_COL32_A_MASK) | (new_alpha << IM_COL32_A_SHIFT); + + const auto& r = info.m_result[i]; + draw_list->AddRectFilled(vec(r.rect.left + r.x_off, r.rect.top + r.y_off), + vec(r.rect.right + r.x_off, r.rect.bottom + r.y_off), new_col); + } + }; + constexpr auto NUM_SCISSOR_COLUMNS = 8; + const auto draw_scissor_table_header = [&]() { + ImGui::TableSetupColumn("#"); + ImGui::TableSetupColumn("x0"); + ImGui::TableSetupColumn("y0"); + ImGui::TableSetupColumn("x1"); + ImGui::TableSetupColumn("y1"); + ImGui::TableSetupColumn("xOff"); + ImGui::TableSetupColumn("yOff"); + ImGui::TableSetupColumn("Affected"); + ImGui::TableHeadersRow(); + }; + const auto draw_scissor_table_row = [&](size_t index) { + const auto& info = scissors[index]; + int x_off = (info.scissor_off.x << 1) - info.viewport_top; + int y_off = (info.scissor_off.y << 1) - info.viewport_left; + int x0 = info.scissor_tl.x - info.viewport_top; + int x1 = info.scissor_br.x - info.viewport_left; + int y0 = info.scissor_tl.y - info.viewport_top; + int y1 = info.scissor_br.y - info.viewport_left; + ImGui::TableNextColumn(); + ImGui::TextColored(COLORS[index % COLORS.size()], "%zu", index + 1); + ImGui::TableNextColumn(); + ImGui::Text("%d", x0); + ImGui::TableNextColumn(); + ImGui::Text("%d", y0); + ImGui::TableNextColumn(); + ImGui::Text("%d", x1); + ImGui::TableNextColumn(); + ImGui::Text("%d", y1); + ImGui::TableNextColumn(); + ImGui::Text("%d", x_off); + ImGui::TableNextColumn(); + ImGui::Text("%d", y_off); + + // Visualization of where things are updated on screen with this specific scissor + ImGui::TableNextColumn(); + float scale = ImGui::GetTextLineHeight() / EFB_HEIGHT; + if (show_raw_scissors) + scale += ImGui::GetTextLineHeightWithSpacing() / EFB_HEIGHT; + ImVec2 p2 = ImGui::GetCursorScreenPos(); + // Use a height of 1 since we want this to span two table rows (if possible) + ImGui::Dummy(ImVec2(EFB_WIDTH * scale, 1)); + for (size_t i = 0; i < info.m_result.size(); i++) + { + // The last entry in the sorted list of results is the one that is used by hardware backends + const u8 new_alpha = (i == info.m_result.size() - 1) ? 0x80 : 0x40; + const ImU32 col = ImGui::GetColorU32(COLORS[index % COLORS.size()]); + const ImU32 new_col = (col & ~IM_COL32_A_MASK) | (new_alpha << IM_COL32_A_SHIFT); + + const auto& r = info.m_result[i]; + draw_list->AddRectFilled(ImVec2(p2.x + r.rect.left * scale, p2.y + r.rect.top * scale), + ImVec2(p2.x + r.rect.right * scale, p2.y + r.rect.bottom * scale), + new_col); + } + draw_list->AddRect(p2, ImVec2(p2.x + EFB_WIDTH * scale, p2.y + EFB_HEIGHT * scale), light_grey); + ImGui::SameLine(); + ImGui::Text("%d", int(info.m_result.size())); + + if (show_raw_scissors) + { + ImGui::TableNextColumn(); + ImGui::TextColored(COLORS[index % COLORS.size()], "Raw"); + ImGui::TableNextColumn(); + ImGui::Text("%d", info.scissor_tl.x_full.Value()); + ImGui::TableNextColumn(); + ImGui::Text("%d", info.scissor_tl.y_full.Value()); + ImGui::TableNextColumn(); + ImGui::Text("%d", info.scissor_br.x_full.Value()); + ImGui::TableNextColumn(); + ImGui::Text("%d", info.scissor_br.y_full.Value()); + ImGui::TableNextColumn(); + ImGui::Text("%d", info.scissor_off.x_full.Value()); + ImGui::TableNextColumn(); + ImGui::Text("%d", info.scissor_off.y_full.Value()); + ImGui::TableNextColumn(); + } + }; + const auto scissor_table_skip_row = [&](size_t index) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextColored(COLORS[index % COLORS.size()], "%zu", index + 1); + if (show_raw_scissors) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextColored(COLORS[index % COLORS.size()], "Raw"); + } + }; + constexpr auto NUM_VIEWPORT_COLUMNS = 5; + const auto draw_viewport_table_header = [&]() { + ImGui::TableSetupColumn("#"); + ImGui::TableSetupColumn("vx0"); + ImGui::TableSetupColumn("vy0"); + ImGui::TableSetupColumn("vx1"); + ImGui::TableSetupColumn("vy1"); + ImGui::TableHeadersRow(); + }; + const auto draw_viewport_table_row = [&](size_t index) { + const auto& info = scissors[index]; + ImGui::TableNextColumn(); + ImGui::TextColored(COLORS[index % COLORS.size()], "%zu", index + 1); + ImGui::TableNextColumn(); + ImGui::Text("%.1f", info.viewport_left); + ImGui::TableNextColumn(); + ImGui::Text("%.1f", info.viewport_top); + ImGui::TableNextColumn(); + ImGui::Text("%.1f", info.viewport_right); + ImGui::TableNextColumn(); + ImGui::Text("%.1f", info.viewport_bottom); + }; + const auto viewport_table_skip_row = [&](size_t index) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextColored(COLORS[index % COLORS.size()], "%zu", index + 1); + }; + if (current_scissor == 0) + { + for (size_t i = 0; i < scissors.size(); i++) + draw_scissor(i); + if (show_text) + { + if (show_scissors) + { + if (ImGui::BeginTable("Scissors", NUM_SCISSOR_COLUMNS)) + { + draw_scissor_table_header(); + for (size_t i = 0; i < scissors.size(); i++) + draw_scissor_table_row(i); + for (size_t i = scissors.size(); i < scissor_expected_count; i++) + scissor_table_skip_row(i); + ImGui::EndTable(); + } + } + if (show_viewports) + { + if (ImGui::BeginTable("Viewports", NUM_VIEWPORT_COLUMNS)) + { + draw_viewport_table_header(); + for (size_t i = 0; i < scissors.size(); i++) + draw_viewport_table_row(i); + for (size_t i = scissors.size(); i < scissor_expected_count; i++) + viewport_table_skip_row(i); + ImGui::EndTable(); + } + } + } + } + else if (current_scissor <= scissors.size()) + { + // This bounds check is needed since we only clamp when changing the value; different frames may + // have different numbers + draw_scissor(current_scissor - 1); + if (show_text) + { + if (show_scissors) + { + if (ImGui::BeginTable("Scissors", NUM_SCISSOR_COLUMNS)) + { + draw_scissor_table_header(); + draw_scissor_table_row(current_scissor - 1); + ImGui::EndTable(); + } + if (ImGui::BeginTable("Viewports", NUM_VIEWPORT_COLUMNS)) + { + draw_viewport_table_header(); + draw_viewport_table_row(current_scissor - 1); + ImGui::EndTable(); + } + } + } + } + else if (show_text) + { + if (show_scissors) + ImGui::Text("Scissor %zu: Does not exist", current_scissor); + if (show_viewports) + ImGui::Text("Viewport %zu: Does not exist", current_scissor); + } + + ImGui::End(); +} diff --git a/Source/Core/VideoCommon/Statistics.h b/Source/Core/VideoCommon/Statistics.h index 394223380d..c6453039ae 100644 --- a/Source/Core/VideoCommon/Statistics.h +++ b/Source/Core/VideoCommon/Statistics.h @@ -4,6 +4,9 @@ #pragma once #include +#include + +#include "VideoCommon/BPFunctions.h" struct Statistics { @@ -22,6 +25,16 @@ struct Statistics std::array gproj; std::array g2proj; + std::vector scissors; + size_t current_scissor = 0; // 0 => all, otherwise index + 1 + int scissor_scale = 10; + int scissor_expected_count = 0; + bool allow_duplicate_scissors = false; + bool show_scissors = true; + bool show_raw_scissors = true; + bool show_viewports = false; + bool show_text = true; + struct ThisFrame { int num_bp_loads; @@ -62,8 +75,10 @@ struct Statistics ThisFrame this_frame; void ResetFrame(); void SwapDL(); + void AddScissorRect(); void Display() const; void DisplayProj() const; + void DisplayScissor(); }; extern Statistics g_stats; diff --git a/Source/Core/VideoCommon/VertexShaderManager.cpp b/Source/Core/VideoCommon/VertexShaderManager.cpp index fc8b9e69f8..2b5fc68185 100644 --- a/Source/Core/VideoCommon/VertexShaderManager.cpp +++ b/Source/Core/VideoCommon/VertexShaderManager.cpp @@ -299,6 +299,7 @@ void VertexShaderManager::SetConstants() dirty = true; BPFunctions::SetScissorAndViewport(); + g_stats.AddScissorRect(); } if (bProjectionChanged || g_freelook_camera.GetController()->IsDirty()) diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index cd35be1ae7..6d8cd7ff29 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -62,6 +62,7 @@ void VideoConfig::Refresh() bLogRenderTimeToFile = Config::Get(Config::GFX_LOG_RENDER_TIME_TO_FILE); bOverlayStats = Config::Get(Config::GFX_OVERLAY_STATS); bOverlayProjStats = Config::Get(Config::GFX_OVERLAY_PROJ_STATS); + bOverlayScissorStats = Config::Get(Config::GFX_OVERLAY_SCISSOR_STATS); bDumpTextures = Config::Get(Config::GFX_DUMP_TEXTURES); bDumpMipmapTextures = Config::Get(Config::GFX_DUMP_MIP_TEXTURES); bDumpBaseTextures = Config::Get(Config::GFX_DUMP_BASE_TEXTURES); diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index 2ff4d502af..f557f7bfe7 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -83,6 +83,7 @@ struct VideoConfig final bool bShowNetPlayMessages = false; bool bOverlayStats = false; bool bOverlayProjStats = false; + bool bOverlayScissorStats = false; bool bTexFmtOverlayEnable = false; bool bTexFmtOverlayCenter = false; bool bLogRenderTimeToFile = false;