GS/HW: Add AA1 triangle corner caps and edge extrapolation.

This commit is contained in:
TJnotJT
2026-05-20 01:02:00 +02:00
committed by lightningterror
parent 30440099c2
commit a19b3747ea
6 changed files with 687 additions and 132 deletions
+166 -31
View File
@@ -1594,6 +1594,110 @@ VS_INPUT load_vertex(uint index)
return vert;
}
// Convert XY from NDC to GS pixel coordinates (i.e. 1.0 = 1 GS pixel).
float2 get_xy_unscaled(float2 xy)
{
return round(xy / VertexScale) / 16.0f;
}
// Get the XY deltas in GS pixel coordinates, using first vertex as the origin.
float2x2 get_xy_deltas_unscaled(VS_OUTPUT v0, VS_OUTPUT v1, VS_OUTPUT v2)
{
float2 xy0 = get_xy_unscaled(v0.p.xy);
float2 xy1 = get_xy_unscaled(v1.p.xy);
float2 xy2 = get_xy_unscaled(v2.p.xy);
return float2x2(xy1 - xy0, xy2 - xy0);
}
// Get the AA1 outward expand direction to the edge formed by the first two vertices.
// This is up or down for shallow (X dominant) edges, and right or left for steep (Y dominant) edges.
// Similar expansion to line AA1 except instead of expanding on both sides of the line,
// expand on on the side towards the outside of the triangle.
float2 get_aa1_triangle_expand_dir(VS_OUTPUT v0, VS_OUTPUT v1, VS_OUTPUT v2)
{
float2x2 xy_deltas = get_xy_deltas_unscaled(v0, v1, v2);
float2 line_delta = xy_deltas[0];
float2 line_opposite = xy_deltas[1];
float2 line_normal = float2(line_delta.y, -line_delta.x);
float2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? float2(0.0f, 1.0f) : float2(1.0f, 0.0f);
if ((dot(line_expand, line_normal) >= 0.0f) == (dot(line_opposite, line_normal) >= 0.0f))
{
// Expand direction point towards the interior so flip it.
line_expand = -line_expand;
}
return line_expand;
}
float2x2 get_inverse(float2x2 mat, float det)
{
return float2x2(mat[1][1], -mat[0][1], -mat[1][0], mat[0][0]) * (1 / det);
}
// Extrapolate triangle attributes from the first vertex along the given direction.
// dp_mat is derived from the input vertices, it is passed in to avoid recomputing.
void extrapolate_aa1_triangle_edge(inout VS_OUTPUT v0, VS_OUTPUT v1, VS_OUTPUT v2, float2x2 dp_mat, float2 dp)
{
// Get texture deltas
#if VS_TME
#if VS_FST
float2x2 dt = float2x2(v1.ti.zw - v0.ti.zw, v2.ti.zw - v0.ti.zw);
#else
float2x2 dt = float2x2(v1.t.xy - v0.t.xy, v2.t.xy - v0.t.xy);
#endif
#endif
// Get color delta if interpolating
#if VS_IIP
float2x4 dc = float2x4(v1.c - v0.c, v2.c - v0.c);
#endif
float2 dz = float2(v1.p.z - v0.p.z, v2.p.z - v0.p.z); // Z deltas
float2 df = float2(v1.t.z - v0.t.z, v2.t.z - v0.t.z); // Fog deltas
float2 dq = float2(v1.t.w - v0.t.w, v2.t.w - v0.t.w); // Q deltas
// To prevent unstable extrapolation, do not extrapolate if the
// minimum perpendicular length of the triangle is < 2 pixels.
float dp_det = determinant(dp_mat); // Twice signed triangle area.
float len0 = length(dp_mat[0]);
float len1 = length(dp_mat[1]);
float len2 = length(dp_mat[1] - dp_mat[0]);
float min_perp_length = abs(dp_det) / max(max(len0, len1), len2);
// Get the position -> barycentric weight matrix
float2x2 inv_dp_mat = get_inverse(dp_mat, dp_det);
float2 weights = min_perp_length < 2 ? 0 : mul(dp, inv_dp_mat);
v0.p.xy += dp * PointSize; // Extrapolate position
// Extrapolate texture coords
#if VS_TME
#if VS_FST
v0.ti.zw += mul(weights, dt);
v0.ti.xy = v0.ti.zw * TextureScale;
#else
v0.t.xy += mul(weights, dt);
v0.ti.zw = v0.t.xy / TextureScale;
v0.t.w += dot(weights, dq);
#endif
#endif
// Extrapolate and clamp color
#if VS_IIP
v0.c += mul(weights, dc);
v0.c = clamp(v0.c, 0, 255);
#endif
v0.p.z += dot(weights, dz); // Extrapolate depth
v0.t.z += dot(weights, df); // Extrapolate fog
}
VS_OUTPUT vs_main_expand(uint vid : SV_VertexID)
{
#if VS_EXPAND == VS_EXPAND_POINT
@@ -1622,13 +1726,12 @@ VS_OUTPUT vs_main_expand(uint vid : SV_VertexID)
// Use bottom minus top for delta regardless of which vertex we are expanding.
float2 line_delta = is_bottom ? (vtx.p.xy - other.p.xy) : (other.p.xy - vtx.p.xy);
float2 line_vector = normalize(line_delta);
float2 line_vector = normalize(line_delta / VertexScale);
#if VS_EXPAND == VS_EXPAND_LINE
float2 line_expand = float2(line_vector.y, -line_vector.x);
#elif VS_EXPAND == VS_EXPAND_LINE_AA1
// Expand in y direction for shallow lines and x direction for steep lines.
line_delta /= VertexScale;
float2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? float2(0.0f, 2.0f) : float2(2.0f, 0.0f);
float2 line_expand = abs(line_vector.x) >= abs(line_vector.y) ? float2(0.0f, 2.0f) : float2(2.0f, 0.0f);
#endif
float2 line_width = (line_expand * PointSize) / 2;
float2 offset = is_right ? line_width : -line_width;
@@ -1674,10 +1777,14 @@ VS_OUTPUT vs_main_expand(uint vid : SV_VertexID)
// - Vertices 3-8: First edge expanded (2 triangles).
// - Vertices 9-14: Second edge expanded (2 triangles).
// - Vertices 15-20: Third edge expanded (2 triangles).
// - Vertices 21-26: First corner cap (2 triangles).
// - Vertices 27-32: Second corner cap (2 triangles).
// - Vertices 33-38: Third corner cap (2 triangles).
uint prim_id = vid / 21;
uint prim_offset = vid - 21 * prim_id; // range: 0-20
uint prim_id = vid / 39;
uint prim_offset = vid - 39 * prim_id; // range: 0-38
bool interior = prim_offset < 3;
bool edge = 3 <= prim_offset && prim_offset < 21;
VS_OUTPUT vtx;
if (interior)
@@ -1686,7 +1793,7 @@ VS_OUTPUT vs_main_expand(uint vid : SV_VertexID)
vtx.inv_cov = 0.0f; // Full coverage
vtx.interior = 1;
}
else
else if (edge)
{
// Vertex indices for this edge. We need all 3 for determining exterior/interior.
uint prim_offset_edges = prim_offset - 3; // range: 0-17
@@ -1695,39 +1802,67 @@ VS_OUTPUT vs_main_expand(uint vid : SV_VertexID)
uint i2 = (i0 >= 1) ? i0 - 1 : i0 + 2;
uint edge_offset = prim_offset_edges - 6 * i0; // range: 0-5
// Note: order of top/bottom, inside/outside order is arbitrary,
// Note: order of top/bottom, inside/outside is arbitrary,
// as long as it assembles into two triangles forming a quad.
bool is_bottom = (2 <= edge_offset) && (edge_offset <= 4);
bool is_outside = edge_offset & 1;
vtx = vs_main(load_vertex(load_index(3 * prim_id + i0)));
VS_OUTPUT other = vs_main(load_vertex(load_index(3 * prim_id + i1)));
vtx = vs_main(load_vertex(load_index(3 * prim_id + (is_bottom ? i1 : i0))));
VS_OUTPUT other = vs_main(load_vertex(load_index(3 * prim_id + (is_bottom ? i0 : i1))));
VS_OUTPUT opposite = vs_main(load_vertex(load_index(3 * prim_id + i2)));
// Similar expansion to line AA1 except instead of expanding on both sides of
// the line we expand on on the side towards the outside of the triangle.
float2 line_delta = vtx.p.xy - other.p.xy;
float2 line_normal = normalize(float2(line_delta.y, -line_delta.x));
float2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? float2(0.0f, 2.0f) : float2(2.0f, 0.0f);
if ((dot(line_expand, line_normal) >= 0.0f) == (dot(opposite.p.xy - vtx.p.xy, line_normal) >= 0.0f))
{
// Expand direction point towards the interior so flip it.
line_expand = -line_expand;
}
float2 line_width = (line_expand * PointSize) / 2;
float2x2 pos_deltas = get_xy_deltas_unscaled(vtx, other, opposite);
if (is_bottom)
vtx = other;
if (is_outside)
{
vtx.p.xy += line_width;
vtx.inv_cov = 1.0f; // No coverage
}
else
{
vtx.inv_cov = 0.0f; // Full coverage
}
float2 expand_dir = is_outside ? get_aa1_triangle_expand_dir(vtx, other, opposite) : 0;
// Do actual extrapolation, or no-op if expand_dir == 0.
extrapolate_aa1_triangle_edge(vtx, other, opposite, pos_deltas, expand_dir);
vtx.inv_cov = is_outside ? 1.0f : 0.0f; // No coverage on outside, otherwise full.
vtx.interior = 0;
}
else // Corner cap
{
// Vertex indices for this cap. We need all 3 for determining exterior/interior.
uint prim_offset_cap = prim_offset - 21; // range: 0-8
uint i0 = prim_offset_cap / 6;
uint i1 = (i0 >= 2) ? i0 - 2 : i0 + 1;
uint i2 = (i0 >= 1) ? i0 - 1 : i0 + 2;
uint cap_offset = prim_offset_cap - 6 * i0; // range: 0-5
bool is_near_corner = cap_offset == 0 || cap_offset == 3;
bool is_far_corner = cap_offset == 2 || cap_offset == 5;
bool is_first_tri = cap_offset < 3;
vtx = vs_main(load_vertex(load_index(3 * prim_id + i0)));
VS_OUTPUT other = vs_main(load_vertex(load_index(3 * prim_id + (is_first_tri ? i1 : i2))));
VS_OUTPUT opposite = vs_main(load_vertex(load_index(3 * prim_id + (is_first_tri ? i2 : i1))));
float2x2 pos_deltas = get_xy_deltas_unscaled(vtx, other, opposite);
// Get the edge expansion directions of both incident edges.
float2 edge_expand_dir_0 = get_aa1_triangle_expand_dir(vtx, other, opposite);
float2 edge_expand_dir_1 = get_aa1_triangle_expand_dir(vtx, opposite, other);
// Check if the corner is already filled by the expanded edges.
// This happens if the expand directions are the same.
// If so we output a degenerate triangle at this corner.
bool corner_filled = all(edge_expand_dir_0 == edge_expand_dir_1);
// Nothing if corner is filled, otherwise opposite to the bisector of the corner angle.
float2 far_corner_dir = corner_filled ? 0 : -normalize((pos_deltas[0] + pos_deltas[1]) / 2);
// Determine the expand direction.
float2 expand_dir = is_near_corner ? 0 : // No extrapolation
is_far_corner ? far_corner_dir : // Opposite to the angle bisector of corner
edge_expand_dir_0; // Standard AA1 edge expansion
// Do the actual extrapolation (no-op if expand_dir == 0).
extrapolate_aa1_triangle_edge(vtx, other, opposite, pos_deltas, expand_dir);
vtx.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
vtx.interior = 0;
#if !VS_IIP
+168 -32
View File
@@ -182,6 +182,110 @@ ProcessedVertex load_vertex(uint index)
return vtx;
}
// Convert XY from NDC to GS pixel coordinates (i.e. 1.0 = 1 GS pixel).
vec2 get_xy_unscaled(vec2 xy)
{
return round(xy / VertexScale) / 16.0f;
}
// Get the XY deltas in GS pixel coordinates, using first vertex as the origin.
mat2 get_xy_deltas_unscaled(ProcessedVertex v0, ProcessedVertex v1, ProcessedVertex v2)
{
vec2 xy0 = get_xy_unscaled(v0.p.xy);
vec2 xy1 = get_xy_unscaled(v1.p.xy);
vec2 xy2 = get_xy_unscaled(v2.p.xy);
return mat2(xy1 - xy0, xy2 - xy0);
}
// Get the AA1 outward expand direction to the edge formed by the first two vertices.
// This is up or down for shallow (X dominant) edges, and right or left for steep (Y dominant) edges.
// Similar expansion to line AA1 except instead of expanding on both sides of the line,
// expand on on the side towards the outside of the triangle.
vec2 get_aa1_triangle_expand_dir(ProcessedVertex v0, ProcessedVertex v1, ProcessedVertex v2)
{
mat2 xy_deltas = get_xy_deltas_unscaled(v0, v1, v2);
vec2 line_delta = xy_deltas[0];
vec2 line_opposite = xy_deltas[1];
vec2 line_normal = vec2(line_delta.y, -line_delta.x);
vec2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? vec2(0.0f, 1.0f) : vec2(1.0f, 0.0f);
if ((dot(line_expand, line_normal) >= 0.0f) == (dot(line_opposite, line_normal) >= 0.0f))
{
// Expand direction point towards the interior so flip it.
line_expand = -line_expand;
}
return line_expand;
}
mat2 get_inverse(mat2 mat, float det)
{
return mat2(mat[1][1], -mat[0][1], -mat[1][0], mat[0][0]) * (1 / det);
}
// Extrapolate triangle attributes from the first vertex along the given direction.
// dp_mat is derived from the input vertices, it is passed in to avoid recomputing.
void extrapolate_aa1_triangle_edge(inout ProcessedVertex v0, ProcessedVertex v1, ProcessedVertex v2, mat2 dp_mat, vec2 dp)
{
// Get texture deltas
#if VS_TME
#if VS_FST
mat2 dt = mat2(v1.t_int.zw - v0.t_int.zw, v2.t_int.zw - v0.t_int.zw);
#else
mat2 dt = mat2(v1.t_float.xy - v0.t_float.xy, v2.t_float.xy - v0.t_float.xy);
#endif
#endif
// Get color delta if interpolating
#if VS_IIP
mat2x4 dc = mat2x4(v1.c - v0.c, v2.c - v0.c);
#endif
vec2 dz = vec2(v1.p.z - v0.p.z, v2.p.z - v0.p.z); // Z deltas
vec2 df = vec2(v1.t_float.z - v0.t_float.z, v2.t_float.z - v0.t_float.z); // Fog deltas
vec2 dq = vec2(v1.t_float.w - v0.t_float.w, v2.t_float.w - v0.t_float.w); // Q deltas
// To prevent unstable extrapolation, do not extrapolate if the
// minimum perpendicular length of the triangle is < 2 pixels.
float dp_det = determinant(dp_mat); // Twice signed triangle area.
float len0 = length(dp_mat[0]);
float len1 = length(dp_mat[1]);
float len2 = length(dp_mat[1] - dp_mat[0]);
float min_perp_length = abs(dp_det) / max(max(len0, len1), len2);
// Get the position -> barycentric weight matrix
mat2 inv_dp_mat = get_inverse(dp_mat, dp_det);
vec2 weights = min_perp_length < 2 ? vec2(0) : inv_dp_mat * dp;
v0.p.xy += dp * PointSize; // Extrapolate position
// Extrapolate texture coords
#if VS_TME
#if VS_FST
v0.t_int.zw += dt * weights;
v0.t_int.xy = v0.t_int.zw * TextureScale;
#else
v0.t_float.xy += dt * weights;
v0.t_int.zw = v0.t_float.xy / TextureScale;
v0.t_float.w += dot(dq, weights);
#endif
#endif
// Extrapolate and clamp color
#if VS_IIP
v0.c += dc * weights;
v0.c = clamp(v0.c, vec4(0), vec4(255));
#endif
v0.p.z += dot(dz, weights); // Extrapolate depth
v0.t_float.z += dot(df, weights); // Extrapolate fog
}
void main()
{
ProcessedVertex vtx;
@@ -206,13 +310,12 @@ void main()
// Use bottom minus top for delta regardless of which vertex we are expanding.
vec2 line_delta = is_bottom ? (vtx.p.xy - other.p.xy) : (other.p.xy - vtx.p.xy);
vec2 line_vector = normalize(line_delta);
vec2 line_vector = normalize(line_delta / VertexScale);
#if VS_EXPAND == VS_EXPAND_LINE
vec2 line_expand = vec2(line_vector.y, -line_vector.x);
#elif VS_EXPAND == VS_EXPAND_LINE_AA1
// Expand in y direction for shallow lines and x direction for steep lines.
line_delta /= VertexScale;
vec2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? vec2(0.0f, 2.0f) : vec2(2.0f, 0.0f);
vec2 line_expand = abs(line_vector.x) >= abs(line_vector.y) ? vec2(0.0f, 2.0f) : vec2(2.0f, 0.0f);
#endif
vec2 line_width = (line_expand * PointSize) / 2;
vec2 offset = is_right ? line_width : -line_width;
@@ -254,10 +357,14 @@ void main()
// - Vertices 3-8: First edge expanded (2 triangles).
// - Vertices 9-14: Second edge expanded (2 triangles).
// - Vertices 15-20: Third edge expanded (2 triangles).
// - Vertices 21-26: First corner cap (2 triangles).
// - Vertices 27-32: Second corner cap (2 triangles).
// - Vertices 33-38: Third corner cap (2 triangles).
uint prim_id = vid / 21;
uint prim_offset = vid - 21 * prim_id; // range: 0-20
uint prim_id = vid / 39;
uint prim_offset = vid - 39 * prim_id; // range: 0-38
bool interior = prim_offset < 3;
bool edge = 3 <= prim_offset && prim_offset < 21;
if (interior)
{
@@ -265,7 +372,7 @@ void main()
VSout.inv_cov = 0.0f; // Full coverage
VSout.interior = 1;
}
else
else if (edge)
{
// Vertex indices for this edge. We need all 3 for determining exterior/interior.
uint prim_offset_edges = prim_offset - 3; // range: 0-17
@@ -274,39 +381,68 @@ void main()
uint i2 = (i0 >= 1) ? i0 - 1 : i0 + 2;
uint edge_offset = prim_offset_edges - 6 * i0; // range: 0-5
// Note: order of top/bottom, inside/outside order is arbitrary,
// Note: order of top/bottom, inside/outside is arbitrary,
// as long as it assembles into two triangles forming a quad.
bool is_bottom = (2 <= edge_offset) && (edge_offset <= 4);
bool is_outside = (edge_offset & 1) != 0;
bool is_outside = (edge_offset & 1u) != 0;
vtx = load_vertex(load_index(3 * prim_id + i0));
ProcessedVertex other = load_vertex(load_index(3 * prim_id + i1));
vtx = load_vertex(load_index(3 * prim_id + (is_bottom ? i1 : i0)));
ProcessedVertex other = load_vertex(load_index(3 * prim_id + (is_bottom ? i0 : i1)));
ProcessedVertex opposite = load_vertex(load_index(3 * prim_id + i2));
// Similar expansion to line AA1 except instead of expanding on both sides of
// the line we expand on on the side towards the outside of the triangle.
vec2 line_delta = vtx.p.xy - other.p.xy;
vec2 line_normal = normalize(vec2(line_delta.y, -line_delta.x));
vec2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? vec2(0.0f, 2.0f) : vec2(2.0f, 0.0f);
if ((dot(line_expand, line_normal) >= 0.0f) == (dot(opposite.p.xy - vtx.p.xy, line_normal) >= 0.0f))
{
// Expand direction point towards the interior so flip it.
line_expand = -line_expand;
}
vec2 line_width = (line_expand * PointSize) / 2;
mat2 pos_deltas = get_xy_deltas_unscaled(vtx, other, opposite);
if (is_bottom)
vtx = other;
if (is_outside)
{
vtx.p.xy += line_width;
VSout.inv_cov = 1.0f; // No coverage
}
else
{
VSout.inv_cov = 0.0f; // Full coverage
}
vec2 expand_dir = is_outside ? get_aa1_triangle_expand_dir(vtx, other, opposite) : vec2(0);
// Do actual extrapolation, or no-op if expand_dir == 0.
extrapolate_aa1_triangle_edge(vtx, other, opposite, pos_deltas, expand_dir);
VSout.inv_cov = is_outside ? 1.0f : 0.0f; // No coverage on outside, otherwise full.
VSout.interior = 0;
}
else // Corner cap
{
// Vertex indices for this cap. We need all 3 for determining exterior/interior.
uint prim_offset_cap = prim_offset - 21; // range: 0-8
uint i0 = prim_offset_cap / 6;
uint i1 = (i0 >= 2) ? i0 - 2 : i0 + 1;
uint i2 = (i0 >= 1) ? i0 - 1 : i0 + 2;
uint cap_offset = prim_offset_cap - 6 * i0; // range: 0-5
bool is_near_corner = cap_offset == 0 || cap_offset == 3;
bool is_far_corner = cap_offset == 2 || cap_offset == 5;
bool is_first_tri = cap_offset < 3;
// First triangle is on the side of vertex i1 and second is on the side of vertex i2.
vtx = load_vertex(load_index(3 * prim_id + i0));
ProcessedVertex other = load_vertex(load_index(3 * prim_id + (is_first_tri ? i1 : i2)));
ProcessedVertex opposite = load_vertex(load_index(3 * prim_id + (is_first_tri ? i2 : i1)));
mat2 pos_deltas = get_xy_deltas_unscaled(vtx, other, opposite);
// Get the edge expansion directions of both incident edges.
vec2 edge_expand_dir_0 = get_aa1_triangle_expand_dir(vtx, other, opposite);
vec2 edge_expand_dir_1 = get_aa1_triangle_expand_dir(vtx, opposite, other);
// Check if the corner is already filled by the expanded edges.
// This happens if the expand directions are the same.
// If so we output a degenerate triangle at this corner.
bool corner_filled = all(equal(edge_expand_dir_0, edge_expand_dir_1));
// Nothing if corner is filled, otherwise opposite to the bisector of the corner angle.
vec2 far_corner_dir = corner_filled ? vec2(0) : -normalize((pos_deltas[0] + pos_deltas[1]) / 2);
// Determine the expand direction.
vec2 expand_dir = is_near_corner ? vec2(0) : // No extrapolation
is_far_corner ? far_corner_dir : // Opposite to the angle bisector of corner
edge_expand_dir_0; // Standard AA1 edge expansion
// Do the actual extrapolation (no-op if expand_dir == 0).
extrapolate_aa1_triangle_edge(vtx, other, opposite, pos_deltas, expand_dir);
VSout.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
VSout.interior = 0;
#if !VS_IIP
+167 -31
View File
@@ -189,6 +189,110 @@ ProcessedVertex load_vertex(uint index)
return vtx;
}
// Convert XY from NDC to GS pixel coordinates (i.e. 1.0 = 1 GS pixel).
vec2 get_xy_unscaled(vec2 xy)
{
return round(xy / VertexScale) / 16.0f;
}
// Get the XY deltas in GS pixel coordinates, using first vertex as the origin.
mat2 get_xy_deltas_unscaled(ProcessedVertex v0, ProcessedVertex v1, ProcessedVertex v2)
{
vec2 xy0 = get_xy_unscaled(v0.p.xy);
vec2 xy1 = get_xy_unscaled(v1.p.xy);
vec2 xy2 = get_xy_unscaled(v2.p.xy);
return mat2(xy1 - xy0, xy2 - xy0);
}
// Get the AA1 outward expand direction to the edge formed by the first two vertices.
// This is up or down for shallow (X dominant) edges, and right or left for steep (Y dominant) edges.
// Similar expansion to line AA1 except instead of expanding on both sides of the line,
// expand on on the side towards the outside of the triangle.
vec2 get_aa1_triangle_expand_dir(ProcessedVertex v0, ProcessedVertex v1, ProcessedVertex v2)
{
mat2 xy_deltas = get_xy_deltas_unscaled(v0, v1, v2);
vec2 line_delta = xy_deltas[0];
vec2 line_opposite = xy_deltas[1];
vec2 line_normal = vec2(line_delta.y, -line_delta.x);
vec2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? vec2(0.0f, 1.0f) : vec2(1.0f, 0.0f);
if ((dot(line_expand, line_normal) >= 0.0f) == (dot(line_opposite, line_normal) >= 0.0f))
{
// Expand direction point towards the interior so flip it.
line_expand = -line_expand;
}
return line_expand;
}
mat2 get_inverse(mat2 mat, float det)
{
return mat2(mat[1][1], -mat[0][1], -mat[1][0], mat[0][0]) * (1 / det);
}
// Extrapolate triangle attributes from the first vertex along the given direction.
// dp_mat is derived from the input vertices, it is passed in to avoid recomputing.
void extrapolate_aa1_triangle_edge(inout ProcessedVertex v0, ProcessedVertex v1, ProcessedVertex v2, mat2 dp_mat, vec2 dp)
{
// Get texture deltas
#if VS_TME
#if VS_FST
mat2 dt = mat2(v1.ti.zw - v0.ti.zw, v2.ti.zw - v0.ti.zw);
#else
mat2 dt = mat2(v1.t.xy - v0.t.xy, v2.t.xy - v0.t.xy);
#endif
#endif
// Get color delta if interpolating
#if VS_IIP
mat2x4 dc = mat2x4(v1.c - v0.c, v2.c - v0.c);
#endif
vec2 dz = vec2(v1.p.z - v0.p.z, v2.p.z - v0.p.z); // Z deltas
vec2 df = vec2(v1.t.z - v0.t.z, v2.t.z - v0.t.z); // Fog deltas
vec2 dq = vec2(v1.t.w - v0.t.w, v2.t.w - v0.t.w); // Q deltas
// To prevent unstable extrapolation, do not extrapolate if the
// minimum perpendicular length of the triangle is < 2 pixels.
float dp_det = determinant(dp_mat); // Twice signed triangle area.
float len0 = length(dp_mat[0]);
float len1 = length(dp_mat[1]);
float len2 = length(dp_mat[1] - dp_mat[0]);
float min_perp_length = abs(dp_det) / max(max(len0, len1), len2);
// Get the position -> barycentric weight matrix
mat2 inv_dp_mat = get_inverse(dp_mat, dp_det);
vec2 weights = min_perp_length < 2 ? vec2(0) : inv_dp_mat * dp;
v0.p.xy += dp * PointSize; // Extrapolate position
// Extrapolate texture coords
#if VS_TME
#if VS_FST
v0.ti.zw += dt * weights;
v0.ti.xy = v0.ti.zw * TextureScale;
#else
v0.t.xy += dt * weights;
v0.ti.zw = v0.t.xy / TextureScale;
v0.t.w += dot(dq, weights);
#endif
#endif
// Extrapolate and clamp color
#if VS_IIP
v0.c += dc * weights;
v0.c = clamp(v0.c, vec4(0), vec4(255));
#endif
v0.p.z += dot(dz, weights); // Extrapolate depth
v0.t.z += dot(df, weights); // Extrapolate fog
}
void main()
{
ProcessedVertex vtx;
@@ -214,13 +318,12 @@ void main()
// Use bottom minus top for delta regardless of which vertex we are expanding.
vec2 line_delta = is_bottom ? (vtx.p.xy - other.p.xy) : (other.p.xy - vtx.p.xy);
vec2 line_vector = normalize(line_delta);
vec2 line_vector = normalize(line_delta / VertexScale);
#if VS_EXPAND == VS_EXPAND_LINE
vec2 line_expand = vec2(line_vector.y, -line_vector.x);
#elif VS_EXPAND == VS_EXPAND_LINE_AA1
// Expand in y direction for shallow lines and x direction for steep lines.
line_delta /= VertexScale;
vec2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? vec2(0.0f, 2.0f) : vec2(2.0f, 0.0f);
vec2 line_expand = abs(line_vector.x) >= abs(line_vector.y) ? vec2(0.0f, 2.0f) : vec2(2.0f, 0.0f);
#endif
vec2 line_width = (line_expand * PointSize) / 2;
vec2 offset = is_right ? line_width : -line_width;
@@ -262,10 +365,14 @@ void main()
// - Vertices 3-8: First edge expanded (2 triangles).
// - Vertices 9-14: Second edge expanded (2 triangles).
// - Vertices 15-20: Third edge expanded (2 triangles).
// - Vertices 21-26: First corner cap (2 triangles).
// - Vertices 27-32: Second corner cap (2 triangles).
// - Vertices 33-38: Third corner cap (2 triangles).
uint prim_id = vid / 21;
uint prim_offset = vid - 21 * prim_id; // range: 0-20
uint prim_id = vid / 39;
uint prim_offset = vid - 39 * prim_id; // range: 0-38
bool interior = prim_offset < 3;
bool edge = 3 <= prim_offset && prim_offset < 21;
if (interior)
{
@@ -273,7 +380,7 @@ void main()
vsOut.inv_cov = 0.0f; // Full coverage
vsOut.interior = 1;
}
else
else if (edge)
{
// Vertex indices for this edge. We need all 3 for determining exterior/interior.
uint prim_offset_edges = prim_offset - 3; // range: 0-17
@@ -282,39 +389,68 @@ void main()
uint i2 = (i0 >= 1) ? i0 - 1 : i0 + 2;
uint edge_offset = prim_offset_edges - 6 * i0; // range: 0-5
// Note: order of top/bottom, inside/outside order is arbitrary,
// Note: order of top/bottom, inside/outside is arbitrary,
// as long as it assembles into two triangles forming a quad.
bool is_bottom = (2 <= edge_offset) && (edge_offset <= 4);
bool is_outside = (edge_offset & 1) != 0;
vtx = load_vertex(load_index(3 * prim_id + i0));
ProcessedVertex other = load_vertex(load_index(3 * prim_id + i1));
vtx = load_vertex(load_index(3 * prim_id + (is_bottom ? i1 : i0)));
ProcessedVertex other = load_vertex(load_index(3 * prim_id + (is_bottom ? i0 : i1)));
ProcessedVertex opposite = load_vertex(load_index(3 * prim_id + i2));
// Similar expansion to line AA1 except instead of expanding on both sides of
// the line we expand on on the side towards the outside of the triangle.
vec2 line_delta = vtx.p.xy - other.p.xy;
vec2 line_normal = normalize(vec2(line_delta.y, -line_delta.x));
vec2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? vec2(0.0f, 2.0f) : vec2(2.0f, 0.0f);
if ((dot(line_expand, line_normal) >= 0.0f) == (dot(opposite.p.xy - vtx.p.xy, line_normal) >= 0.0f))
{
// Expand direction point towards the interior so flip it.
line_expand = -line_expand;
}
vec2 line_width = (line_expand * PointSize) / 2;
mat2 pos_deltas = get_xy_deltas_unscaled(vtx, other, opposite);
if (is_bottom)
vtx = other;
if (is_outside)
{
vtx.p.xy += line_width;
vsOut.inv_cov = 1.0f; // No coverage
}
else
{
vsOut.inv_cov = 0.0f; // Full coverage
}
vec2 expand_dir = is_outside ? get_aa1_triangle_expand_dir(vtx, other, opposite) : vec2(0);
// Do actual extrapolation, or no-op if expand_dir == 0.
extrapolate_aa1_triangle_edge(vtx, other, opposite, pos_deltas, expand_dir);
vsOut.inv_cov = is_outside ? 1.0f : 0.0f; // No coverage on outside, otherwise full.
vsOut.interior = 0;
}
else // Corner cap
{
// Vertex indices for this cap. We need all 3 for determining exterior/interior.
uint prim_offset_cap = prim_offset - 21; // range: 0-8
uint i0 = prim_offset_cap / 6;
uint i1 = (i0 >= 2) ? i0 - 2 : i0 + 1;
uint i2 = (i0 >= 1) ? i0 - 1 : i0 + 2;
uint cap_offset = prim_offset_cap - 6 * i0; // range: 0-5
bool is_near_corner = cap_offset == 0 || cap_offset == 3;
bool is_far_corner = cap_offset == 2 || cap_offset == 5;
bool is_first_tri = cap_offset < 3;
// First triangle is on the side of vertex i1 and second is on the side of vertex i2.
vtx = load_vertex(load_index(3 * prim_id + i0));
ProcessedVertex other = load_vertex(load_index(3 * prim_id + (is_first_tri ? i1 : i2)));
ProcessedVertex opposite = load_vertex(load_index(3 * prim_id + (is_first_tri ? i2 : i1)));
mat2 pos_deltas = get_xy_deltas_unscaled(vtx, other, opposite);
// Get the edge expansion directions of both incident edges.
vec2 edge_expand_dir_0 = get_aa1_triangle_expand_dir(vtx, other, opposite);
vec2 edge_expand_dir_1 = get_aa1_triangle_expand_dir(vtx, opposite, other);
// Check if the corner is already filled by the expanded edges.
// This happens if the expand directions are the same.
// If so we output a degenerate triangle at this corner.
bool corner_filled = all(equal(edge_expand_dir_0, edge_expand_dir_1));
// Nothing if corner is filled, otherwise opposite to the bisector of the corner angle.
vec2 far_corner_dir = corner_filled ? vec2(0) : -normalize((pos_deltas[0] + pos_deltas[1]) / 2);
// Determine the expand direction.
vec2 expand_dir = is_near_corner ? vec2(0) : // No extrapolation
is_far_corner ? far_corner_dir : // Opposite to the angle bisector of corner
edge_expand_dir_0; // Standard AA1 edge expansion
// Do the actual extrapolation (no-op if expand_dir == 0).
extrapolate_aa1_triangle_edge(vtx, other, opposite, pos_deltas, expand_dir);
vsOut.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
vsOut.interior = 0;
#if !VS_IIP
+1 -1
View File
@@ -894,7 +894,7 @@ static inline u32 GetExpansionFactor(GSHWDrawConfig::VSExpand expand)
case GSHWDrawConfig::VSExpand::Sprite:
return 2;
case GSHWDrawConfig::VSExpand::TriangleAA1:
return 7;
return 13;
default:
return 1;
}
+184 -36
View File
@@ -253,6 +253,119 @@ static MainVSIn load_vertex(GSMTLMainVertex base)
return out;
}
// Convert XY from NDC to GS pixel coordinates (i.e. 1.0 = 1 GS pixel).
static float2 get_xy_unscaled(float2 xy, constant GSMTLMainVSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]])
{
return round(xy / cb.vertex_scale) / 16.0f;
}
// Get the XY deltas in GS pixel coordinates, using first vertex as the origin.
static float2x2 get_xy_deltas_unscaled(thread const MainVSOut& v0, thread const MainVSOut& v1, thread const MainVSOut& v2,
constant GSMTLMainVSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]])
{
float2 xy0 = get_xy_unscaled(v0.p.xy, cb);
float2 xy1 = get_xy_unscaled(v1.p.xy, cb);
float2 xy2 = get_xy_unscaled(v2.p.xy, cb);
return float2x2(xy1 - xy0, xy2 - xy0);
}
// Get the AA1 outward expand direction to the edge formed by the first two vertices.
// This is up or down for shallow (X dominant) edges, and right or left for steep (Y dominant) edges.
// Similar expansion to line AA1 except instead of expanding on both sides of the line,
// expand on on the side towards the outside of the triangle.
float2 get_aa1_triangle_expand_dir(thread const MainVSOut& v0, thread const MainVSOut& v1, thread const MainVSOut& v2,
constant GSMTLMainVSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]])
{
float2x2 xy_deltas = get_xy_deltas_unscaled(v0, v1, v2, cb);
float2 line_delta = xy_deltas[0];
float2 line_opposite = xy_deltas[1];
float2 line_normal = float2(line_delta.y, -line_delta.x);
float2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? float2(0.0f, 1.0f) : float2(1.0f, 0.0f);
if ((dot(line_expand, line_normal) >= 0.0f) == (dot(line_opposite, line_normal) >= 0.0f))
{
// Expand direction point towards the interior so flip it.
line_expand = -line_expand;
}
return line_expand;
}
float2x2 get_inverse(const thread float2x2& mat, float det)
{
return float2x2(mat[1][1], -mat[0][1], -mat[1][0], mat[0][0]) * (1 / det);
}
// Extrapolate triangle attributes from the first vertex along the given direction.
// dp_mat is derived from the input vertices, it is passed in to avoid recomputing.
void extrapolate_aa1_triangle_edge(thread MainVSOut& v0, thread const MainVSOut& v1, thread const MainVSOut& v2,
thread const float2x2& dp_mat, float2 dp, constant GSMTLMainVSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]])
{
// Get texture deltas
float2x2 dt;
if (FST)
{
dt = float2x2(v1.ti.zw - v0.ti.zw, v2.ti.zw - v0.ti.zw);
}
else
{
dt = float2x2(v1.t.xy - v0.t.xy, v2.t.xy - v0.t.xy);
}
// Get color delta if interpolating
float2x4 dc;
if (IIP)
{
dc = float2x4(v1.c - v0.c, v2.c - v0.c);
}
float2 dz = float2(v1.p.z - v0.p.z, v2.p.z - v0.p.z); // Z deltas
float2 df = float2(v1.t.z - v0.t.z, v2.t.z - v0.t.z); // Fog deltas
float2 dq = float2(v1.t.w - v0.t.w, v2.t.w - v0.t.w); // Q deltas
// To prevent unstable extrapolation, do not extrapolate if the
// minimum perpendicular length of the triangle is < 2 pixels.
float dp_det = determinant(dp_mat); // Twice signed triangle area.
float len0 = length(dp_mat[0]);
float len1 = length(dp_mat[1]);
float len2 = length(dp_mat[1] - dp_mat[0]);
float min_perp_length = abs(dp_det) / max(max(len0, len1), len2);
// Get the position -> barycentric weight matrix
float2x2 inv_dp_mat = get_inverse(dp_mat, dp_det);
float2 weights = min_perp_length < 2 ? 0 : inv_dp_mat * dp;
v0.p.xy += dp * cb.point_size; // Extrapolate position
// Extrapolate texture coords
if (FST)
{
v0.ti.zw += dt * weights;
v0.ti.xy = v0.ti.zw * cb.texture_scale;
}
else
{
v0.t.xy += dt * weights;
v0.ti.zw = v0.t.xy / cb.texture_scale;
v0.t.w += dot(dq, weights);
}
// Extrapolate and clamp color
if (IIP)
{
v0.c += dc * weights;
v0.c = clamp(v0.c, 0, 255);
}
v0.p.z += dot(dz, weights); // Extrapolate depth
v0.t.z += dot(df, weights); // Extrapolate fog
}
vertex MainVSOut vs_main_expand(
uint vid [[vertex_id]],
device const GSMTLMainVertex* vertices [[buffer(GSMTLBufferIndexHWVertices)]],
@@ -284,17 +397,16 @@ vertex MainVSOut vs_main_expand(
// Use bottom minus top for delta regardless of which vertex we are expanding.
float2 line_delta = is_bottom ? point.p.xy - other.p.xy : other.p.xy - point.p.xy;
float2 line_vector = normalize(line_delta / cb.vertex_scale);
float2 line_expand;
if (VS_EXPAND_TYPE == VSExpand::Line)
{
float2 line_vector = normalize(line_delta);
line_expand = float2(line_vector.y, -line_vector.x);
}
else
{
// Expand in y direction for shallow lines and x direction for steep lines.
line_delta /= cb.vertex_scale;
line_expand = abs(line_delta.x) >= abs(line_delta.y) ? float2(0, 2) : float2(2, 0);
line_expand = abs(line_vector.x) >= abs(line_vector.y) ? float2(0, 2) : float2(2, 0);
}
float2 line_width = (line_expand * cb.point_size) / 2;
float2 offset = is_right ? line_width : -line_width;
@@ -345,56 +457,92 @@ vertex MainVSOut vs_main_expand(
// - Vertices 3-8: First edge expanded (2 triangles).
// - Vertices 9-14: Second edge expanded (2 triangles).
// - Vertices 15-20: Third edge expanded (2 triangles).
// - Vertices 21-26: First corner cap (2 triangles).
// - Vertices 27-32: Second corner cap (2 triangles).
// - Vertices 33-38: Third corner cap (2 triangles).
uint prim_id = vid / 21;
uint prim_offset = vid - 21 * prim_id; // range: 0-20
uint prim_id = vid / 39;
uint prim_offset = vid - 39 * prim_id; // range: 0-38
bool interior = prim_offset < 3;
uint i0 = interior ? prim_offset : (prim_offset - 3) / 6;
uint i1 = (i0 >= 2) ? i0 - 2 : i0 + 1;
uint i2 = (i0 >= 1) ? i0 - 1 : i0 + 2;
MainVSOut out = vs_main_run(load_vertex(vertices[indices[3 * prim_id + i0]]), cb);
MainVSOut other = vs_main_run(load_vertex(vertices[indices[3 * prim_id + i1]]), cb);
MainVSOut opposite = vs_main_run(load_vertex(vertices[indices[3 * prim_id + i2]]), cb);
bool edge = 3 <= prim_offset && prim_offset < 21;
MainVSOut out;
if (interior)
{
out = vs_main_run(load_vertex(vertices[indices[3 * prim_id + prim_offset]]), cb);
out.inv_cov = 0.f;
out.interior = 1;
}
else
else if (edge)
{
// Vertex indices for this edge. We need all 3 for determining exterior/interior.
uint prim_offset_edges = prim_offset - 3; // range: 0-17
uint i0 = prim_offset_edges / 6;
uint i1 = (i0 >= 2) ? i0 - 2 : i0 + 1;
uint i2 = (i0 >= 1) ? i0 - 1 : i0 + 2;
uint edge_offset = prim_offset_edges - 6 * i0; // range: 0-5
// Note: order of top/bottom, inside/outside order is arbitrary,
// Note: order of top/bottom, inside/outside is arbitrary,
// as long as it assembles into two triangles forming a quad.
bool is_bottom = (2 <= edge_offset) && (edge_offset <= 4);
bool is_outside = (edge_offset & 1) != 0;
bool is_outside = edge_offset & 1;
// Similar expansion to line AA1 except instead of expanding on both sides of
// the line we expand on on the side towards the outside of the triangle.
float2 line_delta = out.p.xy - other.p.xy;
float2 line_normal = normalize(float2(line_delta.y, -line_delta.x));
float2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? float2(0, 2) : float2(2, 0);
if ((dot(line_expand, line_normal) >= 0.0f) == (dot(opposite.p.xy - out.p.xy, line_normal) >= 0.0f))
{
// Expand direction point towards the interior so flip it.
line_expand = -line_expand;
}
float2 line_width = (line_expand * cb.point_size) / 2;
out = vs_main_run(load_vertex(vertices[indices[3 * prim_id + (is_bottom ? i1 : i0)]]), cb);
MainVSOut other = vs_main_run(load_vertex(vertices[indices[3 * prim_id + (is_bottom ? i0 : i1)]]), cb);
MainVSOut opposite = vs_main_run(load_vertex(vertices[indices[3 * prim_id + i2]]), cb);
if (is_bottom)
out = other;
if (is_outside)
{
out.p.xy += line_width;
out.inv_cov = 1.0f; // No coverage
}
else
{
out.inv_cov = 0.0f; // Full coverage
}
float2x2 pos_deltas = get_xy_deltas_unscaled(out, other, opposite, cb);
float2 expand_dir = is_outside ? get_aa1_triangle_expand_dir(out, other, opposite, cb) : 0;
// Do actual extrapolation, or no-op if expand_dir == 0.
extrapolate_aa1_triangle_edge(out, other, opposite, pos_deltas, expand_dir, cb);
out.inv_cov = is_outside ? 1.0f : 0.0f; // No coverage on outside, otherwise full.
out.interior = 0;
}
else // Corner cap
{
// Vertex indices for this cap. We need all 3 for determining exterior/interior.
uint prim_offset_cap = prim_offset - 21; // range: 0-8
uint i0 = prim_offset_cap / 6;
uint i1 = (i0 >= 2) ? i0 - 2 : i0 + 1;
uint i2 = (i0 >= 1) ? i0 - 1 : i0 + 2;
uint cap_offset = prim_offset_cap - 6 * i0; // range: 0-5
bool is_near_corner = cap_offset == 0 || cap_offset == 3;
bool is_far_corner = cap_offset == 2 || cap_offset == 5;
bool is_first_tri = cap_offset < 3;
out = vs_main_run(load_vertex(vertices[indices[3 * prim_id + i0]]), cb);
MainVSOut other = vs_main_run(load_vertex(vertices[indices[3 * prim_id + (is_first_tri ? i1 : i2)]]), cb);
MainVSOut opposite = vs_main_run(load_vertex(vertices[indices[3 * prim_id + (is_first_tri ? i2 : i1)]]), cb);
float2x2 pos_deltas = get_xy_deltas_unscaled(out, other, opposite, cb);
// Get the edge expansion directions of both incident edges.
float2 edge_expand_dir_0 = get_aa1_triangle_expand_dir(out, other, opposite, cb);
float2 edge_expand_dir_1 = get_aa1_triangle_expand_dir(out, opposite, other, cb);
// Check if the corner is already filled by the expanded edges.
// This happens if the expand directions are the same.
// If so we output a degenerate triangle at this corner.
bool corner_filled = all(edge_expand_dir_0 == edge_expand_dir_1);
// Nothing if corner is filled, otherwise opposite to the bisector of the corner angle.
float2 far_corner_dir = corner_filled ? 0 : -normalize((pos_deltas[0] + pos_deltas[1]) / 2);
// Determine the expand direction.
float2 expand_dir = is_near_corner ? 0 : // No extrapolation
is_far_corner ? far_corner_dir : // Opposite to the angle bisector of corner
edge_expand_dir_0; // Standard AA1 edge expansion
// Do the actual extrapolation (no-op if expand_dir == 0).
extrapolate_aa1_triangle_edge(out, other, opposite, pos_deltas, expand_dir, cb);
out.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
out.interior = 0;
if (NOT_IIP)
+1 -1
View File
@@ -3,4 +3,4 @@
/// Version number for GS and other shaders. Increment whenever any of the contents of the
/// shaders change, to invalidate the cache.
static constexpr u32 SHADER_CACHE_VERSION = 94; // Last changed in PR 14461
static constexpr u32 SHADER_CACHE_VERSION = 95; // Last changed in PR 14439