1// Graphite-specific fragment shader code 2 3const int $kTileModeClamp = 0; 4const int $kTileModeRepeat = 1; 5const int $kTileModeMirror = 2; 6const int $kTileModeDecal = 3; 7 8const int $kFilterModeNearest = 0; 9const int $kFilterModeLinear = 1; 10 11const int $kTFTypeSRGB = 1; 12const int $kTFTypePQ = 2; 13const int $kTFTypeHLG = 3; 14const int $kTFTypeHLGinv = 4; 15 16const int $kColorSpaceXformFlagUnpremul = 0x1; 17const int $kColorSpaceXformFlagLinearize = 0x2; 18const int $kColorSpaceXformFlagGamutTransform = 0x4; 19const int $kColorSpaceXformFlagEncode = 0x8; 20const int $kColorSpaceXformFlagPremul = 0x10; 21const int $kColorSpaceXformFlagAlphaSwizzle = 0x20; 22 23const int $kMaskFormatA8 = 0; 24const int $kMaskFormatA565 = 1; 25const int $kMaskFormatARGB = 2; 26 27const int $kShapeTypeRect = 0; 28const int $kShapeTypeRRect = 1; 29const int $kShapeTypeCircle = 2; 30 31// Matches GrTextureEffect::kLinearInset, to make sure we don't touch an outer 32// row or column with a weight of 0 when linear filtering. 33const float $kLinearInset = 0.5 + 0.00001; 34 35$pure half4 sk_error() { 36 return half4(1.0, 0.0, 0.0, 1.0); 37} 38 39$pure half4 sk_passthrough(half4 color) { 40 return color; 41} 42 43$pure half4 sk_solid_shader(float4 colorParam) { 44 return half4(colorParam); 45} 46 47$pure half4 sk_rgb_opaque(float4 colorParam) { 48 return half4(colorParam.rgb, 1.0); 49} 50 51$pure half4 sk_alpha_only(float4 colorParam) { 52 return half4(0.0, 0.0, 0.0, colorParam.a); 53} 54 55$pure float3 $apply_xfer_fn(int kind, float3 x, half4 coeffs0, half4 coeffs1) { 56 float G = coeffs0[0], A = coeffs0[1], B = coeffs0[2], C = coeffs0[3], 57 D = coeffs1[0], E = coeffs1[1], F = coeffs1[2]; 58 float3 s = sign(x); 59 x = abs(x); 60 switch (kind) { 61 case $kTFTypeSRGB: 62 x = mix(pow(A * x + B, float3(G)) + E, 63 (C * x) + F, 64 lessThan(x, float3(D))); 65 break; 66 case $kTFTypePQ: 67 float3 x_C = pow(x, float3(C)); 68 x = pow(max(A + B * x_C, 0) / (D + E * x_C), float3(F)); 69 break; 70 case $kTFTypeHLG: 71 x = mix(exp((x - E) * C) + D, 72 pow(x * A, float3(B)), 73 lessThanEqual(x * A, float3(1))); 74 x *= (F + 1); 75 break; 76 case $kTFTypeHLGinv: 77 x /= (F + 1); 78 x = mix(C * log(x - D) + E, 79 A * pow(x, float3(B)), 80 lessThanEqual(x, float3(1))); 81 break; 82 } 83 return s * x; 84} 85 86$pure half4 sk_premul_alpha(float4 color) { 87 return half4(color.rgb*color.a, color.a); 88} 89 90$pure half4 sk_color_space_transform(half4 halfColor, 91 int flags, 92 int srcKind, 93 half3x3 gamutTransform, 94 int dstKind, 95 half4x4 coeffs) { 96 if (flags != 0) { 97 float4 color = float4(halfColor); 98 99 if (bool(flags & $kColorSpaceXformFlagAlphaSwizzle)) { 100 color.a = dot(color.r1, float2(coeffs[1][3], coeffs[3][3])); 101 } 102 if (bool(flags & $kColorSpaceXformFlagUnpremul)) { 103 color = unpremul(color); 104 } 105 if (bool(flags & $kColorSpaceXformFlagLinearize)) { 106 color.rgb = $apply_xfer_fn(srcKind, color.rgb, coeffs[0], coeffs[1]); 107 } 108 if (bool(flags & $kColorSpaceXformFlagGamutTransform)) { 109 color.rgb = gamutTransform * color.rgb; 110 } 111 if (bool(flags & $kColorSpaceXformFlagEncode)) { 112 color.rgb = $apply_xfer_fn(dstKind, color.rgb, coeffs[2], coeffs[3]); 113 } 114 115 halfColor = bool(flags & $kColorSpaceXformFlagPremul) ? sk_premul_alpha(color) 116 : half4(color); 117 } 118 return halfColor; 119} 120 121$pure half4 sk_circular_rrect_clip(float4 rect, 122 float2 radiusPlusHalf, 123 half4 edgeSelect) { 124 float2 fragCoord = sk_FragCoord.xy; 125 126 // Negative x indicates inverse fill 127 float2 radius = float2(abs(radiusPlusHalf.x)); 128 float2 dxy0 = edgeSelect.LT*((rect.LT + radius) - fragCoord); 129 float2 dxy1 = edgeSelect.RB*(fragCoord - (rect.RB - radius)); 130 float2 dxy = max(max(dxy0, dxy1), 0.0); 131 // Use more complex length calculation to manage precision issues 132 half circleCornerAlpha = half(saturate(radius.x*(1.0 - length(dxy*radiusPlusHalf.y)))); 133 134 half4 rectEdgeAlphas = saturate(half4(fragCoord - rect.LT, rect.RB - fragCoord)); 135 rectEdgeAlphas = mix(rectEdgeAlphas, half4(1), edgeSelect); 136 137 half alpha = circleCornerAlpha * rectEdgeAlphas.x * rectEdgeAlphas.y * 138 rectEdgeAlphas.z * rectEdgeAlphas.w; 139 // Check for inverse fill 140 alpha = radiusPlusHalf.x < 0 ? (1-alpha) : alpha; 141 142 return half4(alpha); 143} 144 145$pure float $tile(int tileMode, float f, float low, float high) { 146 switch (tileMode) { 147 case $kTileModeClamp: 148 return clamp(f, low, high); 149 150 case $kTileModeRepeat: { 151 float length = high - low; 152 return (mod(f - low, length) + low); 153 } 154 case $kTileModeMirror: { 155 float length = high - low; 156 float length2 = 2 * length; 157 float tmp = mod(f - low, length2); 158 return (mix(tmp, length2 - tmp, step(length, tmp)) + low); 159 } 160 default: // $kTileModeDecal 161 // Decal is handled later. 162 return f; 163 } 164} 165 166$pure half4 $sample_image(float2 pos, float2 invImgSize, sampler2D s) { 167 return sample(s, pos * invImgSize); 168} 169 170$pure half4 $sample_image_subset(float2 pos, 171 float2 invImgSize, 172 float4 subset, 173 int tileModeX, 174 int tileModeY, 175 int filterMode, 176 float2 linearFilterInset, 177 sampler2D s) { 178 // Do hard-edge shader transitions to the border color for nearest-neighbor decal tiling at the 179 // subset boundaries. Snap the input coordinates to nearest neighbor before comparing to the 180 // subset rect, to avoid GPU interpolation errors. See https://crbug.com/skia/10403. 181 if (tileModeX == $kTileModeDecal && filterMode == $kFilterModeNearest) { 182 float snappedX = floor(pos.x) + 0.5; 183 if (snappedX < subset.x || snappedX > subset.z) { 184 return half4(0); 185 } 186 } 187 if (tileModeY == $kTileModeDecal && filterMode == $kFilterModeNearest) { 188 float snappedY = floor(pos.y) + 0.5; 189 if (snappedY < subset.y || snappedY > subset.w) { 190 return half4(0); 191 } 192 } 193 194 pos.x = $tile(tileModeX, pos.x, subset.x, subset.z); 195 pos.y = $tile(tileModeY, pos.y, subset.y, subset.w); 196 197 // Clamp to an inset subset to prevent sampling neighboring texels when coords fall exactly at 198 // texel boundaries. 199 float4 insetClamp; 200 if (filterMode == $kFilterModeNearest) { 201 insetClamp = float4(floor(subset.xy) + $kLinearInset, ceil(subset.zw) - $kLinearInset); 202 } else { 203 insetClamp = float4(subset.xy + linearFilterInset.x, subset.zw - linearFilterInset.y); 204 } 205 float2 clampedPos = clamp(pos, insetClamp.xy, insetClamp.zw); 206 half4 color = $sample_image(clampedPos, invImgSize, s); 207 208 if (filterMode == $kFilterModeLinear) { 209 // Remember the amount the coord moved for clamping. This is used to implement shader-based 210 // filtering for repeat and decal tiling. 211 half2 error = half2(pos - clampedPos); 212 half2 absError = abs(error); 213 214 // Do 1 or 3 more texture reads depending on whether both x and y tiling modes are repeat 215 // and whether we're near a single subset edge or a corner. Then blend the multiple reads 216 // using the error values calculated above. 217 bool sampleExtraX = tileModeX == $kTileModeRepeat; 218 bool sampleExtraY = tileModeY == $kTileModeRepeat; 219 if (sampleExtraX || sampleExtraY) { 220 float extraCoordX; 221 float extraCoordY; 222 half4 extraColorX; 223 half4 extraColorY; 224 if (sampleExtraX) { 225 extraCoordX = error.x > 0 ? insetClamp.x : insetClamp.z; 226 extraColorX = $sample_image(float2(extraCoordX, clampedPos.y), 227 invImgSize, s); 228 } 229 if (sampleExtraY) { 230 extraCoordY = error.y > 0 ? insetClamp.y : insetClamp.w; 231 extraColorY = $sample_image(float2(clampedPos.x, extraCoordY), 232 invImgSize, s); 233 } 234 if (sampleExtraX && sampleExtraY) { 235 half4 extraColorXY = $sample_image(float2(extraCoordX, extraCoordY), 236 invImgSize, s); 237 color = mix(mix(color, extraColorX, absError.x), 238 mix(extraColorY, extraColorXY, absError.x), 239 absError.y); 240 } else if (sampleExtraX) { 241 color = mix(color, extraColorX, absError.x); 242 } else if (sampleExtraY) { 243 color = mix(color, extraColorY, absError.y); 244 } 245 } 246 247 // Do soft edge shader filtering for decal tiling and linear filtering using the error 248 // values calculated above. 249 if (tileModeX == $kTileModeDecal) { 250 color *= max(1 - absError.x, 0); 251 } 252 if (tileModeY == $kTileModeDecal) { 253 color *= max(1 - absError.y, 0); 254 } 255 } 256 257 return color; 258} 259 260$pure half4 $cubic_filter_image(float2 pos, 261 float2 invImgSize, 262 float4 subset, 263 int tileModeX, 264 int tileModeY, 265 half4x4 coeffs, 266 sampler2D s) { 267 // Determine pos's fractional offset f between texel centers. 268 float2 f = fract(pos - 0.5); 269 // Sample 16 points at 1-pixel intervals from [p - 1.5 ... p + 1.5]. 270 pos -= 1.5; 271 // Snap to texel centers to prevent sampling neighboring texels. 272 pos = floor(pos) + 0.5; 273 274 half4 wx = coeffs * half4(1.0, f.x, f.x * f.x, f.x * f.x * f.x); 275 half4 wy = coeffs * half4(1.0, f.y, f.y * f.y, f.y * f.y * f.y); 276 half4 color = half4(0); 277 for (int y = 0; y < 4; ++y) { 278 half4 rowColor = half4(0); 279 for (int x = 0; x < 4; ++x) { 280 rowColor += wx[x] * $sample_image_subset(pos + float2(x, y), invImgSize, subset, 281 tileModeX, tileModeY, $kFilterModeNearest, 282 float2($kLinearInset), s); 283 } 284 color += wy[y] * rowColor; 285 } 286 // Bicubic can send colors out of range, so clamp to get them back in gamut, assuming premul. 287 color.a = saturate(color.a); 288 color.rgb = clamp(color.rgb, half3(0.0), color.aaa); 289 return color; 290} 291 292$pure half4 sk_image_shader(float2 coords, 293 float2 invImgSize, 294 float4 subset, 295 int tileModeX, 296 int tileModeY, 297 int filterMode, 298 sampler2D s) { 299 return $sample_image_subset(coords, invImgSize, subset, tileModeX, tileModeY, 300 filterMode, float2($kLinearInset), s); 301} 302 303$pure half4 sk_cubic_image_shader(float2 coords, 304 float2 invImgSize, 305 float4 subset, 306 int tileModeX, 307 int tileModeY, 308 half4x4 cubicCoeffs, 309 sampler2D s) { 310 return $cubic_filter_image(coords, invImgSize, subset, tileModeX, tileModeY, cubicCoeffs, s); 311} 312 313$pure half4 sk_hw_image_shader(float2 coords, 314 float2 invImgSize, 315 sampler2D s) { 316 return $sample_image(coords, invImgSize, s); 317} 318 319$pure half4 $yuv_to_rgb_no_swizzle(half Y, 320 half U, 321 half V, 322 half alpha, 323 half3x3 yuvToRGBMatrix, 324 half3 yuvToRGBTranslate) { 325 half3 preColor = half3(Y, U, V); 326 half4 sampleColor; 327 sampleColor.rgb = saturate(yuvToRGBMatrix * preColor.rgb + yuvToRGBTranslate); 328 sampleColor.a = alpha; 329 330 // We always return an unpremul color here to skip work when transforming colorspaces 331 return sampleColor; 332} 333 334$pure half4 $yuv_to_rgb(half4 sampleColorY, 335 half4 sampleColorU, 336 half4 sampleColorV, 337 half alpha, 338 half4 channelSelectY, 339 half4 channelSelectU, 340 half4 channelSelectV, 341 half3x3 yuvToRGBMatrix, 342 half3 yuvToRGBTranslate) { 343 half Y = dot(channelSelectY, sampleColorY); 344 half U = dot(channelSelectU, sampleColorU); 345 half V = dot(channelSelectV, sampleColorV); 346 return $yuv_to_rgb_no_swizzle(Y, U, V, alpha, yuvToRGBMatrix, yuvToRGBTranslate); 347} 348 349$pure half4 sk_yuv_image_shader(float2 coords, 350 float2 invImgSizeY, 351 float2 invImgSizeUV, // Relative to Y's coordinate space 352 float4 subset, 353 float2 linearFilterUVInset, 354 int tileModeX, 355 int tileModeY, 356 int filterModeY, 357 int filterModeUV, 358 half4 channelSelectY, 359 half4 channelSelectU, 360 half4 channelSelectV, 361 half4 channelSelectA, 362 half3x3 yuvToRGBMatrix, 363 half3 yuvToRGBTranslate, 364 sampler2D sY, 365 sampler2D sU, 366 sampler2D sV, 367 sampler2D sA) { 368 // If the filter modes are different between Y and UV, this means that 369 // the base filtermode is nearest and we have to snap the coords to Y's 370 // texel centers to get the correct positions for UV. 371 if (filterModeY != filterModeUV) { 372 coords = floor(coords) + 0.5; 373 } 374 375 int tileModeX_UV = tileModeX == $kTileModeDecal ? $kTileModeClamp : tileModeX; 376 int tileModeY_UV = tileModeY == $kTileModeDecal ? $kTileModeClamp : tileModeY; 377 378 half4 sampleColorY, sampleColorU, sampleColorV; 379 sampleColorY = $sample_image_subset(coords, invImgSizeY, subset, tileModeX, tileModeY, 380 filterModeY, float2($kLinearInset), sY); 381 sampleColorU = $sample_image_subset(coords, invImgSizeUV, subset, tileModeX_UV, tileModeY_UV, 382 filterModeUV, linearFilterUVInset, sU); 383 sampleColorV = $sample_image_subset(coords, invImgSizeUV, subset, tileModeX_UV, tileModeY_UV, 384 filterModeUV, linearFilterUVInset, sV); 385 half alpha; 386 if (channelSelectA == half4(1)) { 387 alpha = 1; 388 } else { 389 half4 sampleColorA = $sample_image_subset(coords, invImgSizeY, subset, tileModeX, tileModeY, 390 filterModeY, float2($kLinearInset), sA); 391 alpha = dot(channelSelectA, sampleColorA); 392 } 393 394 return $yuv_to_rgb(sampleColorY, sampleColorU, sampleColorV, alpha, 395 channelSelectY, channelSelectU, channelSelectV, 396 yuvToRGBMatrix, yuvToRGBTranslate); 397} 398 399$pure half4 sk_cubic_yuv_image_shader(float2 coords, 400 float2 invImgSizeY, 401 float2 invImgSizeUV, // Relative to Y's coordinate space 402 float4 subset, 403 int tileModeX, 404 int tileModeY, 405 half4x4 cubicCoeffs, 406 half4 channelSelectY, 407 half4 channelSelectU, 408 half4 channelSelectV, 409 half4 channelSelectA, 410 half3x3 yuvToRGBMatrix, 411 half3 yuvToRGBTranslate, 412 sampler2D sY, 413 sampler2D sU, 414 sampler2D sV, 415 sampler2D sA) { 416 int tileModeX_UV = tileModeX == $kTileModeDecal ? $kTileModeClamp : tileModeX; 417 int tileModeY_UV = tileModeY == $kTileModeDecal ? $kTileModeClamp : tileModeY; 418 419 half4 sampleColorY, sampleColorU, sampleColorV; 420 sampleColorY = $cubic_filter_image(coords, invImgSizeY, subset, tileModeX, tileModeY, 421 cubicCoeffs, sY); 422 sampleColorU = $cubic_filter_image(coords, invImgSizeUV, subset, tileModeX_UV, tileModeY_UV, 423 cubicCoeffs, sU); 424 sampleColorV = $cubic_filter_image(coords, invImgSizeUV, subset, tileModeX_UV, tileModeY_UV, 425 cubicCoeffs, sV); 426 half alpha; 427 if (channelSelectA == half4(1)) { 428 alpha = 1; 429 } else { 430 half4 sampleColorA = $cubic_filter_image(coords, invImgSizeY, subset, tileModeX, tileModeY, 431 cubicCoeffs, sA); 432 alpha = dot(channelSelectA, sampleColorA); 433 } 434 435 return $yuv_to_rgb(sampleColorY, sampleColorU, sampleColorV, alpha, 436 channelSelectY, channelSelectU, channelSelectV, 437 yuvToRGBMatrix, yuvToRGBTranslate); 438} 439 440$pure half4 sk_hw_yuv_image_shader(float2 coords, 441 float2 invImgSizeY, 442 float2 invImgSizeUV, // Relative to Y's coordinate space 443 half4 channelSelectY, 444 half4 channelSelectU, 445 half4 channelSelectV, 446 half4 channelSelectA, 447 half3x3 yuvToRGBMatrix, 448 half3 yuvToRGBTranslate, 449 sampler2D sY, 450 sampler2D sU, 451 sampler2D sV, 452 sampler2D sA) { 453 half4 sampleColorY, sampleColorU, sampleColorV, sampleColorA; 454 sampleColorY = $sample_image(coords, invImgSizeY, sY); 455 sampleColorU = $sample_image(coords, invImgSizeUV, sU); 456 sampleColorV = $sample_image(coords, invImgSizeUV, sV); 457 half alpha; 458 if (channelSelectA == half4(1)) { 459 alpha = 1; 460 } else { 461 half4 sampleColorA = $sample_image(coords, invImgSizeY, sA); 462 alpha = dot(channelSelectA, sampleColorA); 463 } 464 465 return $yuv_to_rgb(sampleColorY, sampleColorU, sampleColorV, alpha, 466 channelSelectY, channelSelectU, channelSelectV, 467 yuvToRGBMatrix, yuvToRGBTranslate); 468} 469 470$pure half4 sk_hw_yuv_no_swizzle_image_shader(float2 coords, 471 float2 invImgSizeY, 472 float2 invImgSizeUV, // Relative to Y's coord space 473 half3x3 yuvToRGBMatrix, 474 half4 yuvToRGBXlateAlphaParam, 475 sampler2D sY, 476 sampler2D sU, 477 sampler2D sV, 478 sampler2D sA) { 479 half4 sampleColorY, sampleColorU, sampleColorV, sampleColorA; 480 half Y = $sample_image(coords, invImgSizeY, sY).r; 481 half U = $sample_image(coords, invImgSizeUV, sU).r; 482 half V = $sample_image(coords, invImgSizeUV, sV).r; 483 // When it's a Y_U_V_A texture, yuvToRGBXlateAlphaParam.w is 0 and we have a real A sampler so 484 // alpha is just that saturated value (should be a no-op). For Y_U_V, we set 485 // yuvToRGBXlateAlphaParam.w to 1 and sample from Y, which after the saturate is always 1. 486 half alpha = saturate($sample_image(coords, invImgSizeY, sA).r + yuvToRGBXlateAlphaParam.w); 487 488 return $yuv_to_rgb_no_swizzle(Y, U, V, alpha, 489 yuvToRGBMatrix, yuvToRGBXlateAlphaParam.xyz); 490} 491 492$pure half4 sk_dither(half4 colorIn, half range, sampler2D lut) { 493 const float kImgSize = 8; 494 495 half value = sample(lut, sk_FragCoord.xy / kImgSize).r - 0.5; // undo the bias in the table 496 // For each color channel, add the random offset to the channel value and then clamp 497 // between 0 and alpha to keep the color premultiplied. 498 return half4(clamp(colorIn.rgb + value * range, 0.0, colorIn.a), colorIn.a); 499} 500 501$pure float2 $tile_grad(int tileMode, float2 t) { 502 switch (tileMode) { 503 case $kTileModeClamp: 504 t.x = saturate(t.x); 505 break; 506 507 case $kTileModeRepeat: 508 t.x = fract(t.x); 509 break; 510 511 case $kTileModeMirror: { 512 float t_1 = t.x - 1; 513 t.x = t_1 - 2 * floor(t_1 * 0.5) - 1; 514 if (sk_Caps.mustDoOpBetweenFloorAndAbs) { 515 // At this point the expected value of tiled_t should between -1 and 1, so this 516 // clamp has no effect other than to break up the floor and abs calls and make sure 517 // the compiler doesn't merge them back together. 518 t.x = clamp(t.x, -1, 1); 519 } 520 t.x = abs(t.x); 521 break; 522 } 523 524 case $kTileModeDecal: 525 if (t.x < 0 || t.x > 1) { 526 return float2(0, -1); 527 } 528 break; 529 } 530 531 return t; 532} 533 534$pure half4 $colorize_grad_4(float4 colorsParam[4], float4 offsetsParam, float2 t) { 535 if (t.y < 0) { 536 return half4(0); 537 538 } else if (t.x <= offsetsParam[0]) { 539 return half4(colorsParam[0]); 540 } else if (t.x < offsetsParam[1]) { 541 return half4(mix(colorsParam[0], colorsParam[1], (t.x - offsetsParam[0]) / 542 (offsetsParam[1] - offsetsParam[0]))); 543 } else if (t.x < offsetsParam[2]) { 544 return half4(mix(colorsParam[1], colorsParam[2], (t.x - offsetsParam[1]) / 545 (offsetsParam[2] - offsetsParam[1]))); 546 } else if (t.x < offsetsParam[3]) { 547 return half4(mix(colorsParam[2], colorsParam[3], (t.x - offsetsParam[2]) / 548 (offsetsParam[3] - offsetsParam[2]))); 549 } else { 550 return half4(colorsParam[3]); 551 } 552} 553 554$pure half4 $colorize_grad_8(float4 colorsParam[8], float4 offsetsParam[2], float2 t) { 555 if (t.y < 0) { 556 return half4(0); 557 558 // Unrolled binary search through intervals 559 // ( .. 0), (0 .. 1), (1 .. 2), (2 .. 3), (3 .. 4), (4 .. 5), (5 .. 6), (6 .. 7), (7 .. ). 560 } else if (t.x < offsetsParam[1][0]) { 561 if (t.x < offsetsParam[0][2]) { 562 if (t.x <= offsetsParam[0][0]) { 563 return half4(colorsParam[0]); 564 } else if (t.x < offsetsParam[0][1]) { 565 return half4(mix(colorsParam[0], colorsParam[1], 566 (t.x - offsetsParam[0][0]) / 567 (offsetsParam[0][1] - offsetsParam[0][0]))); 568 } else { 569 return half4(mix(colorsParam[1], colorsParam[2], 570 (t.x - offsetsParam[0][1]) / 571 (offsetsParam[0][2] - offsetsParam[0][1]))); 572 } 573 } else { 574 if (t.x < offsetsParam[0][3]) { 575 return half4(mix(colorsParam[2], colorsParam[3], 576 (t.x - offsetsParam[0][2]) / 577 (offsetsParam[0][3] - offsetsParam[0][2]))); 578 } else { 579 return half4(mix(colorsParam[3], colorsParam[4], 580 (t.x - offsetsParam[0][3]) / 581 (offsetsParam[1][0] - offsetsParam[0][3]))); 582 } 583 } 584 } else { 585 if (t.x < offsetsParam[1][2]) { 586 if (t.x < offsetsParam[1][1]) { 587 return half4(mix(colorsParam[4], colorsParam[5], 588 (t.x - offsetsParam[1][0]) / 589 (offsetsParam[1][1] - offsetsParam[1][0]))); 590 } else { 591 return half4(mix(colorsParam[5], colorsParam[6], 592 (t.x - offsetsParam[1][1]) / 593 (offsetsParam[1][2] - offsetsParam[1][1]))); 594 } 595 } else { 596 if (t.x < offsetsParam[1][3]) { 597 return half4(mix(colorsParam[6], colorsParam[7], 598 (t.x - offsetsParam[1][2]) / 599 (offsetsParam[1][3] - offsetsParam[1][2]))); 600 } else { 601 return half4(colorsParam[7]); 602 } 603 } 604 } 605} 606 607$pure half4 $colorize_grad_tex(sampler2D colorsAndOffsetsSampler, int numStops, float2 t) { 608 const float kColorCoord = 0.25; 609 const float kOffsetCoord = 0.75; 610 611 if (t.y < 0) { 612 return half4(0); 613 } else if (t.x == 0) { 614 return sampleLod(colorsAndOffsetsSampler, float2(0, kColorCoord), 0); 615 } else if (t.x == 1) { 616 return sampleLod(colorsAndOffsetsSampler, float2(1, kColorCoord), 0); 617 } else { 618 float low = 0; 619 float high = float(numStops); 620 float invNumStops = 1.0 / high; 621 for (int loop = 1; loop < numStops; loop += loop) { 622 float mid = floor((low + high) * 0.5); 623 float samplePos = (mid + 0.5) * invNumStops; 624 625 float2 tmp = sampleLod(colorsAndOffsetsSampler, float2(samplePos, kOffsetCoord), 0).xy; 626 float offset = ldexp(tmp.x, int(tmp.y)); 627 628 if (t.x < offset) { 629 high = mid; 630 } else { 631 low = mid; 632 } 633 } 634 635 high = (low + 1.5) * invNumStops; 636 low = (low + 0.5) * invNumStops; 637 half4 color0 = sampleLod(colorsAndOffsetsSampler, float2(low, kColorCoord), 0); 638 half4 color1 = sampleLod(colorsAndOffsetsSampler, float2(high, kColorCoord), 0); 639 640 float2 tmp = sampleLod(colorsAndOffsetsSampler, float2(low, kOffsetCoord), 0).xy; 641 float offset0 = ldexp(tmp.x, int(tmp.y)); 642 643 tmp = sampleLod(colorsAndOffsetsSampler, float2(high, kOffsetCoord), 0).xy; 644 float offset1 = ldexp(tmp.x, int(tmp.y)); 645 646 return half4(mix(color0, color1, 647 (t.x - offset0) / 648 (offset1 - offset0))); 649 } 650} 651 652$pure half4 $half4_from_array(float[] arr, int offset) { 653 return half4(arr[offset + 0], 654 arr[offset + 1], 655 arr[offset + 2], 656 arr[offset + 3]); 657} 658 659$pure half4 $colorize_grad_buf(float[] colorAndOffsetData, 660 int offsetsBaseIndex, 661 int numStops, 662 float2 t) { 663 int colorsBaseIndex = offsetsBaseIndex + numStops; 664 if (t.y < 0) { 665 return half4(0); 666 } else if (t.x == 0) { 667 return $half4_from_array(colorAndOffsetData, colorsBaseIndex); 668 } else if (t.x == 1) { 669 int lastColorIndex = colorsBaseIndex + (numStops - 1) * 4; 670 return $half4_from_array(colorAndOffsetData, lastColorIndex); 671 } else { 672 // Binary search for the matching adjacent stop offsets running log2(numStops). 673 int lowOffsetIndex = offsetsBaseIndex; 674 int highOffsetIndex = lowOffsetIndex + numStops - 1; 675 for (int i = 1; i < numStops; i += i) { 676 int middleOffsetIndex = (lowOffsetIndex + highOffsetIndex) / 2; 677 if (t.x < colorAndOffsetData[middleOffsetIndex]) { 678 highOffsetIndex = middleOffsetIndex; 679 } else { 680 lowOffsetIndex = middleOffsetIndex; 681 } 682 } 683 int lowColorIndex = colorsBaseIndex + (lowOffsetIndex - offsetsBaseIndex) * 4; 684 float lowOffset = colorAndOffsetData[lowOffsetIndex]; 685 half4 lowColor = $half4_from_array(colorAndOffsetData, lowColorIndex); 686 687 int highColorIndex = colorsBaseIndex + (highOffsetIndex - offsetsBaseIndex) * 4; 688 float highOffset = colorAndOffsetData[highOffsetIndex]; 689 if (highOffset == lowOffset) { 690 // If the t value falls exactly on a color stop, both lowOffset 691 // and highOffset will be exactly the same so we avoid having 692 // 0/0=NaN as our mix value. 693 return lowColor; 694 } else { 695 half4 highColor = $half4_from_array(colorAndOffsetData, highColorIndex); 696 697 return half4(mix(lowColor, highColor, (t.x - lowOffset) / (highOffset - lowOffset))); 698 } 699 } 700} 701 702$pure float2 $linear_grad_layout(float2 pos) { 703 // Add small epsilon since when the gradient is horizontally or vertically aligned, 704 // pixels along the same column or row can have slightly different interpolated t values 705 // causing pixels to choose the wrong offset when colorizing. This helps ensure pixels 706 // along the same column or row choose the same gradient offsets. 707 return float2(pos.x + 0.00001, 1); 708} 709 710$pure float2 $radial_grad_layout(float2 pos) { 711 float t = length(pos); 712 return float2(t, 1); 713} 714 715$pure float2 $sweep_grad_layout(float biasParam, float scaleParam, float2 pos) { 716 // Some devices incorrectly implement atan2(y,x) as atan(y/x). In actuality it is 717 // atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). To work around this we pass in 718 // (sqrt(x^2 + y^2) + x) as the second parameter to atan2 in these cases. We let the device 719 // handle the undefined behavior if the second parameter is 0, instead of doing the divide 720 // ourselves and calling atan with the quotient. 721 float angle; 722 if (sk_Caps.atan2ImplementedAsAtanYOverX) { 723 angle = 2 * atan(-pos.y, length(pos) - pos.x); 724 } else { 725 // Hardcode pi/2 for the angle when x == 0, to avoid undefined behavior in this 726 // case. This hasn't proven to be necessary in the atan workaround case. 727 angle = pos.x != 0.0 ? atan(-pos.y, -pos.x) : sign(pos.y) * -1.5707963267949; 728 } 729 730 // 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi] 731 float t = (angle * 0.1591549430918 + 0.5 + biasParam) * scaleParam; 732 return float2(t, 1); 733} 734 735$pure float2 $conical_grad_layout(float radius0, 736 float dRadius, 737 float a, 738 float invA, 739 float2 pos) { 740 // When writing uniform values, if the gradient is radial, we encode a == 0 and since 741 // the linear edge case is when a == 0, we differentiate the radial case with invA == 1. 742 if (a == 0 && invA == 1) { 743 // Radial case 744 float t = length(pos) * dRadius - radius0; 745 746 return float2(t, 1); 747 } else { 748 // Focal/strip case. 749 float c = dot(pos, pos) - radius0 * radius0; 750 float negB = 2 * (dRadius * radius0 + pos.x); 751 752 float t; 753 if (a == 0) { 754 // Linear case, both circles intersect as exactly one point 755 // with the focal point sitting on that point. 756 757 // It is highly unlikely that b and c would be 0 resulting in NaN, if b == 0 due to 758 // a specific translation, it would result is +/-Inf which propogates the sign to 759 // isValid resulting in how the pixel is expected to look. 760 t = c / negB; 761 } else { 762 // Quadratic case 763 float d = negB*negB - 4*a*c; 764 if (d < 0) { 765 return float2(0, -1); 766 } 767 768 // T should be as large as possible, so when one circle fully encloses the other, 769 // the sign will be positive or negative depending on the sign of dRadius. 770 // When this isn't the case and they form a cone, the sign will always be positive. 771 float quadSign = sign(1 - dRadius); 772 t = invA * (negB + quadSign * sqrt(d)); 773 } 774 775 // Interpolated radius must be positive. 776 float isValid = sign(t * dRadius + radius0); 777 return float2(t, isValid); 778 } 779} 780 781$pure half4 sk_linear_grad_4_shader(float2 coords, 782 float4 colorsParam[4], 783 float4 offsetsParam, 784 int tileMode, 785 int colorSpace, 786 int doUnpremul) { 787 float2 t = $linear_grad_layout(coords); 788 t = $tile_grad(tileMode, t); 789 half4 color = $colorize_grad_4(colorsParam, offsetsParam, t); 790 return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); 791} 792 793$pure half4 sk_linear_grad_8_shader(float2 coords, 794 float4 colorsParam[8], 795 float4 offsetsParam[2], 796 int tileMode, 797 int colorSpace, 798 int doUnpremul) { 799 float2 t = $linear_grad_layout(coords); 800 t = $tile_grad(tileMode, t); 801 half4 color = $colorize_grad_8(colorsParam, offsetsParam, t); 802 return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); 803} 804 805$pure half4 sk_linear_grad_tex_shader(float2 coords, 806 int numStops, 807 int tileMode, 808 int colorSpace, 809 int doUnpremul, 810 sampler2D colorAndOffsetSampler) { 811 float2 t = $linear_grad_layout(coords); 812 t = $tile_grad(tileMode, t); 813 half4 color = $colorize_grad_tex(colorAndOffsetSampler, numStops, t); 814 return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); 815} 816 817$pure half4 sk_linear_grad_buf_shader(float2 coords, 818 int numStops, 819 int bufferOffset, 820 int tileMode, 821 int colorSpace, 822 int doUnpremul, 823 float[] colorAndOffsetData) { 824 float2 t = $linear_grad_layout(coords); 825 t = $tile_grad(tileMode, t); 826 half4 color = $colorize_grad_buf(colorAndOffsetData, bufferOffset, numStops, t); 827 return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); 828} 829 830$pure half4 sk_radial_grad_4_shader(float2 coords, 831 float4 colorsParam[4], 832 float4 offsetsParam, 833 int tileMode, 834 int colorSpace, 835 int doUnpremul) { 836 float2 t = $radial_grad_layout(coords); 837 t = $tile_grad(tileMode, t); 838 half4 color = $colorize_grad_4(colorsParam, offsetsParam, t); 839 return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); 840} 841 842$pure half4 sk_radial_grad_8_shader(float2 coords, 843 float4 colorsParam[8], 844 float4 offsetsParam[2], 845 int tileMode, 846 int colorSpace, 847 int doUnpremul) { 848 float2 t = $radial_grad_layout(coords); 849 t = $tile_grad(tileMode, t); 850 half4 color = $colorize_grad_8(colorsParam, offsetsParam, t); 851 return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); 852} 853 854$pure half4 sk_radial_grad_tex_shader(float2 coords, 855 int numStops, 856 int tileMode, 857 int colorSpace, 858 int doUnpremul, 859 sampler2D colorAndOffsetSampler) { 860 float2 t = $radial_grad_layout(coords); 861 t = $tile_grad(tileMode, t); 862 half4 color = $colorize_grad_tex(colorAndOffsetSampler, numStops, t); 863 return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); 864} 865 866$pure half4 sk_radial_grad_buf_shader(float2 coords, 867 int numStops, 868 int bufferOffset, 869 int tileMode, 870 int colorSpace, 871 int doUnpremul, 872 float[] colorAndOffsetData) { 873 float2 t = $radial_grad_layout(coords); 874 t = $tile_grad(tileMode, t); 875 half4 color = $colorize_grad_buf(colorAndOffsetData, bufferOffset, numStops, t); 876 return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); 877} 878 879$pure half4 sk_sweep_grad_4_shader(float2 coords, 880 float4 colorsParam[4], 881 float4 offsetsParam, 882 float biasParam, 883 float scaleParam, 884 int tileMode, 885 int colorSpace, 886 int doUnpremul) { 887 float2 t = $sweep_grad_layout(biasParam, scaleParam, coords); 888 t = $tile_grad(tileMode, t); 889 half4 color = $colorize_grad_4(colorsParam, offsetsParam, t); 890 return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); 891} 892 893$pure half4 sk_sweep_grad_8_shader(float2 coords, 894 float4 colorsParam[8], 895 float4 offsetsParam[2], 896 float biasParam, 897 float scaleParam, 898 int tileMode, 899 int colorSpace, 900 int doUnpremul) { 901 float2 t = $sweep_grad_layout(biasParam, scaleParam, coords); 902 t = $tile_grad(tileMode, t); 903 half4 color = $colorize_grad_8(colorsParam, offsetsParam, t); 904 return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); 905} 906 907$pure half4 sk_sweep_grad_tex_shader(float2 coords, 908 float biasParam, 909 float scaleParam, 910 int numStops, 911 int tileMode, 912 int colorSpace, 913 int doUnpremul, 914 sampler2D colorAndOffsetSampler) { 915 float2 t = $sweep_grad_layout(biasParam, scaleParam, coords); 916 t = $tile_grad(tileMode, t); 917 half4 color = $colorize_grad_tex(colorAndOffsetSampler, numStops, t); 918 return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); 919} 920 921$pure half4 sk_sweep_grad_buf_shader(float2 coords, 922 float biasParam, 923 float scaleParam, 924 int numStops, 925 int bufferOffset, 926 int tileMode, 927 int colorSpace, 928 int doUnpremul, 929 float[] colorAndOffsetData) { 930 float2 t = $sweep_grad_layout(biasParam, scaleParam, coords); 931 t = $tile_grad(tileMode, t); 932 half4 color = $colorize_grad_buf(colorAndOffsetData, bufferOffset, numStops, t); 933 return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); 934} 935 936$pure half4 sk_conical_grad_4_shader(float2 coords, 937 float4 colorsParam[4], 938 float4 offsetsParam, 939 float radius0Param, 940 float dRadiusParam, 941 float aParam, 942 float invAParam, 943 int tileMode, 944 int colorSpace, 945 int doUnpremul) { 946 float2 t = $conical_grad_layout(radius0Param, dRadiusParam, aParam, invAParam, coords); 947 t = $tile_grad(tileMode, t); 948 half4 color = $colorize_grad_4(colorsParam, offsetsParam, t); 949 return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); 950} 951 952$pure half4 sk_conical_grad_8_shader(float2 coords, 953 float4 colorsParam[8], 954 float4 offsetsParam[2], 955 float radius0Param, 956 float dRadiusParam, 957 float aParam, 958 float invAParam, 959 int tileMode, 960 int colorSpace, 961 int doUnpremul) { 962 float2 t = $conical_grad_layout(radius0Param, dRadiusParam, aParam, invAParam, coords); 963 t = $tile_grad(tileMode, t); 964 half4 color = $colorize_grad_8(colorsParam, offsetsParam, t); 965 return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); 966} 967 968$pure half4 sk_conical_grad_tex_shader(float2 coords, 969 float radius0Param, 970 float dRadiusParam, 971 float aParam, 972 float invAParam, 973 int numStops, 974 int tileMode, 975 int colorSpace, 976 int doUnpremul, 977 sampler2D colorAndOffsetSampler) { 978 float2 t = $conical_grad_layout(radius0Param, dRadiusParam, aParam, invAParam, coords); 979 t = $tile_grad(tileMode, t); 980 half4 color = $colorize_grad_tex(colorAndOffsetSampler, numStops, t); 981 return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); 982} 983 984$pure half4 sk_conical_grad_buf_shader(float2 coords, 985 float radius0Param, 986 float dRadiusParam, 987 float aParam, 988 float invAParam, 989 int numStops, 990 int bufferOffset, 991 int tileMode, 992 int colorSpace, 993 int doUnpremul, 994 float[] colorAndOffsetData) { 995 float2 t = $conical_grad_layout(radius0Param, dRadiusParam, aParam, invAParam, coords); 996 t = $tile_grad(tileMode, t); 997 half4 color = $colorize_grad_buf(colorAndOffsetData, bufferOffset, numStops, t); 998 return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); 999} 1000 1001$pure half4 sk_matrix_colorfilter(half4 colorIn, float4x4 m, float4 v, int inHSLA, int clampRGB) { 1002 if (bool(inHSLA)) { 1003 colorIn = $rgb_to_hsl(colorIn.rgb, colorIn.a); // includes unpremul 1004 } else { 1005 colorIn = unpremul(colorIn); 1006 } 1007 1008 half4 colorOut = half4((m * colorIn) + v); 1009 1010 if (bool(inHSLA)) { 1011 colorOut = $hsl_to_rgb(colorOut.rgb, colorOut.a); // includes clamp and premul 1012 } else { 1013 if (bool(clampRGB)) { 1014 colorOut = saturate(colorOut); 1015 } else { 1016 colorOut.a = saturate(colorOut.a); 1017 } 1018 colorOut.rgb *= colorOut.a; 1019 } 1020 1021 return colorOut; 1022} 1023 1024// This method computes the 4 x-coodinates ([0..1]) that should be used to look 1025// up in the Perlin noise shader's noise table. 1026$pure half4 $noise_helper(half2 noiseVec, 1027 half2 stitchData, 1028 int stitching, 1029 sampler2D permutationSampler) { 1030 const half kBlockSize = 256.0; 1031 1032 half4 floorVal; 1033 floorVal.xy = floor(noiseVec); 1034 floorVal.zw = floorVal.xy + half2(1); 1035 1036 // Adjust frequencies if we're stitching tiles 1037 if (bool(stitching)) { 1038 floorVal -= step(stitchData.xyxy, floorVal) * stitchData.xyxy; 1039 } 1040 1041 half sampleX = sample(permutationSampler, half2((floorVal.x + 0.5) / kBlockSize, 0.5)).r; 1042 half sampleY = sample(permutationSampler, half2((floorVal.z + 0.5) / kBlockSize, 0.5)).r; 1043 1044 half2 latticeIdx = half2(sampleX, sampleY); 1045 1046 if (sk_Caps.PerlinNoiseRoundingFix) { 1047 // Aggressively round to the nearest exact (N / 255) floating point values. 1048 // This prevents rounding errors on some platforms (e.g., Tegras) 1049 const half kInv255 = 1.0 / 255.0; 1050 1051 latticeIdx = floor(latticeIdx * half2(255.0) + half2(0.5)) * half2(kInv255); 1052 } 1053 1054 // Get (x,y) coordinates with the permuted x 1055 half4 noiseXCoords = kBlockSize*latticeIdx.xyxy + floorVal.yyww; 1056 1057 noiseXCoords /= half4(kBlockSize); 1058 return noiseXCoords; 1059} 1060 1061$pure half4 $noise_function(half2 noiseVec, 1062 half4 noiseXCoords, 1063 sampler2D noiseSampler) { 1064 half2 fractVal = fract(noiseVec); 1065 1066 // Hermite interpolation : t^2*(3 - 2*t) 1067 half2 noiseSmooth = smoothstep(0, 1, fractVal); 1068 1069 // This is used to convert the two 16bit integers packed into rgba 8 bit input into 1070 // a [-1,1] vector 1071 const half kInv256 = 0.00390625; // 1.0 / 256.0 1072 1073 half4 result; 1074 1075 for (int channel = 0; channel < 4; channel++) { 1076 1077 // There are 4 lines in the noise texture, put y coords at center of each. 1078 half chanCoord = (half(channel) + 0.5) / 4.0; 1079 1080 half4 sampleA = sample(noiseSampler, float2(noiseXCoords.x, chanCoord)); 1081 half4 sampleB = sample(noiseSampler, float2(noiseXCoords.y, chanCoord)); 1082 half4 sampleC = sample(noiseSampler, float2(noiseXCoords.w, chanCoord)); 1083 half4 sampleD = sample(noiseSampler, float2(noiseXCoords.z, chanCoord)); 1084 1085 half2 tmpFractVal = fractVal; 1086 1087 // Compute u, at offset (0,0) 1088 half u = dot((sampleA.ga + sampleA.rb*kInv256)*2 - 1, tmpFractVal); 1089 1090 // Compute v, at offset (-1,0) 1091 tmpFractVal.x -= 1.0; 1092 half v = dot((sampleB.ga + sampleB.rb*kInv256)*2 - 1, tmpFractVal); 1093 1094 // Compute 'a' as a linear interpolation of 'u' and 'v' 1095 half a = mix(u, v, noiseSmooth.x); 1096 1097 // Compute v, at offset (-1,-1) 1098 tmpFractVal.y -= 1.0; 1099 v = dot((sampleC.ga + sampleC.rb*kInv256)*2 - 1, tmpFractVal); 1100 1101 // Compute u, at offset (0,-1) 1102 tmpFractVal.x += 1.0; 1103 u = dot((sampleD.ga + sampleD.rb*kInv256)*2 - 1, tmpFractVal); 1104 1105 // Compute 'b' as a linear interpolation of 'u' and 'v' 1106 half b = mix(u, v, noiseSmooth.x); 1107 1108 // Compute the noise as a linear interpolation of 'a' and 'b' 1109 result[channel] = mix(a, b, noiseSmooth.y); 1110 } 1111 1112 return result; 1113} 1114 1115// permutationSampler is [kBlockSize x 1] A8 1116// noiseSampler is [kBlockSize x 4] RGBA8 premul 1117$pure half4 sk_perlin_noise_shader(float2 coords, 1118 float2 baseFrequency, 1119 float2 stitchDataIn, 1120 int noiseType, 1121 int numOctaves, 1122 int stitching, 1123 sampler2D permutationSampler, 1124 sampler2D noiseSampler) { 1125 const int kFractalNoise = 0; 1126 const int kTurbulence = 1; 1127 1128 // In the past, Perlin noise handled coordinates a bit differently than most shaders. 1129 // It operated in device space, floored; it also had a one-pixel transform matrix applied to 1130 // both the X and Y coordinates. This is roughly equivalent to adding 0.5 to the coordinates. 1131 // This was originally done in order to better match preexisting golden images from WebKit. 1132 // Perlin noise now operates in local space, which allows rotation to work correctly. To better 1133 // approximate past behavior, we add 0.5 to the coordinates here. This is _not_ exactly the same 1134 // because this adjustment is occurring in local space, not device space. 1135 half2 noiseVec = half2((coords + 0.5) * baseFrequency); 1136 1137 // Clear the color accumulator 1138 half4 color = half4(0); 1139 1140 half2 stitchData = half2(stitchDataIn); 1141 1142 half ratio = 1.0; 1143 1144 // Loop over all octaves 1145 for (int octave = 0; octave < numOctaves; ++octave) { 1146 half4 noiseXCoords = $noise_helper(noiseVec, stitchData, stitching, permutationSampler); 1147 1148 half4 tmp = $noise_function(noiseVec, noiseXCoords, noiseSampler); 1149 1150 if (noiseType != kFractalNoise) { 1151 // For kTurbulence the result is: abs(noise[-1,1]) 1152 tmp = abs(tmp); 1153 } 1154 1155 color += tmp * ratio; 1156 1157 noiseVec *= half2(2.0); 1158 ratio *= 0.5; 1159 stitchData *= half2(2.0); 1160 } 1161 1162 if (noiseType == kFractalNoise) { 1163 // For kFractalNoise the result is: noise[-1,1] * 0.5 + 0.5 1164 color = color * half4(0.5) + half4(0.5); 1165 } 1166 1167 // Clamp values 1168 color = saturate(color); 1169 1170 // Pre-multiply the result 1171 return sk_premul_alpha(color); 1172} 1173 1174$pure half4 sk_porter_duff_blend(half4 src, half4 dst, half4 coeffs) { 1175 return blend_porter_duff(coeffs, src, dst); 1176} 1177 1178$pure half4 sk_hslc_blend(half4 src, half4 dst, half2 flipSat) { 1179 return blend_hslc(flipSat, src, dst); 1180} 1181 1182$pure half4 sk_table_colorfilter(half4 inColor, sampler2D s) { 1183 half4 coords = unpremul(inColor) * (255.0/256.0) + (0.5/256.0); 1184 half4 color = half4(sample(s, half2(coords.r, 3.0/8.0)).r, 1185 sample(s, half2(coords.g, 5.0/8.0)).r, 1186 sample(s, half2(coords.b, 7.0/8.0)).r, 1187 1); 1188 return color * sample(s, half2(coords.a, 1.0/8.0)).r; 1189} 1190 1191$pure half4 sk_gaussian_colorfilter(half4 inColor) { 1192 half factor = 1 - inColor.a; 1193 factor = exp(-factor * factor * 4) - 0.018; 1194 return half4(factor); 1195} 1196 1197$pure half4 sample_indexed_atlas(float2 textureCoords, 1198 int atlasIndex, 1199 sampler2D atlas0, 1200 sampler2D atlas1, 1201 sampler2D atlas2, 1202 sampler2D atlas3) { 1203 switch (atlasIndex) { 1204 case 1: 1205 return sample(atlas1, textureCoords); 1206 case 2: 1207 return sample(atlas2, textureCoords); 1208 case 3: 1209 return sample(atlas3, textureCoords); 1210 default: 1211 return sample(atlas0, textureCoords); 1212 } 1213} 1214 1215$pure half3 $sample_indexed_atlas_lcd(float2 textureCoords, 1216 int atlasIndex, 1217 half2 offset, 1218 sampler2D atlas0, 1219 sampler2D atlas1, 1220 sampler2D atlas2, 1221 sampler2D atlas3) { 1222 half3 distance = half3(1); 1223 switch (atlasIndex) { 1224 case 1: 1225 distance.x = sample(atlas1, half2(textureCoords) - offset).r; 1226 distance.y = sample(atlas1, textureCoords).r; 1227 distance.z = sample(atlas1, half2(textureCoords) + offset).r; 1228 case 2: 1229 distance.x = sample(atlas2, half2(textureCoords) - offset).r; 1230 distance.y = sample(atlas2, textureCoords).r; 1231 distance.z = sample(atlas2, half2(textureCoords) + offset).r; 1232 case 3: 1233 distance.x = sample(atlas3, half2(textureCoords) - offset).r; 1234 distance.y = sample(atlas3, textureCoords).r; 1235 distance.z = sample(atlas3, half2(textureCoords) + offset).r; 1236 default: 1237 distance.x = sample(atlas0, half2(textureCoords) - offset).r; 1238 distance.y = sample(atlas0, textureCoords).r; 1239 distance.z = sample(atlas0, half2(textureCoords) + offset).r; 1240 } 1241 return distance; 1242} 1243 1244$pure half4 bitmap_text_coverage_fn(half4 texColor, int maskFormat) { 1245 return (maskFormat == $kMaskFormatA8) ? texColor.rrrr 1246 : texColor; 1247} 1248 1249$pure half4 sdf_text_coverage_fn(half texColor, 1250 half2 gammaParams, 1251 float2 unormTexCoords) { 1252 // TODO: To minimize the number of shaders generated this is the full affine shader. 1253 // For best performance it may be worth creating the uniform scale shader as well, 1254 // as that's the most common case. 1255 // TODO: Need to add 565 support. 1256 1257 // The distance field is constructed as uchar8_t values, so that the zero value is at 128, 1258 // and the supported range of distances is [-4 * 127/128, 4]. 1259 // Hence to convert to floats our multiplier (width of the range) is 4 * 255/128 = 7.96875 1260 // and zero threshold is 128/255 = 0.50196078431. 1261 half dist = 7.96875 * (texColor - 0.50196078431); 1262 1263 // We may further adjust the distance for gamma correction. 1264 dist -= gammaParams.x; 1265 1266 // After the distance is unpacked, we need to correct it by a factor dependent on the 1267 // current transformation. For general transforms, to determine the amount of correction 1268 // we multiply a unit vector pointing along the SDF gradient direction by the Jacobian of 1269 // unormTexCoords (which is the inverse transform for this fragment) and take the length of 1270 // the result. 1271 half2 dist_grad = half2(dFdx(dist), dFdy(dist)); 1272 half dg_len2 = dot(dist_grad, dist_grad); 1273 1274 // The length of the gradient may be near 0, so we need to check for that. This also 1275 // compensates for the Adreno, which likes to drop tiles on division by 0. 1276 dist_grad = (dg_len2 >= 0.0001) ? dist_grad * inversesqrt(dg_len2) 1277 : half2(0.7071); 1278 1279 // Computing the Jacobian and multiplying by the gradient. 1280 float2x2 jacobian = float2x2(dFdx(unormTexCoords), dFdy(unormTexCoords)); 1281 half2 grad = half2(jacobian * dist_grad); 1282 1283 // This gives us a smooth step across approximately one fragment. 1284 half approxFragWidth = 0.65 * length(grad); 1285 1286 // TODO: handle aliased rendering 1287 if (gammaParams.y > 0) { 1288 // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are 1289 // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want 1290 // distance mapped linearly to coverage, so use a linear step: 1291 return half4(saturate((dist + approxFragWidth) / (2.0 * approxFragWidth))); 1292 } else { 1293 return half4(smoothstep(-approxFragWidth, approxFragWidth, dist)); 1294 } 1295} 1296 1297$pure half4 sdf_text_lcd_coverage_fn(float2 textureCoords, 1298 half2 pixelGeometryDelta, 1299 half4 gammaParams, 1300 float2 unormTexCoords, 1301 float texIndex, 1302 sampler2D atlas0, 1303 sampler2D atlas1, 1304 sampler2D atlas2, 1305 sampler2D atlas3) { 1306 // TODO: To minimize the number of shaders generated this is the full affine shader. 1307 // For best performance it may be worth creating the uniform scale shader as well, 1308 // as that's the most common case. 1309 1310 float2x2 jacobian = float2x2(dFdx(unormTexCoords), dFdy(unormTexCoords)); 1311 half2 offset = half2(jacobian * pixelGeometryDelta); 1312 1313 half3 distance = $sample_indexed_atlas_lcd(textureCoords, 1314 int(texIndex), 1315 offset, 1316 atlas0, 1317 atlas1, 1318 atlas2, 1319 atlas3); 1320 // The distance field is constructed as uchar8_t values, so that the zero value is at 128, 1321 // and the supported range of distances is [-4 * 127/128, 4]. 1322 // Hence to convert to floats our multiplier (width of the range) is 4 * 255/128 = 7.96875 1323 // and zero threshold is 128/255 = 0.50196078431. 1324 half3 dist = half3(7.96875) * (distance - half3(0.50196078431)); 1325 1326 // We may further adjust the distance for gamma correction. 1327 dist -= gammaParams.xyz; 1328 1329 // After the distance is unpacked, we need to correct it by a factor dependent on the 1330 // current transformation. For general transforms, to determine the amount of correction 1331 // we multiply a unit vector pointing along the SDF gradient direction by the Jacobian of 1332 // unormTexCoords (which is the inverse transform for this fragment) and take the length of 1333 // the result. 1334 half2 dist_grad = half2(dFdx(dist.g), dFdy(dist.g)); 1335 half dg_len2 = dot(dist_grad, dist_grad); 1336 1337 // The length of the gradient may be near 0, so we need to check for that. This also 1338 // compensates for the Adreno, which likes to drop tiles on division by 0. 1339 dist_grad = (dg_len2 >= 0.0001) ? dist_grad * inversesqrt(dg_len2) 1340 : half2(0.7071); 1341 1342 // Multiplying the Jacobian by the gradient. 1343 half2 grad = half2(jacobian * dist_grad); 1344 1345 // This gives us a smooth step across approximately one fragment. 1346 half3 approxFragWidth = half3(0.65 * length(grad)); 1347 1348 // TODO: handle aliased rendering 1349 if (gammaParams.w > 0) { 1350 // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are 1351 // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want 1352 // distance mapped linearly to coverage, so use a linear step: 1353 return half4(saturate((dist + approxFragWidth / (2.0 * approxFragWidth))), 1); 1354 } else { 1355 return half4(smoothstep(half3(-approxFragWidth), half3(approxFragWidth), dist), 1); 1356 } 1357} 1358 1359/////////////////////////////////////////////////////////////////////////////////////////////////// 1360// Support functions for analytic round rectangles 1361 1362// Calculates 1/|∇| in device space by applying the chain rule to a local gradient vector and the 1363// 2x2 Jacobian describing the transform from local-to-device space. For non-perspective, this is 1364// equivalent to the "normal matrix", or the inverse transpose. For perspective, J should be 1365// W(u,v) [m00' - m20'u m01' - m21'u] derived from the first two columns of the 3x3 inverse. 1366// [m10' - m20'v m11' - m21'v] 1367$pure float $inverse_grad_len(float2 localGrad, float2x2 jacobian) { 1368 // NOTE: By chain rule, the local gradient is on the left side of the Jacobian matrix 1369 float2 devGrad = localGrad * jacobian; 1370 // NOTE: This uses the L2 norm, which is more accurate than the L1 norm used by fwidth(). 1371 // TODO: Switch to L1 since it is a 2x perf improvement according to Xcode with little visual 1372 // impact, but start with L2 to measure the change separately from the algorithmic update. 1373 // return 1.0 / (abs(devGrad.x) + abs(devGrad.y)); 1374 return inversesqrt(dot(devGrad, devGrad)); 1375} 1376 1377// Returns distance from both sides of a stroked circle or ellipse. Elliptical coverage is 1378// only accurate if strokeRadius = 0. A positive value represents the interior of the stroke. 1379$pure float2 $elliptical_distance(float2 uv, float2 radii, float strokeRadius, float2x2 jacobian) { 1380 // We do need to evaluate up to two circle equations: one with 1381 // R = cornerRadius(r)+strokeRadius(s), and another with R = r-s. 1382 // This can be consolidated into a common evaluation against a circle of radius sqrt(r^2+s^2): 1383 // (x/(r+/-s))^2 + (y/(r+/-s))^2 = 1 1384 // x^2 + y^2 = (r+/-s)^2 1385 // x^2 + y^2 = r^2 + s^2 +/- 2rs 1386 // (x/sqrt(r^2+s^2))^2 + (y/sqrt(r^2+s^2)) = 1 +/- 2rs/(r^2+s^2) 1387 // The 2rs/(r^2+s^2) is the "width" that adjusts the implicit function to the outer or inner 1388 // edge of the stroke. For fills and hairlines, s = 0, which means these operations remain valid 1389 // for elliptical corners where radii holds the different X and Y corner radii. 1390 float2 invR2 = 1.0 / (radii * radii + strokeRadius*strokeRadius); 1391 float2 normUV = invR2 * uv; 1392 float invGradLength = $inverse_grad_len(normUV, jacobian); 1393 1394 // Since normUV already includes 1/r^2 in the denominator, dot with just 'uv' instead. 1395 float f = 0.5 * invGradLength * (dot(uv, normUV) - 1.0); 1396 1397 // This is 0 for fills/hairlines, which are the only types that allow 1398 // elliptical corners (strokeRadius == 0). For regular strokes just use X. 1399 float width = radii.x * strokeRadius * invR2.x * invGradLength; 1400 return float2(width - f, width + f); 1401} 1402 1403// Accumulates the minimum (and negative maximum) of the outer and inner corner distances in 'dist' 1404// for a possibly elliptical corner with 'radii' and relative pixel location specified by 1405// 'cornerEdgeDist'. The corner's basis relative to the jacobian is defined in 'xyFlip'. 1406void $corner_distance(inout float2 dist, 1407 float2x2 jacobian, 1408 float2 strokeParams, 1409 float2 cornerEdgeDist, 1410 float2 xyFlip, 1411 float2 radii) { 1412 float2 uv = radii - cornerEdgeDist; 1413 // NOTE: For mitered corners uv > 0 only if it's stroked, and in that case the 1414 // subsequent conditions skip calculating anything. 1415 if (all(greaterThan(uv, float2(0.0)))) { 1416 if (all(greaterThan(radii, float2(0.0))) || 1417 (strokeParams.x > 0.0 && strokeParams.y < 0.0 /* round-join */)) { 1418 // A rounded corner so incorporate outer elliptical distance if we're within the 1419 // quarter circle. 1420 float2 d = $elliptical_distance(uv * xyFlip, radii, strokeParams.x, jacobian); 1421 d.y = (radii.x - strokeParams.x <= 0.0) 1422 ? 1.0 // Disregard inner curve since it's collapsed into an inner miter. 1423 : -d.y; // Negate so that "min" accumulates the maximum value instead. 1424 dist = min(dist, d); 1425 } else if (strokeParams.y == 0.0 /* bevel-join */) { 1426 // Bevels are--by construction--interior mitered, so inner distance is based 1427 // purely on the edge distance calculations, but the outer distance is to a 45-degree 1428 // line and not the vertical/horizontal lines of the other edges. 1429 float bevelDist = (strokeParams.x - uv.x - uv.y) * $inverse_grad_len(xyFlip, jacobian); 1430 dist.x = min(dist.x, bevelDist); 1431 } // Else it's a miter so both inner and outer distances are unmodified 1432 } // Else we're not affected by the corner so leave distances unmodified 1433} 1434 1435// Accumulates the minimum (and negative maximum) of the outer and inner corner distances into 'd', 1436// for all four corners of a [round] rectangle. 'edgeDists' should be ordered LTRB with positive 1437// distance representing the interior of the edge. 'xRadii' and 'yRadii' should hold the per-corner 1438// elliptical radii, ordered TL, TR, BR, BL. 1439void $corner_distances(inout float2 d, 1440 float2x2 J, 1441 float2 stroke, // {radii, joinStyle}, see StrokeStyle struct definition 1442 float4 edgeDists, 1443 float4 xRadii, 1444 float4 yRadii) { 1445 $corner_distance(d, J, stroke, edgeDists.xy, float2(-1.0, -1.0), float2(xRadii[0], yRadii[0])); 1446 $corner_distance(d, J, stroke, edgeDists.zy, float2( 1.0, -1.0), float2(xRadii[1], yRadii[1])); 1447 $corner_distance(d, J, stroke, edgeDists.zw, float2( 1.0, 1.0), float2(xRadii[2], yRadii[2])); 1448 $corner_distance(d, J, stroke, edgeDists.xw, float2(-1.0, 1.0), float2(xRadii[3], yRadii[3])); 1449} 1450 1451$pure half4 analytic_rrect_coverage_fn(float4 coords, 1452 float4 jacobian, 1453 float4 edgeDistances, 1454 float4 xRadii, 1455 float4 yRadii, 1456 float2 strokeParams, 1457 float2 perPixelControl) { 1458 if (perPixelControl.x > 0.0) { 1459 // A trivially solid interior pixel, either from a filled rect or round rect, or a 1460 // stroke with sufficiently large width that the interior completely overlaps itself. 1461 return half4(1.0); 1462 } else if (perPixelControl.y > 1.0) { 1463 // This represents a filled rectangle or quadrilateral, where the distances have already 1464 // been converted to device space. Mitered strokes cannot use this optimization because 1465 // their scale and bias is not uniform over the shape; Rounded shapes cannot use this 1466 // because they rely on the edge distances being in local space to reconstruct the 1467 // per-corner positions for the elliptical implicit functions. 1468 float2 outerDist = min(edgeDistances.xy, edgeDistances.zw); 1469 float c = min(outerDist.x, outerDist.y) * coords.w; 1470 float scale = (perPixelControl.y - 1.0) * coords.w; 1471 float bias = coverage_bias(scale); 1472 return half4(saturate(scale * (c + bias))); 1473 } else { 1474 // Compute per-pixel coverage, mixing four outer edge distances, possibly four inner 1475 // edge distances, and per-corner elliptical distances into a final coverage value. 1476 // The Jacobian needs to be multiplied by W, but coords.w stores 1/w. 1477 float2x2 J = float2x2(jacobian) / coords.w; 1478 1479 float2 invGradLen = float2($inverse_grad_len(float2(1.0, 0.0), J), 1480 $inverse_grad_len(float2(0.0, 1.0), J)); 1481 float2 outerDist = invGradLen * (strokeParams.x + min(edgeDistances.xy, 1482 edgeDistances.zw)); 1483 1484 // d.x tracks minimum outer distance (pre scale-and-biasing to a coverage value). 1485 // d.y tracks negative maximum inner distance (so min() over c accumulates min and outer 1486 // and max inner simultaneously).) 1487 float2 d = float2(min(outerDist.x, outerDist.y), -1.0); 1488 float scale, bias; 1489 1490 // Check for bidirectional coverage, which is is marked as a -1 from the vertex shader. 1491 // We don't just check for < 0 since extrapolated fill triangle samples can have small 1492 // negative values. 1493 if (perPixelControl.x > -0.95) { 1494 // A solid interior, so update scale and bias based on full width and height 1495 float2 dim = invGradLen * (edgeDistances.xy + edgeDistances.zw + 2*strokeParams.xx); 1496 scale = min(min(dim.x, dim.y), 1.0); 1497 bias = coverage_bias(scale); 1498 // Since we leave d.y = -1.0, no inner curve coverage will adjust it closer to 0, 1499 // so 'finalCoverage' is based solely on outer edges and curves. 1500 } else { 1501 // Bidirectional coverage, so we modify c.y to hold the negative of the maximum 1502 // interior coverage, and update scale and bias based on stroke width. 1503 float2 strokeWidth = 2.0 * strokeParams.x * invGradLen; 1504 float2 innerDist = strokeWidth - outerDist; 1505 1506 d.y = -max(innerDist.x, innerDist.y); 1507 if (strokeParams.x > 0.0) { 1508 float narrowStroke = min(strokeWidth.x, strokeWidth.y); 1509 // On an axis where innerDist >= -0.5, allow strokeWidth.x/y to be preserved as-is. 1510 // On an axis where innerDist < -0.5, use the smaller of strokeWidth.x/y. 1511 float2 strokeDim = mix(float2(narrowStroke), strokeWidth, 1512 greaterThanEqual(innerDist, float2(-0.5))); 1513 // Preserve the wider axis from the above calculation. 1514 scale = saturate(max(strokeDim.x, strokeDim.y)); 1515 bias = coverage_bias(scale); 1516 } else { 1517 // A hairline, so scale and bias should both be 1 1518 scale = bias = 1.0; 1519 } 1520 } 1521 1522 // Check all corners, although most pixels should only be influenced by 1. 1523 $corner_distances(d, J, strokeParams, edgeDistances, xRadii, yRadii); 1524 1525 float outsetDist = min(perPixelControl.y, 0.0) * coords.w; 1526 float finalCoverage = scale * (min(d.x + outsetDist, -d.y) + bias); 1527 1528 return half4(saturate(finalCoverage)); 1529 } 1530} 1531 1532$pure half4 per_edge_aa_quad_coverage_fn(float4 coords, 1533 float4 edgeDistances) { 1534 // This represents a filled rectangle or quadrilateral, where the distances have already 1535 // been converted to device space. 1536 float2 outerDist = min(edgeDistances.xy, edgeDistances.zw); 1537 float c = min(outerDist.x, outerDist.y) * coords.w; 1538 return half4(saturate(c)); 1539} 1540 1541$pure half4 circular_arc_coverage_fn(float4 circleEdge, 1542 float3 clipPlane, 1543 float3 isectPlane, 1544 float3 unionPlane, 1545 float roundCapRadius, 1546 float4 roundCapPos) { 1547 float d = length(circleEdge.xy); 1548 half distanceToOuterEdge = half(circleEdge.z * (1.0 - d)); 1549 half edgeAlpha = saturate(distanceToOuterEdge); 1550 half distanceToInnerEdge = half(circleEdge.z * (d - circleEdge.w)); 1551 half innerAlpha = saturate(distanceToInnerEdge); 1552 edgeAlpha *= innerAlpha; 1553 1554 half clip = half(saturate(circleEdge.z * dot(circleEdge.xy, clipPlane.xy) + clipPlane.z)); 1555 clip *= half(saturate(circleEdge.z * dot(circleEdge.xy, isectPlane.xy) + isectPlane.z)); 1556 clip = clip + half(saturate(circleEdge.z * dot(circleEdge.xy, unionPlane.xy) + unionPlane.z)); 1557 1558 // We compute coverage of the round caps as circles at the butt caps produced 1559 // by the clip planes, and union it with the current clip. 1560 half dcap1 = half(circleEdge.z * (roundCapRadius - length(circleEdge.xy - roundCapPos.xy))); 1561 half dcap2 = half(circleEdge.z * (roundCapRadius - length(circleEdge.xy - roundCapPos.zw))); 1562 half capAlpha = max(dcap1, 0) + max(dcap2, 0); 1563 clip = saturate(clip + capAlpha); 1564 1565 return half4(clip*edgeAlpha); 1566} 1567 1568$pure half4 $rect_blur_coverage_fn(float2 coords, 1569 float4 rect, 1570 half isFast, 1571 half invSixSigma, 1572 sampler2D integral) { 1573 half xCoverage; 1574 half yCoverage; 1575 1576 if (isFast != 0.0) { 1577 // Get the smaller of the signed distance from the frag coord to the left and right 1578 // edges and similar for y. 1579 // The integral texture goes "backwards" (from 3*sigma to -3*sigma), So, the below 1580 // computations align the left edge of the integral texture with the inset rect's 1581 // edge extending outward 6 * sigma from the inset rect. 1582 half2 pos = max(half2(rect.LT - coords), half2(coords - rect.RB)); 1583 xCoverage = sample(integral, float2(invSixSigma * pos.x, 0.5)).r; 1584 yCoverage = sample(integral, float2(invSixSigma * pos.y, 0.5)).r; 1585 1586 } else { 1587 // We just consider just the x direction here. In practice we compute x and y 1588 // separately and multiply them together. 1589 // We define our coord system so that the point at which we're evaluating a kernel 1590 // defined by the normal distribution (K) at 0. In this coord system let L be left 1591 // edge and R be the right edge of the rectangle. 1592 // We can calculate C by integrating K with the half infinite ranges outside the 1593 // L to R range and subtracting from 1: 1594 // C = 1 - <integral of K from from -inf to L> - <integral of K from R to inf> 1595 // K is symmetric about x=0 so: 1596 // C = 1 - <integral of K from from -inf to L> - <integral of K from -inf to -R> 1597 1598 // The integral texture goes "backwards" (from 3*sigma to -3*sigma) which is 1599 // factored in to the below calculations. 1600 // Also, our rect uniform was pre-inset by 3 sigma from the actual rect being 1601 // blurred, also factored in. 1602 half4 rect = half4(half2(rect.LT - coords), half2(coords - rect.RB)); 1603 xCoverage = 1 - sample(integral, float2(invSixSigma * rect.L, 0.5)).r 1604 - sample(integral, float2(invSixSigma * rect.R, 0.5)).r; 1605 yCoverage = 1 - sample(integral, float2(invSixSigma * rect.T, 0.5)).r 1606 - sample(integral, float2(invSixSigma * rect.B, 0.5)).r; 1607 } 1608 1609 return half4(xCoverage * yCoverage); 1610} 1611 1612$pure half4 $circle_blur_coverage_fn(float2 coords, float4 circle, sampler2D blurProfile) { 1613 // We just want to compute "(length(vec) - solidRadius + 0.5) / textureRadius" but need to 1614 // rearrange to avoid passing large values to length() that would overflow. We've precalculated 1615 // "1 / textureRadius" and "(solidRadius - 0.5) / textureRadius" on the CPU as circle.z and 1616 // circle.w, respectively. 1617 float invTextureRadius = circle.z; 1618 float normSolidRadius = circle.w; 1619 1620 half2 vec = half2((coords - circle.xy) * invTextureRadius); 1621 float dist = length(vec) - normSolidRadius; 1622 return sample(blurProfile, float2(dist, 0.5)).rrrr; 1623} 1624 1625$pure half4 $rrect_blur_coverage_fn(float2 coords, 1626 float4 proxyRect, 1627 half edgeSize, 1628 sampler2D ninePatch) { 1629 // Warp the fragment position to the appropriate part of the 9-patch blur texture by 1630 // snipping out the middle section of the proxy rect. 1631 float2 translatedFragPosFloat = coords - proxyRect.LT; 1632 float2 proxyCenter = (proxyRect.RB - proxyRect.LT) * 0.5; 1633 1634 // Position the fragment so that (0, 0) marks the center of the proxy rectangle. 1635 // Negative coordinates are on the left/top side and positive numbers are on the 1636 // right/bottom. 1637 translatedFragPosFloat -= proxyCenter; 1638 1639 // Temporarily strip off the fragment's sign. x/y are now strictly increasing as we 1640 // move away from the center. 1641 half2 fragDirection = half2(sign(translatedFragPosFloat)); 1642 translatedFragPosFloat = abs(translatedFragPosFloat); 1643 1644 // Our goal is to snip out the "middle section" of the proxy rect (everything but the 1645 // edge). We've repositioned our fragment position so that (0, 0) is the centerpoint 1646 // and x/y are always positive, so we can subtract here and interpret negative results 1647 // as being within the middle section. 1648 half2 translatedFragPosHalf = half2(translatedFragPosFloat - (proxyCenter - edgeSize)); 1649 1650 // Remove the middle section by clamping to zero. 1651 translatedFragPosHalf = max(translatedFragPosHalf, 0); 1652 1653 // Reapply the fragment's sign, so that negative coordinates once again mean left/top 1654 // side and positive means bottom/right side. 1655 translatedFragPosHalf *= fragDirection; 1656 1657 // Offset the fragment so that (0, 0) marks the upper-left again, instead of the center 1658 // point. 1659 translatedFragPosHalf += half2(edgeSize); 1660 1661 half2 proxyDims = half2(2.0 * edgeSize); 1662 half2 texCoord = translatedFragPosHalf / proxyDims; 1663 1664 return sample(ninePatch, texCoord).rrrr; 1665} 1666 1667$pure half4 blur_coverage_fn(float2 coords, 1668 float4 shapeData, 1669 half2 blurData, 1670 int shapeType, 1671 sampler2D s) { 1672 switch (shapeType) { 1673 case $kShapeTypeRect: { 1674 return $rect_blur_coverage_fn(coords, shapeData, blurData.x, blurData.y, s); 1675 } 1676 case $kShapeTypeCircle: { 1677 return $circle_blur_coverage_fn(coords, shapeData, s); 1678 } 1679 case $kShapeTypeRRect: { 1680 return $rrect_blur_coverage_fn(coords, shapeData, blurData.x, s); 1681 } 1682 } 1683 return half4(0); 1684} 1685