|
|
|
@@ -33,6 +33,7 @@
|
|
|
|
|
#include "GPU/Common/TransformCommon.h"
|
|
|
|
|
#include "GPU/Common/VertexDecoderCommon.h"
|
|
|
|
|
#include "GPU/Common/DrawEngineCommon.h"
|
|
|
|
|
#include "GPU/Software/Clipper.h"
|
|
|
|
|
|
|
|
|
|
// This is the software transform pipeline, which is necessary for supporting RECT
|
|
|
|
|
// primitives correctly without geometry shaders, and may be easier to use for
|
|
|
|
@@ -112,8 +113,6 @@ static bool IsReallyAClear(const TransformedVertex *transformed, int numVerts, f
|
|
|
|
|
|
|
|
|
|
// At the end, this calls ProjectClipAndExpand which will expand rectangles as necessary, or apply culling.
|
|
|
|
|
SoftwareTransformAction SoftwareTransform::Transform(SoftwareTransformParams ¶ms, int prim, u32 vertType, const DecVtxFormat &decVtxFormat, int &numDecodedVerts, int vertsSize, int vertexCount, u16 *&inds, int indsSize, SoftwareTransformResult *result) {
|
|
|
|
|
u8 *decoded = params.decoded;
|
|
|
|
|
TransformedVertex *transformed = params.transformed;
|
|
|
|
|
bool throughmode = (vertType & GE_VTYPE_THROUGH_MASK) != 0;
|
|
|
|
|
bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled();
|
|
|
|
|
|
|
|
|
@@ -143,7 +142,8 @@ SoftwareTransformAction SoftwareTransform::Transform(SoftwareTransformParams &pa
|
|
|
|
|
fog_slope = std::signbit(fog_slope) ? -largeFogValue : largeFogValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VertexReader reader(decoded, decVtxFormat, vertType);
|
|
|
|
|
TransformedVertex *transformed = params.transformed;
|
|
|
|
|
VertexReader reader(params.decoded, decVtxFormat, vertType);
|
|
|
|
|
if (throughmode) {
|
|
|
|
|
const u32 materialAmbientRGBA = gstate.getMaterialAmbientRGBA();
|
|
|
|
|
const bool hasColor = reader.hasColor0();
|
|
|
|
@@ -151,7 +151,6 @@ SoftwareTransformAction SoftwareTransform::Transform(SoftwareTransformParams &pa
|
|
|
|
|
for (int index = 0; index < numDecodedVerts; index++) {
|
|
|
|
|
// Do not touch the coordinates or the colors. No lighting.
|
|
|
|
|
reader.Goto(index);
|
|
|
|
|
// TODO: Write to a flexible buffer, we don't always need all four components.
|
|
|
|
|
TransformedVertex &vert = transformed[index];
|
|
|
|
|
reader.ReadPosThrough(vert.pos);
|
|
|
|
|
vert.pos_w = 1.0f;
|
|
|
|
@@ -378,6 +377,7 @@ SoftwareTransformAction SoftwareTransform::Transform(SoftwareTransformParams &pa
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: This doesn't seem to be a very good check, but let's leave it for now.
|
|
|
|
|
|
|
|
|
|
// Detect full screen "clears" that might not be so obvious, to set the safe size if possible.
|
|
|
|
|
if (!result->setSafeSize && prim == GE_PRIM_RECTANGLES && numDecodedVerts == 2 && throughmode) {
|
|
|
|
|
bool clearingColor = gstate.isModeClear() && (gstate.isClearModeColorMask() || gstate.isClearModeAlphaMask());
|
|
|
|
@@ -428,6 +428,142 @@ void SoftwareTransform::ProjectVertices(TransformedVertex *transformed, int vert
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper to check if a vertex is inside the near plane (z >= -w)
|
|
|
|
|
// We add a tiny epsilon to prevent floating-point precision issues at the exact boundary
|
|
|
|
|
inline bool IsInsideNearPlane(const TransformedVertex& v) {
|
|
|
|
|
return v.z >= -v.pos_w;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool IsInsideFarPlane(const TransformedVertex& v) {
|
|
|
|
|
return v.z <= v.pos_w;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generated by Gemini, and adapted to fit.
|
|
|
|
|
void ClipTrianglesAgainstNearPlane(
|
|
|
|
|
TransformedVertex *transformed, int &transformedCount, int maxTransformed,
|
|
|
|
|
u16 *indicesIn, int numIndicesIn,
|
|
|
|
|
u16 *indicesOut, int &numIndicesOut, int maxIndicesOut
|
|
|
|
|
) {
|
|
|
|
|
int clipCount = 0;
|
|
|
|
|
// Process one triangle (3 indices) at a time
|
|
|
|
|
for (size_t i = 0; i < numIndicesIn; i += 3) {
|
|
|
|
|
u16 idx0 = indicesIn[i];
|
|
|
|
|
u16 idx1 = indicesIn[i + 1];
|
|
|
|
|
u16 idx2 = indicesIn[i + 2];
|
|
|
|
|
|
|
|
|
|
TransformedVertex& v0 = transformed[idx0];
|
|
|
|
|
TransformedVertex& v1 = transformed[idx1];
|
|
|
|
|
TransformedVertex& v2 = transformed[idx2];
|
|
|
|
|
|
|
|
|
|
bool in0 = IsInsideNearPlane(v0);
|
|
|
|
|
bool in1 = IsInsideNearPlane(v1);
|
|
|
|
|
bool in2 = IsInsideNearPlane(v2);
|
|
|
|
|
|
|
|
|
|
bool inFar0 = IsInsideFarPlane(v0);
|
|
|
|
|
bool inFar1 = IsInsideFarPlane(v1);
|
|
|
|
|
bool inFar2 = IsInsideFarPlane(v2);
|
|
|
|
|
|
|
|
|
|
int insideCount = (in0 ? 1 : 0) + (in1 ? 1 : 0) + (in2 ? 1 : 0);
|
|
|
|
|
int insideFarCount = (inFar0 ? 1 : 0) + (inFar1 ? 1 : 0) + (inFar2 ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
// Case 1: Entirely visible
|
|
|
|
|
if (insideCount == 3) {
|
|
|
|
|
indicesOut[numIndicesOut++] = idx0;
|
|
|
|
|
indicesOut[numIndicesOut++] = idx1;
|
|
|
|
|
indicesOut[numIndicesOut++] = idx2;
|
|
|
|
|
}
|
|
|
|
|
// Case 2: Entirely clipped / behind near plane
|
|
|
|
|
else if (insideCount == 0) {
|
|
|
|
|
// Cull, no clipping needed.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// Case 3: Entirely beyond far plane
|
|
|
|
|
else if (insideFarCount == 0) {
|
|
|
|
|
// All are beyond the far plane. Cull.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// Case 3: Partially clipped
|
|
|
|
|
else {
|
|
|
|
|
clipCount++;
|
|
|
|
|
|
|
|
|
|
// We need to organize vertices cleanly to calculate intersections.
|
|
|
|
|
// We will create a local polygon array of the inside/outside states.
|
|
|
|
|
u16 triIdx[3] = {idx0, idx1, idx2};
|
|
|
|
|
bool triIn[3] = {in0, in1, in2};
|
|
|
|
|
|
|
|
|
|
// Output generated vertex indices for this clipped polygon
|
|
|
|
|
u16 polyIndices[4];
|
|
|
|
|
int polyLength = 0;
|
|
|
|
|
|
|
|
|
|
for (int j = 0; j < 3; ++j) {
|
|
|
|
|
int next = (j + 1) % 3;
|
|
|
|
|
|
|
|
|
|
u16 currIdx = triIdx[j];
|
|
|
|
|
u16 nextIdx = triIdx[next];
|
|
|
|
|
|
|
|
|
|
// If current vertex is inside, it stays a part of the output polygon
|
|
|
|
|
if (triIn[j]) {
|
|
|
|
|
polyIndices[polyLength++] = currIdx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we cross the clipping plane line (inside->outside or outside->inside)
|
|
|
|
|
if (triIn[j] != triIn[next]) {
|
|
|
|
|
/* const */ TransformedVertex& a = transformed[currIdx];
|
|
|
|
|
/* const */ TransformedVertex& b = transformed[nextIdx];
|
|
|
|
|
|
|
|
|
|
// Find interpolation factor 't' where: z_interpolated = -w_interpolated
|
|
|
|
|
// Lerp formulation:
|
|
|
|
|
// z = a.z + t*(b.z - a.z)
|
|
|
|
|
// w = a.w + t*(b.w - a.w)
|
|
|
|
|
// Set z = -w => a.z + t*(b.z - a.z) = -(a.pos_w + t*(b.pos_w - a.pos_w))
|
|
|
|
|
// Solve for t:
|
|
|
|
|
float denominator = (b.z - a.z) + (b.pos_w - a.pos_w);
|
|
|
|
|
float t = 0.0f;
|
|
|
|
|
if (fabsf(denominator) > 0.000001f) {
|
|
|
|
|
t = (-a.z - a.pos_w) / denominator;
|
|
|
|
|
}
|
|
|
|
|
// Clamp safely due to float precision
|
|
|
|
|
if (t < 0.0f) t = 0.0f;
|
|
|
|
|
if (t > 1.0f) t = 1.0f;
|
|
|
|
|
|
|
|
|
|
// Generate new vertex at the intersection point
|
|
|
|
|
TransformedVertex newVertex;
|
|
|
|
|
TransformedVertex::Lerp(&newVertex, const_cast<TransformedVertex&>(a), const_cast<TransformedVertex&>(b), t);
|
|
|
|
|
|
|
|
|
|
// Force exact intersection to eliminate precision creeping down the pipeline
|
|
|
|
|
newVertex.z = -newVertex.pos_w;
|
|
|
|
|
newVertex.color0_32 = 0xFFFF00FF;
|
|
|
|
|
// a.color0_32 = 0xFFFF00FF;
|
|
|
|
|
// b.color0_32 = 0xFFFF00FF;
|
|
|
|
|
|
|
|
|
|
// Append to global vertex buffer
|
|
|
|
|
transformed[transformedCount++] = newVertex;
|
|
|
|
|
u16 newIdx = static_cast<u16>(transformedCount - 1);
|
|
|
|
|
polyIndices[polyLength++] = newIdx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Triangulate the resulting polygon array (will be either 3 or 4 vertices)
|
|
|
|
|
if (polyLength == 3) {
|
|
|
|
|
indicesOut[numIndicesOut++] = polyIndices[0];
|
|
|
|
|
indicesOut[numIndicesOut++] = polyIndices[1];
|
|
|
|
|
indicesOut[numIndicesOut++] = polyIndices[2];
|
|
|
|
|
} else if (polyLength == 4) {
|
|
|
|
|
// Triangle 1
|
|
|
|
|
indicesOut[numIndicesOut++] = polyIndices[0];
|
|
|
|
|
indicesOut[numIndicesOut++] = polyIndices[1];
|
|
|
|
|
indicesOut[numIndicesOut++] = polyIndices[2];
|
|
|
|
|
// Triangle 2
|
|
|
|
|
indicesOut[numIndicesOut++] = polyIndices[0];
|
|
|
|
|
indicesOut[numIndicesOut++] = polyIndices[2];
|
|
|
|
|
indicesOut[numIndicesOut++] = polyIndices[3];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gpuStats.perFrame.numSoftClippedTriangles++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SoftwareTransformAction SoftwareTransform::ProjectClipAndExpand(SoftwareTransformParams ¶ms, int prim, int vertexCount, u32 vertType, u16 *&inds, int indsSize, int &numDecodedVerts, int vertsSize, SoftwareTransformResult *result) {
|
|
|
|
|
TransformedVertex *transformed = params.transformed;
|
|
|
|
|
TransformedVertex *transformedExpanded = params.transformedExpanded;
|
|
|
|
@@ -440,6 +576,7 @@ SoftwareTransformAction SoftwareTransform::ProjectClipAndExpand(SoftwareTransfor
|
|
|
|
|
FramebufferManagerCommon *fbman = params.fbman;
|
|
|
|
|
bool useBufferedRendering = fbman->UseBufferedRendering();
|
|
|
|
|
|
|
|
|
|
// NOTE: ExpandRectanges/lines/etc should do clipping while they're at it.
|
|
|
|
|
if (prim == GE_PRIM_RECTANGLES) {
|
|
|
|
|
// TODO: We should cull rectangles outzide -W<Z<W here if *both* points are outside in the same direction,
|
|
|
|
|
// like we do with triangles.
|
|
|
|
@@ -538,29 +675,7 @@ SoftwareTransformAction SoftwareTransform::ProjectClipAndExpand(SoftwareTransfor
|
|
|
|
|
// Culling and clipping needs to be done here, it doesn't happen in the shader in the case of software transform.
|
|
|
|
|
// However, fast culling should already have taken care of the Z<-W and Z>W culling, but we check for it on a per-triangle
|
|
|
|
|
// basis here anyway.
|
|
|
|
|
|
|
|
|
|
const u16 *indsIn = (const u16 *)inds;
|
|
|
|
|
u16 *newInds = inds + vertexCount;
|
|
|
|
|
u16 *indsOut = newInds;
|
|
|
|
|
|
|
|
|
|
std::vector<int> outsideZ;
|
|
|
|
|
outsideZ.resize(vertexCount);
|
|
|
|
|
|
|
|
|
|
// First, check inside/outside directions for each index.
|
|
|
|
|
// We are still in clip space here, so we can cull aggressively in Z.
|
|
|
|
|
// TODO: This is so cheap now that we can probably avoid the buffer and just do the work below.
|
|
|
|
|
for (int i = 0; i < vertexCount; ++i) {
|
|
|
|
|
float z = transformed[indsIn[i]].z;
|
|
|
|
|
float w = transformed[indsIn[i]].pos_w;
|
|
|
|
|
if (z > w) {
|
|
|
|
|
outsideZ[i] = 1;
|
|
|
|
|
} else if (z < -w) {
|
|
|
|
|
outsideZ[i] = -1;
|
|
|
|
|
} else {
|
|
|
|
|
outsideZ[i] = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u16 *origInds = inds;
|
|
|
|
|
|
|
|
|
|
// TODO: We should either merge the two loops, or avoid the second loop if no culling is needed.
|
|
|
|
@@ -568,20 +683,34 @@ SoftwareTransformAction SoftwareTransform::ProjectClipAndExpand(SoftwareTransfor
|
|
|
|
|
// Now, for each triangle, throw away the indices if:
|
|
|
|
|
// - Depth clip/clamp on, and ALL verts are outside *in the same direction*.
|
|
|
|
|
// - Depth clip/clamp off, and ANY vert is outside.
|
|
|
|
|
|
|
|
|
|
u16 *newInds = inds + vertexCount;
|
|
|
|
|
u16 *indsOut = newInds;
|
|
|
|
|
|
|
|
|
|
if (gstate.isDepthClipEnabled()) {
|
|
|
|
|
numTrans = 0;
|
|
|
|
|
for (int i = 0; i < vertexCount - 2; i += 3) {
|
|
|
|
|
if (outsideZ[i + 0] != 0 && outsideZ[i + 0] == outsideZ[i + 1] && outsideZ[i + 0] == outsideZ[i + 2]) {
|
|
|
|
|
// All outside, and all the same direction. Nuke this triangle.
|
|
|
|
|
continue;
|
|
|
|
|
const u16 *indsIn = (const u16 *)inds;
|
|
|
|
|
int newIndexCount = 0;
|
|
|
|
|
ClipTrianglesAgainstNearPlane(transformed, numDecodedVerts, 65536, inds, vertexCount, indsOut, newIndexCount, 65336);
|
|
|
|
|
numTrans = newIndexCount;
|
|
|
|
|
} else {
|
|
|
|
|
std::vector<int> outsideZ;
|
|
|
|
|
outsideZ.resize(vertexCount);
|
|
|
|
|
|
|
|
|
|
// First, check inside/outside directions for each index.
|
|
|
|
|
// We are still in clip space here, so we can cull aggressively in Z.
|
|
|
|
|
// TODO: This is so cheap now that we can probably avoid the buffer and just do the work below.
|
|
|
|
|
for (int i = 0; i < vertexCount; ++i) {
|
|
|
|
|
float z = transformed[indsIn[i]].z;
|
|
|
|
|
float w = transformed[indsIn[i]].pos_w;
|
|
|
|
|
if (z > w) {
|
|
|
|
|
outsideZ[i] = 1;
|
|
|
|
|
} else if (z < -w) {
|
|
|
|
|
outsideZ[i] = -1;
|
|
|
|
|
} else {
|
|
|
|
|
outsideZ[i] = 0;
|
|
|
|
|
}
|
|
|
|
|
memcpy(indsOut, indsIn + i, 3 * sizeof(uint16_t));
|
|
|
|
|
indsOut += 3;
|
|
|
|
|
numTrans += 3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inds = newInds;
|
|
|
|
|
} else if (prim == GE_PRIM_TRIANGLES) {
|
|
|
|
|
numTrans = 0;
|
|
|
|
|
for (int i = 0; i < vertexCount - 2; i += 3) {
|
|
|
|
|
if (outsideZ[i + 0] != 0 || outsideZ[i + 1] != 0 || outsideZ[i + 2] != 0) {
|
|
|
|
@@ -593,10 +722,10 @@ SoftwareTransformAction SoftwareTransform::ProjectClipAndExpand(SoftwareTransfor
|
|
|
|
|
indsOut += 3;
|
|
|
|
|
numTrans += 3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inds = newInds;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inds = newInds;
|
|
|
|
|
|
|
|
|
|
// Now that we're done culling and generating clipped vertices if needed (not yet implemented), we go ahead and project.
|
|
|
|
|
ProjectVertices(transformed, numDecodedVerts);
|
|
|
|
|
|
|
|
|
@@ -605,33 +734,44 @@ SoftwareTransformAction SoftwareTransform::ProjectClipAndExpand(SoftwareTransfor
|
|
|
|
|
// If not we technically need to cut it in two parts to clamp accurately.
|
|
|
|
|
// However, in most cases that matter (such as missing skies, etc), this is fine.
|
|
|
|
|
const int maxZInt = gstate.getDepthRangeMax();
|
|
|
|
|
float maxZ = maxZInt / 65535.0f;
|
|
|
|
|
// float maxZ = maxZInt / 65535.0f;
|
|
|
|
|
const int minZInt = gstate.getDepthRangeMin();
|
|
|
|
|
float minZ = minZInt / 65535.0f;
|
|
|
|
|
// float minZ = minZInt / 65535.0f;
|
|
|
|
|
// We only need to clamp if minZ and maxZ aren't at the extreme in each direction, as otherwise
|
|
|
|
|
// minZ and maxZ will cut things off.
|
|
|
|
|
if (!gstate_c.Use(GPU_USE_DEPTH_CLAMP) && gstate.isDepthClipEnabled() && (maxZInt == 0 || maxZInt == 65535)) {
|
|
|
|
|
for (int i = 0; i < vertexCount - 2; i += 3) {
|
|
|
|
|
|
|
|
|
|
// If gstate_c.Use(GPU_USE_DEPTH_CLAMP), we can theoretically skip this. However, to keep behavior the same regardless of the flag, we won't.
|
|
|
|
|
if (gstate.isDepthClipEnabled() && (maxZInt == 0 || maxZInt == 65535)) {
|
|
|
|
|
for (int i = 0; i < numTrans - 2; i += 3) {
|
|
|
|
|
TransformedVertex &v0 = transformed[indsIn[i]];
|
|
|
|
|
TransformedVertex &v1 = transformed[indsIn[i + 1]];
|
|
|
|
|
TransformedVertex &v2 = transformed[indsIn[i + 2]];
|
|
|
|
|
if (v0.x < 0.0f || v0.x > 4096.0f ||
|
|
|
|
|
v1.x < 0.0f || v1.y > 4096.0f ||
|
|
|
|
|
v2.x < 0.0f || v2.y > 4096.0f) {
|
|
|
|
|
// If it's outside the viewport, we might as well skip the clamping, as it won't be visible anyway.
|
|
|
|
|
// continue;
|
|
|
|
|
}
|
|
|
|
|
if (minZInt == 0) {
|
|
|
|
|
bool v0InFront = transformed[indsIn[i]].z < 0.0f;
|
|
|
|
|
bool v1InFront = transformed[indsIn[i + 1]].z < 0.0f;
|
|
|
|
|
bool v2InFront = transformed[indsIn[i + 2]].z < 0.0f;
|
|
|
|
|
bool v0InFront = v0.z < 0.0f;
|
|
|
|
|
bool v1InFront = v1.z < 0.0f;
|
|
|
|
|
bool v2InFront = v2.z < 0.0f;
|
|
|
|
|
if (v0InFront && v1InFront && v2InFront) {
|
|
|
|
|
transformed[indsIn[i]].z = 0.0f;
|
|
|
|
|
transformed[indsIn[i + 1]].z = 0.0f;
|
|
|
|
|
transformed[indsIn[i + 2]].z = 0.0f;
|
|
|
|
|
v0.z = 0.0f;
|
|
|
|
|
v1.z = 0.0f;
|
|
|
|
|
v2.z = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (maxZInt == 65535) {
|
|
|
|
|
bool v0Beyond = transformed[indsIn[i]].z >= 65535.0f;
|
|
|
|
|
bool v1Beyond = transformed[indsIn[i + 1]].z >= 65535.0f;
|
|
|
|
|
bool v2Beyond = transformed[indsIn[i + 2]].z >= 65535.0f;
|
|
|
|
|
bool v0Beyond = v0.z >= 65535.0f;
|
|
|
|
|
bool v1Beyond = v1.z >= 65535.0f;
|
|
|
|
|
bool v2Beyond = v2.z >= 65535.0f;
|
|
|
|
|
|
|
|
|
|
if (v0Beyond && v1Beyond && v2Beyond) {
|
|
|
|
|
transformed[indsIn[i]].z = 65535.0f;
|
|
|
|
|
transformed[indsIn[i + 1]].z = 65535.0f;
|
|
|
|
|
transformed[indsIn[i + 2]].z = 65535.0f;
|
|
|
|
|
v0.z = 65535.0f;
|
|
|
|
|
v1.z = 65535.0f;
|
|
|
|
|
v2.z = 65535.0f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|