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;