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