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