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
25 changed files with 844 additions and 208 deletions
@@ -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};
@@ -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;
-1
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);
@@ -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;
@@ -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;
@@ -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;
@@ -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;
+1 -1
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,
+22 -7
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
@@ -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
@@ -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);
@@ -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
@@ -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;
@@ -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;
@@ -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.
+159 -45
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;
+142 -4
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);
+52 -12
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
+4 -19
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();
+5 -4
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());

Some files were not shown because too many files have changed in this diff Show More