summaryrefslogtreecommitdiff
path: root/Source/Core/VideoCommon
diff options
context:
space:
mode:
authorPokechu22 <Pokechu022@gmail.com>2021-11-27 17:09:55 -0800
committerPokechu22 <Pokechu022@gmail.com>2022-04-16 12:35:00 -0700
commitf3eff70e2fda8ad0894f34336584f331ab989b68 (patch)
tree0b2f23c5206c990bff21bce2073e54454a733d3e /Source/Core/VideoCommon
parent4d1e1db3c55c1d51a325e46274a1f8587805d529 (diff)
Implement ImGui window for scissor rectangles
This is mainly for debugging, and is only exposed by manually editing the configuration.
Diffstat (limited to 'Source/Core/VideoCommon')
-rw-r--r--Source/Core/VideoCommon/BPFunctions.h23
-rw-r--r--Source/Core/VideoCommon/RenderBase.cpp3
-rw-r--r--Source/Core/VideoCommon/Statistics.cpp360
-rw-r--r--Source/Core/VideoCommon/Statistics.h15
-rw-r--r--Source/Core/VideoCommon/VertexShaderManager.cpp1
-rw-r--r--Source/Core/VideoCommon/VideoConfig.cpp1
-rw-r--r--Source/Core/VideoCommon/VideoConfig.h1
7 files changed, 404 insertions, 0 deletions
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<float, float> viewport_x,
std::pair<float, float> 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 <cstring>
#include <utility>
#include <imgui.h>
+#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<ImVec4, 6> 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 <array>
+#include <vector>
+
+#include "VideoCommon/BPFunctions.h"
struct Statistics
{
@@ -22,6 +25,16 @@ struct Statistics
std::array<float, 16> gproj;
std::array<float, 16> g2proj;
+ std::vector<BPFunctions::ScissorResult> 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;