From 0b47d87f2fde1dc1fbb50f8a883486a78be9c82d Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Sat, 25 Apr 2020 19:43:20 -0400 Subject: [PATCH 1/3] Tweak pen shader to avoid float precision issues --- src/shaders/sprite.frag | 9 +++++++-- src/shaders/sprite.vert | 5 ++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/shaders/sprite.frag b/src/shaders/sprite.frag index a9b107a33..9e7b8dcf9 100644 --- a/src/shaders/sprite.frag +++ b/src/shaders/sprite.frag @@ -214,7 +214,7 @@ void main() // Un-premultiply alpha. gl_FragColor.rgb /= gl_FragColor.a + epsilon; #endif - + #else // DRAW_MODE_line // Maaaaagic antialiased-line-with-round-caps shader. // Adapted from Inigo Quilez' 2D distance function cheat sheet @@ -223,9 +223,14 @@ void main() // The xy component of u_penPoints is the first point; the zw is the second point. // This is done to minimize the number of gl.uniform calls, which can add up. vec2 pa = v_texCoord - u_penPoints.xy, ba = u_penPoints.zw - u_penPoints.xy; + + // Avoid division by zero + float baDot = dot(ba, ba); + baDot = (abs(baDot) < epsilon) ? epsilon : baDot; + // Magnitude of vector projection of this fragment onto the line (both relative to the line's start point). // This results in a "linear gradient" which goes from 0.0 at the start point to 1.0 at the end point. - float projMagnitude = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); + float projMagnitude = clamp(dot(pa, ba) / baDot, 0.0, 1.0); float lineDistance = length(pa - (ba * projMagnitude)); diff --git a/src/shaders/sprite.vert b/src/shaders/sprite.vert index c92468e11..646acaf4a 100644 --- a/src/shaders/sprite.vert +++ b/src/shaders/sprite.vert @@ -40,7 +40,10 @@ void main() { position -= expandedRadius; // Rotate quad to line angle - vec2 normalized = (u_penPoints.zw - u_penPoints.xy + epsilon) / (lineLength + epsilon); + vec2 pointDiff = u_penPoints.zw - u_penPoints.xy; + // Ensure line has a nonzero length so it's rendered properly + pointDiff.x = abs(pointDiff.x) < epsilon ? epsilon : pointDiff.x; + vec2 normalized = pointDiff / max(lineLength, epsilon); position = mat2(normalized.x, normalized.y, -normalized.y, normalized.x) * position; // Translate quad position += u_penPoints.xy; From 4b7558f2d274558f5316093eebcf9b4f3d7b948a Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Fri, 1 May 2020 13:46:28 -0400 Subject: [PATCH 2/3] Fix another annoying precision bug. Who knew fragment shaders were so difficult? --- src/shaders/sprite.frag | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/shaders/sprite.frag b/src/shaders/sprite.frag index 9e7b8dcf9..ec783658a 100644 --- a/src/shaders/sprite.frag +++ b/src/shaders/sprite.frag @@ -220,9 +220,17 @@ void main() // Adapted from Inigo Quilez' 2D distance function cheat sheet // https://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm + // On (some?) devices with 16-bit float precision, sufficiently long lines will overflow the float's range. + // Avoid this by scaling these problematic values down to fit within (-1, 1) then scaling them back up later. + // TODO: Is this a problem on all drivers with 16-bit mediump floats, or just Mali? + vec2 pointDiff = abs(u_penPoints.zw - u_penPoints.xy); + float FLOAT_SCALING_INVERSE = max(1.0, max(pointDiff.x, pointDiff.y)); + float FLOAT_SCALING = 1.0 / FLOAT_SCALING_INVERSE; + // The xy component of u_penPoints is the first point; the zw is the second point. // This is done to minimize the number of gl.uniform calls, which can add up. - vec2 pa = v_texCoord - u_penPoints.xy, ba = u_penPoints.zw - u_penPoints.xy; + // vec2 pa = v_texCoord - u_penPoints.xy, ba = u_penPoints.zw - u_penPoints.xy; + vec2 pa = (v_texCoord - u_penPoints.xy) * FLOAT_SCALING, ba = (u_penPoints.zw - u_penPoints.xy) * FLOAT_SCALING; // Avoid division by zero float baDot = dot(ba, ba); @@ -232,7 +240,7 @@ void main() // This results in a "linear gradient" which goes from 0.0 at the start point to 1.0 at the end point. float projMagnitude = clamp(dot(pa, ba) / baDot, 0.0, 1.0); - float lineDistance = length(pa - (ba * projMagnitude)); + float lineDistance = length(pa - (ba * projMagnitude)) * FLOAT_SCALING_INVERSE; // The distance to the line allows us to create lines of any thickness. // Instead of checking whether this fragment's distance < the line thickness, From 51bceb68e516ee9973447eb0eda6c3132c2e1302 Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Wed, 6 May 2020 16:21:18 -0400 Subject: [PATCH 3/3] Address review feedback --- src/shaders/sprite.frag | 3 ++- src/shaders/sprite.vert | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/shaders/sprite.frag b/src/shaders/sprite.frag index ec783658a..a98a39eee 100644 --- a/src/shaders/sprite.frag +++ b/src/shaders/sprite.frag @@ -234,7 +234,8 @@ void main() // Avoid division by zero float baDot = dot(ba, ba); - baDot = (abs(baDot) < epsilon) ? epsilon : baDot; + // the dot product of a vector and itself is always positive + baDot = max(baDot, epsilon); // Magnitude of vector projection of this fragment onto the line (both relative to the line's start point). // This results in a "linear gradient" which goes from 0.0 at the start point to 1.0 at the end point. diff --git a/src/shaders/sprite.vert b/src/shaders/sprite.vert index 646acaf4a..b7b161529 100644 --- a/src/shaders/sprite.vert +++ b/src/shaders/sprite.vert @@ -42,7 +42,10 @@ void main() { // Rotate quad to line angle vec2 pointDiff = u_penPoints.zw - u_penPoints.xy; // Ensure line has a nonzero length so it's rendered properly + // As long as either component is nonzero, the line length will be nonzero pointDiff.x = abs(pointDiff.x) < epsilon ? epsilon : pointDiff.x; + // The `normalized` vector holds rotational values equivalent to sine/cosine + // We're applying the standard rotation matrix formula to the position to rotate the quad to the line angle vec2 normalized = pointDiff / max(lineLength, epsilon); position = mat2(normalized.x, normalized.y, -normalized.y, normalized.x) * position; // Translate quad