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