blob: 4a89ebe2759addd9c65322915219744dd394fee0 [file] [log] [blame]
// Graphite-specific fragment shader code
const int $kTileModeClamp = 0;
const int $kTileModeRepeat = 1;
const int $kTileModeMirror = 2;
const int $kTileModeDecal = 3;
const int $kFilterModeNearest = 0;
const int $kFilterModeLinear = 1;
$pure half4 sk_error() {
return half4(1.0, 0.0, 1.0, 1.0);
}
$pure half4 sk_passthrough(half4 color) {
return color;
}
$pure half4 sk_solid_shader(float4 colorParam) {
return half4(colorParam);
}
// The localMatrix is passed to the child by the glue code. This snippet just needs to bubble the
// child's output back up. Local coordinates are passed in from the default glue code and can
// just be ignored here.
$pure half4 sk_local_matrix_shader(float4, float4x4, half4 childResult) {
return childResult;
}
$pure float $tile(int tileMode, float f, float low, float high) {
switch (tileMode) {
case $kTileModeClamp:
return clamp(f, low, high);
case $kTileModeRepeat: {
float length = high - low;
return (mod(f - low, length) + low);
}
case $kTileModeMirror: {
float length = high - low;
float length2 = 2 * length;
float tmp = mod(f - low, length2);
return (mix(tmp, length2 - tmp, step(length, tmp)) + low);
}
default: // $kTileModeDecal
// Decal is handled later.
return f;
}
}
$pure half4 sk_image_shader(float4 coords,
float4x4 preLocal,
float4 subset,
int tileModeX,
int tileModeY,
int filterMode,
int imgWidth,
int imgHeight,
sampler2D s) {
float2 pos = (preLocal * coords).xy;
// Do hard-edge shader transitions to the border color for nearest-neighbor decal tiling at the
// subset boundaries. Snap the input coordinates to nearest neighbor before comparing to the
// subset rect, to avoid GPU interpolation errors. See https://crbug.com/skia/10403.
if (tileModeX == $kTileModeDecal && filterMode == $kFilterModeNearest) {
float snappedX = floor(pos.x) + 0.5;
if (snappedX < subset.x || snappedX > subset.z) {
return half4(0);
}
}
if (tileModeY == $kTileModeDecal && filterMode == $kFilterModeNearest) {
float snappedY = floor(pos.y) + 0.5;
if (snappedY < subset.y || snappedY > subset.w) {
return half4(0);
}
}
pos.x = $tile(tileModeX, pos.x, subset.x, subset.z);
pos.y = $tile(tileModeY, pos.y, subset.y, subset.w);
// Clamp to an inset subset to prevent sampling neighboring texels when coords fall exactly at
// texel boundaries.
float4 insetClamp;
if (filterMode == $kFilterModeNearest) {
insetClamp = float4(floor(subset.xy) + 0.5, ceil(subset.zw) - 0.5);
} else {
insetClamp = float4(subset.xy + 0.5, subset.zw - 0.5);
}
float2 clampedPos = clamp(pos, insetClamp.xy, insetClamp.zw);
half4 color = sample(s, clampedPos / float2(imgWidth, imgHeight));
// Remember the amount the coord moved for clamping. This will be used to implement shader-based
// filtering for repeat and decal tiling.
half2 error = half2(pos - clampedPos);
// Do soft edge shader filtering for decal tiling and linear filtering using the error values
// calculated above.
if (tileModeX == $kTileModeDecal && filterMode == $kFilterModeLinear) {
color *= max(1 - abs(error.x), 0);
}
if (tileModeY == $kTileModeDecal && filterMode == $kFilterModeLinear) {
color *= max(1 - abs(error.y), 0);
}
return color;
}
$pure float2 $tile_grad(int tileMode, float2 t) {
switch (tileMode) {
case $kTileModeClamp:
t.x = clamp(t.x, 0, 1);
break;
case $kTileModeRepeat:
t.x = fract(t.x);
break;
case $kTileModeMirror: {
float t_1 = t.x - 1;
t.x = t_1 - 2 * floor(t_1 * 0.5) - 1;
if (sk_Caps.mustDoOpBetweenFloorAndAbs) {
// At this point the expected value of tiled_t should between -1 and 1, so this
// clamp has no effect other than to break up the floor and abs calls and make sure
// the compiler doesn't merge them back together.
t.x = clamp(t.x, -1, 1);
}
t.x = abs(t.x);
break;
}
case $kTileModeDecal:
if (t.x < 0 || t.x > 1) {
return float2(0, -1);
}
break;
}
return t;
}
$pure half4 $colorize_grad_4(float4 colorsParam[4], float offsetsParam[4], float2 t) {
if (t.y < 0) {
return half4(0);
} else if (t.x <= offsetsParam[0]) {
return half4(colorsParam[0]);
} else if (t.x < offsetsParam[1]) {
return half4(mix(colorsParam[0], colorsParam[1], (t.x - offsetsParam[0]) /
(offsetsParam[1] - offsetsParam[0])));
} else if (t.x < offsetsParam[2]) {
return half4(mix(colorsParam[1], colorsParam[2], (t.x - offsetsParam[1]) /
(offsetsParam[2] - offsetsParam[1])));
} else if (t.x < offsetsParam[3]) {
return half4(mix(colorsParam[2], colorsParam[3], (t.x - offsetsParam[2]) /
(offsetsParam[3] - offsetsParam[2])));
} else {
return half4(colorsParam[3]);
}
}
$pure half4 $colorize_grad_8(float4 colorsParam[8], float offsetsParam[8], float2 t) {
if (t.y < 0) {
return half4(0);
// Unrolled binary search through intervals
// ( .. 0), (0 .. 1), (1 .. 2), (2 .. 3), (3 .. 4), (4 .. 5), (5 .. 6), (6 .. 7), (7 .. ).
} else if (t.x < offsetsParam[4]) {
if (t.x < offsetsParam[2]) {
if (t.x <= offsetsParam[0]) {
return half4(colorsParam[0]);
} else if (t.x < offsetsParam[1]) {
return half4(mix(colorsParam[0], colorsParam[1],
(t.x - offsetsParam[0]) /
(offsetsParam[1] - offsetsParam[0])));
} else {
return half4(mix(colorsParam[1], colorsParam[2],
(t.x - offsetsParam[1]) /
(offsetsParam[2] - offsetsParam[1])));
}
} else {
if (t.x < offsetsParam[3]) {
return half4(mix(colorsParam[2], colorsParam[3],
(t.x - offsetsParam[2]) /
(offsetsParam[3] - offsetsParam[2])));
} else {
return half4(mix(colorsParam[3], colorsParam[4],
(t.x - offsetsParam[3]) /
(offsetsParam[4] - offsetsParam[3])));
}
}
} else {
if (t.x < offsetsParam[6]) {
if (t.x < offsetsParam[5]) {
return half4(mix(colorsParam[4], colorsParam[5],
(t.x - offsetsParam[4]) /
(offsetsParam[5] - offsetsParam[4])));
} else {
return half4(mix(colorsParam[5], colorsParam[6],
(t.x - offsetsParam[5]) /
(offsetsParam[6] - offsetsParam[5])));
}
} else {
if (t.x < offsetsParam[7]) {
return half4(mix(colorsParam[6], colorsParam[7],
(t.x - offsetsParam[6]) /
(offsetsParam[7] - offsetsParam[6])));
} else {
return half4(colorsParam[7]);
}
}
}
}
$pure float2 $linear_grad_layout(float2 point0Param, float2 point1Param, float2 pos) {
pos -= point0Param;
float2 delta = point1Param - point0Param;
float t = dot(pos, delta) / dot(delta, delta);
return float2(t, 1);
}
$pure float2 $radial_grad_layout(float2 centerParam, float radiusParam, float2 pos) {
float t = distance(pos, centerParam) / radiusParam;
return float2(t, 1);
}
$pure float2 $sweep_grad_layout(float2 centerParam, float biasParam, float scaleParam, float2 pos) {
pos -= centerParam;
// Some devices incorrectly implement atan2(y,x) as atan(y/x). In actuality it is
// atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). To work around this we pass in
// (sqrt(x^2 + y^2) + x) as the second parameter to atan2 in these cases. We let the device
// handle the undefined behavior if the second parameter is 0, instead of doing the divide
// ourselves and calling atan with the quotient.
float angle = sk_Caps.atan2ImplementedAsAtanYOverX ? 2 * atan(-pos.y, length(pos) - pos.x)
: atan(-pos.y, -pos.x);
// 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi]
float t = (angle * 0.1591549430918 + 0.5 + biasParam) * scaleParam;
return float2(t, 1);
}
$pure float3x3 $map_to_unit_x(float2 p0, float2 p1) {
// Returns a matrix that maps [p0, p1] to [(0, 0), (1, 0)]. Results are undefined if p0 = p1.
// From skia/src/core/SkMatrix.cpp, SkMatrix::setPolyToPoly.
return float3x3(
0, -1, 0,
1, 0, 0,
0, 0, 1
) * inverse(float3x3(
p1.y - p0.y, p0.x - p1.x, 0,
p1.x - p0.x, p1.y - p0.y, 0,
p0.x, p0.y, 1
));
}
$pure float2 $conical_grad_layout(float2 point0Param,
float2 point1Param,
float radius0Param,
float radius1Param,
float2 pos) {
const float SK_ScalarNearlyZero = 1.0 / (1 << 12);
float dCenter = distance(point0Param, point1Param);
float dRadius = radius1Param - radius0Param;
// Degenerate case: a radial gradient (p0 = p1).
bool radial = dCenter < SK_ScalarNearlyZero;
// Degenerate case: a strip with bandwidth 2r (r0 = r1).
bool strip = abs(dRadius) < SK_ScalarNearlyZero;
if (radial) {
if (strip) {
// The start and end inputs are the same in both position and radius.
// We don't expect to see this input, but just in case we avoid dividing by zero.
return float2(0, -1);
}
float scale = 1 / dRadius;
float scaleSign = sign(dRadius);
float bias = radius0Param / dRadius;
float2 pt = (pos - point0Param) * scale;
float t = length(pt) * scaleSign - bias;
return float2(t, 1);
} else if (strip) {
float3x3 transform = $map_to_unit_x(point0Param, point1Param);
float r = radius0Param / dCenter;
float r_2 = r * r;
float2 pt = (transform * pos.xy1).xy;
float t = r_2 - pt.y * pt.y;
if (t < 0) {
return float2(0, -1);
}
t = pt.x + sqrt(t);
return float2(t, 1);
} else {
// See https://skia.org/docs/dev/design/conical/ for details on how this algorithm works.
// Calculate f and swap inputs if necessary (steps 1 and 2).
float f = radius0Param / (radius0Param - radius1Param);
bool isSwapped = abs(f - 1) < SK_ScalarNearlyZero;
if (isSwapped) {
float2 tmpPt = point0Param;
point0Param = point1Param;
point1Param = tmpPt;
f = 0;
}
// Apply mapping from [Cf, C1] to unit x, and apply the precalculations from steps 3 and 4,
// all in the same transformation.
float2 Cf = point0Param * (1 - f) + point1Param * f;
float3x3 transform = $map_to_unit_x(Cf, point1Param);
float scaleX = abs(1 - f);
float scaleY = scaleX;
float r1 = abs(radius1Param - radius0Param) / dCenter;
bool isFocalOnCircle = abs(r1 - 1) < SK_ScalarNearlyZero;
if (isFocalOnCircle) {
scaleX *= 0.5;
scaleY *= 0.5;
} else {
scaleX *= r1 / (r1 * r1 - 1);
scaleY /= sqrt(abs(r1 * r1 - 1));
}
transform = float3x3(
scaleX, 0, 0,
0, scaleY, 0,
0, 0, 1
) * transform;
float2 pt = (transform * pos.xy1).xy;
// Continue with step 5 onward.
float invR1 = 1 / r1;
float dRadiusSign = sign(1 - f);
bool isWellBehaved = !isFocalOnCircle && r1 > 1;
float x_t = -1;
if (isFocalOnCircle) {
x_t = dot(pt, pt) / pt.x;
} else if (isWellBehaved) {
x_t = length(pt) - pt.x * invR1;
} else {
float temp = pt.x * pt.x - pt.y * pt.y;
if (temp >= 0) {
if (isSwapped || dRadiusSign < 0) {
x_t = -sqrt(temp) - pt.x * invR1;
} else {
x_t = sqrt(temp) - pt.x * invR1;
}
}
}
if (!isWellBehaved && x_t < 0) {
return float2(0, -1);
}
float t = f + dRadiusSign * x_t;
if (isSwapped) {
t = 1 - t;
}
return float2(t, 1);
}
}
$pure half4 sk_linear_grad_4_shader(float4 coords,
float4x4 preLocal,
float4 colorsParam[4],
float offsetsParam[4],
float2 point0Param,
float2 point1Param,
int tileMode) {
float2 pos = (preLocal * coords).xy;
float2 t = $linear_grad_layout(point0Param, point1Param, pos);
t = $tile_grad(tileMode, t);
return $colorize_grad_4(colorsParam, offsetsParam, t);
}
$pure half4 sk_linear_grad_8_shader(float4 coords,
float4x4 preLocal,
float4 colorsParam[8],
float offsetsParam[8],
float2 point0Param,
float2 point1Param,
int tileMode) {
float2 pos = (preLocal * coords).xy;
float2 t = $linear_grad_layout(point0Param, point1Param, pos);
t = $tile_grad(tileMode, t);
return $colorize_grad_8(colorsParam, offsetsParam, t);
}
$pure half4 sk_radial_grad_4_shader(float4 coords,
float4x4 preLocal,
float4 colorsParam[4],
float offsetsParam[4],
float2 centerParam,
float radiusParam,
int tileMode) {
float2 pos = (preLocal * coords).xy;
float2 t = $radial_grad_layout(centerParam, radiusParam, pos);
t = $tile_grad(tileMode, t);
return $colorize_grad_4(colorsParam, offsetsParam, t);
}
$pure half4 sk_radial_grad_8_shader(float4 coords,
float4x4 preLocal,
float4 colorsParam[8],
float offsetsParam[8],
float2 centerParam,
float radiusParam,
int tileMode) {
float2 pos = (preLocal * coords).xy;
float2 t = $radial_grad_layout(centerParam, radiusParam, pos);
t = $tile_grad(tileMode, t);
return $colorize_grad_8(colorsParam, offsetsParam, t);
}
$pure half4 sk_sweep_grad_4_shader(float4 coords,
float4x4 preLocal,
float4 colorsParam[4],
float offsetsParam[4],
float2 centerParam,
float biasParam,
float scaleParam,
int tileMode) {
float2 pos = (preLocal * coords).xy;
float2 t = $sweep_grad_layout(centerParam, biasParam, scaleParam, pos);
t = $tile_grad(tileMode, t);
return $colorize_grad_4(colorsParam, offsetsParam, t);
}
$pure half4 sk_sweep_grad_8_shader(float4 coords,
float4x4 preLocal,
float4 colorsParam[8],
float offsetsParam[8],
float2 centerParam,
float biasParam,
float scaleParam,
int tileMode) {
float2 pos = (preLocal * coords).xy;
float2 t = $sweep_grad_layout(centerParam, biasParam, scaleParam, pos);
t = $tile_grad(tileMode, t);
return $colorize_grad_8(colorsParam, offsetsParam, t);
}
$pure half4 sk_conical_grad_4_shader(float4 coords,
float4x4 preLocal,
float4 colorsParam[4],
float offsetsParam[4],
float2 point0Param,
float2 point1Param,
float radius0Param,
float radius1Param,
int tileMode) {
float2 pos = (preLocal * coords).xy;
float2 t = $conical_grad_layout(point0Param, point1Param, radius0Param, radius1Param, pos);
t = $tile_grad(tileMode, t);
return $colorize_grad_4(colorsParam, offsetsParam, t);
}
$pure half4 sk_conical_grad_8_shader(float4 coords,
float4x4 preLocal,
float4 colorsParam[8],
float offsetsParam[8],
float2 point0Param,
float2 point1Param,
float radius0Param,
float radius1Param,
int tileMode) {
float2 pos = (preLocal * coords).xy;
float2 t = $conical_grad_layout(point0Param, point1Param, radius0Param, radius1Param, pos);
t = $tile_grad(tileMode, t);
return $colorize_grad_8(colorsParam, offsetsParam, t);
}
$pure half4 sk_matrix_colorfilter(half4 colorIn, float4x4 m, float4 v, int inHSLA) {
if (bool(inHSLA)) {
colorIn = $rgb_to_hsl(colorIn.rgb, colorIn.a); // includes unpremul
} else {
colorIn = unpremul(colorIn);
}
half4 colorOut = half4((m * colorIn) + v);
if (bool(inHSLA)) {
colorOut = $hsl_to_rgb(colorOut.rgb, colorOut.a); // includes clamp and premul
} else {
colorOut = saturate(colorOut);
colorOut.rgb *= colorOut.a;
}
return colorOut;
}
$pure half4 sk_blend(int blendMode, half4 src, half4 dst) {
const int kClear = 0;
const int kSrc = 1;
const int kDst = 2;
const int kSrcOver = 3;
const int kDstOver = 4;
const int kSrcIn = 5;
const int kDstIn = 6;
const int kSrcOut = 7;
const int kDstOut = 8;
const int kSrcATop = 9;
const int kDstATop = 10;
const int kXor = 11;
const int kPlus = 12;
const int kModulate = 13;
const int kScreen = 14;
const int kOverlay = 15;
const int kDarken = 16;
const int kLighten = 17;
const int kColorDodge = 18;
const int kColorBurn = 19;
const int kHardLight = 20;
const int kSoftLight = 21;
const int kDifference = 22;
const int kExclusion = 23;
const int kMultiply = 24;
const int kHue = 25;
const int kSaturation = 26;
const int kColor = 27;
const int kLuminosity = 28;
switch (blendMode) {
case kClear: { return blend_clear(src, dst); }
case kSrc: { return blend_src(src, dst); }
case kDst: { return blend_dst(src, dst); }
case kSrcOver: { return blend_porter_duff(half4(1, 0, 0, -1), src, dst); }
case kDstOver: { return blend_porter_duff(half4(0, 1, -1, 0), src, dst); }
case kSrcIn: { return blend_porter_duff(half4(0, 0, 1, 0), src, dst); }
case kDstIn: { return blend_porter_duff(half4(0, 0, 0, 1), src, dst); }
case kSrcOut: { return blend_porter_duff(half4(0, 0, -1, 0), src, dst); }
case kDstOut: { return blend_porter_duff(half4(0, 0, 0, -1), src, dst); }
case kSrcATop: { return blend_porter_duff(half4(0, 0, 1, -1), src, dst); }
case kDstATop: { return blend_porter_duff(half4(0, 0, -1, 1), src, dst); }
case kXor: { return blend_porter_duff(half4(0, 0, -1, -1), src, dst); }
case kPlus: { return blend_porter_duff(half4(1, 1, 0, 0), src, dst); }
case kModulate: { return blend_modulate(src, dst); }
case kScreen: { return blend_screen(src, dst); }
case kOverlay: { return blend_overlay(/*flip=*/0, src, dst); }
case kDarken: { return blend_darken(/*mode=*/1, src, dst); }
case kLighten: { return blend_darken(/*mode=*/-1, src, dst); }
case kColorDodge: { return blend_color_dodge(src, dst); }
case kColorBurn: { return blend_color_burn(src, dst); }
case kHardLight: { return blend_overlay(/*flip=*/1, src, dst); }
case kSoftLight: { return blend_soft_light(src, dst); }
case kDifference: { return blend_difference(src, dst); }
case kExclusion: { return blend_exclusion(src, dst); }
case kMultiply: { return blend_multiply(src, dst); }
case kHue: { return blend_hslc(/*flipSat=*/half2(0, 1), src, dst); }
case kSaturation: { return blend_hslc(/*flipSat=*/half2(1), src, dst); }
case kColor: { return blend_hslc(/*flipSat=*/half2(0), src, dst); }
case kLuminosity: { return blend_hslc(/*flipSat=*/half2(1, 0), src, dst); }
default: return half4(0); // Avoids 'blend can exit without returning a value' error
}
}
$pure half4 sk_blend_shader(int blendMode, half4 src, half4 dst) {
return sk_blend(blendMode, src, dst);
}
$pure half4 sk_blend_colorfilter(half4 dstColor, int blendMode, float4 srcColor) {
return sk_blend(blendMode, half4(srcColor), dstColor);
}
$pure half4 sk_table_colorfilter(half4 inColor, sampler2D s) {
half4 coords = unpremul(inColor) * 255.0/256.0 + 0.5/256.0;
half4 color = half4(sample(s, half2(coords.r, 3.0/8.0)).r,
sample(s, half2(coords.g, 5.0/8.0)).r,
sample(s, half2(coords.b, 7.0/8.0)).r,
1);
return color * sample(s, half2(coords.a, 1.0/8.0)).r;
}
$pure half4 sk_gaussian_colorfilter(half4 inColor) {
half factor = 1 - inColor.a;
factor = exp(-factor * factor * 4) - 0.018;
return half4(factor);
}