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;
|
|
|
|
|
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-08-22 21:25:32 -04:00
|
|
|
vec2 transformVertexPositionST(vec2 position, vec4 stTransform) {
|
|
|
|
return position * stTransform.xy + stTransform.zw;
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2017-10-30 16:34:55 -04:00
|
|
|
int convertWindowDepthValueToPathIndex(float depthValue) {
|
|
|
|
float pathIndex = floor(depthValue * float(MAX_PATHS));
|
|
|
|
return int(pathIndex);
|
|
|
|
}
|
|
|
|
|
2017-11-09 19:20:15 -05:00
|
|
|
vec2 dilatePosition(vec2 position, float normalAngle, vec2 amount) {
|
|
|
|
return position + vec2(cos(normalAngle), -sin(normalAngle)) * amount;
|
|
|
|
}
|
|
|
|
|
2017-10-09 17:14:24 -04:00
|
|
|
bool computeMCAAQuadPosition(out vec2 outPosition,
|
|
|
|
inout vec2 leftPosition,
|
|
|
|
inout vec2 rightPosition,
|
|
|
|
vec2 quadPosition,
|
|
|
|
ivec2 framebufferSize,
|
|
|
|
vec4 localTransformST,
|
|
|
|
vec4 globalTransformST,
|
|
|
|
vec4 hints) {
|
2017-09-09 16:12:51 -04:00
|
|
|
leftPosition = hintPosition(leftPosition, hints);
|
|
|
|
rightPosition = hintPosition(rightPosition, hints);
|
|
|
|
|
2017-09-12 22:43:43 -04:00
|
|
|
leftPosition = transformVertexPositionST(leftPosition, localTransformST);
|
|
|
|
rightPosition = transformVertexPositionST(rightPosition, localTransformST);
|
|
|
|
|
|
|
|
leftPosition = transformVertexPositionST(leftPosition, globalTransformST);
|
|
|
|
rightPosition = transformVertexPositionST(rightPosition, globalTransformST);
|
|
|
|
|
|
|
|
leftPosition = convertClipToScreenSpace(leftPosition, framebufferSize);
|
|
|
|
rightPosition = convertClipToScreenSpace(rightPosition, framebufferSize);
|
2017-08-18 23:23:45 -04:00
|
|
|
|
2017-08-18 20:12:58 -04:00
|
|
|
if (abs(leftPosition.x - rightPosition.x) <= EPSILON) {
|
|
|
|
outPosition = vec2(0.0);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
vec2 verticalExtents = vec2(min(leftPosition.y, rightPosition.y),
|
|
|
|
max(leftPosition.y, rightPosition.y));
|
|
|
|
|
|
|
|
vec4 roundedExtents = vec4(floor(vec2(leftPosition.x, verticalExtents.x)),
|
2017-08-18 23:23:45 -04:00
|
|
|
ceil(vec2(rightPosition.x, verticalExtents.y)));
|
2017-08-18 20:12:58 -04:00
|
|
|
|
2017-08-23 22:18:24 -04:00
|
|
|
vec2 position = mix(roundedExtents.xy, roundedExtents.zw, quadPosition);
|
2017-08-18 20:12:58 -04:00
|
|
|
outPosition = convertScreenToClipSpace(position, framebufferSize);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
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-23 00:10:51 -05:00
|
|
|
mat4 globalTransform,
|
2017-10-09 17:14:24 -04:00
|
|
|
vec4 hints,
|
|
|
|
vec4 bounds,
|
|
|
|
float leftNormalAngle,
|
|
|
|
float rightNormalAngle,
|
|
|
|
vec2 emboldenAmount) {
|
2017-11-23 00:10:51 -05:00
|
|
|
vec2 edgeBL = bounds.xy, edgeTL = bounds.xw, edgeTR = bounds.zw, edgeBR = bounds.zy;
|
|
|
|
|
2017-11-09 19:20:15 -05:00
|
|
|
leftPosition = dilatePosition(leftPosition, leftNormalAngle, emboldenAmount);
|
|
|
|
rightPosition = dilatePosition(rightPosition, rightNormalAngle, emboldenAmount);
|
2017-10-09 17:14:24 -04:00
|
|
|
|
|
|
|
leftPosition = hintPosition(leftPosition, hints);
|
|
|
|
rightPosition = hintPosition(rightPosition, hints);
|
|
|
|
|
|
|
|
leftPosition = transformVertexPositionST(leftPosition, localTransformST);
|
|
|
|
rightPosition = transformVertexPositionST(rightPosition, localTransformST);
|
2017-11-23 00:10:51 -05:00
|
|
|
edgeBL = transformVertexPositionST(edgeBL, localTransformST);
|
|
|
|
edgeTL = transformVertexPositionST(edgeTL, localTransformST);
|
|
|
|
edgeBR = transformVertexPositionST(edgeBR, localTransformST);
|
|
|
|
edgeTR = transformVertexPositionST(edgeTR, localTransformST);
|
|
|
|
|
|
|
|
leftPosition = transformVertexPosition(leftPosition, globalTransform);
|
|
|
|
rightPosition = transformVertexPosition(rightPosition, globalTransform);
|
|
|
|
edgeBL = transformVertexPosition(edgeBL, globalTransform);
|
|
|
|
edgeTL = transformVertexPosition(edgeTL, globalTransform);
|
|
|
|
edgeBR = transformVertexPosition(edgeBR, globalTransform);
|
|
|
|
edgeTR = transformVertexPosition(edgeTR, globalTransform);
|
2017-10-09 17:14:24 -04:00
|
|
|
|
|
|
|
leftPosition = convertClipToScreenSpace(leftPosition, framebufferSize);
|
|
|
|
rightPosition = convertClipToScreenSpace(rightPosition, framebufferSize);
|
|
|
|
|
|
|
|
float winding = sign(leftPosition.x - rightPosition.x);
|
2017-11-23 00:10:51 -05:00
|
|
|
outWinding = winding;
|
2017-10-09 17:14:24 -04:00
|
|
|
if (winding > 0.0) {
|
|
|
|
vec2 tmp = leftPosition;
|
|
|
|
leftPosition = rightPosition;
|
|
|
|
rightPosition = tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rightPosition.x - leftPosition.x <= EPSILON) {
|
|
|
|
outPosition = vec2(0.0);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-11-23 00:10:51 -05:00
|
|
|
// 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);
|
|
|
|
|
2017-10-09 17:14:24 -04:00
|
|
|
vec4 roundedExtents = vec4(floor(leftPosition.x),
|
|
|
|
floor(min(leftPosition.y, rightPosition.y)),
|
|
|
|
ceil(rightPosition.x),
|
2017-11-23 00:10:51 -05:00
|
|
|
ceil(pathBottomY));
|
2017-10-09 17:14:24 -04:00
|
|
|
|
|
|
|
vec2 position = mix(roundedExtents.xy, roundedExtents.zw, quadPosition);
|
|
|
|
outPosition = convertScreenToClipSpace(position, framebufferSize);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-11-28 20:05:59 -05:00
|
|
|
bool computeECAAMultiEdgeMaskQuadPosition(out vec2 outPosition,
|
|
|
|
inout vec2 leftPosition,
|
|
|
|
inout vec2 rightPosition,
|
|
|
|
vec2 quadPosition,
|
|
|
|
ivec2 framebufferSize,
|
|
|
|
vec4 localTransformST,
|
|
|
|
mat4 globalTransform) {
|
|
|
|
leftPosition = transformVertexPositionST(leftPosition, localTransformST);
|
|
|
|
rightPosition = transformVertexPositionST(rightPosition, localTransformST);
|
|
|
|
|
|
|
|
leftPosition = transformVertexPosition(leftPosition, globalTransform);
|
|
|
|
rightPosition = transformVertexPosition(rightPosition, globalTransform);
|
|
|
|
|
|
|
|
leftPosition = convertClipToScreenSpace(leftPosition, framebufferSize);
|
|
|
|
rightPosition = convertClipToScreenSpace(rightPosition, framebufferSize);
|
|
|
|
|
|
|
|
float winding = sign(leftPosition.x - rightPosition.x);
|
|
|
|
if (winding > 0.0) {
|
|
|
|
vec2 tmp = leftPosition;
|
|
|
|
leftPosition = rightPosition;
|
|
|
|
rightPosition = tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rightPosition.x - leftPosition.x <= EPSILON) {
|
|
|
|
outPosition = vec2(0.0);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
vec2 verticalExtents = vec2(min(leftPosition.y, rightPosition.y),
|
|
|
|
max(leftPosition.y, rightPosition.y));
|
|
|
|
|
|
|
|
vec4 roundedExtents = vec4(floor(vec2(leftPosition.x, verticalExtents.x)),
|
|
|
|
ceil(vec2(rightPosition.x, verticalExtents.y)));
|
|
|
|
|
|
|
|
vec2 position = mix(roundedExtents.xy, roundedExtents.zw, quadPosition);
|
|
|
|
outPosition = convertScreenToClipSpace(position, framebufferSize);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
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-14 20:10:48 -05:00
|
|
|
bool lineDoesNotPassThroughPixel(vec2 t) {
|
|
|
|
return t.x >= t.y;
|
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-11-21 18:51:27 -05:00
|
|
|
if (isNearZero(dP.x) && isNearZero(dP.y))
|
|
|
|
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-11-11 01:09:35 -05:00
|
|
|
// Solve 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.
|
|
|
|
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-09-07 17:58:41 -04:00
|
|
|
// https://www.freetype.org/freetype2/docs/reference/ft2-lcd_filtering.html
|
|
|
|
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-16 22:51:13 -04:00
|
|
|
int unpackUInt16(vec2 packedValue) {
|
|
|
|
ivec2 valueBytes = ivec2(floor(packedValue * 255.0));
|
|
|
|
return valueBytes.y * 256 + valueBytes.x;
|
|
|
|
}
|
|
|
|
|
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-08-16 01:09:09 -04:00
|
|
|
vec2 packPathID(int pathID) {
|
|
|
|
return vec2(imod(pathID, 256), pathID / 256) / 255.0;
|
|
|
|
}
|
2017-08-16 19:37:39 -04:00
|
|
|
|
|
|
|
int unpackPathID(vec2 packedPathID) {
|
2017-08-16 22:51:13 -04:00
|
|
|
return unpackUInt16(packedPathID);
|
2017-08-16 19:37:39 -04:00
|
|
|
}
|