Merge pull request #10251 from Pokechu22/negative-scissor

Rework scissor handling
This commit is contained in:
JMC47 2022-04-24 15:00:42 -04:00 committed by GitHub
commit c0488de482
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 844 additions and 208 deletions

View File

@ -33,6 +33,7 @@ const Info<bool> GFX_LOG_RENDER_TIME_TO_FILE{{System::GFX, "Settings", "LogRende
false};
const Info<bool> GFX_OVERLAY_STATS{{System::GFX, "Settings", "OverlayStats"}, false};
const Info<bool> GFX_OVERLAY_PROJ_STATS{{System::GFX, "Settings", "OverlayProjStats"}, false};
const Info<bool> GFX_OVERLAY_SCISSOR_STATS{{System::GFX, "Settings", "OverlayScissorStats"}, false};
const Info<bool> GFX_DUMP_TEXTURES{{System::GFX, "Settings", "DumpTextures"}, false};
const Info<bool> GFX_DUMP_MIP_TEXTURES{{System::GFX, "Settings", "DumpMipTextures"}, true};
const Info<bool> GFX_DUMP_BASE_TEXTURES{{System::GFX, "Settings", "DumpBaseTextures"}, true};

View File

@ -34,6 +34,7 @@ extern const Info<bool> GFX_SHOW_NETPLAY_MESSAGES;
extern const Info<bool> GFX_LOG_RENDER_TIME_TO_FILE;
extern const Info<bool> GFX_OVERLAY_STATS;
extern const Info<bool> GFX_OVERLAY_PROJ_STATS;
extern const Info<bool> GFX_OVERLAY_SCISSOR_STATS;
extern const Info<bool> GFX_DUMP_TEXTURES;
extern const Info<bool> GFX_DUMP_MIP_TEXTURES;
extern const Info<bool> GFX_DUMP_BASE_TEXTURES;

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -958,7 +958,7 @@ void Renderer::ClearScreen(const MathUtil::Rectangle<int>& rc, bool colorEnable,
glDepthMask(m_current_depth_state.updateenable);
// Scissor rect must be restored.
BPFunctions::SetScissor();
BPFunctions::SetScissorAndViewport();
}
void Renderer::RenderXFBToScreen(const MathUtil::Rectangle<int>& target_rc,

View File

@ -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;
}
@ -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)
{
@ -533,10 +550,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

View File

@ -5,11 +5,16 @@
#include <algorithm>
#include <cstring>
#include <vector>
#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<BPFunctions::ScissorRect> 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};
@ -421,20 +419,23 @@ void DrawTriangleFrontFace(const OutputVertexData* v0, const OutputVertexData* v
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;
@ -462,7 +463,8 @@ void DrawTriangleFrontFace(const OutputVertexData* v0, const OutputVertexData* v
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++)
{
@ -488,7 +490,10 @@ void DrawTriangleFrontFace(const OutputVertexData* v0, const OutputVertexData* v
{
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;
@ -504,4 +509,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

View File

@ -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);

View File

@ -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<NativeVertexFormat>(vtx_decl);
}
void SWRenderer::SetScissorRect(const MathUtil::Rectangle<int>& 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

View File

@ -58,6 +58,8 @@ public:
const AbstractTexture* src_texture,
const MathUtil::Rectangle<int>& src_rect) override;
void SetScissorRect(const MathUtil::Rectangle<int>& rc) override;
protected:
std::unique_ptr<BoundingBox> CreateBoundingBox() const override;

View File

@ -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;

View File

@ -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.

View File

@ -7,6 +7,7 @@
#include <cmath>
#include <string_view>
#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<int>(rect.rect.left + rect.x_off, viewport_left, viewport_right);
int x1 = std::clamp<int>(rect.rect.right + rect.x_off, viewport_left, viewport_right);
MathUtil::Rectangle<int> 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<int>(rect.rect.top + rect.y_off, viewport_top, viewport_bottom);
int y1 = std::clamp<int>(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<ScissorRange, MAX_RANGES> 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<float, float> viewport_x,
std::pair<float, float> 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())
@ -153,17 +278,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<float>(g_renderer->GetCurrentFramebuffer()->GetWidth());
const float max_height = static_cast<float>(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<float>(g_renderer->GetCurrentFramebuffer()->GetHeight()) - y - height;

View File

@ -7,16 +7,154 @@
#pragma once
#include "Common/MathUtil.h"
#include <utility>
#include <vector>
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<int> 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.
//
// 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);
~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<ScissorRect> m_result;
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);
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<int>& rc);

View File

@ -1212,24 +1212,64 @@ struct fmt::formatter<LPSize>
}
};
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<ScissorPos>
{
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
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<ScissorOffset>
{
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
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

View File

@ -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;
@ -815,19 +813,10 @@ std::pair<std::string, std::string> 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 +991,8 @@ std::pair<std::string, std::string> 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);
@ -1284,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();

View File

@ -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
@ -629,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());

View File

@ -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();
}

View File

@ -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;

View File

@ -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
@ -347,14 +298,8 @@ 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;
}
BPFunctions::SetScissorAndViewport();
g_stats.AddScissorRect();
}
if (bProjectionChanged || g_freelook_camera.GetController()->IsDirty())

View File

@ -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);

View File

@ -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;
@ -199,7 +200,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;