2017-08-11 21:11:57 -04:00
|
|
|
// pathfinder/shaders/gles2/common.inc.glsl
|
|
|
|
//
|
2017-10-03 18:32:03 -04:00
|
|
|
// Copyright (c) 2017 The Pathfinder Project Developers.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
2017-08-11 21:11:57 -04:00
|
|
|
|
2017-08-13 16:39:51 -04:00
|
|
|
#version 100
|
|
|
|
|
2017-08-15 20:28:07 -04:00
|
|
|
#extension GL_EXT_frag_depth : require
|
|
|
|
|
2017-09-07 17:58:41 -04:00
|
|
|
#define LCD_FILTER_FACTOR_0 (86.0 / 255.0)
|
|
|
|
#define LCD_FILTER_FACTOR_1 (77.0 / 255.0)
|
|
|
|
#define LCD_FILTER_FACTOR_2 (8.0 / 255.0)
|
|
|
|
|
2017-08-11 21:11:57 -04:00
|
|
|
#define MAX_PATHS 65536
|
|
|
|
|
2017-08-18 14:44:17 -04:00
|
|
|
#define EPSILON 0.001
|
|
|
|
|
2017-08-13 16:39:51 -04:00
|
|
|
precision highp float;
|
|
|
|
|
2018-01-05 15:18:50 -05:00
|
|
|
/// Returns true if the given number is close to zero.
|
2017-11-21 18:51:27 -05:00
|
|
|
bool isNearZero(float x) {
|
|
|
|
return abs(x) < EPSILON;
|
|
|
|
}
|
|
|
|
|
2017-10-26 23:30:47 -04:00
|
|
|
/// Computes `ia % ib`.
|
|
|
|
///
|
|
|
|
/// This function is not in OpenGL ES 2 but can be polyfilled.
|
|
|
|
/// See: https://stackoverflow.com/a/36078859
|
2017-08-13 16:39:51 -04:00
|
|
|
int imod(int ia, int ib) {
|
|
|
|
float a = float(ia), b = float(ib);
|
|
|
|
float m = a - floor((a + 0.5) / b) * b;
|
|
|
|
return int(floor(m + 0.5));
|
|
|
|
}
|
|
|
|
|
2017-10-26 23:30:47 -04:00
|
|
|
/// Returns the *2D* result of transforming the given 2D point with the given 4D transformation
|
|
|
|
/// matrix.
|
|
|
|
///
|
|
|
|
/// The z and w coordinates are treated as 0.0 and 1.0, respectively.
|
2017-08-11 21:11:57 -04:00
|
|
|
vec2 transformVertexPosition(vec2 position, mat4 transform) {
|
|
|
|
return (transform * vec4(position, 0.0, 1.0)).xy;
|
|
|
|
}
|
|
|
|
|
2017-10-26 23:30:47 -04:00
|
|
|
/// Returns the 2D result of transforming the given 2D position by the given ST-transform.
|
|
|
|
///
|
|
|
|
/// An ST-transform is a combined 2D scale and translation, where the (x, y) coordinates specify
|
|
|
|
/// the scale and and the (z, w) coordinates specify the translation.
|
2017-11-29 13:50:47 -05:00
|
|
|
vec2 transformVertexPositionST(vec2 position, vec4 transformST) {
|
|
|
|
return position * transformST.xy + transformST.zw;
|
|
|
|
}
|
|
|
|
|
|
|
|
vec2 transformVertexPositionAffine(vec2 position, vec4 transformST, vec2 transformExt) {
|
|
|
|
return position * transformST.xy + position.yx * transformExt + transformST.zw;
|
2017-08-22 21:25:32 -04:00
|
|
|
}
|
|
|
|
|
2017-10-26 23:30:47 -04:00
|
|
|
/// Interpolates the given 2D position in the vertical direction using the given ultra-slight
|
|
|
|
/// hints.
|
|
|
|
///
|
|
|
|
/// Similar in spirit to the `IUP[y]` TrueType command, but minimal.
|
|
|
|
///
|
|
|
|
/// pathHints.x: xHeight
|
|
|
|
/// pathHints.y: hintedXHeight
|
|
|
|
/// pathHints.z: stemHeight
|
|
|
|
/// pathHints.w: hintedStemHeight
|
|
|
|
///
|
|
|
|
/// TODO(pcwalton): Do something smarter with overshoots and the blue zone.
|
|
|
|
/// TODO(pcwalton): Support interpolating relative to arbitrary horizontal stems, not just the
|
|
|
|
/// baseline, x-height, and stem height.
|
2017-09-09 16:12:51 -04:00
|
|
|
vec2 hintPosition(vec2 position, vec4 pathHints) {
|
2017-09-29 21:02:33 -04:00
|
|
|
float y;
|
|
|
|
if (position.y >= pathHints.z) {
|
|
|
|
y = position.y - pathHints.z + pathHints.w;
|
|
|
|
} else if (position.y >= pathHints.x) {
|
|
|
|
float t = (position.y - pathHints.x) / (pathHints.z - pathHints.x);
|
|
|
|
y = mix(pathHints.y, pathHints.w, t);
|
|
|
|
} else if (position.y >= 0.0) {
|
|
|
|
y = mix(0.0, pathHints.y, position.y / pathHints.x);
|
|
|
|
} else {
|
|
|
|
y = position.y;
|
|
|
|
}
|
|
|
|
|
|
|
|
return vec2(position.x, y);
|
2017-09-09 16:12:51 -04:00
|
|
|
}
|
|
|
|
|
2017-10-26 23:30:47 -04:00
|
|
|
/// Converts the given 2D position in clip space to device pixel space (with origin in the lower
|
|
|
|
/// left).
|
2017-09-12 22:43:43 -04:00
|
|
|
vec2 convertClipToScreenSpace(vec2 position, ivec2 framebufferSize) {
|
|
|
|
return (position + 1.0) * 0.5 * vec2(framebufferSize);
|
|
|
|
}
|
|
|
|
|
2017-10-26 23:30:47 -04:00
|
|
|
/// Converts the given 2D position in device pixel space (with origin in the lower left) to clip
|
|
|
|
/// space.
|
2017-08-11 21:11:57 -04:00
|
|
|
vec2 convertScreenToClipSpace(vec2 position, ivec2 framebufferSize) {
|
|
|
|
return position / vec2(framebufferSize) * 2.0 - 1.0;
|
|
|
|
}
|
|
|
|
|
2017-10-26 23:30:47 -04:00
|
|
|
/// Packs the given path ID into a floating point value suitable for storage in the depth buffer.
|
|
|
|
/// This function returns values in clip space (i.e. what `gl_Position` is in).
|
2017-08-23 22:18:24 -04:00
|
|
|
float convertPathIndexToViewportDepthValue(int pathIndex) {
|
2017-10-20 19:29:05 -04:00
|
|
|
return float(pathIndex) / float(MAX_PATHS) * 2.0 - 1.0;
|
2017-08-11 21:11:57 -04:00
|
|
|
}
|
|
|
|
|
2017-10-26 23:30:47 -04:00
|
|
|
/// Packs the given path ID into a floating point value suitable for storage in the depth buffer.
|
|
|
|
///
|
|
|
|
/// This function returns values in window space (i.e. what `gl_FragDepth`/`gl_FragDepthEXT` is
|
|
|
|
/// in).
|
2017-08-23 22:18:24 -04:00
|
|
|
float convertPathIndexToWindowDepthValue(int pathIndex) {
|
|
|
|
return float(pathIndex) / float(MAX_PATHS);
|
|
|
|
}
|
|
|
|
|
2018-01-05 15:18:50 -05:00
|
|
|
/// Displaces the given point by the given distance in the direction of the normal angle.
|
2017-11-09 19:20:15 -05:00
|
|
|
vec2 dilatePosition(vec2 position, float normalAngle, vec2 amount) {
|
|
|
|
return position + vec2(cos(normalAngle), -sin(normalAngle)) * amount;
|
|
|
|
}
|
|
|
|
|
2018-01-07 14:14:24 -05:00
|
|
|
vec2 offsetPositionVertically(vec2 position, ivec2 framebufferSize, bool roundUp) {
|
|
|
|
position = convertClipToScreenSpace(position, framebufferSize);
|
|
|
|
position.y = roundUp ? ceil(position.y + 1.0) : floor(position.y - 1.0);
|
|
|
|
return convertScreenToClipSpace(position, framebufferSize);
|
|
|
|
}
|
|
|
|
|
2017-12-02 16:05:11 -05:00
|
|
|
vec2 computeMCAAPosition(vec2 position,
|
|
|
|
vec4 hints,
|
|
|
|
vec4 localTransformST,
|
|
|
|
vec4 globalTransformST,
|
|
|
|
ivec2 framebufferSize) {
|
2017-12-15 16:41:27 -05:00
|
|
|
if (position == vec2(0.0))
|
|
|
|
return position;
|
|
|
|
|
2017-12-02 16:05:11 -05:00
|
|
|
position = hintPosition(position, hints);
|
|
|
|
position = transformVertexPositionST(position, localTransformST);
|
|
|
|
position = transformVertexPositionST(position, globalTransformST);
|
|
|
|
return convertClipToScreenSpace(position, framebufferSize);
|
|
|
|
}
|
|
|
|
|
2017-12-15 16:41:27 -05:00
|
|
|
vec2 computeMCAASnappedPosition(vec2 position,
|
|
|
|
vec4 hints,
|
|
|
|
vec4 localTransformST,
|
|
|
|
vec4 globalTransformST,
|
|
|
|
ivec2 framebufferSize,
|
2018-01-02 22:15:19 -05:00
|
|
|
float slope,
|
|
|
|
bool snapToPixelGrid) {
|
2017-12-15 16:41:27 -05:00
|
|
|
position = hintPosition(position, hints);
|
|
|
|
position = transformVertexPositionST(position, localTransformST);
|
|
|
|
position = transformVertexPositionST(position, globalTransformST);
|
|
|
|
position = convertClipToScreenSpace(position, framebufferSize);
|
|
|
|
|
2018-01-02 22:15:19 -05:00
|
|
|
float xNudge;
|
|
|
|
if (snapToPixelGrid) {
|
|
|
|
xNudge = fract(position.x);
|
|
|
|
if (xNudge < 0.5)
|
|
|
|
xNudge = -xNudge;
|
|
|
|
else
|
|
|
|
xNudge = 1.0 - xNudge;
|
|
|
|
} else {
|
|
|
|
xNudge = 0.0;
|
|
|
|
}
|
2017-12-15 16:41:27 -05:00
|
|
|
|
2017-12-19 16:47:57 -05:00
|
|
|
return position + vec2(xNudge, xNudge * slope);
|
2017-12-15 16:41:27 -05:00
|
|
|
}
|
|
|
|
|
2017-12-02 16:05:11 -05:00
|
|
|
vec2 transformECAAPosition(vec2 position,
|
|
|
|
vec4 localTransformST,
|
|
|
|
vec2 localTransformExt,
|
|
|
|
mat4 globalTransform) {
|
|
|
|
position = transformVertexPositionAffine(position, localTransformST, localTransformExt);
|
|
|
|
return transformVertexPosition(position, globalTransform);
|
|
|
|
}
|
2017-08-18 20:12:58 -04:00
|
|
|
|
2017-12-02 16:05:11 -05:00
|
|
|
vec2 transformECAAPositionToScreenSpace(vec2 position,
|
|
|
|
vec4 localTransformST,
|
|
|
|
vec2 localTransformExt,
|
|
|
|
mat4 globalTransform,
|
|
|
|
ivec2 framebufferSize) {
|
|
|
|
position = transformECAAPosition(position,
|
|
|
|
localTransformST,
|
|
|
|
localTransformExt,
|
|
|
|
globalTransform);
|
|
|
|
return convertClipToScreenSpace(position, framebufferSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
vec2 computeECAAPosition(vec2 position,
|
|
|
|
float normalAngle,
|
|
|
|
vec2 emboldenAmount,
|
|
|
|
vec4 hints,
|
|
|
|
vec4 localTransformST,
|
|
|
|
vec2 localTransformExt,
|
|
|
|
mat4 globalTransform,
|
|
|
|
ivec2 framebufferSize) {
|
|
|
|
position = dilatePosition(position, normalAngle, emboldenAmount);
|
|
|
|
position = hintPosition(position, hints);
|
|
|
|
position = transformECAAPositionToScreenSpace(position,
|
|
|
|
localTransformST,
|
|
|
|
localTransformExt,
|
|
|
|
globalTransform,
|
|
|
|
framebufferSize);
|
|
|
|
return position;
|
|
|
|
}
|
|
|
|
|
|
|
|
float computeECAAWinding(inout vec2 leftPosition, inout vec2 rightPosition) {
|
|
|
|
float winding = sign(leftPosition.x - rightPosition.x);
|
|
|
|
if (winding > 0.0) {
|
|
|
|
vec2 tmp = leftPosition;
|
|
|
|
leftPosition = rightPosition;
|
|
|
|
rightPosition = tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rightPosition.x - leftPosition.x > EPSILON ? winding : 0.0;
|
2017-08-18 20:12:58 -04:00
|
|
|
}
|
|
|
|
|
2017-12-03 16:20:46 -05:00
|
|
|
vec2 computeECAAQuadPositionFromTransformedPositions(vec2 leftPosition,
|
|
|
|
vec2 rightPosition,
|
|
|
|
vec2 quadPosition,
|
|
|
|
ivec2 framebufferSize,
|
|
|
|
vec4 localTransformST,
|
|
|
|
vec2 localTransformExt,
|
|
|
|
mat4 globalTransform,
|
|
|
|
vec4 bounds,
|
|
|
|
vec3 leftTopRightEdges) {
|
|
|
|
vec2 edgeBL = bounds.xy, edgeTL = bounds.xw, edgeTR = bounds.zw, edgeBR = bounds.zy;
|
|
|
|
edgeBL = transformECAAPosition(edgeBL, localTransformST, localTransformExt, globalTransform);
|
|
|
|
edgeBR = transformECAAPosition(edgeBR, localTransformST, localTransformExt, globalTransform);
|
|
|
|
edgeTL = transformECAAPosition(edgeTL, localTransformST, localTransformExt, globalTransform);
|
|
|
|
edgeTR = transformECAAPosition(edgeTR, localTransformST, localTransformExt, globalTransform);
|
|
|
|
|
|
|
|
// Find the bottom of the path, and convert to clip space.
|
|
|
|
//
|
|
|
|
// FIXME(pcwalton): Speed this up somehow?
|
|
|
|
float pathBottomY = max(max(edgeBL.y, edgeBR.y), max(edgeTL.y, edgeTR.y));
|
|
|
|
pathBottomY = (pathBottomY + 1.0) * 0.5 * float(framebufferSize.y);
|
|
|
|
|
|
|
|
vec4 extents = vec4(leftTopRightEdges, pathBottomY);
|
2018-01-05 15:18:50 -05:00
|
|
|
vec2 position = mix(floor(extents.xy), ceil(extents.zw), quadPosition);
|
|
|
|
return convertScreenToClipSpace(position, framebufferSize);
|
2017-12-03 16:20:46 -05:00
|
|
|
}
|
|
|
|
|
2017-10-09 17:14:24 -04:00
|
|
|
// FIXME(pcwalton): Clean up this signature somehow?
|
|
|
|
bool computeECAAQuadPosition(out vec2 outPosition,
|
|
|
|
out float outWinding,
|
|
|
|
inout vec2 leftPosition,
|
|
|
|
inout vec2 rightPosition,
|
|
|
|
vec2 quadPosition,
|
|
|
|
ivec2 framebufferSize,
|
|
|
|
vec4 localTransformST,
|
2017-11-29 13:50:47 -05:00
|
|
|
vec2 localTransformExt,
|
2017-11-23 00:10:51 -05:00
|
|
|
mat4 globalTransform,
|
2017-10-09 17:14:24 -04:00
|
|
|
vec4 hints,
|
|
|
|
vec4 bounds,
|
2017-12-02 16:21:13 -05:00
|
|
|
vec2 normalAngles,
|
2017-10-09 17:14:24 -04:00
|
|
|
vec2 emboldenAmount) {
|
2017-12-02 16:05:11 -05:00
|
|
|
leftPosition = computeECAAPosition(leftPosition,
|
2017-12-02 16:21:13 -05:00
|
|
|
normalAngles.x,
|
2017-12-02 16:05:11 -05:00
|
|
|
emboldenAmount,
|
|
|
|
hints,
|
|
|
|
localTransformST,
|
|
|
|
localTransformExt,
|
|
|
|
globalTransform,
|
|
|
|
framebufferSize);
|
|
|
|
rightPosition = computeECAAPosition(rightPosition,
|
2017-12-02 16:21:13 -05:00
|
|
|
normalAngles.y,
|
2017-12-02 16:05:11 -05:00
|
|
|
emboldenAmount,
|
|
|
|
hints,
|
|
|
|
localTransformST,
|
|
|
|
localTransformExt,
|
|
|
|
globalTransform,
|
|
|
|
framebufferSize);
|
2017-11-23 00:10:51 -05:00
|
|
|
|
2017-12-02 16:05:11 -05:00
|
|
|
float winding = computeECAAWinding(leftPosition, rightPosition);
|
2017-11-23 00:10:51 -05:00
|
|
|
outWinding = winding;
|
2017-12-02 16:05:11 -05:00
|
|
|
if (winding == 0.0) {
|
2017-10-09 17:14:24 -04:00
|
|
|
outPosition = vec2(0.0);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-12-03 16:20:46 -05:00
|
|
|
vec3 leftTopRightEdges = vec3(leftPosition.x,
|
|
|
|
min(leftPosition.y, rightPosition.y),
|
|
|
|
rightPosition.x);
|
|
|
|
outPosition = computeECAAQuadPositionFromTransformedPositions(leftPosition,
|
|
|
|
rightPosition,
|
|
|
|
quadPosition,
|
|
|
|
framebufferSize,
|
|
|
|
localTransformST,
|
|
|
|
localTransformExt,
|
|
|
|
globalTransform,
|
|
|
|
bounds,
|
|
|
|
leftTopRightEdges);
|
2017-10-09 17:14:24 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-12-03 16:20:46 -05:00
|
|
|
bool splitCurveAndComputeECAAWinding(out float outWinding,
|
|
|
|
out vec3 outLeftTopRightEdges,
|
|
|
|
inout vec2 leftPosition,
|
|
|
|
inout vec2 rightPosition,
|
|
|
|
vec2 controlPointPosition,
|
|
|
|
int passIndex) {
|
|
|
|
// Split at the X inflection point if necessary.
|
|
|
|
float num = leftPosition.x - controlPointPosition.x;
|
|
|
|
float denom = leftPosition.x - 2.0 * controlPointPosition.x + rightPosition.x;
|
|
|
|
float inflectionT = num / denom;
|
|
|
|
if (inflectionT > EPSILON && inflectionT < 1.0 - EPSILON) {
|
|
|
|
vec2 newCP0 = mix(leftPosition, controlPointPosition, inflectionT);
|
|
|
|
vec2 newCP1 = mix(controlPointPosition, rightPosition, inflectionT);
|
|
|
|
vec2 inflectionPoint = mix(newCP0, newCP1, inflectionT);
|
|
|
|
if (passIndex == 0) {
|
|
|
|
controlPointPosition = newCP0;
|
|
|
|
rightPosition = inflectionPoint;
|
|
|
|
} else {
|
|
|
|
controlPointPosition = newCP1;
|
|
|
|
leftPosition = inflectionPoint;
|
|
|
|
}
|
|
|
|
} else if (passIndex != 0) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-12-02 16:05:11 -05:00
|
|
|
|
|
|
|
float winding = computeECAAWinding(leftPosition, rightPosition);
|
2017-12-03 16:20:46 -05:00
|
|
|
outWinding = winding;
|
|
|
|
if (winding == 0.0)
|
2017-11-28 20:05:59 -05:00
|
|
|
return false;
|
|
|
|
|
2017-12-03 16:20:46 -05:00
|
|
|
outLeftTopRightEdges = vec3(min(leftPosition.x, controlPointPosition.x),
|
|
|
|
min(min(leftPosition.y, controlPointPosition.y), rightPosition.y),
|
|
|
|
max(rightPosition.x, controlPointPosition.x));
|
2017-11-28 20:05:59 -05:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-12-30 20:32:51 -05:00
|
|
|
/// Returns true if the slope of the line along the given vector is negative.
|
2017-11-11 01:09:35 -05:00
|
|
|
bool slopeIsNegative(vec2 dp) {
|
2017-11-14 20:10:48 -05:00
|
|
|
return dp.y < 0.0;
|
2017-11-11 01:09:35 -05:00
|
|
|
}
|
|
|
|
|
2017-11-21 18:51:27 -05:00
|
|
|
/// 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));
|
|
|
|
}
|
2017-08-18 20:12:58 -04:00
|
|
|
|
2017-11-21 18:51:27 -05:00
|
|
|
/// 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);
|
|
|
|
return vec4(p0 + dP * tY.x, dP * (tY.y - tY.x));
|
2017-11-11 01:09:35 -05:00
|
|
|
}
|
|
|
|
|
2017-11-21 18:51:27 -05:00
|
|
|
/// 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;
|
2017-11-11 01:09:35 -05:00
|
|
|
|
2017-08-18 20:12:58 -04:00
|
|
|
// If the line doesn't pass through this pixel, detect that and bail.
|
2017-10-09 17:14:24 -04:00
|
|
|
//
|
|
|
|
// This should be worth a branch because it's very common for fragment blocks to all hit this
|
|
|
|
// path.
|
2017-12-05 21:09:58 -05:00
|
|
|
//
|
|
|
|
// The variable is required to work around a bug in the macOS Nvidia drivers.
|
|
|
|
// Without moving the condition in a variable, the early return is ignored. See #51.
|
|
|
|
bool lineDoesNotPassThroughPixel = isNearZero(dP.x) && isNearZero(dP.y);
|
|
|
|
if (lineDoesNotPassThroughPixel)
|
2017-11-21 18:51:27 -05:00
|
|
|
return p0X.y < pixelTop ? winding * dPX.x : 0.0;
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
2017-08-18 20:12:58 -04:00
|
|
|
|
|
|
|
// Calculate area with the shoelace formula.
|
2017-11-11 01:09:35 -05:00
|
|
|
// This is conceptually the sum of 5 determinants for points A0-A5, where A2-A5 are:
|
|
|
|
//
|
|
|
|
// A2 = (a2.x, a1.y)
|
2017-11-14 20:10:48 -05:00
|
|
|
// A3 = (a2.x, top)
|
|
|
|
// A4 = (a0.x, top)
|
2017-11-11 01:09:35 -05:00
|
|
|
//
|
|
|
|
// The formula is optimized. See: http://geomalgorithms.com/a01-_area.html
|
2017-11-14 20:10:48 -05:00
|
|
|
float area = a0.x * (a0.y + a1.y - 2.0 * pixelTop) +
|
2017-11-11 01:09:35 -05:00
|
|
|
a1.x * (a1.y - a0.y) +
|
2017-11-14 20:10:48 -05:00
|
|
|
2.0 * a2x * (pixelTop - a1.y);
|
2017-10-09 17:14:24 -04:00
|
|
|
return abs(area) * winding * 0.5;
|
2017-08-18 20:12:58 -04:00
|
|
|
}
|
|
|
|
|
2017-11-21 18:51:27 -05:00
|
|
|
/// 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);
|
2017-11-11 01:09:35 -05:00
|
|
|
}
|
2017-10-30 16:34:55 -04:00
|
|
|
|
2017-12-30 20:32:51 -05:00
|
|
|
/// Solves the equation:
|
|
|
|
///
|
|
|
|
/// x = p0x + t^2 * (p0x - 2*p1x + p2x) + t*(2*p1x - 2*p0x)
|
|
|
|
///
|
|
|
|
/// We use the Citardauq Formula to avoid floating point precision issues.
|
2017-11-11 01:09:35 -05:00
|
|
|
vec2 solveCurveT(float p0x, float p1x, float p2x, vec2 x) {
|
|
|
|
float a = p0x - 2.0 * p1x + p2x;
|
|
|
|
float b = 2.0 * p1x - 2.0 * p0x;
|
|
|
|
vec2 c = p0x - x;
|
|
|
|
return 2.0 * c / (-b - sqrt(b * b - 4.0 * a * c));
|
2017-10-30 16:34:55 -04:00
|
|
|
}
|
|
|
|
|
2017-12-30 20:32:51 -05:00
|
|
|
/// Applies a slight horizontal blur to reduce color fringing on LCD screens
|
|
|
|
/// when performing subpixel AA.
|
|
|
|
///
|
|
|
|
/// The algorithm should be identical to that of FreeType:
|
|
|
|
/// https://www.freetype.org/freetype2/docs/reference/ft2-lcd_filtering.html
|
2017-09-07 17:58:41 -04:00
|
|
|
float lcdFilter(float shadeL2, float shadeL1, float shade0, float shadeR1, float shadeR2) {
|
|
|
|
return LCD_FILTER_FACTOR_2 * shadeL2 +
|
|
|
|
LCD_FILTER_FACTOR_1 * shadeL1 +
|
|
|
|
LCD_FILTER_FACTOR_0 * shade0 +
|
|
|
|
LCD_FILTER_FACTOR_1 * shadeR1 +
|
|
|
|
LCD_FILTER_FACTOR_2 * shadeR2;
|
|
|
|
}
|
|
|
|
|
2017-11-07 20:24:19 -05:00
|
|
|
float gammaCorrectChannel(float fgColor, float bgColor, sampler2D gammaLUT) {
|
|
|
|
return texture2D(gammaLUT, vec2(fgColor, 1.0 - bgColor)).r;
|
|
|
|
}
|
|
|
|
|
|
|
|
// `fgColor` is in linear space.
|
|
|
|
vec3 gammaCorrect(vec3 fgColor, vec3 bgColor, sampler2D gammaLUT) {
|
|
|
|
return vec3(gammaCorrectChannel(fgColor.r, bgColor.r, gammaLUT),
|
|
|
|
gammaCorrectChannel(fgColor.g, bgColor.g, gammaLUT),
|
|
|
|
gammaCorrectChannel(fgColor.b, bgColor.b, gammaLUT));
|
|
|
|
}
|
|
|
|
|
2017-08-11 21:11:57 -04:00
|
|
|
vec4 fetchFloat4Data(sampler2D dataTexture, int index, ivec2 dimensions) {
|
2017-08-13 16:39:51 -04:00
|
|
|
ivec2 pixelCoord = ivec2(imod(index, dimensions.x), index / dimensions.x);
|
|
|
|
return texture2D(dataTexture, (vec2(pixelCoord) + 0.5) / vec2(dimensions));
|
|
|
|
}
|
|
|
|
|
2017-11-29 13:50:47 -05:00
|
|
|
vec2 fetchFloat2Data(sampler2D dataTexture, int index, ivec2 dimensions) {
|
|
|
|
int texelIndex = index / 2;
|
|
|
|
vec4 texel = fetchFloat4Data(dataTexture, texelIndex, dimensions);
|
|
|
|
return texelIndex * 2 == index ? texel.xy : texel.zw;
|
|
|
|
}
|
|
|
|
|
|
|
|
vec4 fetchPathAffineTransform(out vec2 outPathTransformExt,
|
|
|
|
sampler2D pathTransformSTTexture,
|
|
|
|
ivec2 pathTransformSTDimensions,
|
|
|
|
sampler2D pathTransformExtTexture,
|
|
|
|
ivec2 pathTransformExtDimensions,
|
|
|
|
int pathID) {
|
|
|
|
outPathTransformExt = fetchFloat2Data(pathTransformExtTexture,
|
|
|
|
pathID,
|
|
|
|
pathTransformExtDimensions);
|
|
|
|
return fetchFloat4Data(pathTransformSTTexture, pathID, pathTransformSTDimensions);
|
|
|
|
}
|