From 7bd3a481105fa9f20bafbcfba6b9ed1cc1e7f573 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 21 Nov 2017 15:51:27 -0800 Subject: [PATCH] Don't clip curves to the left and right sides of the pixel column twice --- shaders/gles2/common.inc.glsl | 122 ++++++++++-------- shaders/gles2/xcaa-curve.fs.glsl | 18 +-- shaders/gles2/xcaa-line.fs.glsl | 8 +- .../gles2/xcaa-multi-edge-mask-curve.fs.glsl | 18 +-- .../gles2/xcaa-multi-edge-mask-line.fs.glsl | 8 +- 5 files changed, 93 insertions(+), 81 deletions(-) diff --git a/shaders/gles2/common.inc.glsl b/shaders/gles2/common.inc.glsl index 4cfaee57..8dec9f4c 100644 --- a/shaders/gles2/common.inc.glsl +++ b/shaders/gles2/common.inc.glsl @@ -22,6 +22,10 @@ precision highp float; +bool isNearZero(float x) { + return abs(x) < EPSILON; +} + /// Computes `ia % ib`. /// /// This function is not in OpenGL ES 2 but can be polyfilled. @@ -208,64 +212,69 @@ bool slopeIsNegative(vec2 dp) { return dp.y < 0.0; } -vec2 clipToPixelBounds(vec2 p0, - vec2 dP, - vec2 center, - out float outPixelTop, - out vec2 outLeftIntercept, - out float outHorizontalSpan) { - // Determine the bounds of this pixel. - vec4 pixelSides = center.xxyy + vec4(-0.5, 0.5, -0.5, 0.5); +/// Uses Liang-Barsky to clip the line to the left and right of the pixel square. +/// +/// Returns vec4(P0', dP'). +vec4 clipLineToPixelColumn(vec2 p0, vec2 dP, float pixelCenterX) { + vec2 pixelColumnBounds = vec2(-0.5, 0.5) + pixelCenterX; + vec2 qX = pixelColumnBounds - p0.xx; + vec2 tX = clamp(qX / dP.xx, 0.0, 1.0); + return vec4(p0 + dP * tX.x, dP * (tX.y - tX.x)); +} - // Set up Liang-Barsky clipping. - vec4 q = pixelSides - p0.xxyy; - - // Use Liang-Barsky to clip to the sides of the pixel. - // - // These can be yield -Infinity or -Infinity in the case of horizontal lines, but the math all - // works out in the end. - vec2 tX = clamp(q.xy / dP.xx, 0.0, 1.0); - vec2 tY = (slopeIsNegative(dP) ? q.wz : q.zw) / dP.yy; - vec2 t = vec2(max(tX.x, tY.x), min(tX.y, tY.y)); - - outPixelTop = pixelSides.w; - outLeftIntercept = p0 + dP * tX.x; - outHorizontalSpan = dP.x * (tX.y - tX.x); - return t; +/// Uses Liang-Barsky to clip the line to the top and bottom of the pixel square. +/// +/// Returns vec4(P0'', dP''). In the case of horizontal lines, this can yield -Infinity or +/// Infinity. +vec4 clipLineToPixelRow(vec2 p0, vec2 dP, float pixelCenterY, out float outPixelTop) { + vec2 pixelRowBounds = vec2(-0.5, 0.5) + pixelCenterY; + outPixelTop = pixelRowBounds.y; + vec2 qY = pixelRowBounds - p0.yy; + vec2 tY = clamp((slopeIsNegative(dP) ? qY.yx : qY.xy) / dP.yy, 0.0, 1.0); + //vec2 tY = clamp(vec2(min(qY.x, qY.y), max(qY.x, qY.y)) / dP.yy, 0.0, 1.0); + return vec4(p0 + dP * tY.x, dP * (tY.y - tY.x)); } bool lineDoesNotPassThroughPixel(vec2 t) { return t.x >= t.y; } -// Computes the area of the polygon covering the pixel with the given boundaries. -// -// * `p0` is the start point of the line. -// * `dP` is the vector from the start point to the endpoint of the line. -// * `center` is the center of the pixel in window coordinates (i.e. `gl_FragCoord.xy`). -// * `winding` is the winding number (1 or -1). -float computeCoverage(vec2 p0, vec2 dP, vec2 center, float winding) { - // Clip to the pixel bounds. - vec2 leftIntercept; - float horizontalSpan, pixelTop; - vec2 t = clipToPixelBounds(p0, dP, center, pixelTop, leftIntercept, horizontalSpan); +/// Computes the area of the polygon covering the pixel with the given boundaries. +/// +/// The line must run left-to-right and must already be clipped to the left and right sides of the +/// pixel, which implies that `dP.x` must be within the range [0.0, 1.0]. +/// +/// * `p0X` is the start point of the line. +/// * `dPX` is the vector from the start point to the endpoint of the line. +/// * `pixelCenterY` is the Y coordinate of the center of the pixel in window coordinates (i.e. +/// `gl_FragCoord.y`). +/// * `winding` is the winding number (1 or -1). +float computeCoverage(vec2 p0X, vec2 dPX, float pixelCenterY, float winding) { + // Clip to the pixel row. + float pixelTop; + vec4 p0DPY = clipLineToPixelRow(p0X, dPX, pixelCenterY, pixelTop); + vec2 p0 = p0DPY.xy, dP = p0DPY.zw; + vec2 p1 = p0 + dP; // If the line doesn't pass through this pixel, detect that and bail. // // This should be worth a branch because it's very common for fragment blocks to all hit this // path. - if (lineDoesNotPassThroughPixel(t)) - return leftIntercept.y < pixelTop ? winding * horizontalSpan : 0.0; + if (isNearZero(dP.x) && isNearZero(dP.y)) + return p0X.y < pixelTop ? winding * dPX.x : 0.0; - // Calculate point A2, and swap the two clipped endpoints around if necessary. - float a2x = leftIntercept.x; - if (slopeIsNegative(dP)) - a2x += horizontalSpan; - else - t.xy = t.yx; - - // Calculate clipped points. - vec2 a0 = p0 + dP * t.x, a1 = p0 + dP * t.y; + // Calculate points A0-A2. + float a2x; + vec2 a0, a1; + if (slopeIsNegative(dP)) { + a2x = p0X.x + dPX.x; + a0 = p0; + a1 = p1; + } else { + a2x = p0X.x; + a0 = p1; + a1 = p0; + } // Calculate area with the shoelace formula. // This is conceptually the sum of 5 determinants for points A0-A5, where A2-A5 are: @@ -281,16 +290,19 @@ float computeCoverage(vec2 p0, vec2 dP, vec2 center, float winding) { return abs(area) * winding * 0.5; } -// * `p0` is the start point of the line. -// * `dP` is the vector from the start point to the endpoint of the line. -// * `center` is the center of the pixel in window coordinates (i.e. `gl_FragCoord.xy`). -// * `winding` is the winding number (1 or -1). -bool isPartiallyCovered(vec2 p0, vec2 dP, vec2 center, float winding) { - // Clip to the pixel bounds. - vec2 leftIntercept; - float horizontalSpan, top; - vec2 t = clipToPixelBounds(p0, dP, center, top, leftIntercept, horizontalSpan); - return !lineDoesNotPassThroughPixel(t); +/// Returns true if the line runs through this pixel or false otherwise. +/// +/// The line must run left-to-right and must already be clipped to the left and right sides of the +/// pixel, which implies that `dP.x` must be within the range [0.0, 1.0]. +/// +/// * `p0X` is the start point of the line. +/// * `dPX` is the vector from the start point to the endpoint of the line. +/// * `pixelCenterY` is the Y coordinate of the center of the pixel in window coordinates (i.e. +/// `gl_FragCoord.y`). +bool isPartiallyCovered(vec2 p0X, vec2 dPX, float pixelCenterY) { + float pixelTop; + vec2 dP = clipLineToPixelRow(p0X, dPX, pixelCenterY, pixelTop).zw; + return !isNearZero(dP.x) || !isNearZero(dP.y); } // Solve the equation: diff --git a/shaders/gles2/xcaa-curve.fs.glsl b/shaders/gles2/xcaa-curve.fs.glsl index 6cfe1674..a34bfdd9 100644 --- a/shaders/gles2/xcaa-curve.fs.glsl +++ b/shaders/gles2/xcaa-curve.fs.glsl @@ -16,29 +16,25 @@ varying float vWinding; void main() { // Unpack. - vec2 center = gl_FragCoord.xy; + vec2 pixelCenter = gl_FragCoord.xy; vec2 p0 = vEndpoints.xy, p1 = vEndpoints.zw; vec2 cp = vControlPoint; // Compute pixel extents. - vec2 pixelExtents = center.xx + vec2(-0.5, 0.5); + vec2 pixelColumnBounds = pixelCenter.xx + vec2(-0.5, 0.5); // Clip the curve to the left and right edges to create a line. - // - // TODO(pcwalton): Consider clipping to the bottom and top edges properly too. (I kind of doubt - // it's worth it to do this, though, given that the maximum error doing it this way will always - // be less than a pixel, and it saves a lot of time.) - vec2 t = solveCurveT(p0.x, cp.x, p1.x, pixelExtents); + vec2 t = solveCurveT(p0.x, cp.x, p1.x, pixelColumnBounds); // Handle endpoints properly. These tests are negated to handle NaNs. - if (!(p0.x < pixelExtents.x)) + if (!(p0.x < pixelColumnBounds.x)) t.x = 0.0; - if (!(p1.x > pixelExtents.y)) + if (!(p1.x > pixelColumnBounds.y)) t.y = 1.0; vec2 clippedP0 = mix(mix(p0, cp, t.x), mix(cp, p1, t.x), t.x); - vec2 clippedP1 = mix(mix(p0, cp, t.y), mix(cp, p1, t.y), t.y); + vec2 clippedDP = mix(mix(p0, cp, t.y), mix(cp, p1, t.y), t.y) - clippedP0; // Compute area. - gl_FragColor = vec4(computeCoverage(clippedP0, clippedP1 - clippedP0, center, vWinding)); + gl_FragColor = vec4(computeCoverage(clippedP0, clippedDP, pixelCenter.y, vWinding)); } diff --git a/shaders/gles2/xcaa-line.fs.glsl b/shaders/gles2/xcaa-line.fs.glsl index 4355d1f1..034b8bde 100644 --- a/shaders/gles2/xcaa-line.fs.glsl +++ b/shaders/gles2/xcaa-line.fs.glsl @@ -15,9 +15,13 @@ varying float vWinding; void main() { // Unpack. - vec2 center = gl_FragCoord.xy; + vec2 pixelCenter = gl_FragCoord.xy; vec2 p0 = vEndpoints.xy, p1 = vEndpoints.zw; + // Clip to left and right pixel boundaries. + vec2 dP = p1 - p0; + vec4 p0DPX = clipLineToPixelColumn(p0, dP, pixelCenter.x); + // Compute area. - gl_FragColor = vec4(computeCoverage(p0, p1 - p0, center, vWinding)); + gl_FragColor = vec4(computeCoverage(p0DPX.xy, p0DPX.zw, pixelCenter.y, vWinding)); } diff --git a/shaders/gles2/xcaa-multi-edge-mask-curve.fs.glsl b/shaders/gles2/xcaa-multi-edge-mask-curve.fs.glsl index 32ac2994..e5a1bee7 100644 --- a/shaders/gles2/xcaa-multi-edge-mask-curve.fs.glsl +++ b/shaders/gles2/xcaa-multi-edge-mask-curve.fs.glsl @@ -16,31 +16,27 @@ varying float vWinding; void main() { // Unpack. - vec2 center = gl_FragCoord.xy; + vec2 pixelCenter = gl_FragCoord.xy; vec2 p0 = vEndpoints.xy, p1 = vEndpoints.zw; vec2 cp = vControlPoint; - // Compute pixel extents. - vec2 pixelExtents = center.xx + vec2(-0.5, 0.5); + // Clip to left and right pixel boundaries. + vec2 pixelColumnBounds = pixelCenter.xx + vec2(-0.5, 0.5); // Clip the curve to the left and right edges to create a line. - // - // TODO(pcwalton): Consider clipping to the bottom and top edges properly too. (I kind of doubt - // it's worth it to do this, though, given that the maximum error doing it this way will always - // be less than a pixel, and it saves a lot of time.) - vec2 t = solveCurveT(p0.x, cp.x, p1.x, pixelExtents); + vec2 t = solveCurveT(p0.x, cp.x, p1.x, pixelColumnBounds); // Handle endpoints properly. These tests are negated to handle NaNs. - if (!(p0.x < pixelExtents.x)) + if (!(p0.x < pixelColumnBounds.x)) t.x = 0.0; - if (!(p1.x > pixelExtents.y)) + if (!(p1.x > pixelColumnBounds.y)) t.y = 1.0; vec2 clippedP0 = mix(mix(p0, cp, t.x), mix(cp, p1, t.x), t.x); vec2 clippedP1 = mix(mix(p0, cp, t.y), mix(cp, p1, t.y), t.y); // Discard if not edge. - if (!isPartiallyCovered(clippedP0, clippedP1 - clippedP0, center, vWinding)) + if (!isPartiallyCovered(clippedP0, clippedP1 - clippedP0, pixelCenter.y)) discard; gl_FragColor = vec4(1.0); } diff --git a/shaders/gles2/xcaa-multi-edge-mask-line.fs.glsl b/shaders/gles2/xcaa-multi-edge-mask-line.fs.glsl index 11ad5a76..6558dd3c 100644 --- a/shaders/gles2/xcaa-multi-edge-mask-line.fs.glsl +++ b/shaders/gles2/xcaa-multi-edge-mask-line.fs.glsl @@ -15,11 +15,15 @@ varying float vWinding; void main() { // Unpack. - vec2 center = gl_FragCoord.xy; + vec2 pixelCenter = gl_FragCoord.xy; vec2 p0 = vEndpoints.xy, p1 = vEndpoints.zw; + // Clip to left and right pixel boundaries. + vec2 dP = p1 - p0; + vec4 p0DPX = clipLineToPixelColumn(p0, dP, pixelCenter.x); + // Discard if not edge. - if (!isPartiallyCovered(p0, p1 - p0, center, vWinding)) + if (!isPartiallyCovered(p0DPX.xy, p0DPX.zw - p0DPX.xy, pixelCenter.y)) discard; gl_FragColor = vec4(1.0); }