SkRadialGradient.cpp revision 57d3b039c635945e1dc2fcbac3462ed8bfedb068
1 2/* 3 * Copyright 2012 Google Inc. 4 * 5 * Use of this source code is governed by a BSD-style license that can be 6 * found in the LICENSE file. 7 */ 8 9#include "SkRadialGradient.h" 10#include "SkRadialGradient_Table.h" 11#include "SkNx.h" 12 13#define kSQRT_TABLE_BITS 11 14#define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS) 15 16static_assert(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE, "SqrtTableSizesMatch"); 17 18#if 0 19 20#include <stdio.h> 21 22void SkRadialGradient_BuildTable() { 23 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table 24 25 FILE* file = ::fopen("SkRadialGradient_Table.h", "w"); 26 SkASSERT(file); 27 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n"); 28 29 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) { 30 if ((i & 15) == 0) { 31 ::fprintf(file, "\t"); 32 } 33 34 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8); 35 36 ::fprintf(file, "0x%02X", value); 37 if (i < kSQRT_TABLE_SIZE-1) { 38 ::fprintf(file, ", "); 39 } 40 if ((i & 15) == 15) { 41 ::fprintf(file, "\n"); 42 } 43 } 44 ::fprintf(file, "};\n"); 45 ::fclose(file); 46} 47 48#endif 49 50namespace { 51 52// GCC doesn't like using static functions as template arguments. So force these to be non-static. 53inline SkFixed mirror_tileproc_nonstatic(SkFixed x) { 54 return mirror_tileproc(x); 55} 56 57inline SkFixed repeat_tileproc_nonstatic(SkFixed x) { 58 return repeat_tileproc(x); 59} 60 61SkMatrix rad_to_unit_matrix(const SkPoint& center, SkScalar radius) { 62 SkScalar inv = SkScalarInvert(radius); 63 64 SkMatrix matrix; 65 matrix.setTranslate(-center.fX, -center.fY); 66 matrix.postScale(inv, inv); 67 return matrix; 68} 69 70typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx, 71 SkScalar sfy, SkScalar sdy, 72 uint16_t* dstC, const uint16_t* cache, 73 int toggle, int count); 74 75void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx, 76 SkScalar sfy, SkScalar sdy, 77 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache, 78 int toggle, int count) { 79 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table; 80 81 /* knock these down so we can pin against +- 0x7FFF, which is an 82 immediate load, rather than 0xFFFF which is slower. This is a 83 compromise, since it reduces our precision, but that appears 84 to be visually OK. If we decide this is OK for all of our cases, 85 we could (it seems) put this scale-down into fDstToIndex, 86 to avoid having to do these extra shifts each time. 87 */ 88 SkFixed fx = SkScalarToFixed(sfx) >> 1; 89 SkFixed dx = SkScalarToFixed(sdx) >> 1; 90 SkFixed fy = SkScalarToFixed(sfy) >> 1; 91 SkFixed dy = SkScalarToFixed(sdy) >> 1; 92 // might perform this check for the other modes, 93 // but the win will be a smaller % of the total 94 if (dy == 0) { 95 fy = SkTPin(fy, -0xFFFF >> 1, 0xFFFF >> 1); 96 fy *= fy; 97 do { 98 unsigned xx = SkTPin(fx, -0xFFFF >> 1, 0xFFFF >> 1); 99 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS); 100 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS)); 101 fx += dx; 102 *dstC++ = cache[toggle + 103 (sqrt_table[fi] >> SkGradientShaderBase::kSqrt16Shift)]; 104 toggle = next_dither_toggle16(toggle); 105 } while (--count != 0); 106 } else { 107 do { 108 unsigned xx = SkTPin(fx, -0xFFFF >> 1, 0xFFFF >> 1); 109 unsigned fi = SkTPin(fy, -0xFFFF >> 1, 0xFFFF >> 1); 110 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS); 111 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS)); 112 fx += dx; 113 fy += dy; 114 *dstC++ = cache[toggle + 115 (sqrt_table[fi] >> SkGradientShaderBase::kSqrt16Shift)]; 116 toggle = next_dither_toggle16(toggle); 117 } while (--count != 0); 118 } 119} 120 121template <SkFixed (*TileProc)(SkFixed)> 122void shadeSpan16_radial(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, 123 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache, 124 int toggle, int count) { 125 do { 126 const SkFixed dist = SkFloatToFixed(sk_float_sqrt(fx*fx + fy*fy)); 127 const unsigned fi = TileProc(dist); 128 SkASSERT(fi <= 0xFFFF); 129 *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache16Shift)]; 130 toggle = next_dither_toggle16(toggle); 131 fx += dx; 132 fy += dy; 133 } while (--count != 0); 134} 135 136void shadeSpan16_radial_mirror(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, 137 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache, 138 int toggle, int count) { 139 shadeSpan16_radial<mirror_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, toggle, count); 140} 141 142void shadeSpan16_radial_repeat(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, 143 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache, 144 int toggle, int count) { 145 shadeSpan16_radial<repeat_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, toggle, count); 146} 147 148} // namespace 149 150///////////////////////////////////////////////////////////////////// 151 152SkRadialGradient::SkRadialGradient(const SkPoint& center, SkScalar radius, const Descriptor& desc) 153 : SkGradientShaderBase(desc, rad_to_unit_matrix(center, radius)) 154 , fCenter(center) 155 , fRadius(radius) { 156} 157 158size_t SkRadialGradient::contextSize() const { 159 return sizeof(RadialGradientContext); 160} 161 162SkShader::Context* SkRadialGradient::onCreateContext(const ContextRec& rec, void* storage) const { 163 return new (storage) RadialGradientContext(*this, rec); 164} 165 166SkRadialGradient::RadialGradientContext::RadialGradientContext( 167 const SkRadialGradient& shader, const ContextRec& rec) 168 : INHERITED(shader, rec) {} 169 170void SkRadialGradient::RadialGradientContext::shadeSpan16(int x, int y, uint16_t* dstCParam, 171 int count) { 172 SkASSERT(count > 0); 173 174 const SkRadialGradient& radialGradient = static_cast<const SkRadialGradient&>(fShader); 175 176 uint16_t* SK_RESTRICT dstC = dstCParam; 177 178 SkPoint srcPt; 179 SkMatrix::MapXYProc dstProc = fDstToIndexProc; 180 TileProc proc = radialGradient.fTileProc; 181 const uint16_t* SK_RESTRICT cache = fCache->getCache16(); 182 int toggle = init_dither_toggle16(x, y); 183 184 if (fDstToIndexClass != kPerspective_MatrixClass) { 185 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, 186 SkIntToScalar(y) + SK_ScalarHalf, &srcPt); 187 188 SkScalar sdx = fDstToIndex.getScaleX(); 189 SkScalar sdy = fDstToIndex.getSkewY(); 190 191 if (fDstToIndexClass == kFixedStepInX_MatrixClass) { 192 SkFixed storage[2]; 193 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), 194 &storage[0], &storage[1]); 195 sdx = SkFixedToScalar(storage[0]); 196 sdy = SkFixedToScalar(storage[1]); 197 } else { 198 SkASSERT(fDstToIndexClass == kLinear_MatrixClass); 199 } 200 201 RadialShade16Proc shadeProc = shadeSpan16_radial_repeat; 202 if (SkShader::kClamp_TileMode == radialGradient.fTileMode) { 203 shadeProc = shadeSpan16_radial_clamp; 204 } else if (SkShader::kMirror_TileMode == radialGradient.fTileMode) { 205 shadeProc = shadeSpan16_radial_mirror; 206 } else { 207 SkASSERT(SkShader::kRepeat_TileMode == radialGradient.fTileMode); 208 } 209 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, 210 cache, toggle, count); 211 } else { // perspective case 212 SkScalar dstX = SkIntToScalar(x); 213 SkScalar dstY = SkIntToScalar(y); 214 do { 215 dstProc(fDstToIndex, dstX, dstY, &srcPt); 216 unsigned fi = proc(SkScalarToFixed(srcPt.length())); 217 SkASSERT(fi <= 0xFFFF); 218 219 int index = fi >> (16 - kCache16Bits); 220 *dstC++ = cache[toggle + index]; 221 toggle = next_dither_toggle16(toggle); 222 223 dstX += SK_Scalar1; 224 } while (--count != 0); 225 } 226} 227 228SkShader::GradientType SkRadialGradient::asAGradient(GradientInfo* info) const { 229 if (info) { 230 commonAsAGradient(info); 231 info->fPoint[0] = fCenter; 232 info->fRadius[0] = fRadius; 233 } 234 return kRadial_GradientType; 235} 236 237SkFlattenable* SkRadialGradient::CreateProc(SkReadBuffer& buffer) { 238 DescriptorScope desc; 239 if (!desc.unflatten(buffer)) { 240 return nullptr; 241 } 242 const SkPoint center = buffer.readPoint(); 243 const SkScalar radius = buffer.readScalar(); 244 return SkGradientShader::CreateRadial(center, radius, desc.fColors, desc.fPos, desc.fCount, 245 desc.fTileMode, desc.fGradFlags, desc.fLocalMatrix); 246} 247 248void SkRadialGradient::flatten(SkWriteBuffer& buffer) const { 249 this->INHERITED::flatten(buffer); 250 buffer.writePoint(fCenter); 251 buffer.writeScalar(fRadius); 252} 253 254namespace { 255 256inline bool radial_completely_pinned(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy) { 257 // fast, overly-conservative test: checks unit square instead of unit circle 258 bool xClamped = (fx >= 1 && dx >= 0) || (fx <= -1 && dx <= 0); 259 bool yClamped = (fy >= 1 && dy >= 0) || (fy <= -1 && dy <= 0); 260 return xClamped || yClamped; 261} 262 263typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx, 264 SkScalar sfy, SkScalar sdy, 265 SkPMColor* dstC, const SkPMColor* cache, 266 int count, int toggle); 267 268static inline Sk4f fast_sqrt(const Sk4f& R) { 269 // R * R.rsqrt0() is much faster, but it's non-monotonic, which isn't so pretty for gradients. 270 return R * R.rsqrt1(); 271} 272 273static inline Sk4f sum_squares(const Sk4f& a, const Sk4f& b) { 274 return a * a + b * b; 275} 276 277void shadeSpan_radial_clamp2(SkScalar sfx, SkScalar sdx, SkScalar sfy, SkScalar sdy, 278 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, 279 int count, int toggle) { 280 if (radial_completely_pinned(sfx, sdx, sfy, sdy)) { 281 unsigned fi = SkGradientShaderBase::kCache32Count - 1; 282 sk_memset32_dither(dstC, 283 cache[toggle + fi], 284 cache[next_dither_toggle(toggle) + fi], 285 count); 286 } else { 287 const Sk4f max(255); 288 const float scale = 255; 289 sfx *= scale; 290 sfy *= scale; 291 sdx *= scale; 292 sdy *= scale; 293 const Sk4f fx4(sfx, sfx + sdx, sfx + 2*sdx, sfx + 3*sdx); 294 const Sk4f fy4(sfy, sfy + sdy, sfy + 2*sdy, sfy + 3*sdy); 295 const Sk4f dx4(sdx * 4); 296 const Sk4f dy4(sdy * 4); 297 298 Sk4f tmpxy = fx4 * dx4 + fy4 * dy4; 299 Sk4f tmpdxdy = sum_squares(dx4, dy4); 300 Sk4f R = sum_squares(fx4, fy4); 301 Sk4f dR = tmpxy + tmpxy + tmpdxdy; 302 const Sk4f ddR = tmpdxdy + tmpdxdy; 303 304 for (int i = 0; i < (count >> 2); ++i) { 305 Sk4f dist = Sk4f::Min(fast_sqrt(R), max); 306 R = R + dR; 307 dR = dR + ddR; 308 309 uint8_t fi[4]; 310 dist.toBytes(fi); 311 312 for (int i = 0; i < 4; i++) { 313 *dstC++ = cache[toggle + fi[i]]; 314 toggle = next_dither_toggle(toggle); 315 } 316 } 317 count &= 3; 318 if (count) { 319 Sk4f dist = Sk4f::Min(fast_sqrt(R), max); 320 321 uint8_t fi[4]; 322 dist.toBytes(fi); 323 for (int i = 0; i < count; i++) { 324 *dstC++ = cache[toggle + fi[i]]; 325 toggle = next_dither_toggle(toggle); 326 } 327 } 328 } 329} 330 331// Unrolling this loop doesn't seem to help (when float); we're stalling to 332// get the results of the sqrt (?), and don't have enough extra registers to 333// have many in flight. 334template <SkFixed (*TileProc)(SkFixed)> 335void shadeSpan_radial(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, 336 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, 337 int count, int toggle) { 338 do { 339 const SkFixed dist = SkFloatToFixed(sk_float_sqrt(fx*fx + fy*fy)); 340 const unsigned fi = TileProc(dist); 341 SkASSERT(fi <= 0xFFFF); 342 *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache32Shift)]; 343 toggle = next_dither_toggle(toggle); 344 fx += dx; 345 fy += dy; 346 } while (--count != 0); 347} 348 349void shadeSpan_radial_mirror(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, 350 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, 351 int count, int toggle) { 352 shadeSpan_radial<mirror_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, count, toggle); 353} 354 355void shadeSpan_radial_repeat(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, 356 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, 357 int count, int toggle) { 358 shadeSpan_radial<repeat_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, count, toggle); 359} 360 361} // namespace 362 363void SkRadialGradient::RadialGradientContext::shadeSpan(int x, int y, 364 SkPMColor* SK_RESTRICT dstC, int count) { 365 SkASSERT(count > 0); 366 367 const SkRadialGradient& radialGradient = static_cast<const SkRadialGradient&>(fShader); 368 369 SkPoint srcPt; 370 SkMatrix::MapXYProc dstProc = fDstToIndexProc; 371 TileProc proc = radialGradient.fTileProc; 372 const SkPMColor* SK_RESTRICT cache = fCache->getCache32(); 373 int toggle = init_dither_toggle(x, y); 374 375 if (fDstToIndexClass != kPerspective_MatrixClass) { 376 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, 377 SkIntToScalar(y) + SK_ScalarHalf, &srcPt); 378 SkScalar sdx = fDstToIndex.getScaleX(); 379 SkScalar sdy = fDstToIndex.getSkewY(); 380 381 if (fDstToIndexClass == kFixedStepInX_MatrixClass) { 382 SkFixed storage[2]; 383 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), 384 &storage[0], &storage[1]); 385 sdx = SkFixedToScalar(storage[0]); 386 sdy = SkFixedToScalar(storage[1]); 387 } else { 388 SkASSERT(fDstToIndexClass == kLinear_MatrixClass); 389 } 390 391 RadialShadeProc shadeProc = shadeSpan_radial_repeat; 392 if (SkShader::kClamp_TileMode == radialGradient.fTileMode) { 393 shadeProc = shadeSpan_radial_clamp2; 394 } else if (SkShader::kMirror_TileMode == radialGradient.fTileMode) { 395 shadeProc = shadeSpan_radial_mirror; 396 } else { 397 SkASSERT(SkShader::kRepeat_TileMode == radialGradient.fTileMode); 398 } 399 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle); 400 } else { // perspective case 401 SkScalar dstX = SkIntToScalar(x); 402 SkScalar dstY = SkIntToScalar(y); 403 do { 404 dstProc(fDstToIndex, dstX, dstY, &srcPt); 405 unsigned fi = proc(SkScalarToFixed(srcPt.length())); 406 SkASSERT(fi <= 0xFFFF); 407 *dstC++ = cache[fi >> SkGradientShaderBase::kCache32Shift]; 408 dstX += SK_Scalar1; 409 } while (--count != 0); 410 } 411} 412 413///////////////////////////////////////////////////////////////////// 414 415#if SK_SUPPORT_GPU 416 417#include "SkGr.h" 418#include "gl/builders/GrGLProgramBuilder.h" 419 420class GrGLRadialGradient : public GrGLGradientEffect { 421public: 422 423 GrGLRadialGradient(const GrProcessor&) {} 424 virtual ~GrGLRadialGradient() { } 425 426 virtual void emitCode(EmitArgs&) override; 427 428 static void GenKey(const GrProcessor& processor, const GrGLSLCaps&, GrProcessorKeyBuilder* b) { 429 b->add32(GenBaseGradientKey(processor)); 430 } 431 432private: 433 434 typedef GrGLGradientEffect INHERITED; 435 436}; 437 438///////////////////////////////////////////////////////////////////// 439 440class GrRadialGradient : public GrGradientEffect { 441public: 442 static GrFragmentProcessor* Create(GrContext* ctx, 443 const SkRadialGradient& shader, 444 const SkMatrix& matrix, 445 SkShader::TileMode tm) { 446 return new GrRadialGradient(ctx, shader, matrix, tm); 447 } 448 449 virtual ~GrRadialGradient() { } 450 451 const char* name() const override { return "Radial Gradient"; } 452 453private: 454 GrRadialGradient(GrContext* ctx, 455 const SkRadialGradient& shader, 456 const SkMatrix& matrix, 457 SkShader::TileMode tm) 458 : INHERITED(ctx, shader, matrix, tm) { 459 this->initClassID<GrRadialGradient>(); 460 } 461 462 GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { 463 return new GrGLRadialGradient(*this); 464 } 465 466 virtual void onGetGLSLProcessorKey(const GrGLSLCaps& caps, 467 GrProcessorKeyBuilder* b) const override { 468 GrGLRadialGradient::GenKey(*this, caps, b); 469 } 470 471 GR_DECLARE_FRAGMENT_PROCESSOR_TEST; 472 473 typedef GrGradientEffect INHERITED; 474}; 475 476///////////////////////////////////////////////////////////////////// 477 478GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRadialGradient); 479 480const GrFragmentProcessor* GrRadialGradient::TestCreate(GrProcessorTestData* d) { 481 SkPoint center = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; 482 SkScalar radius = d->fRandom->nextUScalar1(); 483 484 SkColor colors[kMaxRandomGradientColors]; 485 SkScalar stopsArray[kMaxRandomGradientColors]; 486 SkScalar* stops = stopsArray; 487 SkShader::TileMode tm; 488 int colorCount = RandomGradientParams(d->fRandom, colors, &stops, &tm); 489 SkAutoTUnref<SkShader> shader(SkGradientShader::CreateRadial(center, radius, 490 colors, stops, colorCount, 491 tm)); 492 const GrFragmentProcessor* fp = shader->asFragmentProcessor(d->fContext, 493 GrTest::TestMatrix(d->fRandom), NULL, kNone_SkFilterQuality); 494 GrAlwaysAssert(fp); 495 return fp; 496} 497 498///////////////////////////////////////////////////////////////////// 499 500void GrGLRadialGradient::emitCode(EmitArgs& args) { 501 const GrRadialGradient& ge = args.fFp.cast<GrRadialGradient>(); 502 this->emitUniforms(args.fBuilder, ge); 503 SkString t("length("); 504 t.append(args.fBuilder->getFragmentShaderBuilder()->ensureFSCoords2D(args.fCoords, 0)); 505 t.append(")"); 506 this->emitColor(args.fBuilder, ge, t.c_str(), args.fOutputColor, args.fInputColor, 507 args.fSamplers); 508} 509 510///////////////////////////////////////////////////////////////////// 511 512const GrFragmentProcessor* SkRadialGradient::asFragmentProcessor( 513 GrContext* context, 514 const SkMatrix& viewM, 515 const SkMatrix* localMatrix, 516 SkFilterQuality) const { 517 SkASSERT(context); 518 519 SkMatrix matrix; 520 if (!this->getLocalMatrix().invert(&matrix)) { 521 return nullptr; 522 } 523 if (localMatrix) { 524 SkMatrix inv; 525 if (!localMatrix->invert(&inv)) { 526 return nullptr; 527 } 528 matrix.postConcat(inv); 529 } 530 matrix.postConcat(fPtsToUnit); 531 SkAutoTUnref<const GrFragmentProcessor> inner( 532 GrRadialGradient::Create(context, *this, matrix, fTileMode)); 533 return GrFragmentProcessor::MulOutputByInputAlpha(inner); 534} 535 536#endif 537 538#ifndef SK_IGNORE_TO_STRING 539void SkRadialGradient::toString(SkString* str) const { 540 str->append("SkRadialGradient: ("); 541 542 str->append("center: ("); 543 str->appendScalar(fCenter.fX); 544 str->append(", "); 545 str->appendScalar(fCenter.fY); 546 str->append(") radius: "); 547 str->appendScalar(fRadius); 548 str->append(" "); 549 550 this->INHERITED::toString(str); 551 552 str->append(")"); 553} 554#endif 555