| // 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); |
| } |