From 24851f28f8c609a42f8b87a461a2456d7c117a05 Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Sun, 1 Dec 2019 03:05:38 -0500 Subject: [PATCH 1/5] Draw pen lines via fragment shader --- src/PenSkin.js | 133 +++++++++++++--------------------------- src/ShaderManager.js | 4 +- src/shaders/sprite.frag | 32 +++++----- src/shaders/sprite.vert | 24 +++++--- 4 files changed, 76 insertions(+), 117 deletions(-) diff --git a/src/PenSkin.js b/src/PenSkin.js index fe5ae58a8..93aa6a695 100644 --- a/src/PenSkin.js +++ b/src/PenSkin.js @@ -44,11 +44,6 @@ const __projectionMatrix = twgl.m4.identity(); */ const __modelTranslationMatrix = twgl.m4.identity(); -/** - * Reused memory location for rotation matrix for building a model matrix. - * @type {FloatArray} - */ -const __modelRotationMatrix = twgl.m4.identity(); /** * Reused memory location for scaling matrix for building a model matrix. @@ -129,7 +124,7 @@ class PenSkin extends Skin { this._stampShader = this._renderer._shaderManager.getShader(ShaderManager.DRAW_MODE.default, NO_EFFECTS); /** @type {twgl.ProgramInfo} */ - this._lineShader = this._renderer._shaderManager.getShader(ShaderManager.DRAW_MODE.lineSample, NO_EFFECTS); + this._lineShader = this._renderer._shaderManager.getShader(ShaderManager.DRAW_MODE.line, NO_EFFECTS); this._createLineGeometry(); @@ -215,10 +210,14 @@ class PenSkin extends Skin { * @param {number} y1 - the Y coordinate of the end of the line. */ drawLine (penAttributes, x0, y0, x1, y1) { + // Width 1 and 3 lines need to be offset by 0.5. + const diameter = penAttributes.diameter || DefaultPenAttributes.diameter; + const offset = (Math.max(4 - diameter, 0) % 2) / 2; + this._drawLineOnBuffer( penAttributes, - this._rotationCenter[0] + x0, this._rotationCenter[1] - y0, - this._rotationCenter[0] + x1, this._rotationCenter[1] - y1 + this._rotationCenter[0] + x0 + offset, this._rotationCenter[1] - y0 + offset, + this._rotationCenter[0] + x1 + offset, this._rotationCenter[1] - y1 + offset ); this._silhouetteDirty = true; @@ -228,72 +227,27 @@ class PenSkin extends Skin { * Create 2D geometry for drawing lines to a framebuffer. */ _createLineGeometry () { - // Create a set of triangulated quads that break up a line into 3 parts: - // 2 caps and a body. The y component of these position vertices are - // divided to bring a value of 1 down to 0.5 to 0. The large y values - // are set so they will still be at least 0.5 after division. The - // divisor is scaled based on the length of the line and the lines - // width. - // - // Texture coordinates are based on a "generated" texture whose general - // shape is a circle. The line caps set their texture values to define - // there roundedness with the texture. The body has all of its texture - // values set to the center of the texture so it's a solid block. const quads = { a_position: { numComponents: 2, data: [ - -0.5, 1, - 0.5, 1, - -0.5, 100000, - - -0.5, 100000, - 0.5, 1, - 0.5, 100000, - - -0.5, 1, - 0.5, 1, - -0.5, -1, - - -0.5, -1, - 0.5, 1, - 0.5, -1, - - -0.5, -100000, - 0.5, -100000, - -0.5, -1, - - -0.5, -1, - 0.5, -100000, - 0.5, -1 + -1, -1, + 1, -1, + -1, 1, + -1, 1, + 1, -1, + 1, 1 ] }, a_texCoord: { numComponents: 2, data: [ - 1, 0.5, - 0, 0.5, 1, 0, - - 1, 0, - 0, 0.5, 0, 0, - - 0.5, 0, - 0.5, 1, - 0.5, 0, - - 0.5, 0, - 0.5, 1, - 0.5, 1, - - 1, 0, + 1, 1, + 1, 1, 0, 0, - 1, 0.5, - - 1, 0.5, - 0, 0, - 0, 0.5 + 0, 1 ] } }; @@ -351,25 +305,31 @@ class PenSkin extends Skin { this._renderer.enterDrawRegion(this._lineOnBufferDrawRegionId); - const diameter = penAttributes.diameter || DefaultPenAttributes.diameter; - const length = Math.hypot(Math.abs(x1 - x0) - 0.001, Math.abs(y1 - y0) - 0.001); - const avgX = (x0 + x1) / 2; - const avgY = (y0 + y1) / 2; - const theta = Math.atan2(y0 - y1, x0 - x1); - const alias = 1; - - // The line needs a bit of aliasing to look smooth. Add a small offset - // and a small size boost to scaling to give a section to alias. + const radius = penAttributes.diameter / 2; + + // Clip drawn polygon to line's AABB. + const transformMatrix = __modelMatrix; + + const left = Math.floor(Math.min(x0, x1) - radius) - 1; + const right = Math.ceil(Math.max(x0, x1) + radius) + 1; + const top = Math.floor(Math.min(y0, y1) - radius) - 1; + const bottom = Math.floor(Math.max(y0, y1) + radius) + 1; + + const width = this._bounds.width; + const height = this._bounds.height; + const translationVector = __modelTranslationVector; - translationVector[0] = avgX - (alias / 2); - translationVector[1] = avgY + (alias / 4); + translationVector[0] = (left / (width * 0.5)) - 1; + translationVector[1] = (top / (height * 0.5)) - 1; const scalingVector = __modelScalingVector; - scalingVector[0] = diameter + alias; - scalingVector[1] = length + diameter - (alias / 2); + scalingVector[0] = (right - left) / width; + scalingVector[1] = (bottom - top) / height; - const radius = diameter / 2; - const yScalar = (0.50001 - (radius / (length + diameter))); + transformMatrix[0] = scalingVector[0]; + transformMatrix[5] = scalingVector[1]; + transformMatrix[12] = translationVector[0] + scalingVector[0]; + transformMatrix[13] = translationVector[1] + scalingVector[1]; // Premultiply pen color by pen transparency const penColor = penAttributes.color4f || DefaultPenAttributes.color4f; @@ -379,19 +339,12 @@ class PenSkin extends Skin { __premultipliedColor[3] = penColor[3]; const uniforms = { - u_positionScalar: yScalar, - u_capScale: diameter, - u_aliasAmount: alias, - u_modelMatrix: twgl.m4.multiply( - twgl.m4.multiply( - twgl.m4.translation(translationVector, __modelTranslationMatrix), - twgl.m4.rotationZ(theta - (Math.PI / 2), __modelRotationMatrix), - __modelMatrix - ), - twgl.m4.scaling(scalingVector, __modelScalingMatrix), - __modelMatrix - ), - u_lineColor: __premultipliedColor + u_modelMatrix: transformMatrix, + u_lineColor: __premultipliedColor, + u_lineThickness: penAttributes.diameter, + u_p1: [x0, y0], + u_p2: [x1, y1], + u_stageSize: this.size }; twgl.setUniforms(currentShader, uniforms); diff --git a/src/ShaderManager.js b/src/ShaderManager.js index a71ef2d2e..32f032f3d 100644 --- a/src/ShaderManager.js +++ b/src/ShaderManager.js @@ -169,9 +169,9 @@ ShaderManager.DRAW_MODE = { colorMask: 'colorMask', /** - * Sample a "texture" to draw a line with caps. + * Draw a line with caps. */ - lineSample: 'lineSample' + line: 'line' }; module.exports = ShaderManager; diff --git a/src/shaders/sprite.frag b/src/shaders/sprite.frag index 13fe19deb..d8dfa820b 100644 --- a/src/shaders/sprite.frag +++ b/src/shaders/sprite.frag @@ -33,11 +33,12 @@ uniform float u_mosaic; uniform float u_ghost; #endif // ENABLE_ghost -#ifdef DRAW_MODE_lineSample +#ifdef DRAW_MODE_line uniform vec4 u_lineColor; -uniform float u_capScale; -uniform float u_aliasAmount; -#endif // DRAW_MODE_lineSample +uniform float u_lineThickness; +uniform vec2 u_p1; +uniform vec2 u_p2; +#endif // DRAW_MODE_line uniform sampler2D u_skin; @@ -109,7 +110,7 @@ const vec2 kCenter = vec2(0.5, 0.5); void main() { - #ifndef DRAW_MODE_lineSample + #ifndef DRAW_MODE_line vec2 texcoord0 = v_texCoord; #ifdef ENABLE_mosaic @@ -210,14 +211,15 @@ void main() #endif // DRAW_MODE_colorMask #endif // DRAW_MODE_silhouette - #else // DRAW_MODE_lineSample - gl_FragColor = u_lineColor * clamp( - // Scale the capScale a little to have an aliased region. - (u_capScale + u_aliasAmount - - u_capScale * 2.0 * distance(v_texCoord, vec2(0.5, 0.5)) - ) / (u_aliasAmount + 1.0), - 0.0, - 1.0 - ); - #endif // DRAW_MODE_lineSample + #else // DRAW_MODE_line + // Maaaaagic antialiased-line-with-round-caps shader. + // Adapted from Inigo Quilez' 2D distance function cheat sheet + // https://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm + vec2 pa = v_texCoord - u_p1, ba = u_p2 - u_p1; + float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); + + float cappedLine = clamp((u_lineThickness + 1.0) * 0.5 - length(pa - ba*h), 0.0, 1.0); + + gl_FragColor = u_lineColor * cappedLine; + #endif // DRAW_MODE_line } diff --git a/src/shaders/sprite.vert b/src/shaders/sprite.vert index a9bbe1507..a48933d74 100644 --- a/src/shaders/sprite.vert +++ b/src/shaders/sprite.vert @@ -1,22 +1,26 @@ -uniform mat4 u_projectionMatrix; uniform mat4 u_modelMatrix; +#ifdef DRAW_MODE_line +uniform vec2 u_stageSize; +#endif + +#ifndef DRAW_MODE_line +uniform mat4 u_projectionMatrix; +#endif + attribute vec2 a_position; attribute vec2 a_texCoord; varying vec2 v_texCoord; -#ifdef DRAW_MODE_lineSample -uniform float u_positionScalar; -#endif - void main() { - #ifdef DRAW_MODE_lineSample - vec2 position = a_position; - position.y = clamp(position.y * u_positionScalar, -0.5, 0.5); - gl_Position = u_projectionMatrix * u_modelMatrix * vec4(position, 0, 1); + #ifdef DRAW_MODE_line + vec4 screenCoord = u_modelMatrix * vec4(a_position, 0, 1); + + gl_Position = screenCoord; + v_texCoord = ((screenCoord.xy * 0.5) + 0.5) * u_stageSize; #else gl_Position = u_projectionMatrix * u_modelMatrix * vec4(a_position, 0, 1); - #endif v_texCoord = a_texCoord; + #endif } From 9e12f093f614e1335f3e7f9ea023596f0e5d43ea Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Tue, 4 Feb 2020 19:55:32 -0500 Subject: [PATCH 2/5] Cleanup --- src/PenSkin.js | 5 +++-- src/shaders/sprite.vert | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/PenSkin.js b/src/PenSkin.js index 93aa6a695..90184f290 100644 --- a/src/PenSkin.js +++ b/src/PenSkin.js @@ -210,9 +210,10 @@ class PenSkin extends Skin { * @param {number} y1 - the Y coordinate of the end of the line. */ drawLine (penAttributes, x0, y0, x1, y1) { - // Width 1 and 3 lines need to be offset by 0.5. + // For compatibility with Scratch 2.0, offset pen lines of width 1 and 3 so they're pixel-aligned. + // See https://github.com/LLK/scratch-render/pull/314 const diameter = penAttributes.diameter || DefaultPenAttributes.diameter; - const offset = (Math.max(4 - diameter, 0) % 2) / 2; + const offset = (diameter === 1 || diameter === 3) ? 0.5 : 0; this._drawLineOnBuffer( penAttributes, diff --git a/src/shaders/sprite.vert b/src/shaders/sprite.vert index a48933d74..37e662ff5 100644 --- a/src/shaders/sprite.vert +++ b/src/shaders/sprite.vert @@ -6,10 +6,10 @@ uniform vec2 u_stageSize; #ifndef DRAW_MODE_line uniform mat4 u_projectionMatrix; +attribute vec2 a_texCoord; #endif attribute vec2 a_position; -attribute vec2 a_texCoord; varying vec2 v_texCoord; From 936afeef6098f726cb00c7cae35300c0f3e9138f Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Tue, 4 Feb 2020 19:56:28 -0500 Subject: [PATCH 3/5] Tighten pen bounds --- src/PenSkin.js | 60 ++++++++++++++++------------------------- src/shaders/sprite.frag | 5 ++-- 2 files changed, 25 insertions(+), 40 deletions(-) diff --git a/src/PenSkin.js b/src/PenSkin.js index 90184f290..02a81bd82 100644 --- a/src/PenSkin.js +++ b/src/PenSkin.js @@ -217,8 +217,8 @@ class PenSkin extends Skin { this._drawLineOnBuffer( penAttributes, - this._rotationCenter[0] + x0 + offset, this._rotationCenter[1] - y0 + offset, - this._rotationCenter[0] + x1 + offset, this._rotationCenter[1] - y1 + offset + x0 + offset, -y0 + offset, + x1 + offset, -y1 + offset ); this._silhouetteDirty = true; @@ -230,17 +230,6 @@ class PenSkin extends Skin { _createLineGeometry () { const quads = { a_position: { - numComponents: 2, - data: [ - -1, -1, - 1, -1, - -1, 1, - -1, 1, - 1, -1, - 1, 1 - ] - }, - a_texCoord: { numComponents: 2, data: [ 1, 0, @@ -293,6 +282,8 @@ class PenSkin extends Skin { /** * Draw a line on the framebuffer. + * Note that the point coordinates are in the following coordinate space: + * +y is down, (0, 0) is the center, and the coords range from (-width / 2, -height / 2) to (height / 2, width / 2). * @param {PenAttributes} penAttributes - how the line should be drawn. * @param {number} x0 - the X coordinate of the beginning of the line. * @param {number} y0 - the Y coordinate of the beginning of the line. @@ -306,31 +297,27 @@ class PenSkin extends Skin { this._renderer.enterDrawRegion(this._lineOnBufferDrawRegionId); - const radius = penAttributes.diameter / 2; - - // Clip drawn polygon to line's AABB. - const transformMatrix = __modelMatrix; - - const left = Math.floor(Math.min(x0, x1) - radius) - 1; - const right = Math.ceil(Math.max(x0, x1) + radius) + 1; - const top = Math.floor(Math.min(y0, y1) - radius) - 1; - const bottom = Math.floor(Math.max(y0, y1) + radius) + 1; + const diameter = penAttributes.diameter || DefaultPenAttributes.diameter; + const radius = diameter / 2; + // Expand line bounds by sqrt(2) / 2 each side-- this ensures that all antialiased pixels + // fall within the quad, even at a 45-degree diagonal + const expandedRadius = radius + 1.4142135623730951; - const width = this._bounds.width; - const height = this._bounds.height; + const lineLength = Math.hypot(x1 - x0, y1 - y0); + const lineAngle = Math.atan2(y1 - y0, x1 - x0); - const translationVector = __modelTranslationVector; - translationVector[0] = (left / (width * 0.5)) - 1; - translationVector[1] = (top / (height * 0.5)) - 1; + const halfWidth = this._bounds.width * 0.5; + const halfHeight = this._bounds.height * 0.5; - const scalingVector = __modelScalingVector; - scalingVector[0] = (right - left) / width; - scalingVector[1] = (bottom - top) / height; + const transformMatrix = __modelMatrix; + twgl.m4.identity(transformMatrix); + // Apply view transform to matrix + twgl.m4.scale(transformMatrix, [1 / halfWidth, 1 / halfHeight, 1], transformMatrix); - transformMatrix[0] = scalingVector[0]; - transformMatrix[5] = scalingVector[1]; - transformMatrix[12] = translationVector[0] + scalingVector[0]; - transformMatrix[13] = translationVector[1] + scalingVector[1]; + twgl.m4.translate(transformMatrix, [x0, y0, 0], transformMatrix); + twgl.m4.rotateZ(transformMatrix, lineAngle, transformMatrix); + twgl.m4.translate(transformMatrix, [-expandedRadius, -expandedRadius, 0], transformMatrix); + twgl.m4.scale(transformMatrix, [lineLength + (expandedRadius * 2), (expandedRadius * 2), 1], transformMatrix); // Premultiply pen color by pen transparency const penColor = penAttributes.color4f || DefaultPenAttributes.color4f; @@ -342,9 +329,8 @@ class PenSkin extends Skin { const uniforms = { u_modelMatrix: transformMatrix, u_lineColor: __premultipliedColor, - u_lineThickness: penAttributes.diameter, - u_p1: [x0, y0], - u_p2: [x1, y1], + u_lineThickness: diameter, + u_penPoints: [x0 + halfWidth, y0 + halfHeight, x1 + halfWidth, y1 + halfHeight], u_stageSize: this.size }; diff --git a/src/shaders/sprite.frag b/src/shaders/sprite.frag index d8dfa820b..8c7bddfcd 100644 --- a/src/shaders/sprite.frag +++ b/src/shaders/sprite.frag @@ -36,8 +36,7 @@ uniform float u_ghost; #ifdef DRAW_MODE_line uniform vec4 u_lineColor; uniform float u_lineThickness; -uniform vec2 u_p1; -uniform vec2 u_p2; +uniform vec4 u_penPoints; #endif // DRAW_MODE_line uniform sampler2D u_skin; @@ -215,7 +214,7 @@ void main() // Maaaaagic antialiased-line-with-round-caps shader. // Adapted from Inigo Quilez' 2D distance function cheat sheet // https://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm - vec2 pa = v_texCoord - u_p1, ba = u_p2 - u_p1; + vec2 pa = v_texCoord - u_penPoints.xy, ba = u_penPoints.zw - u_penPoints.xy; float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); float cappedLine = clamp((u_lineThickness + 1.0) * 0.5 - length(pa - ba*h), 0.0, 1.0); From 6bc1c32cf89286f7d1b126b03e838b97b77f6dc3 Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Tue, 4 Feb 2020 20:36:32 -0500 Subject: [PATCH 4/5] Explain pen line shader better --- src/shaders/sprite.frag | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/shaders/sprite.frag b/src/shaders/sprite.frag index 8c7bddfcd..bd989ca92 100644 --- a/src/shaders/sprite.frag +++ b/src/shaders/sprite.frag @@ -214,10 +214,21 @@ void main() // Maaaaagic antialiased-line-with-round-caps shader. // Adapted from Inigo Quilez' 2D distance function cheat sheet // https://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm + + // 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; - float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); + // 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 lineDistance = length(pa - (ba * projMagnitude)); - float cappedLine = clamp((u_lineThickness + 1.0) * 0.5 - length(pa - ba*h), 0.0, 1.0); + // The distance to the line allows us to create lines of any thickness. + // Instead of checking whether this fragment's distance < the line thickness, + // utilize the distance field to get some antialiasing. Fragments far away from the line are 0, + // fragments close to the line are 1, and fragments that are within a 1-pixel border of the line are in between. + float cappedLine = clamp((u_lineThickness + 1.0) * 0.5 - lineDistance, 0.0, 1.0); gl_FragColor = u_lineColor * cappedLine; #endif // DRAW_MODE_line From 7d652b44d36c0787b264c26d7523d61909f69359 Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Tue, 11 Feb 2020 17:07:51 -0500 Subject: [PATCH 5/5] Move pen line bounds calculation to vertex shader --- src/PenSkin.js | 31 ++++--------------------- src/shaders/sprite.vert | 51 +++++++++++++++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 37 deletions(-) diff --git a/src/PenSkin.js b/src/PenSkin.js index 02a81bd82..827248edb 100644 --- a/src/PenSkin.js +++ b/src/PenSkin.js @@ -217,8 +217,8 @@ class PenSkin extends Skin { this._drawLineOnBuffer( penAttributes, - x0 + offset, -y0 + offset, - x1 + offset, -y1 + offset + x0 + offset, y0 + offset, + x1 + offset, y1 + offset ); this._silhouetteDirty = true; @@ -297,28 +297,6 @@ class PenSkin extends Skin { this._renderer.enterDrawRegion(this._lineOnBufferDrawRegionId); - const diameter = penAttributes.diameter || DefaultPenAttributes.diameter; - const radius = diameter / 2; - // Expand line bounds by sqrt(2) / 2 each side-- this ensures that all antialiased pixels - // fall within the quad, even at a 45-degree diagonal - const expandedRadius = radius + 1.4142135623730951; - - const lineLength = Math.hypot(x1 - x0, y1 - y0); - const lineAngle = Math.atan2(y1 - y0, x1 - x0); - - const halfWidth = this._bounds.width * 0.5; - const halfHeight = this._bounds.height * 0.5; - - const transformMatrix = __modelMatrix; - twgl.m4.identity(transformMatrix); - // Apply view transform to matrix - twgl.m4.scale(transformMatrix, [1 / halfWidth, 1 / halfHeight, 1], transformMatrix); - - twgl.m4.translate(transformMatrix, [x0, y0, 0], transformMatrix); - twgl.m4.rotateZ(transformMatrix, lineAngle, transformMatrix); - twgl.m4.translate(transformMatrix, [-expandedRadius, -expandedRadius, 0], transformMatrix); - twgl.m4.scale(transformMatrix, [lineLength + (expandedRadius * 2), (expandedRadius * 2), 1], transformMatrix); - // Premultiply pen color by pen transparency const penColor = penAttributes.color4f || DefaultPenAttributes.color4f; __premultipliedColor[0] = penColor[0] * penColor[3]; @@ -327,10 +305,9 @@ class PenSkin extends Skin { __premultipliedColor[3] = penColor[3]; const uniforms = { - u_modelMatrix: transformMatrix, u_lineColor: __premultipliedColor, - u_lineThickness: diameter, - u_penPoints: [x0 + halfWidth, y0 + halfHeight, x1 + halfWidth, y1 + halfHeight], + u_lineThickness: penAttributes.diameter || DefaultPenAttributes.diameter, + u_penPoints: [x0, -y0, x1, -y1], u_stageSize: this.size }; diff --git a/src/shaders/sprite.vert b/src/shaders/sprite.vert index 37e662ff5..c92468e11 100644 --- a/src/shaders/sprite.vert +++ b/src/shaders/sprite.vert @@ -1,11 +1,18 @@ -uniform mat4 u_modelMatrix; +precision mediump float; #ifdef DRAW_MODE_line uniform vec2 u_stageSize; +uniform float u_lineThickness; +uniform vec4 u_penPoints; + +// Add this to divisors to prevent division by 0, which results in NaNs propagating through calculations. +// Smaller values can cause problems on some mobile devices. +const float epsilon = 1e-3; #endif #ifndef DRAW_MODE_line uniform mat4 u_projectionMatrix; +uniform mat4 u_modelMatrix; attribute vec2 a_texCoord; #endif @@ -14,13 +21,37 @@ attribute vec2 a_position; varying vec2 v_texCoord; void main() { - #ifdef DRAW_MODE_line - vec4 screenCoord = u_modelMatrix * vec4(a_position, 0, 1); - - gl_Position = screenCoord; - v_texCoord = ((screenCoord.xy * 0.5) + 0.5) * u_stageSize; - #else - gl_Position = u_projectionMatrix * u_modelMatrix * vec4(a_position, 0, 1); - v_texCoord = a_texCoord; - #endif + #ifdef DRAW_MODE_line + // Calculate a rotated ("tight") bounding box around the two pen points. + // Yes, we're doing this 6 times (once per vertex), but on actual GPU hardware, + // it's still faster than doing it in JS combined with the cost of uniformMatrix4fv. + + // Expand line bounds by sqrt(2) / 2 each side-- this ensures that all antialiased pixels + // fall within the quad, even at a 45-degree diagonal + vec2 position = a_position; + float expandedRadius = (u_lineThickness * 0.5) + 1.4142135623730951; + + float lineLength = length(u_penPoints.zw - u_penPoints.xy); + + position.x *= lineLength + (2.0 * expandedRadius); + position.y *= 2.0 * expandedRadius; + + // Center around first pen point + position -= expandedRadius; + + // Rotate quad to line angle + vec2 normalized = (u_penPoints.zw - u_penPoints.xy + epsilon) / (lineLength + epsilon); + position = mat2(normalized.x, normalized.y, -normalized.y, normalized.x) * position; + // Translate quad + position += u_penPoints.xy; + + // Apply view transform + position *= 2.0 / u_stageSize; + + gl_Position = vec4(position, 0, 1); + v_texCoord = position * 0.5 * u_stageSize; + #else + gl_Position = u_projectionMatrix * u_modelMatrix * vec4(a_position, 0, 1); + v_texCoord = a_texCoord; + #endif }