diff --git a/GPU/Software/Clipper.cpp b/GPU/Software/Clipper.cpp index ee1fcb8f63..2b6386105c 100644 --- a/GPU/Software/Clipper.cpp +++ b/GPU/Software/Clipper.cpp @@ -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 diff --git a/GPU/Software/Clipper.h b/GPU/Software/Clipper.h index f55d56510d..c79fe7d730 100644 --- a/GPU/Software/Clipper.h +++ b/GPU/Software/Clipper.h @@ -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); } diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index c6c3d53683..616f8d797d 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -765,9 +765,9 @@ void DrawTriangleSlice( if (AnyMask(mask)) { Vec4 wsum_recip = EdgeRecip(w0, w1, w2); + // Color interpolation is not perspective corrected on the PSP. Vec4 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::AssignToAll(v2.screenpos.z); } else { - // TODO: Is that the correct way to interpolate? + // Z is interpolated pretty much directly. Vec4 zfloats = w0.Cast() * v0.screenpos.z + w1.Cast() * v1.screenpos.z + w2.Cast() * v2.screenpos.z; z = (zfloats * wsum_recip).Cast(); } diff --git a/GPU/Software/RasterizerRectangle.cpp b/GPU/Software/RasterizerRectangle.cpp index 165ee3f40c..336d62474e 100644 --- a/GPU/Software/RasterizerRectangle.cpp +++ b/GPU/Software/RasterizerRectangle.cpp @@ -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; diff --git a/GPU/Software/RasterizerRectangle.h b/GPU/Software/RasterizerRectangle.h index d18b7e6bd0..4407b10a01 100644 --- a/GPU/Software/RasterizerRectangle.h +++ b/GPU/Software/RasterizerRectangle.h @@ -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]); } diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index 3c5e554b87..a548bdb6b5 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -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; }