SkRadialGradient.cpp revision f6eac8af585e44d56e6b18d269e6c34f9917ea88
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
12#define kSQRT_TABLE_BITS    11
13#define kSQRT_TABLE_SIZE    (1 << kSQRT_TABLE_BITS)
14
15#if defined(SK_BUILD_FOR_WIN32) && defined(SK_DEBUG)
16
17#include <stdio.h>
18
19void SkRadialGradient_BuildTable() {
20    // build it 0..127 x 0..127, so we use 2^15 - 1 in the numerator for our "fixed" table
21
22    FILE* file = ::fopen("SkRadialGradient_Table.h", "w");
23    SkASSERT(file);
24    ::fprintf(file, "static const uint8_t gSqrt8Table[] = {\n");
25
26    for (int i = 0; i < kSQRT_TABLE_SIZE; i++) {
27        if ((i & 15) == 0) {
28            ::fprintf(file, "\t");
29        }
30
31        uint8_t value = SkToU8(SkFixedSqrt(i * SK_Fixed1 / kSQRT_TABLE_SIZE) >> 8);
32
33        ::fprintf(file, "0x%02X", value);
34        if (i < kSQRT_TABLE_SIZE-1) {
35            ::fprintf(file, ", ");
36        }
37        if ((i & 15) == 15) {
38            ::fprintf(file, "\n");
39        }
40    }
41    ::fprintf(file, "};\n");
42    ::fclose(file);
43}
44
45#endif
46
47namespace {
48
49void rad_to_unit_matrix(const SkPoint& center, SkScalar radius,
50                               SkMatrix* matrix) {
51    SkScalar    inv = SkScalarInvert(radius);
52
53    matrix->setTranslate(-center.fX, -center.fY);
54    matrix->postScale(inv, inv);
55}
56
57typedef void (* RadialShade16Proc)(SkScalar sfx, SkScalar sdx,
58        SkScalar sfy, SkScalar sdy,
59        uint16_t* dstC, const uint16_t* cache,
60        int toggle, int count);
61
62void shadeSpan16_radial_clamp(SkScalar sfx, SkScalar sdx,
63        SkScalar sfy, SkScalar sdy,
64        uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
65        int toggle, int count) {
66    const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
67
68    /* knock these down so we can pin against +- 0x7FFF, which is an
69       immediate load, rather than 0xFFFF which is slower. This is a
70       compromise, since it reduces our precision, but that appears
71       to be visually OK. If we decide this is OK for all of our cases,
72       we could (it seems) put this scale-down into fDstToIndex,
73       to avoid having to do these extra shifts each time.
74    */
75    SkFixed fx = SkScalarToFixed(sfx) >> 1;
76    SkFixed dx = SkScalarToFixed(sdx) >> 1;
77    SkFixed fy = SkScalarToFixed(sfy) >> 1;
78    SkFixed dy = SkScalarToFixed(sdy) >> 1;
79    // might perform this check for the other modes,
80    // but the win will be a smaller % of the total
81    if (dy == 0) {
82        fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
83        fy *= fy;
84        do {
85            unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
86            unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
87            fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
88            fx += dx;
89            *dstC++ = cache[toggle +
90                            (sqrt_table[fi] >> SkGradientShaderBase::kSqrt16Shift)];
91            toggle ^= SkGradientShaderBase::kDitherStride16;
92        } while (--count != 0);
93    } else {
94        do {
95            unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
96            unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
97            fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
98            fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
99            fx += dx;
100            fy += dy;
101            *dstC++ = cache[toggle +
102                            (sqrt_table[fi] >> SkGradientShaderBase::kSqrt16Shift)];
103            toggle ^= SkGradientShaderBase::kDitherStride16;
104        } while (--count != 0);
105    }
106}
107
108void shadeSpan16_radial_mirror(SkScalar sfx, SkScalar sdx,
109        SkScalar sfy, SkScalar sdy,
110        uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
111        int toggle, int count) {
112    do {
113#ifdef SK_SCALAR_IS_FLOAT
114        float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
115        SkFixed dist = SkFloatToFixed(fdist);
116#else
117        SkFixed magnitudeSquared = SkFixedSquare(sfx) +
118            SkFixedSquare(sfy);
119        if (magnitudeSquared < 0) // Overflow.
120            magnitudeSquared = SK_FixedMax;
121        SkFixed dist = SkFixedSqrt(magnitudeSquared);
122#endif
123        unsigned fi = mirror_tileproc(dist);
124        SkASSERT(fi <= 0xFFFF);
125        *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache16Shift)];
126        toggle ^= SkGradientShaderBase::kDitherStride16;
127        sfx += sdx;
128        sfy += sdy;
129    } while (--count != 0);
130}
131
132void shadeSpan16_radial_repeat(SkScalar sfx, SkScalar sdx,
133        SkScalar sfy, SkScalar sdy,
134        uint16_t* SK_RESTRICT dstC, const uint16_t* SK_RESTRICT cache,
135        int toggle, int count) {
136    SkFixed fx = SkScalarToFixed(sfx);
137    SkFixed dx = SkScalarToFixed(sdx);
138    SkFixed fy = SkScalarToFixed(sfy);
139    SkFixed dy = SkScalarToFixed(sdy);
140    do {
141        SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
142        unsigned fi = repeat_tileproc(dist);
143        SkASSERT(fi <= 0xFFFF);
144        fx += dx;
145        fy += dy;
146        *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache16Shift)];
147        toggle ^= SkGradientShaderBase::kDitherStride16;
148    } while (--count != 0);
149}
150
151}
152
153/////////////////////////////////////////////////////////////////////
154
155SkRadialGradient::SkRadialGradient(const SkPoint& center, SkScalar radius,
156                const SkColor colors[], const SkScalar pos[], int colorCount,
157                SkShader::TileMode mode, SkUnitMapper* mapper)
158    : SkGradientShaderBase(colors, pos, colorCount, mode, mapper),
159      fCenter(center),
160      fRadius(radius)
161{
162    // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
163    SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
164
165    rad_to_unit_matrix(center, radius, &fPtsToUnit);
166}
167
168void SkRadialGradient::shadeSpan16(int x, int y, uint16_t* dstCParam,
169                         int count) {
170    SkASSERT(count > 0);
171
172    uint16_t* SK_RESTRICT dstC = dstCParam;
173
174    SkPoint             srcPt;
175    SkMatrix::MapXYProc dstProc = fDstToIndexProc;
176    TileProc            proc = fTileProc;
177    const uint16_t* SK_RESTRICT cache = this->getCache16();
178    int                 toggle = ((x ^ y) & 1) * kDitherStride16;
179
180    if (fDstToIndexClass != kPerspective_MatrixClass) {
181        dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
182                             SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
183
184        SkScalar sdx = fDstToIndex.getScaleX();
185        SkScalar sdy = fDstToIndex.getSkewY();
186
187        if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
188            SkFixed storage[2];
189            (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
190                                           &storage[0], &storage[1]);
191            sdx = SkFixedToScalar(storage[0]);
192            sdy = SkFixedToScalar(storage[1]);
193        } else {
194            SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
195        }
196
197        RadialShade16Proc shadeProc = shadeSpan16_radial_repeat;
198        if (SkShader::kClamp_TileMode == fTileMode) {
199            shadeProc = shadeSpan16_radial_clamp;
200        } else if (SkShader::kMirror_TileMode == fTileMode) {
201            shadeProc = shadeSpan16_radial_mirror;
202        } else {
203            SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
204        }
205        (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC,
206                     cache, toggle, count);
207    } else {    // perspective case
208        SkScalar dstX = SkIntToScalar(x);
209        SkScalar dstY = SkIntToScalar(y);
210        do {
211            dstProc(fDstToIndex, dstX, dstY, &srcPt);
212            unsigned fi = proc(SkScalarToFixed(srcPt.length()));
213            SkASSERT(fi <= 0xFFFF);
214
215            int index = fi >> (16 - kCache16Bits);
216            *dstC++ = cache[toggle + index];
217            toggle ^= kDitherStride16;
218
219            dstX += SK_Scalar1;
220        } while (--count != 0);
221    }
222}
223
224SkShader::BitmapType SkRadialGradient::asABitmap(SkBitmap* bitmap,
225    SkMatrix* matrix, SkShader::TileMode* xy) const {
226    if (bitmap) {
227        this->getGradientTableBitmap(bitmap);
228    }
229    if (matrix) {
230        matrix->setScale(SkIntToScalar(kGradient32Length),
231                         SkIntToScalar(kGradient32Length));
232        matrix->preConcat(fPtsToUnit);
233    }
234    if (xy) {
235        xy[0] = fTileMode;
236        xy[1] = kClamp_TileMode;
237    }
238    return kRadial_BitmapType;
239}
240
241SkShader::GradientType SkRadialGradient::asAGradient(GradientInfo* info) const {
242    if (info) {
243        commonAsAGradient(info);
244        info->fPoint[0] = fCenter;
245        info->fRadius[0] = fRadius;
246    }
247    return kRadial_GradientType;
248}
249
250SkRadialGradient::SkRadialGradient(SkFlattenableReadBuffer& buffer)
251    : INHERITED(buffer),
252      fCenter(buffer.readPoint()),
253      fRadius(buffer.readScalar()) {
254}
255
256void SkRadialGradient::flatten(SkFlattenableWriteBuffer& buffer) const {
257    this->INHERITED::flatten(buffer);
258    buffer.writePoint(fCenter);
259    buffer.writeScalar(fRadius);
260}
261
262namespace {
263
264inline bool radial_completely_pinned(int fx, int dx, int fy, int dy) {
265    // fast, overly-conservative test: checks unit square instead
266    // of unit circle
267    bool xClamped = (fx >= SK_FixedHalf && dx >= 0) ||
268                    (fx <= -SK_FixedHalf && dx <= 0);
269    bool yClamped = (fy >= SK_FixedHalf && dy >= 0) ||
270                    (fy <= -SK_FixedHalf && dy <= 0);
271
272    return xClamped || yClamped;
273}
274
275// Return true if (fx * fy) is always inside the unit circle
276// SkPin32 is expensive, but so are all the SkFixedMul in this test,
277// so it shouldn't be run if count is small.
278inline bool no_need_for_radial_pin(int fx, int dx,
279                                          int fy, int dy, int count) {
280    SkASSERT(count > 0);
281    if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
282        return false;
283    }
284    if (fx*fx + fy*fy > 0x7FFF*0x7FFF) {
285        return false;
286    }
287    fx += (count - 1) * dx;
288    fy += (count - 1) * dy;
289    if (SkAbs32(fx) > 0x7FFF || SkAbs32(fy) > 0x7FFF) {
290        return false;
291    }
292    return fx*fx + fy*fy <= 0x7FFF*0x7FFF;
293}
294
295#define UNPINNED_RADIAL_STEP \
296    fi = (fx * fx + fy * fy) >> (14 + 16 - kSQRT_TABLE_BITS); \
297    *dstC++ = cache[toggle + \
298                    (sqrt_table[fi] >> SkGradientShaderBase::kSqrt32Shift)]; \
299    toggle ^= SkGradientShaderBase::kDitherStride32; \
300    fx += dx; \
301    fy += dy;
302
303typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx,
304        SkScalar sfy, SkScalar sdy,
305        SkPMColor* dstC, const SkPMColor* cache,
306        int count, int toggle);
307
308// On Linux, this is faster with SkPMColor[] params than SkPMColor* SK_RESTRICT
309void shadeSpan_radial_clamp(SkScalar sfx, SkScalar sdx,
310        SkScalar sfy, SkScalar sdy,
311        SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
312        int count, int toggle) {
313    // Floating point seems to be slower than fixed point,
314    // even when we have float hardware.
315    const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
316    SkFixed fx = SkScalarToFixed(sfx) >> 1;
317    SkFixed dx = SkScalarToFixed(sdx) >> 1;
318    SkFixed fy = SkScalarToFixed(sfy) >> 1;
319    SkFixed dy = SkScalarToFixed(sdy) >> 1;
320    if ((count > 4) && radial_completely_pinned(fx, dx, fy, dy)) {
321        unsigned fi = SkGradientShaderBase::kGradient32Length;
322        sk_memset32_dither(dstC,
323            cache[toggle + fi],
324            cache[(toggle ^ SkGradientShaderBase::kDitherStride32) + fi],
325            count);
326    } else if ((count > 4) &&
327               no_need_for_radial_pin(fx, dx, fy, dy, count)) {
328        unsigned fi;
329        // 4x unroll appears to be no faster than 2x unroll on Linux
330        while (count > 1) {
331            UNPINNED_RADIAL_STEP;
332            UNPINNED_RADIAL_STEP;
333            count -= 2;
334        }
335        if (count) {
336            UNPINNED_RADIAL_STEP;
337        }
338    }
339    else  {
340        // Specializing for dy == 0 gains us 25% on Skia benchmarks
341        if (dy == 0) {
342            unsigned yy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
343            yy *= yy;
344            do {
345                unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
346                unsigned fi = (xx * xx + yy) >> (14 + 16 - kSQRT_TABLE_BITS);
347                fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
348                *dstC++ = cache[toggle + (sqrt_table[fi] >>
349                    SkGradientShaderBase::kSqrt32Shift)];
350                toggle ^= SkGradientShaderBase::kDitherStride32;
351                fx += dx;
352            } while (--count != 0);
353        } else {
354            do {
355                unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
356                unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
357                fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
358                fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
359                *dstC++ = cache[toggle + (sqrt_table[fi] >>
360                    SkGradientShaderBase::kSqrt32Shift)];
361                toggle ^= SkGradientShaderBase::kDitherStride32;
362                fx += dx;
363                fy += dy;
364            } while (--count != 0);
365        }
366    }
367}
368
369// Unrolling this loop doesn't seem to help (when float); we're stalling to
370// get the results of the sqrt (?), and don't have enough extra registers to
371// have many in flight.
372void shadeSpan_radial_mirror(SkScalar sfx, SkScalar sdx,
373        SkScalar sfy, SkScalar sdy,
374        SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
375        int count, int toggle) {
376    do {
377#ifdef SK_SCALAR_IS_FLOAT
378        float fdist = sk_float_sqrt(sfx*sfx + sfy*sfy);
379        SkFixed dist = SkFloatToFixed(fdist);
380#else
381        SkFixed magnitudeSquared = SkFixedSquare(sfx) +
382            SkFixedSquare(sfy);
383        if (magnitudeSquared < 0) // Overflow.
384            magnitudeSquared = SK_FixedMax;
385        SkFixed dist = SkFixedSqrt(magnitudeSquared);
386#endif
387        unsigned fi = mirror_tileproc(dist);
388        SkASSERT(fi <= 0xFFFF);
389        *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache32Shift)];
390        toggle ^= SkGradientShaderBase::kDitherStride32;
391        sfx += sdx;
392        sfy += sdy;
393    } while (--count != 0);
394}
395
396void shadeSpan_radial_repeat(SkScalar sfx, SkScalar sdx,
397        SkScalar sfy, SkScalar sdy,
398        SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache,
399        int count, int toggle) {
400    SkFixed fx = SkScalarToFixed(sfx);
401    SkFixed dx = SkScalarToFixed(sdx);
402    SkFixed fy = SkScalarToFixed(sfy);
403    SkFixed dy = SkScalarToFixed(sdy);
404    do {
405        SkFixed magnitudeSquared = SkFixedSquare(fx) +
406            SkFixedSquare(fy);
407        if (magnitudeSquared < 0) // Overflow.
408            magnitudeSquared = SK_FixedMax;
409        SkFixed dist = SkFixedSqrt(magnitudeSquared);
410        unsigned fi = repeat_tileproc(dist);
411        SkASSERT(fi <= 0xFFFF);
412        *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache32Shift)];
413        toggle ^= SkGradientShaderBase::kDitherStride32;
414        fx += dx;
415        fy += dy;
416    } while (--count != 0);
417}
418}
419
420void SkRadialGradient::shadeSpan(int x, int y,
421                                SkPMColor* SK_RESTRICT dstC, int count) {
422    SkASSERT(count > 0);
423
424    SkPoint             srcPt;
425    SkMatrix::MapXYProc dstProc = fDstToIndexProc;
426    TileProc            proc = fTileProc;
427    const SkPMColor* SK_RESTRICT cache = this->getCache32();
428#ifdef USE_DITHER_32BIT_GRADIENT
429    int toggle = ((x ^ y) & 1) * SkGradientShaderBase::kDitherStride32;
430#else
431    int toggle = 0;
432#endif
433
434    if (fDstToIndexClass != kPerspective_MatrixClass) {
435        dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
436                             SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
437        SkScalar sdx = fDstToIndex.getScaleX();
438        SkScalar sdy = fDstToIndex.getSkewY();
439
440        if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
441            SkFixed storage[2];
442            (void)fDstToIndex.fixedStepInX(SkIntToScalar(y),
443                                           &storage[0], &storage[1]);
444            sdx = SkFixedToScalar(storage[0]);
445            sdy = SkFixedToScalar(storage[1]);
446        } else {
447            SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
448        }
449
450        RadialShadeProc shadeProc = shadeSpan_radial_repeat;
451        if (SkShader::kClamp_TileMode == fTileMode) {
452            shadeProc = shadeSpan_radial_clamp;
453        } else if (SkShader::kMirror_TileMode == fTileMode) {
454            shadeProc = shadeSpan_radial_mirror;
455        } else {
456            SkASSERT(SkShader::kRepeat_TileMode == fTileMode);
457        }
458        (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle);
459    } else {    // perspective case
460        SkScalar dstX = SkIntToScalar(x);
461        SkScalar dstY = SkIntToScalar(y);
462        do {
463            dstProc(fDstToIndex, dstX, dstY, &srcPt);
464            unsigned fi = proc(SkScalarToFixed(srcPt.length()));
465            SkASSERT(fi <= 0xFFFF);
466            *dstC++ = cache[fi >> SkGradientShaderBase::kCache32Shift];
467            dstX += SK_Scalar1;
468        } while (--count != 0);
469    }
470}
471
472/////////////////////////////////////////////////////////////////////
473
474#if SK_SUPPORT_GPU
475
476class GrGLRadialGradient : public GrGLGradientStage {
477public:
478
479    GrGLRadialGradient(const GrProgramStageFactory& factory,
480                       const GrCustomStage&) : INHERITED (factory) { }
481    virtual ~GrGLRadialGradient() { }
482
483    virtual void emitVS(GrGLShaderBuilder* builder,
484                        const char* vertexCoords) SK_OVERRIDE { }
485    virtual void emitFS(GrGLShaderBuilder* builder,
486                        const char* outputColor,
487                        const char* inputColor,
488                        const TextureSamplerArray&) SK_OVERRIDE;
489
490    static StageKey GenKey(const GrCustomStage& s, const GrGLCaps& caps) { return 0; }
491
492private:
493
494    typedef GrGLGradientStage INHERITED;
495
496};
497
498/////////////////////////////////////////////////////////////////////
499
500class GrRadialGradient : public GrGradientEffect {
501public:
502
503    GrRadialGradient(GrContext* ctx, const SkRadialGradient& shader, SkShader::TileMode tm)
504        : INHERITED(ctx, shader, tm) {
505    }
506
507    virtual ~GrRadialGradient() { }
508
509    static const char* Name() { return "Radial Gradient"; }
510    virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE {
511        return GrTProgramStageFactory<GrRadialGradient>::getInstance();
512    }
513
514    typedef GrGLRadialGradient GLProgramStage;
515
516private:
517    GR_DECLARE_CUSTOM_STAGE_TEST;
518
519    typedef GrGradientEffect INHERITED;
520};
521
522/////////////////////////////////////////////////////////////////////
523
524GR_DEFINE_CUSTOM_STAGE_TEST(GrRadialGradient);
525
526GrCustomStage* GrRadialGradient::TestCreate(SkRandom* random,
527                                            GrContext* context,
528                                            GrTexture**) {
529    SkPoint center = {random->nextUScalar1(), random->nextUScalar1()};
530    SkScalar radius = random->nextUScalar1();
531
532    SkColor colors[kMaxRandomGradientColors];
533    SkScalar stopsArray[kMaxRandomGradientColors];
534    SkScalar* stops = stopsArray;
535    SkShader::TileMode tm;
536    int colorCount = RandomGradientParams(random, colors, &stops, &tm);
537    SkAutoTUnref<SkShader> shader(SkGradientShader::CreateRadial(center, radius,
538                                                                 colors, stops, colorCount,
539                                                                 tm));
540    GrSamplerState sampler;
541    GrCustomStage* stage = shader->asNewCustomStage(context, &sampler);
542    GrAssert(NULL != stage);
543    return stage;
544}
545
546/////////////////////////////////////////////////////////////////////
547
548void GrGLRadialGradient::emitFS(GrGLShaderBuilder* builder,
549                                const char* outputColor,
550                                const char* inputColor,
551                                const TextureSamplerArray& samplers) {
552    SkString t;
553    t.printf("length(%s.xy)", builder->defaultTexCoordsName());
554    this->emitColorLookup(builder, t.c_str(), outputColor, inputColor, samplers[0]);
555}
556
557/////////////////////////////////////////////////////////////////////
558
559GrCustomStage* SkRadialGradient::asNewCustomStage(GrContext* context,
560    GrSamplerState* sampler) const {
561    SkASSERT(NULL != context && NULL != sampler);
562    sampler->matrix()->preConcat(fPtsToUnit);
563    return SkNEW_ARGS(GrRadialGradient, (context, *this, fTileMode));
564}
565
566#else
567
568GrCustomStage* SkRadialGradient::asNewCustomStage(GrContext* context,
569    GrSamplerState* sampler) const {
570    SkDEBUGFAIL("Should not call in GPU-less build");
571    return NULL;
572}
573
574#endif
575