blob: 4f544fc662af95d1425f87faa18269e6443eccea [file] [log] [blame]
// Graphite-specific fragment shader code
half4 sk_error() {
return half4(1.0, 0.0, 1.0, 1.0);
}
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. 'localMatrix' is passed in to be consistent w/ the default glue code.
half4 sk_local_matrix_shader(float4x4 localMatrix, half4 childResult) {
return childResult;
}
float $tile(int tm, float f, float min, float max, float normalizer) {
const int kClamp = 0;
const int kRepeat = 1;
const int kMirrorRepeat = 2;
const int kClampToBorder = 3;
if (tm == kClamp) {
return clamp(f, min, max) / normalizer;
} else if (tm == kRepeat) {
float length = max - min;
return (mod(f - min, length) + min) / normalizer;
} else if (tm == kMirrorRepeat) {
float length = max - min;
float length2 = 2 * length;
float tmp = mod(f - min, length2);
return (mix(tmp, length2 - tmp, step(length, tmp)) + min) / normalizer;
} else { // kClampToBorder
// For now, just clamp.
return clamp(f, min, max) / normalizer;
}
}
float2 sk_compute_coords(float4x4 dev2Local,
float4 subset,
int tmX,
int tmY,
int imgWidth,
int imgHeight) {
float4 localCoords = dev2Local * sk_FragCoord;
float2 coords = float2($tile(tmX, localCoords.x, subset.x, subset.z, float(imgWidth)),
$tile(tmY, localCoords.y, subset.y, subset.w, float(imgHeight)));
return coords;
}
float2 $tile_grad(int tileMode, float2 t) {
const int kGradientClamp = 0;
const int kGradientRepeat = 1;
const int kGradientMirror = 2;
const int kGradientDecal = 3;
switch (tileMode) {
case kGradientClamp:
t.x = clamp(t.x, 0, 1);
break;
case kGradientRepeat:
t.x = fract(t.x);
break;
case kGradientMirror: {
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 kGradientDecal:
if (t.x < 0 || t.x > 1) {
return float2(0, -1);
}
break;
}
return t;
}
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]);
}
}
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]);
}
}
}
}
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);
}
float2 $radial_grad_layout(float2 centerParam, float radiusParam, float2 pos) {
float t = distance(pos, centerParam) / radiusParam;
return float2(t, 1);
}
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);
}
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
));
}
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);
}
}
half4 sk_linear_grad_4_shader(float4x4 dev2Local,
float4 colorsParam[4],
float offsetsParam[4],
float2 point0Param,
float2 point1Param,
int tileMode,
float padding0,
float padding1,
float padding2) {
float2 pos = (dev2Local * sk_FragCoord).xy;
float2 t = $linear_grad_layout(point0Param, point1Param, pos);
t = $tile_grad(tileMode, t);
return $colorize_grad_4(colorsParam, offsetsParam, t);
}
half4 sk_linear_grad_8_shader(float4x4 dev2Local,
float4 colorsParam[8],
float offsetsParam[8],
float2 point0Param,
float2 point1Param,
int tileMode,
float padding0,
float padding1,
float padding2) {
float2 pos = (dev2Local * sk_FragCoord).xy;
float2 t = $linear_grad_layout(point0Param, point1Param, pos);
t = $tile_grad(tileMode, t);
return $colorize_grad_8(colorsParam, offsetsParam, t);
}
half4 sk_radial_grad_4_shader(float4x4 dev2Local,
float4 colorsParam[4],
float offsetsParam[4],
float2 centerParam,
float radiusParam,
int tileMode) {
float2 pos = (dev2Local * sk_FragCoord).xy;
float2 t = $radial_grad_layout(centerParam, radiusParam, pos);
t = $tile_grad(tileMode, t);
return $colorize_grad_4(colorsParam, offsetsParam, t);
}
half4 sk_radial_grad_8_shader(float4x4 dev2Local,
float4 colorsParam[8],
float offsetsParam[8],
float2 centerParam,
float radiusParam,
int tileMode) {
float2 pos = (dev2Local * sk_FragCoord).xy;
float2 t = $radial_grad_layout(centerParam, radiusParam, pos);
t = $tile_grad(tileMode, t);
return $colorize_grad_8(colorsParam, offsetsParam, t);
}
half4 sk_sweep_grad_4_shader(float4x4 dev2Local,
float4 colorsParam[4],
float offsetsParam[4],
float2 centerParam,
float biasParam,
float scaleParam,
int tileMode,
float padding0,
float padding1,
float padding2) {
float2 pos = (dev2Local * sk_FragCoord).xy;
float2 t = $sweep_grad_layout(centerParam, biasParam, scaleParam, pos);
t = $tile_grad(tileMode, t);
return $colorize_grad_4(colorsParam, offsetsParam, t);
}
half4 sk_sweep_grad_8_shader(float4x4 dev2Local,
float4 colorsParam[8],
float offsetsParam[8],
float2 centerParam,
float biasParam,
float scaleParam,
int tileMode,
float padding0,
float padding1,
float padding2) {
float2 pos = (dev2Local * sk_FragCoord).xy;
float2 t = $sweep_grad_layout(centerParam, biasParam, scaleParam, pos);
t = $tile_grad(tileMode, t);
return $colorize_grad_8(colorsParam, offsetsParam, t);
}
half4 sk_conical_grad_4_shader(float4x4 dev2Local,
float4 colorsParam[4],
float offsetsParam[4],
float2 point0Param,
float2 point1Param,
float radius0Param,
float radius1Param,
int tileMode,
float padding) {
float2 pos = (dev2Local * sk_FragCoord).xy;
float2 t = $conical_grad_layout(point0Param, point1Param, radius0Param, radius1Param, pos);
t = $tile_grad(tileMode, t);
return $colorize_grad_4(colorsParam, offsetsParam, t);
}
half4 sk_conical_grad_8_shader(float4x4 dev2Local,
float4 colorsParam[8],
float offsetsParam[8],
float2 point0Param,
float2 point1Param,
float radius0Param,
float radius1Param,
int tileMode,
float padding) {
float2 pos = (dev2Local * sk_FragCoord).xy;
float2 t = $conical_grad_layout(point0Param, point1Param, radius0Param, radius1Param, pos);
t = $tile_grad(tileMode, t);
return $colorize_grad_8(colorsParam, offsetsParam, t);
}
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(src, dst, half4(1, 0, 0, -1)); }
case kDstOver: { return blend_porter_duff(src, dst, half4(0, 1, -1, 0)); }
case kSrcIn: { return blend_porter_duff(src, dst, half4(0, 0, 1, 0)); }
case kDstIn: { return blend_porter_duff(src, dst, half4(0, 0, 0, 1)); }
case kSrcOut: { return blend_porter_duff(src, dst, half4(0, 0, -1, 0)); }
case kDstOut: { return blend_porter_duff(src, dst, half4(0, 0, 0, -1)); }
case kSrcATop: { return blend_porter_duff(src, dst, half4(0, 0, 1, -1)); }
case kDstATop: { return blend_porter_duff(src, dst, half4(0, 0, -1, 1)); }
case kXor: { return blend_porter_duff(src, dst, half4(0, 0, -1, -1)); }
case kPlus: { return blend_porter_duff(src, dst, half4(1, 1, 0, 0)); }
case kModulate: { return blend_modulate(src, dst); }
case kScreen: { return blend_screen(src, dst); }
case kOverlay: { return blend_overlay(src, dst, /*flip=*/0); }
case kDarken: { return blend_darken(src, dst, /*mode=*/1); }
case kLighten: { return blend_darken(src, dst, /*mode=*/-1); }
case kColorDodge: { return blend_color_dodge(src, dst); }
case kColorBurn: { return blend_color_burn(src, dst); }
case kHardLight: { return blend_overlay(src, dst, /*flip=*/1); }
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(src, dst, /*flipSat=*/half2(0, 1)); }
case kSaturation: { return blend_hslc(src, dst, /*flipSat=*/half2(1)); }
case kColor: { return blend_hslc(src, dst, /*flipSat=*/half2(0)); }
case kLuminosity: { return blend_hslc(src, dst, /*flipSat=*/half2(1, 0)); }
default: return half4(0); // Avoids 'blend can exit without returning a value' error
}
}
half4 sk_blend_shader(int blendMode, int pad0, int pad1, int pad2, half4 child0, half4 child1) {
return sk_blend(blendMode, child1, child0);
}