Merge pull request #15399 from unknownbrackets/softgpu-vertices

Convert more verts to rects, fix strip/fan skew on clip
This commit is contained in:
Henrik Rydgård 2022-02-13 15:28:16 +01:00 committed by GitHub
commit df1a15938d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 119 additions and 109 deletions

View file

@ -128,8 +128,6 @@ static void RotateUV(const VertexData &tl, const VertexData &br, VertexData &tr,
}
}
void ProcessTriangleInternal(VertexData &v0, VertexData &v1, VertexData &v2, const VertexData &provoking, BinManager &binner, bool fromRectangle);
static inline bool CheckOutsideZ(ClipCoords p, int &pos, int &neg) {
constexpr float outsideValue = 1.000030517578125f;
float z = p.z / p.w;
@ -197,10 +195,11 @@ void ProcessRect(const VertexData &v0, const VertexData &v1, BinManager &binner)
RotateUV(*topleft, *bottomright, *topright, *bottomleft);
// Four triangles to do backfaces as well. Two of them will get backface culled.
ProcessTriangleInternal(*topleft, *topright, *bottomright, buf[3], binner, true);
ProcessTriangleInternal(*bottomright, *topright, *topleft, buf[3], binner, true);
ProcessTriangleInternal(*bottomright, *bottomleft, *topleft, buf[3], binner, true);
ProcessTriangleInternal(*topleft, *bottomleft, *bottomright, buf[3], binner, true);
// We already clipped, so we don't need additional processing.
binner.AddTriangle(*topleft, *topright, *bottomright);
binner.AddTriangle(*bottomright, *topright, *topleft);
binner.AddTriangle(*bottomright, *bottomleft, *topleft);
binner.AddTriangle(*topleft, *bottomleft, *bottomright);
} else {
// through mode handling
@ -257,12 +256,12 @@ void ProcessRect(const VertexData &v0, const VertexData &v1, BinManager &binner)
}
}
void ProcessPoint(VertexData &v0, BinManager &binner) {
void ProcessPoint(const VertexData &v0, BinManager &binner) {
// Points need no clipping. Will be bounds checked in the rasterizer (which seems backwards?)
binner.AddPoint(v0);
}
void ProcessLine(VertexData &v0, VertexData &v1, BinManager &binner) {
void ProcessLine(const VertexData &v0, const VertexData &v1, BinManager &binner) {
if (gstate.isModeThrough()) {
// Actually, should clip this one too so we don't need to do bounds checks in the rasterizer.
binner.AddLine(v0, v1);
@ -280,16 +279,19 @@ void ProcessLine(VertexData &v0, VertexData &v1, BinManager &binner) {
else if (outsidePos >= 2 || outsideNeg >= 2)
return;
VertexData *Vertices[2] = {&v0, &v1};
int mask0 = CalcClipMask(v0.clippos);
int mask1 = CalcClipMask(v1.clippos);
int mask = mask0 | mask1;
bool clipped = false;
if (mask) {
CLIP_LINE(CLIP_NEG_Z_BIT, 0, 0, 1, 1);
if ((mask & CLIP_NEG_Z_BIT) == 0) {
binner.AddLine(v0, v1);
return;
}
VertexData ClippedVertices[2] = { v0, v1 };
VertexData *Vertices[2] = { &ClippedVertices[0], &ClippedVertices[1] };
bool clipped = false;
CLIP_LINE(CLIP_NEG_Z_BIT, 0, 0, 1, 1);
VertexData data[2] = { *Vertices[0], *Vertices[1] };
if (clipped) {
data[0].screenpos = TransformUnit::ClipToScreen(data[0].clippos);
@ -298,10 +300,31 @@ void ProcessLine(VertexData &v0, VertexData &v1, BinManager &binner) {
binner.AddLine(data[0], data[1]);
}
void ProcessTriangleInternal(VertexData &v0, VertexData &v1, VertexData &v2, const VertexData &provoking, BinManager &binner, bool fromRectangle) {
if (gstate.isModeThrough()) {
// In case of cull reordering, make sure the right color is on the final vertex.
void ProcessTriangle(const VertexData &v0, const VertexData &v1, const VertexData &v2, const VertexData &provoking, BinManager &binner) {
int mask = 0;
if (!gstate.isModeThrough()) {
mask |= CalcClipMask(v0.clippos);
mask |= CalcClipMask(v1.clippos);
mask |= CalcClipMask(v2.clippos);
// We may discard the entire triangle based on depth values. First check what's outside.
int outsidePos = 0, outsideNeg = 0;
CheckOutsideZ(v0.clippos, outsidePos, outsideNeg);
CheckOutsideZ(v1.clippos, outsidePos, outsideNeg);
CheckOutsideZ(v2.clippos, outsidePos, outsideNeg);
// With depth clamp off, we discard the triangle if even one vert is outside.
if (outsidePos + outsideNeg > 0 && !gstate.isDepthClampEnabled())
return;
// With it on, all three must be outside in the same direction.
else if (outsidePos >= 3 || outsideNeg >= 3)
return;
}
// No clipping is common, let's skip processing if we can.
if ((mask & CLIP_NEG_Z_BIT) == 0) {
if (gstate.getShadeMode() == GE_SHADE_FLAT) {
// So that the order of clipping doesn't matter...
VertexData corrected2 = v2;
corrected2.color0 = provoking.color0;
corrected2.color1 = provoking.color1;
@ -312,52 +335,22 @@ void ProcessTriangleInternal(VertexData &v0, VertexData &v1, VertexData &v2, con
return;
}
int mask = 0;
mask |= CalcClipMask(v0.clippos);
mask |= CalcClipMask(v1.clippos);
mask |= CalcClipMask(v2.clippos);
// No clipping is common, let's skip processing if we can.
if ((mask & CLIP_NEG_Z_BIT) == 0 || fromRectangle) {
if (gstate.getShadeMode() == GE_SHADE_FLAT) {
// So that the order of clipping doesn't matter...
v2.color0 = provoking.color0;
v2.color1 = provoking.color1;
}
binner.AddTriangle(v0, v1, v2);
return;
}
enum { NUM_CLIPPED_VERTICES = 3, NUM_INDICES = NUM_CLIPPED_VERTICES + 3 };
VertexData* Vertices[NUM_INDICES];
VertexData ClippedVertices[NUM_CLIPPED_VERTICES];
for (int i = 0; i < NUM_CLIPPED_VERTICES; ++i)
Vertices[i+3] = &ClippedVertices[i];
VertexData ClippedVertices[NUM_INDICES];
for (int i = 0; i < NUM_INDICES; ++i)
Vertices[i] = &ClippedVertices[i];
// TODO: Change logic when it's a backface (why? In what way?)
Vertices[0] = &v0;
Vertices[1] = &v1;
Vertices[2] = &v2;
ClippedVertices[0] = v0;
ClippedVertices[1] = v1;
ClippedVertices[2] = v2;
int indices[NUM_INDICES] = { 0, 1, 2, SKIP_FLAG, SKIP_FLAG, SKIP_FLAG };
int numIndices = 3;
bool clipped = false;
// We may discard the entire triangle based on depth values. First check what's outside.
int outsidePos = 0, outsideNeg = 0;
for (int i = 0; i < 3; ++i) {
CheckOutsideZ(Vertices[i]->clippos, outsidePos, outsideNeg);
}
// With depth clamp off, we discard the triangle if even one vert is outside.
if (outsidePos + outsideNeg > 0 && !gstate.isDepthClampEnabled())
return;
// With it on, all three must be outside in the same direction.
else if (outsidePos >= 3 || outsideNeg >= 3)
return;
for (int i = 0; i < 3; i += 3) {
int vlist[2][2*6+1];
int *inlist = vlist[0], *outlist = vlist[1];
@ -409,8 +402,4 @@ void ProcessTriangleInternal(VertexData &v0, VertexData &v1, VertexData &v2, con
}
}
void ProcessTriangle(VertexData &v0, VertexData &v1, VertexData &v2, const VertexData &provoking, BinManager &binner) {
ProcessTriangleInternal(v0, v1, v2, provoking, binner, false);
}
} // namespace

View file

@ -26,9 +26,9 @@ class BinManager;
namespace Clipper {
void ProcessPoint(VertexData &v0, BinManager &binner);
void ProcessLine(VertexData &v0, VertexData &v1, BinManager &binner);
void ProcessTriangle(VertexData &v0, VertexData &v1, VertexData &v2, const VertexData &provoking, BinManager &binner);
void ProcessPoint(const VertexData &v0, BinManager &binner);
void ProcessLine(const VertexData &v0, const VertexData &v1, BinManager &binner);
void ProcessTriangle(const VertexData &v0, const VertexData &v1, const VertexData &v2, const VertexData &provoking, BinManager &binner);
void ProcessRect(const VertexData &v0, const VertexData &v1, BinManager &binner);
}

View file

@ -765,9 +765,9 @@ void DrawTriangleSlice(
if (AnyMask<useSSE4>(mask)) {
Vec4<float> wsum_recip = EdgeRecip(w0, w1, w2);
// Color interpolation is not perspective corrected on the PSP.
Vec4<int> prim_color[4];
if (!flatColor0) {
// Does the PSP do perspective-correct color interpolation? The GC doesn't.
for (int i = 0; i < 4; ++i) {
if (mask[i] >= 0)
prim_color[i] = Interpolate(v0.color0, v1.color0, v2.color0, w0[i], w1[i], w2[i], wsum_recip[i]);
@ -831,7 +831,7 @@ void DrawTriangleSlice(
if (flatZ) {
z = Vec4<int>::AssignToAll(v2.screenpos.z);
} else {
// TODO: Is that the correct way to interpolate?
// Z is interpolated pretty much directly.
Vec4<float> zfloats = w0.Cast<float>() * v0.screenpos.z + w1.Cast<float>() * v1.screenpos.z + w2.Cast<float>() * v2.screenpos.z;
z = (zfloats * wsum_recip).Cast<int>();
}

View file

@ -306,41 +306,50 @@ bool RectangleFastPath(const VertexData &v0, const VertexData &v1, BinManager &b
return false;
}
bool DetectRectangleFromThroughModeStrip(const VertexData data[4]) {
// We'll only do this when the color is flat.
if (!(data[0].color0 == data[1].color0))
return false;
if (!(data[1].color0 == data[2].color0))
return false;
if (!(data[2].color0 == data[3].color0))
return false;
bool DetectRectangleFromStrip(const RasterizerState &state, const VertexData data[4], int *tlIndex, int *brIndex) {
// Color and Z must be flat. Also find the TL and BR meanwhile.
int tl = 0, br = 0;
for (int i = 1; i < 4; ++i) {
if (!(data[i].color0 == data[0].color0))
return false;
if (!(data[i].screenpos.z == data[0].screenpos.z)) {
// Sometimes, we don't actually care about z.
if (state.pixelID.depthWrite || state.pixelID.DepthTestFunc() != GE_COMP_ALWAYS)
return false;
}
if (!state.throughMode) {
if (!state.throughMode && !(data[i].color1 == data[0].color1))
return false;
// Do we have to think about perspective correction or slope mip level?
if (state.enableTextures && data[i].clippos.w != data[0].clippos.w)
return false;
if (state.pixelID.applyFog && data[i].fogdepth != data[0].fogdepth)
return false;
}
// And the depth must also be flat.
if (!(data[0].screenpos.z == data[1].screenpos.z))
return false;
if (!(data[1].screenpos.z == data[2].screenpos.z))
return false;
if (!(data[2].screenpos.z == data[3].screenpos.z))
return false;
if (data[i].screenpos.x <= data[tl].screenpos.x && data[i].screenpos.y <= data[tl].screenpos.y)
tl = i;
if (data[i].screenpos.x >= data[br].screenpos.x && data[i].screenpos.y >= data[br].screenpos.y)
br = i;
}
*tlIndex = tl;
*brIndex = br;
// OK, now let's look at data to detect rectangles. There are a few possibilities
// but we focus on Darkstalkers for now.
if (data[0].screenpos.x == data[1].screenpos.x &&
data[0].screenpos.y == data[2].screenpos.y &&
data[2].screenpos.x == data[3].screenpos.x &&
data[1].screenpos.y == data[3].screenpos.y &&
data[1].screenpos.y > data[0].screenpos.y &&
data[2].screenpos.x > data[0].screenpos.x) {
// Okay, this is in the shape of a triangle, but what about rotation/texture?
if (!gstate.isTextureMapEnabled())
data[1].screenpos.y == data[3].screenpos.y) {
// Okay, this is in the shape of a rectangle, but what about texture?
if (!state.enableTextures)
return true;
if (data[0].texturecoords.x == data[1].texturecoords.x &&
data[0].texturecoords.y == data[2].texturecoords.y &&
data[2].texturecoords.x == data[3].texturecoords.x &&
data[1].texturecoords.y == data[3].texturecoords.y &&
data[1].texturecoords.y > data[0].texturecoords.y &&
data[2].texturecoords.x > data[0].texturecoords.x) {
data[1].texturecoords.y == data[3].texturecoords.y) {
// It's a rectangle!
return true;
}
@ -350,19 +359,15 @@ bool DetectRectangleFromThroughModeStrip(const VertexData data[4]) {
if (data[0].screenpos.x == data[2].screenpos.x &&
data[0].screenpos.y == data[1].screenpos.y &&
data[1].screenpos.x == data[3].screenpos.x &&
data[2].screenpos.y == data[3].screenpos.y &&
data[2].screenpos.y > data[0].screenpos.y &&
data[1].screenpos.x > data[0].screenpos.x) {
// Okay, this is in the shape of a triangle, but what about rotation/texture?
if (!gstate.isTextureMapEnabled())
data[2].screenpos.y == data[3].screenpos.y) {
// Okay, this is in the shape of a rectangle, but what about texture?
if (!state.enableTextures)
return true;
if (data[0].texturecoords.x == data[2].texturecoords.x &&
data[0].texturecoords.y == data[1].texturecoords.y &&
data[1].texturecoords.x == data[3].texturecoords.x &&
data[2].texturecoords.y == data[3].texturecoords.y &&
data[2].texturecoords.y > data[0].texturecoords.y &&
data[1].texturecoords.x > data[0].texturecoords.x) {
data[2].texturecoords.y == data[3].texturecoords.y) {
// It's a rectangle!
return true;
}
@ -371,13 +376,25 @@ bool DetectRectangleFromThroughModeStrip(const VertexData data[4]) {
return false;
}
bool DetectRectangleFromThroughModeFan(const VertexData *data, int c, int *tlIndex, int *brIndex) {
bool DetectRectangleFromFan(const RasterizerState &state, const VertexData *data, int c, int *tlIndex, int *brIndex) {
// Color and Z must be flat.
for (int i = 1; i < c; ++i) {
if (!(data[i].color0 == data[0].color0))
return false;
if (!(data[i].screenpos.z == data[0].screenpos.z))
return false;
if (!(data[i].screenpos.z == data[0].screenpos.z)) {
// Sometimes, we don't actually care about z.
if (state.pixelID.depthWrite || state.pixelID.DepthTestFunc() != GE_COMP_ALWAYS)
return false;
}
if (!state.throughMode) {
if (!state.throughMode && !(data[i].color1 == data[0].color1))
return false;
// Do we have to think about perspective correction or slope mip level?
if (state.enableTextures && data[i].clippos.w != data[0].clippos.w)
return false;
if (state.pixelID.applyFog && data[i].fogdepth != data[0].fogdepth)
return false;
}
}
// Check for the common case: a single TL-TR-BR-BL.
@ -395,7 +412,7 @@ bool DetectRectangleFromThroughModeFan(const VertexData *data, int c, int *tlInd
}
// Do we need to think about rotation?
if (!gstate.isTextureMapEnabled())
if (!state.enableTextures)
return true;
const auto &textl = data[*tlIndex].texturecoords, &textr = data[*tlIndex ^ 1].texturecoords;
@ -411,13 +428,16 @@ bool DetectRectangleFromThroughModeFan(const VertexData *data, int c, int *tlInd
return false;
}
bool DetectRectangleSlices(const VertexData data[4]) {
bool DetectRectangleThroughModeSlices(const RasterizerState &state, const VertexData data[4]) {
// Color and Z must be flat.
for (int i = 1; i < 4; ++i) {
if (!(data[i].color0 == data[0].color0))
return false;
if (!(data[i].screenpos.z == data[0].screenpos.z))
return false;
if (!(data[i].screenpos.z == data[0].screenpos.z)) {
// Sometimes, we don't actually care about z.
if (state.pixelID.depthWrite || state.pixelID.DepthTestFunc() != GE_COMP_ALWAYS)
return false;
}
}
// Games very commonly use vertical strips of rectangles. Detect and combine.
@ -425,7 +445,7 @@ bool DetectRectangleSlices(const VertexData data[4]) {
const auto &tl2 = data[2].screenpos, &br2 = data[3].screenpos;
if (tl1.y == tl2.y && br1.y == br2.y && br1.y > tl1.y) {
if (br1.x == tl2.x && tl1.x < br1.x && tl2.x < br2.x) {
if (!gstate.isTextureMapEnabled() || gstate.isModeClear())
if (!state.enableTextures)
return true;
const auto &textl1 = data[0].texturecoords, &texbr1 = data[1].texturecoords;

View file

@ -20,7 +20,7 @@ namespace Rasterizer {
bool RectangleFastPath(const VertexData &v0, const VertexData &v1, BinManager &binner);
void DrawSprite(const VertexData &v0, const VertexData &v1, const BinCoords &range, const RasterizerState &state);
bool DetectRectangleFromThroughModeStrip(const VertexData data[4]);
bool DetectRectangleFromThroughModeFan(const VertexData *data, int c, int *tlIndex, int *brIndex);
bool DetectRectangleSlices(const VertexData data[4]);
bool DetectRectangleFromStrip(const RasterizerState &state, const VertexData data[4], int *tlIndex, int *brIndex);
bool DetectRectangleFromFan(const RasterizerState &state, const VertexData *data, int c, int *tlIndex, int *brIndex);
bool DetectRectangleThroughModeSlices(const RasterizerState &state, const VertexData data[4]);
}

View file

@ -593,7 +593,7 @@ void TransformUnit::SubmitPrimitive(void* vertices, void* indices, GEPrimitiveTy
}
if (data_index == 4 && gstate.isModeThrough() && cullType == CullType::OFF) {
if (Rasterizer::DetectRectangleSlices(data)) {
if (Rasterizer::DetectRectangleThroughModeSlices(binner_->State(), data)) {
data[1] = data[3];
data_index = 2;
}
@ -649,7 +649,7 @@ void TransformUnit::SubmitPrimitive(void* vertices, void* indices, GEPrimitiveTy
// If index count == 4, check if we can convert to a rectangle.
// This is for Darkstalkers (and should speed up many 2D games).
if (data_index == 0 && vertex_count == 4 && gstate.isModeThrough() && cullType == CullType::OFF) {
if (data_index == 0 && vertex_count == 4 && cullType == CullType::OFF) {
for (int vtx = 0; vtx < 4; ++vtx) {
if (indices) {
vreader.Goto(ConvertIndex(vtx) - index_lower_bound);
@ -661,8 +661,9 @@ void TransformUnit::SubmitPrimitive(void* vertices, void* indices, GEPrimitiveTy
}
// If a strip is effectively a rectangle, draw it as such!
if (!outside_range_flag && Rasterizer::DetectRectangleFromThroughModeStrip(data)) {
Clipper::ProcessRect(data[0], data[3], *binner_);
int tl = -1, br = -1;
if (!outside_range_flag && Rasterizer::DetectRectangleFromStrip(binner_->State(), data, &tl, &br)) {
Clipper::ProcessRect(data[tl], data[br], *binner_);
break;
}
}
@ -726,7 +727,7 @@ void TransformUnit::SubmitPrimitive(void* vertices, void* indices, GEPrimitiveTy
break;
}
if (data_index == 1 && vertex_count == 4 && gstate.isModeThrough() && cullType == CullType::OFF) {
if (data_index == 1 && vertex_count == 4 && cullType == CullType::OFF) {
for (int vtx = start_vtx; vtx < vertex_count; ++vtx) {
if (indices) {
vreader.Goto(ConvertIndex(vtx) - index_lower_bound);
@ -737,7 +738,7 @@ void TransformUnit::SubmitPrimitive(void* vertices, void* indices, GEPrimitiveTy
}
int tl = -1, br = -1;
if (!outside_range_flag && Rasterizer::DetectRectangleFromThroughModeFan(data, vertex_count, &tl, &br)) {
if (!outside_range_flag && Rasterizer::DetectRectangleFromFan(binner_->State(), data, vertex_count, &tl, &br)) {
Clipper::ProcessRect(data[tl], data[br], *binner_);
break;
}