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)
15 SK_COMPILE_ASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE, SqrtTableSizesMatch);
21 void SkRadialGradient_BuildTable() {
22 // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
24 FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
26 ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
28 for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
30 ::fprintf(file, "\t");
33 uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
35 ::fprintf(file, "0x%02X", value);
36 if (i < kSQRT_TABLE_SIZE-1) {
37 ::fprintf(file, ", ");
40 ::fprintf(file, "\n");
43 ::fprintf(file, "};\n");
51 // GCC doesn't like using static functions as template arguments. So force these to be non-static.
52 inline SkFixed mirror_tileproc_nonstatic(SkFixed x) {
53 return mirror_tileproc(x);
56 inline SkFixed repeat_tileproc_nonstatic(SkFixed x) {
57 return repeat_tileproc(x);
60 SkMatrix rad_to_unit_matrix(const SkPoint& center, SkScalar radius) {
61 SkScalar inv = SkScalarInvert(radius);
64 matrix.setTranslate(-center.fX, -center.fY);
65 matrix.postScale(inv, inv);
69 typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx,
70 SkScalar sfy, SkScalar sdy,
71 uint16_t* dstC, const uint16_t* cache,
72 int toggle, int count);
74 void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
75 SkScalar sfy, SkScalar sdy,
76 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
77 int toggle, int count) {
78 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
80 /* knock these down so we can pin against +- 0x7FFF, which is an
81 immediate load, rather than 0xFFFF which is slower. This is a
82 compromise, since it reduces our precision, but that appears
83 to be visually OK. If we decide this is OK for all of our cases,
84 we could (it seems) put this scale-down into fDstToIndex,
85 to avoid having to do these extra shifts each time.
87 SkFixed fx = SkScalarToFixed(sfx) >> 1;
88 SkFixed dx = SkScalarToFixed(sdx) >> 1;
89 SkFixed fy = SkScalarToFixed(sfy) >> 1;
90 SkFixed dy = SkScalarToFixed(sdy) >> 1;
91 // might perform this check for the other modes,
92 // but the win will be a smaller % of the total
94 fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
97 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
98 unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
99 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
101 *dstC++ = cache[toggle +
102 (sqrt_table[fi] >> SkGradientShaderBase::kSqrt16Shift)];
103 toggle = next_dither_toggle16(toggle);
104 } while (--count != 0);
107 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
108 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
109 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
110 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
113 *dstC++ = cache[toggle +
114 (sqrt_table[fi] >> SkGradientShaderBase::kSqrt16Shift)];
115 toggle = next_dither_toggle16(toggle);
116 } while (--count != 0);
120 template <SkFixed (*TileProc)(SkFixed)>
121 void shadeSpan16_radial(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy,
122 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
123 int toggle, int count) {
125 const SkFixed dist = SkFloatToFixed(sk_float_sqrt(fx*fx + fy*fy));
126 const unsigned fi = TileProc(dist);
127 SkASSERT(fi <= 0xFFFF);
128 *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache16Shift)];
129 toggle = next_dither_toggle16(toggle);
132 } while (--count != 0);
135 void shadeSpan16_radial_mirror(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy,
136 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
137 int toggle, int count) {
138 shadeSpan16_radial<mirror_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, toggle, count);
141 void shadeSpan16_radial_repeat(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy,
142 uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
143 int toggle, int count) {
144 shadeSpan16_radial<repeat_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, toggle, count);
149 /////////////////////////////////////////////////////////////////////
151 SkRadialGradient::SkRadialGradient(const SkPoint& center, SkScalar radius, const Descriptor& desc)
152 : SkGradientShaderBase(desc, rad_to_unit_matrix(center, radius))
157 size_t SkRadialGradient::contextSize() const {
158 return sizeof(RadialGradientContext);
161 SkShader::Context* SkRadialGradient::onCreateContext(const ContextRec& rec, void* storage) const {
162 return SkNEW_PLACEMENT_ARGS(storage, RadialGradientContext, (*this, rec));
165 SkRadialGradient::RadialGradientContext::RadialGradientContext(
166 const SkRadialGradient& shader, const ContextRec& rec)
167 : INHERITED(shader, rec) {}
169 void SkRadialGradient::RadialGradientContext::shadeSpan16(int x, int y, uint16_t* dstCParam,
173 const SkRadialGradient& radialGradient = static_cast<const SkRadialGradient&>(fShader);
175 uint16_t* SK_RESTRICT dstC = dstCParam;
178 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
179 TileProc proc = radialGradient.fTileProc;
180 const uint16_t* SK_RESTRICT cache = fCache->getCache16();
181 int toggle = init_dither_toggle16(x, y);
183 if (fDstToIndexClass != kPerspective_MatrixClass) {
184 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
185 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
187 SkScalar sdx = fDstToIndex.getScaleX();
188 SkScalar sdy = fDstToIndex.getSkewY();
190 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
192 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
193 &storage[0], &storage[1]);
194 sdx = SkFixedToScalar(storage[0]);
195 sdy = SkFixedToScalar(storage[1]);
197 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
200 RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
201 if (SkShader::kClamp_TileMode == radialGradient.fTileMode) {
202 shadeProc = shadeSpan16_radial_clamp;
203 } else if (SkShader::kMirror_TileMode == radialGradient.fTileMode) {
204 shadeProc = shadeSpan16_radial_mirror;
206 SkASSERT(SkShader::kRepeat_TileMode == radialGradient.fTileMode);
208 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC,
209 cache, toggle, count);
210 } else { // perspective case
211 SkScalar dstX = SkIntToScalar(x);
212 SkScalar dstY = SkIntToScalar(y);
214 dstProc(fDstToIndex, dstX, dstY, &srcPt);
215 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
216 SkASSERT(fi <= 0xFFFF);
218 int index = fi >> (16 - kCache16Bits);
219 *dstC++ = cache[toggle + index];
220 toggle = next_dither_toggle16(toggle);
223 } while (--count != 0);
227 SkShader::BitmapType SkRadialGradient::asABitmap(SkBitmap* bitmap,
228 SkMatrix* matrix, SkShader::TileMode* xy) const {
230 this->getGradientTableBitmap(bitmap);
233 matrix->setScale(SkIntToScalar(kCache32Count),
234 SkIntToScalar(kCache32Count));
235 matrix->preConcat(fPtsToUnit);
239 xy[1] = kClamp_TileMode;
241 return kRadial_BitmapType;
244 SkShader::GradientType SkRadialGradient::asAGradient(GradientInfo* info) const {
246 commonAsAGradient(info);
247 info->fPoint[0] = fCenter;
248 info->fRadius[0] = fRadius;
250 return kRadial_GradientType;
253 SkFlattenable* SkRadialGradient::CreateProc(SkReadBuffer& buffer) {
254 DescriptorScope desc;
255 if (!desc.unflatten(buffer)) {
258 const SkPoint center = buffer.readPoint();
259 const SkScalar radius = buffer.readScalar();
260 return SkGradientShader::CreateRadial(center, radius, desc.fColors, desc.fPos, desc.fCount,
261 desc.fTileMode, desc.fGradFlags, desc.fLocalMatrix);
264 void SkRadialGradient::flatten(SkWriteBuffer& buffer) const {
265 this->INHERITED::flatten(buffer);
266 buffer.writePoint(fCenter);
267 buffer.writeScalar(fRadius);
272 inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
273 // fast, overly-conservative test: checks unit square instead
275 bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
276 (fx <= -SK_FixedHalf && dx <= 0);
277 bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
278 (fy <= -SK_FixedHalf && dy <= 0);
280 return xClamped || yClamped;
283 // Return true if (fx * fy) is always inside the unit circle
284 // SkPin32 is expensive, but so are all the SkFixedMul in this test,
285 // so it shouldn't be run if count is small.
286 inline bool no_need_for_radial_pin(int fx, int dx,
287 int fy, int dy, int count) {
289 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
292 if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
295 fx += (count - 1) * dx;
296 fy += (count - 1) * dy;
297 if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
300 return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
303 #define UNPINNED_RADIAL_STEP \
304 fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
305 *dstC++ = cache[toggle + \
306 (sqrt_table[fi] >> SkGradientShaderBase::kSqrt32Shift)]; \
307 toggle = next_dither_toggle(toggle); \
311 typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx,
312 SkScalar sfy, SkScalar sdy,
313 SkPMColor* dstC, const SkPMColor* cache,
314 int count, int toggle);
316 // On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
317 void shadeSpan_radial_clamp(SkScalar sfx, SkScalar sdx,
318 SkScalar sfy, SkScalar sdy,
319 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
320 int count, int toggle) {
321 // Floating point seems to be slower than fixed point,
322 // even when we have float hardware.
323 const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
324 SkFixed fx = SkScalarToFixed(sfx) >> 1;
325 SkFixed dx = SkScalarToFixed(sdx) >> 1;
326 SkFixed fy = SkScalarToFixed(sfy) >> 1;
327 SkFixed dy = SkScalarToFixed(sdy) >> 1;
328 if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
329 unsigned fi = SkGradientShaderBase::kCache32Count - 1;
330 sk_memset32_dither(dstC,
332 cache[next_dither_toggle(toggle) + fi],
334 } else if ((count > 4) &&
335 no_need_for_radial_pin(fx, dx, fy, dy, count)) {
337 // 4x unroll appears to be no faster than 2x unroll on Linux
339 UNPINNED_RADIAL_STEP;
340 UNPINNED_RADIAL_STEP;
344 UNPINNED_RADIAL_STEP;
347 // Specializing for dy == 0 gains us 25% on Skia benchmarks
349 unsigned yy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
352 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
353 unsigned fi = (xx * xx + yy) >> (14 + 16 - kSQRT_TABLE_BITS);
354 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
355 *dstC++ = cache[toggle + (sqrt_table[fi] >>
356 SkGradientShaderBase::kSqrt32Shift)];
357 toggle = next_dither_toggle(toggle);
359 } while (--count != 0);
362 unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
363 unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
364 fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
365 fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
366 *dstC++ = cache[toggle + (sqrt_table[fi] >>
367 SkGradientShaderBase::kSqrt32Shift)];
368 toggle = next_dither_toggle(toggle);
371 } while (--count != 0);
376 // Unrolling this loop doesn't seem to help (when float); we're stalling to
377 // get the results of the sqrt (?), and don't have enough extra registers to
378 // have many in flight.
379 template <SkFixed (*TileProc)(SkFixed)>
380 void shadeSpan_radial(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy,
381 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
382 int count, int toggle) {
384 const SkFixed dist = SkFloatToFixed(sk_float_sqrt(fx*fx + fy*fy));
385 const unsigned fi = TileProc(dist);
386 SkASSERT(fi <= 0xFFFF);
387 *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache32Shift)];
388 toggle = next_dither_toggle(toggle);
391 } while (--count != 0);
394 void shadeSpan_radial_mirror(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy,
395 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
396 int count, int toggle) {
397 shadeSpan_radial<mirror_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, count, toggle);
400 void shadeSpan_radial_repeat(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy,
401 SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
402 int count, int toggle) {
403 shadeSpan_radial<repeat_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, count, toggle);
408 void SkRadialGradient::RadialGradientContext::shadeSpan(int x, int y,
409 SkPMColor* SK_RESTRICT dstC, int count) {
412 const SkRadialGradient& radialGradient = static_cast<const SkRadialGradient&>(fShader);
415 SkMatrix::MapXYProc dstProc = fDstToIndexProc;
416 TileProc proc = radialGradient.fTileProc;
417 const SkPMColor* SK_RESTRICT cache = fCache->getCache32();
418 int toggle = init_dither_toggle(x, y);
420 if (fDstToIndexClass != kPerspective_MatrixClass) {
421 dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
422 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
423 SkScalar sdx = fDstToIndex.getScaleX();
424 SkScalar sdy = fDstToIndex.getSkewY();
426 if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
428 (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
429 &storage[0], &storage[1]);
430 sdx = SkFixedToScalar(storage[0]);
431 sdy = SkFixedToScalar(storage[1]);
433 SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
436 RadialShadeProc shadeProc = shadeSpan_radial_repeat;
437 if (SkShader::kClamp_TileMode == radialGradient.fTileMode) {
438 shadeProc = shadeSpan_radial_clamp;
439 } else if (SkShader::kMirror_TileMode == radialGradient.fTileMode) {
440 shadeProc = shadeSpan_radial_mirror;
442 SkASSERT(SkShader::kRepeat_TileMode == radialGradient.fTileMode);
444 (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle);
445 } else { // perspective case
446 SkScalar dstX = SkIntToScalar(x);
447 SkScalar dstY = SkIntToScalar(y);
449 dstProc(fDstToIndex, dstX, dstY, &srcPt);
450 unsigned fi = proc(SkScalarToFixed(srcPt.length()));
451 SkASSERT(fi <= 0xFFFF);
452 *dstC++ = cache[fi >> SkGradientShaderBase::kCache32Shift];
454 } while (--count != 0);
458 /////////////////////////////////////////////////////////////////////
463 #include "gl/builders/GrGLProgramBuilder.h"
465 class GrGLRadialGradient : public GrGLGradientEffect {
468 GrGLRadialGradient(const GrProcessor&) {}
469 virtual ~GrGLRadialGradient() { }
471 virtual void emitCode(GrGLFPBuilder*,
472 const GrFragmentProcessor&,
473 const char* outputColor,
474 const char* inputColor,
475 const TransformedCoordsArray&,
476 const TextureSamplerArray&) SK_OVERRIDE;
478 static void GenKey(const GrProcessor& processor, const GrGLCaps&, GrProcessorKeyBuilder* b) {
479 b->add32(GenBaseGradientKey(processor));
484 typedef GrGLGradientEffect INHERITED;
488 /////////////////////////////////////////////////////////////////////
490 class GrRadialGradient : public GrGradientEffect {
492 static GrFragmentProcessor* Create(GrContext* ctx,
493 const SkRadialGradient& shader,
494 const SkMatrix& matrix,
495 SkShader::TileMode tm) {
496 return SkNEW_ARGS(GrRadialGradient, (ctx, shader, matrix, tm));
499 virtual ~GrRadialGradient() { }
501 const char* name() const SK_OVERRIDE { return "Radial Gradient"; }
503 virtual void getGLProcessorKey(const GrGLCaps& caps,
504 GrProcessorKeyBuilder* b) const SK_OVERRIDE {
505 GrGLRadialGradient::GenKey(*this, caps, b);
508 GrGLFragmentProcessor* createGLInstance() const SK_OVERRIDE {
509 return SkNEW_ARGS(GrGLRadialGradient, (*this));
513 GrRadialGradient(GrContext* ctx,
514 const SkRadialGradient& shader,
515 const SkMatrix& matrix,
516 SkShader::TileMode tm)
517 : INHERITED(ctx, shader, matrix, tm) {
518 this->initClassID<GrRadialGradient>();
521 GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
523 typedef GrGradientEffect INHERITED;
526 /////////////////////////////////////////////////////////////////////
528 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRadialGradient);
530 GrFragmentProcessor* GrRadialGradient::TestCreate(SkRandom* random,
532 const GrDrawTargetCaps&,
534 SkPoint center = {random->nextUScalar1(), random->nextUScalar1()};
535 SkScalar radius = random->nextUScalar1();
537 SkColor colors[kMaxRandomGradientColors];
538 SkScalar stopsArray[kMaxRandomGradientColors];
539 SkScalar* stops = stopsArray;
540 SkShader::TileMode tm;
541 int colorCount = RandomGradientParams(random, colors, &stops, &tm);
542 SkAutoTUnref<SkShader> shader(SkGradientShader::CreateRadial(center, radius,
543 colors, stops, colorCount,
547 GrFragmentProcessor* fp;
548 SkAssertResult(shader->asFragmentProcessor(context, paint,
549 GrProcessorUnitTest::TestMatrix(random), NULL,
554 /////////////////////////////////////////////////////////////////////
556 void GrGLRadialGradient::emitCode(GrGLFPBuilder* builder,
557 const GrFragmentProcessor& fp,
558 const char* outputColor,
559 const char* inputColor,
560 const TransformedCoordsArray& coords,
561 const TextureSamplerArray& samplers) {
562 const GrRadialGradient& ge = fp.cast<GrRadialGradient>();
563 this->emitUniforms(builder, ge);
564 SkString t("length(");
565 t.append(builder->getFragmentShaderBuilder()->ensureFSCoords2D(coords, 0));
567 this->emitColor(builder, ge, t.c_str(), outputColor, inputColor, samplers);
570 /////////////////////////////////////////////////////////////////////
572 bool SkRadialGradient::asFragmentProcessor(GrContext* context, const SkPaint& paint,
573 const SkMatrix& viewM,
574 const SkMatrix* localMatrix, GrColor* paintColor,
575 GrFragmentProcessor** fp) const {
579 if (!this->getLocalMatrix().invert(&matrix)) {
584 if (!localMatrix->invert(&inv)) {
587 matrix.postConcat(inv);
589 matrix.postConcat(fPtsToUnit);
591 *paintColor = SkColor2GrColorJustAlpha(paint.getColor());
592 *fp = GrRadialGradient::Create(context, *this, matrix, fTileMode);
599 bool SkRadialGradient::asFragmentProcessor(GrContext*, const SkPaint&, const SkMatrix&,
600 const SkMatrix*, GrColor*,
601 GrFragmentProcessor**) const {
602 SkDEBUGFAIL("Should not call in GPU-less build");
608 #ifndef SK_IGNORE_TO_STRING
609 void SkRadialGradient::toString(SkString* str) const {
610 str->append("SkRadialGradient: (");
612 str->append("center: (");
613 str->appendScalar(fCenter.fX);
615 str->appendScalar(fCenter.fY);
616 str->append(") radius: ");
617 str->appendScalar(fRadius);
620 this->INHERITED::toString(str);