3 * Copyright 2012 Google Inc.
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
9 #include "SkRadialGradient.h"
10 #include "SkRadialGradient_Table.h"
12 #define kSQRT_TABLE_BITS 11
13 #define kSQRT_TABLE_SIZE (1 << kSQRT_TABLE_BITS)
19 void SkRadialGradient_BuildTable() {
20 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
22 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
24 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
26 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
28 ::fprintf(file, "\t");
31 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
33 ::fprintf(file, "0x%02X", value);
34 if (i < kSQRT_TABLE_SIZE-1) {
35 ::fprintf(file, ", ");
38 ::fprintf(file, "\n");
41 ::fprintf(file, "};\n");
49 // GCC doesn't like using static functions as template arguments. So force these to be non-static.
50 inline SkFixed mirror_tileproc_nonstatic(SkFixed x) {
51 return mirror_tileproc(x);
54 inline SkFixed repeat_tileproc_nonstatic(SkFixed x) {
55 return repeat_tileproc(x);
58 void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
60 SkScalar inv = SkScalarInvert(radius);
62 matrix->setTranslate(-center.fX, -center.fY);
63 matrix->postScale(inv, inv);
66 typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx,
67 SkScalar sfy, SkScalar sdy,
68 uint16_t* dstC, const uint16_t* cache,
69 int toggle, int count);
71 void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
72 SkScalar sfy, SkScalar sdy,
73 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
74 int toggle, int count) {
75 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
77 /* knock these down so we can pin against +- 0x7FFF, which is an
78 immediate load, rather than 0xFFFF which is slower. This is a
79 compromise, since it reduces our precision, but that appears
80 to be visually OK. If we decide this is OK for all of our cases,
81 we could (it seems) put this scale-down into fDstToIndex,
82 to avoid having to do these extra shifts each time.
84 SkFixed fx = SkScalarToFixed(sfx) >> 1;
85 SkFixed dx = SkScalarToFixed(sdx) >> 1;
86 SkFixed fy = SkScalarToFixed(sfy) >> 1;
87 SkFixed dy = SkScalarToFixed(sdy) >> 1;
88 // might perform this check for the other modes,
89 // but the win will be a smaller % of the total
91 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
94 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
95 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
96 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
98 *dstC++ = cache[toggle +
99 (sqrt_table[fi] >> SkGradientShaderBase::kSqrt16Shift)];
100 toggle = next_dither_toggle16(toggle);
101 } while (--count != 0);
104 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
105 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
106 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
107 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
110 *dstC++ = cache[toggle +
111 (sqrt_table[fi] >> SkGradientShaderBase::kSqrt16Shift)];
112 toggle = next_dither_toggle16(toggle);
113 } while (--count != 0);
117 template <SkFixed (*TileProc)(SkFixed)>
118 void shadeSpan16_radial(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy,
119 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
120 int toggle, int count) {
122 const SkFixed dist = SkFloatToFixed(sk_float_sqrt(fx*fx + fy*fy));
123 const unsigned fi = TileProc(dist);
124 SkASSERT(fi <= 0xFFFF);
125 *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache16Shift)];
126 toggle = next_dither_toggle16(toggle);
129 } while (--count != 0);
132 void shadeSpan16_radial_mirror(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy,
133 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
134 int toggle, int count) {
135 shadeSpan16_radial<mirror_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, toggle, count);
138 void shadeSpan16_radial_repeat(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy,
139 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
140 int toggle, int count) {
141 shadeSpan16_radial<repeat_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, toggle, count);
146 /////////////////////////////////////////////////////////////////////
148 SkRadialGradient::SkRadialGradient(const SkPoint& center, SkScalar radius, const Descriptor& desc)
149 : SkGradientShaderBase(desc)
153 // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
154 SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
156 rad_to_unit_matrix(center, radius, &fPtsToUnit);
159 size_t SkRadialGradient::contextSize() const {
160 return sizeof(RadialGradientContext);
163 SkShader::Context* SkRadialGradient::onCreateContext(const ContextRec& rec, void* storage) const {
164 return SkNEW_PLACEMENT_ARGS(storage, RadialGradientContext, (*this, rec));
167 SkRadialGradient::RadialGradientContext::RadialGradientContext(
168 const SkRadialGradient& shader, const ContextRec& rec)
169 : INHERITED(shader, rec) {}
171 void SkRadialGradient::RadialGradientContext::shadeSpan16(int x, int y, uint16_t* dstCParam,
175 const SkRadialGradient& radialGradient = static_cast<const SkRadialGradient&>(fShader);
177 uint16_t* SK_RESTRICT dstC = dstCParam;
180 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
181 TileProc proc = radialGradient.fTileProc;
182 const uint16_t* SK_RESTRICT cache = fCache->getCache16();
183 int toggle = init_dither_toggle16(x, y);
185 if (fDstToIndexClass != kPerspective_MatrixClass) {
186 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
187 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
189 SkScalar sdx = fDstToIndex.getScaleX();
190 SkScalar sdy = fDstToIndex.getSkewY();
192 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
194 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
195 &storage[0], &storage[1]);
196 sdx = SkFixedToScalar(storage[0]);
197 sdy = SkFixedToScalar(storage[1]);
199 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
202 RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
203 if (SkShader::kClamp_TileMode == radialGradient.fTileMode) {
204 shadeProc = shadeSpan16_radial_clamp;
205 } else if (SkShader::kMirror_TileMode == radialGradient.fTileMode) {
206 shadeProc = shadeSpan16_radial_mirror;
208 SkASSERT(SkShader::kRepeat_TileMode == radialGradient.fTileMode);
210 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC,
211 cache, toggle, count);
212 } else { // perspective case
213 SkScalar dstX = SkIntToScalar(x);
214 SkScalar dstY = SkIntToScalar(y);
216 dstProc(fDstToIndex, dstX, dstY, &srcPt);
217 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
218 SkASSERT(fi <= 0xFFFF);
220 int index = fi >> (16 - kCache16Bits);
221 *dstC++ = cache[toggle + index];
222 toggle = next_dither_toggle16(toggle);
225 } while (--count != 0);
229 SkShader::BitmapType SkRadialGradient::asABitmap(SkBitmap* bitmap,
230 SkMatrix* matrix, SkShader::TileMode* xy) const {
232 this->getGradientTableBitmap(bitmap);
235 matrix->setScale(SkIntToScalar(kCache32Count),
236 SkIntToScalar(kCache32Count));
237 matrix->preConcat(fPtsToUnit);
241 xy[1] = kClamp_TileMode;
243 return kRadial_BitmapType;
246 SkShader::GradientType SkRadialGradient::asAGradient(GradientInfo* info) const {
248 commonAsAGradient(info);
249 info->fPoint[0] = fCenter;
250 info->fRadius[0] = fRadius;
252 return kRadial_GradientType;
255 #ifdef SK_SUPPORT_LEGACY_DEEPFLATTENING
256 SkRadialGradient::SkRadialGradient(SkReadBuffer& buffer)
258 fCenter(buffer.readPoint()),
259 fRadius(buffer.readScalar()) {
263 SkFlattenable* SkRadialGradient::CreateProc(SkReadBuffer& buffer) {
264 DescriptorScope desc;
265 if (!desc.unflatten(buffer)) {
268 const SkPoint center = buffer.readPoint();
269 const SkScalar radius = buffer.readScalar();
270 return SkGradientShader::CreateRadial(center, radius, desc.fColors, desc.fPos, desc.fCount,
271 desc.fTileMode, desc.fGradFlags, desc.fLocalMatrix);
274 void SkRadialGradient::flatten(SkWriteBuffer& buffer) const {
275 this->INHERITED::flatten(buffer);
276 buffer.writePoint(fCenter);
277 buffer.writeScalar(fRadius);
282 inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
283 // fast, overly-conservative test: checks unit square instead
285 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
286 (fx <= -SK_FixedHalf && dx <= 0);
287 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
288 (fy <= -SK_FixedHalf && dy <= 0);
290 return xClamped || yClamped;
293 // Return true if (fx * fy) is always inside the unit circle
294 // SkPin32 is expensive, but so are all the SkFixedMul in this test,
295 // so it shouldn't be run if count is small.
296 inline bool no_need_for_radial_pin(int fx, int dx,
297 int fy, int dy, int count) {
299 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
302 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
305 fx += (count - 1) * dx;
306 fy += (count - 1) * dy;
307 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
310 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
313 #define UNPINNED_RADIAL_STEP \
314 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
315 *dstC++ = cache[toggle + \
316 (sqrt_table[fi] >> SkGradientShaderBase::kSqrt32Shift)]; \
317 toggle = next_dither_toggle(toggle); \
321 typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx,
322 SkScalar sfy, SkScalar sdy,
323 SkPMColor* dstC, const SkPMColor* cache,
324 int count, int toggle);
326 // On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
327 void shadeSpan_radial_clamp(SkScalar sfx, SkScalar sdx,
328 SkScalar sfy, SkScalar sdy,
329 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
330 int count, int toggle) {
331 // Floating point seems to be slower than fixed point,
332 // even when we have float hardware.
333 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
334 SkFixed fx = SkScalarToFixed(sfx) >> 1;
335 SkFixed dx = SkScalarToFixed(sdx) >> 1;
336 SkFixed fy = SkScalarToFixed(sfy) >> 1;
337 SkFixed dy = SkScalarToFixed(sdy) >> 1;
338 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
339 unsigned fi = SkGradientShaderBase::kCache32Count - 1;
340 sk_memset32_dither(dstC,
342 cache[next_dither_toggle(toggle) + fi],
344 } else if ((count > 4) &&
345 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
347 // 4x unroll appears to be no faster than 2x unroll on Linux
349 UNPINNED_RADIAL_STEP;
350 UNPINNED_RADIAL_STEP;
354 UNPINNED_RADIAL_STEP;
357 // Specializing for dy == 0 gains us 25% on Skia benchmarks
359 unsigned yy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
362 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
363 unsigned fi = (xx * xx + yy) >> (14 + 16 - kSQRT_TABLE_BITS);
364 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
365 *dstC++ = cache[toggle + (sqrt_table[fi] >>
366 SkGradientShaderBase::kSqrt32Shift)];
367 toggle = next_dither_toggle(toggle);
369 } while (--count != 0);
372 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
373 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
374 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
375 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
376 *dstC++ = cache[toggle + (sqrt_table[fi] >>
377 SkGradientShaderBase::kSqrt32Shift)];
378 toggle = next_dither_toggle(toggle);
381 } while (--count != 0);
386 // Unrolling this loop doesn't seem to help (when float); we're stalling to
387 // get the results of the sqrt (?), and don't have enough extra registers to
388 // have many in flight.
389 template <SkFixed (*TileProc)(SkFixed)>
390 void shadeSpan_radial(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy,
391 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
392 int count, int toggle) {
394 const SkFixed dist = SkFloatToFixed(sk_float_sqrt(fx*fx + fy*fy));
395 const unsigned fi = TileProc(dist);
396 SkASSERT(fi <= 0xFFFF);
397 *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache32Shift)];
398 toggle = next_dither_toggle(toggle);
401 } while (--count != 0);
404 void shadeSpan_radial_mirror(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy,
405 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
406 int count, int toggle) {
407 shadeSpan_radial<mirror_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, count, toggle);
410 void shadeSpan_radial_repeat(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy,
411 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
412 int count, int toggle) {
413 shadeSpan_radial<repeat_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, count, toggle);
418 void SkRadialGradient::RadialGradientContext::shadeSpan(int x, int y,
419 SkPMColor* SK_RESTRICT dstC, int count) {
422 const SkRadialGradient& radialGradient = static_cast<const SkRadialGradient&>(fShader);
425 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
426 TileProc proc = radialGradient.fTileProc;
427 const SkPMColor* SK_RESTRICT cache = fCache->getCache32();
428 int toggle = init_dither_toggle(x, y);
430 if (fDstToIndexClass != kPerspective_MatrixClass) {
431 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
432 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
433 SkScalar sdx = fDstToIndex.getScaleX();
434 SkScalar sdy = fDstToIndex.getSkewY();
436 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
438 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
439 &storage[0], &storage[1]);
440 sdx = SkFixedToScalar(storage[0]);
441 sdy = SkFixedToScalar(storage[1]);
443 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
446 RadialShadeProc shadeProc = shadeSpan_radial_repeat;
447 if (SkShader::kClamp_TileMode == radialGradient.fTileMode) {
448 shadeProc = shadeSpan_radial_clamp;
449 } else if (SkShader::kMirror_TileMode == radialGradient.fTileMode) {
450 shadeProc = shadeSpan_radial_mirror;
452 SkASSERT(SkShader::kRepeat_TileMode == radialGradient.fTileMode);
454 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle);
455 } else { // perspective case
456 SkScalar dstX = SkIntToScalar(x);
457 SkScalar dstY = SkIntToScalar(y);
459 dstProc(fDstToIndex, dstX, dstY, &srcPt);
460 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
461 SkASSERT(fi <= 0xFFFF);
462 *dstC++ = cache[fi >> SkGradientShaderBase::kCache32Shift];
464 } while (--count != 0);
468 /////////////////////////////////////////////////////////////////////
472 #include "GrTBackendProcessorFactory.h"
473 #include "gl/builders/GrGLProgramBuilder.h"
476 class GrGLRadialGradient : public GrGLGradientEffect {
479 GrGLRadialGradient(const GrBackendProcessorFactory& factory,
480 const GrProcessor&) : INHERITED (factory) { }
481 virtual ~GrGLRadialGradient() { }
483 virtual void emitCode(GrGLProgramBuilder*,
484 const GrFragmentProcessor&,
485 const GrProcessorKey&,
486 const char* outputColor,
487 const char* inputColor,
488 const TransformedCoordsArray&,
489 const TextureSamplerArray&) SK_OVERRIDE;
491 static void GenKey(const GrProcessor& processor, const GrGLCaps&, GrProcessorKeyBuilder* b) {
492 b->add32(GenBaseGradientKey(processor));
497 typedef GrGLGradientEffect INHERITED;
501 /////////////////////////////////////////////////////////////////////
503 class GrRadialGradient : public GrGradientEffect {
505 static GrFragmentProcessor* Create(GrContext* ctx,
506 const SkRadialGradient& shader,
507 const SkMatrix& matrix,
508 SkShader::TileMode tm) {
509 return SkNEW_ARGS(GrRadialGradient, (ctx, shader, matrix, tm));
512 virtual ~GrRadialGradient() { }
514 static const char* Name() { return "Radial Gradient"; }
515 virtual const GrBackendFragmentProcessorFactory& getFactory() const SK_OVERRIDE {
516 return GrTBackendFragmentProcessorFactory<GrRadialGradient>::getInstance();
519 typedef GrGLRadialGradient GLProcessor;
522 GrRadialGradient(GrContext* ctx,
523 const SkRadialGradient& shader,
524 const SkMatrix& matrix,
525 SkShader::TileMode tm)
526 : INHERITED(ctx, shader, matrix, tm) {
529 GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
531 typedef GrGradientEffect INHERITED;
534 /////////////////////////////////////////////////////////////////////
536 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRadialGradient);
538 GrFragmentProcessor* GrRadialGradient::TestCreate(SkRandom* random,
540 const GrDrawTargetCaps&,
542 SkPoint center = {random->nextUScalar1(), random->nextUScalar1()};
543 SkScalar radius = random->nextUScalar1();
545 SkColor colors[kMaxRandomGradientColors];
546 SkScalar stopsArray[kMaxRandomGradientColors];
547 SkScalar* stops = stopsArray;
548 SkShader::TileMode tm;
549 int colorCount = RandomGradientParams(random, colors, &stops, &tm);
550 SkAutoTUnref<SkShader> shader(SkGradientShader::CreateRadial(center, radius,
551 colors, stops, colorCount,
555 GrFragmentProcessor* fp;
556 SkAssertResult(shader->asFragmentProcessor(context, paint, NULL, &paintColor, &fp));
560 /////////////////////////////////////////////////////////////////////
562 void GrGLRadialGradient::emitCode(GrGLProgramBuilder* builder,
563 const GrFragmentProcessor&,
564 const GrProcessorKey& key,
565 const char* outputColor,
566 const char* inputColor,
567 const TransformedCoordsArray& coords,
568 const TextureSamplerArray& samplers) {
569 uint32_t baseKey = key.get32(0);
570 this->emitUniforms(builder, baseKey);
571 SkString t("length(");
572 t.append(builder->getFragmentShaderBuilder()->ensureFSCoords2D(coords, 0));
574 this->emitColor(builder, t.c_str(), baseKey, outputColor, inputColor, samplers);
577 /////////////////////////////////////////////////////////////////////
579 bool SkRadialGradient::asFragmentProcessor(GrContext* context, const SkPaint& paint,
580 const SkMatrix* localMatrix, GrColor* paintColor,
581 GrFragmentProcessor** fp) const {
585 if (!this->getLocalMatrix().invert(&matrix)) {
590 if (!localMatrix->invert(&inv)) {
593 matrix.postConcat(inv);
595 matrix.postConcat(fPtsToUnit);
597 *paintColor = SkColor2GrColorJustAlpha(paint.getColor());
598 *fp = GrRadialGradient::Create(context, *this, matrix, fTileMode);
605 bool SkRadialGradient::asFragmentProcessor(GrContext*, const SkPaint&, const SkMatrix*, GrColor*,
606 GrFragmentProcessor**) const {
607 SkDEBUGFAIL("Should not call in GPU-less build");
613 #ifndef SK_IGNORE_TO_STRING
614 void SkRadialGradient::toString(SkString* str) const {
615 str->append("SkRadialGradient: (");
617 str->append("center: (");
618 str->appendScalar(fCenter.fX);
620 str->appendScalar(fCenter.fY);
621 str->append(") radius: ");
622 str->appendScalar(fRadius);
625 this->INHERITED::toString(str);