GrCircleBlurFragmentProcessor.cpp revision b525721907fa56fd20682116f7645b4d0a861b78
1/*
2 * Copyright 2015 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "GrCircleBlurFragmentProcessor.h"
9
10#if SK_SUPPORT_GPU
11
12#include "GrContext.h"
13#include "GrInvariantOutput.h"
14#include "GrTextureProvider.h"
15
16#include "glsl/GrGLSLFragmentProcessor.h"
17#include "glsl/GrGLSLFragmentShaderBuilder.h"
18#include "glsl/GrGLSLProgramDataManager.h"
19#include "glsl/GrGLSLUniformHandler.h"
20
21#include "SkFixed.h"
22
23class GrGLCircleBlurFragmentProcessor : public GrGLSLFragmentProcessor {
24public:
25    void emitCode(EmitArgs&) override;
26
27protected:
28    void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override;
29
30private:
31    GrGLSLProgramDataManager::UniformHandle fDataUniform;
32
33    typedef GrGLSLFragmentProcessor INHERITED;
34};
35
36void GrGLCircleBlurFragmentProcessor::emitCode(EmitArgs& args) {
37
38    const char *dataName;
39
40    // The data is formatted as:
41    // x,y  - the center of the circle
42    // z    - the distance at which the intensity starts falling off (e.g., the start of the table)
43    // w    - the inverse of the profile texture size
44    fDataUniform = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
45                                                    kVec4f_GrSLType,
46                                                    kDefault_GrSLPrecision,
47                                                    "data",
48                                                    &dataName);
49
50    GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
51    const char *fragmentPos = fragBuilder->fragmentPosition();
52
53    if (args.fInputColor) {
54        fragBuilder->codeAppendf("vec4 src=%s;", args.fInputColor);
55    } else {
56        fragBuilder->codeAppendf("vec4 src=vec4(1);");
57    }
58
59    // We just want to compute "length(vec) - %s.z + 0.5) * %s.w" but need to rearrange
60    // for precision
61    fragBuilder->codeAppendf("vec2 vec = vec2( (%s.x - %s.x) * %s.w , (%s.y - %s.y) * %s.w );",
62                             fragmentPos, dataName, dataName,
63                             fragmentPos, dataName, dataName);
64    fragBuilder->codeAppendf("float dist = length(vec) + ( 0.5 - %s.z ) * %s.w;",
65                             dataName, dataName);
66
67    fragBuilder->codeAppendf("float intensity = ");
68    fragBuilder->appendTextureLookup(args.fTexSamplers[0], "vec2(dist, 0.5)");
69    fragBuilder->codeAppend(".a;");
70
71    fragBuilder->codeAppendf("%s = src * intensity;\n", args.fOutputColor );
72}
73
74void GrGLCircleBlurFragmentProcessor::onSetData(const GrGLSLProgramDataManager& pdman,
75                                                const GrProcessor& proc) {
76    const GrCircleBlurFragmentProcessor& cbfp = proc.cast<GrCircleBlurFragmentProcessor>();
77    const SkRect& circle = cbfp.circle();
78
79    // The data is formatted as:
80    // x,y  - the center of the circle
81    // z    - the distance at which the intensity starts falling off (e.g., the start of the table)
82    // w    - the inverse of the profile texture size
83    pdman.set4f(fDataUniform, circle.centerX(), circle.centerY(), cbfp.offset(),
84                1.0f / cbfp.profileSize());
85}
86
87///////////////////////////////////////////////////////////////////////////////
88
89GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(const SkRect& circle,
90                                                             float sigma,
91                                                             float offset,
92                                                             GrTexture* blurProfile)
93    : fCircle(circle)
94    , fSigma(sigma)
95    , fOffset(offset)
96    , fBlurProfileAccess(blurProfile, GrTextureParams::kBilerp_FilterMode) {
97    this->initClassID<GrCircleBlurFragmentProcessor>();
98    this->addTextureAccess(&fBlurProfileAccess);
99    this->setWillReadFragmentPosition();
100}
101
102GrGLSLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLSLInstance() const {
103    return new GrGLCircleBlurFragmentProcessor;
104}
105
106void GrCircleBlurFragmentProcessor::onGetGLSLProcessorKey(const GrGLSLCaps& caps,
107                                                          GrProcessorKeyBuilder* b) const {
108    GrGLCircleBlurFragmentProcessor::GenKey(*this, caps, b);
109}
110
111void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput* inout) const {
112    inout->mulByUnknownSingleComponent();
113}
114
115// Create a Gaussian half-kernel and a summed area table given a sigma and number of discrete
116// steps. The half kernel is normalized to sum to 0.5.
117static void make_half_kernel_and_summed_table(float* halfKernel, float* summedHalfKernel,
118                                              int halfKernelSize, float sigma) {
119    const float invSigma = 1.f / sigma;
120    const float b = -0.5f * invSigma * invSigma;
121    float tot = 0.0f;
122    // Compute half kernel values at half pixel steps out from the center.
123    float t = 0.5f;
124    for (int i = 0; i < halfKernelSize; ++i) {
125        float value = expf(t * t * b);
126        tot += value;
127        halfKernel[i] = value;
128        t += 1.f;
129    }
130    float sum = 0.f;
131    // The half kernel should sum to 0.5 not 1.0.
132    tot *= 2.f;
133    for (int i = 0; i < halfKernelSize; ++i) {
134        halfKernel[i] /= tot;
135        sum += halfKernel[i];
136        summedHalfKernel[i] = sum;
137    }
138}
139
140// Applies the 1D half kernel vertically at a point (x, 0) to a circle centered at the origin with
141// radius circleR.
142static float eval_vertically(float x, float circleR, const float* summedHalfKernelTable,
143                             int halfKernelSize) {
144    // Given x find the positive y that is on the edge of the circle.
145    float y = sqrtf(fabs(circleR * circleR - x * x));
146    // In the column at x we exit the circle at +y and -y
147    // table entry j is actually the kernel evaluated at j + 0.5.
148    y -= 0.5f;
149    int yInt = SkScalarFloorToInt(y);
150    SkASSERT(yInt >= -1);
151    if (y < 0) {
152        return (y + 0.5f) * summedHalfKernelTable[0];
153    } else if (yInt >= halfKernelSize - 1) {
154        return 0.5f;
155    } else {
156        float yFrac = y - yInt;
157        return (1.f - yFrac) * summedHalfKernelTable[yInt] +
158                       yFrac * summedHalfKernelTable[yInt + 1];
159    }
160}
161
162// Apply the kernel at point (t, 0) to a circle centered at the origin with radius circleR.
163static uint8_t eval_at(float t, float circleR, const float* halfKernel,
164                       const float* summedHalfKernelTable, int halfKernelSize) {
165    float acc = 0;
166
167    for (int i = 0; i < halfKernelSize; ++i) {
168        float x = t - i - 0.5f;
169        if (x < -circleR || x > circleR) {
170            continue;
171        }
172        float verticalEval = eval_vertically(x, circleR, summedHalfKernelTable, halfKernelSize);
173        acc += verticalEval * halfKernel[i];
174    }
175    for (int i = 0; i < halfKernelSize; ++i) {
176        float x = t + i + 0.5f;
177        if (x < -circleR || x > circleR) {
178            continue;
179        }
180        float verticalEval = eval_vertically(x, circleR, summedHalfKernelTable, halfKernelSize);
181        acc += verticalEval * halfKernel[i];
182    }
183    // Since we applied a half kernel in y we multiply acc by 2 (the circle is symmetric about the
184    // x axis).
185    return SkUnitScalarClampToByte(2.f * acc);
186}
187
188static inline void compute_profile_offset_and_size(float circleR, float sigma,
189                                                   float* offset, int* size) {
190    if (3*sigma <= circleR) {
191        // The circle is bigger than the Gaussian. In this case we know the interior of the
192        // blurred circle is solid.
193        *offset = circleR - 3 * sigma; // This location maps to 0.5f in the weights texture.
194                                       // It should always be 255.
195        *size = SkScalarCeilToInt(6*sigma);
196    } else {
197        // The Gaussian is bigger than the circle.
198        *offset = 0.0f;
199        *size = SkScalarCeilToInt(circleR + 3*sigma);
200    }
201}
202
203// This function creates a profile of a blurred circle. It does this by computing a kernel for
204// half the Gaussian and a matching summed area table. To compute a profile value at x = r it steps
205// outward in x from (r, 0) in both directions. There is a step for each direction for each entry
206// in the half kernel. The y contribution at each step is computed from the summed area table using
207// the height of the circle above the step point. Each y contribution is multiplied by the half
208// kernel value corresponding to the step in x.
209static uint8_t* create_profile(float circleR, float sigma) {
210    float offset;
211    int numSteps;
212    compute_profile_offset_and_size(circleR, sigma, &offset, &numSteps);
213
214    uint8_t* weights = new uint8_t[numSteps];
215
216    // The full kernel is 6 sigmas wide.
217    int halfKernelSize = SkScalarCeilToInt(6.0f*sigma);
218    // round up to next multiple of 2 and then divide by 2
219    halfKernelSize = ((halfKernelSize + 1) & ~1) >> 1;
220    SkAutoTArray<float> halfKernel(halfKernelSize);
221    SkAutoTArray<float> summedKernel(halfKernelSize);
222    make_half_kernel_and_summed_table(halfKernel.get(), summedKernel.get(), halfKernelSize,
223                                      sigma);
224    for (int i = 0; i < numSteps - 1; ++i) {
225        weights[i] = eval_at(offset+i, circleR, halfKernel.get(), summedKernel.get(),
226                             halfKernelSize);
227    }
228    // Ensure the tail of the Gaussian goes to zero.
229    weights[numSteps - 1] = 0;
230    return weights;
231}
232
233GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture(
234                                                                GrTextureProvider* textureProvider,
235                                                                const SkRect& circle,
236                                                                float sigma,
237                                                                float* offset) {
238    float circleR = circle.width() / 2.0f;
239
240    static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
241    GrUniqueKey key;
242    GrUniqueKey::Builder builder(&key, kDomain, 2);
243    // The profile curve varies with both the sigma of the Gaussian and the size of the
244    // disk. Quantizing to 16.16 should be close enough though.
245    builder[0] = SkScalarToFixed(sigma);
246    builder[1] = SkScalarToFixed(circleR);
247    builder.finish();
248
249    GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key);
250
251    int profileSize;
252    compute_profile_offset_and_size(circleR, sigma, offset, &profileSize);
253
254    if (!blurProfile) {
255
256        GrSurfaceDesc texDesc;
257        texDesc.fWidth = profileSize;
258        texDesc.fHeight = 1;
259        texDesc.fConfig = kAlpha_8_GrPixelConfig;
260
261        SkAutoTDeleteArray<uint8_t> profile(create_profile(circleR, sigma));
262
263        blurProfile = textureProvider->createTexture(texDesc, SkBudgeted::kYes, profile.get(), 0);
264        if (blurProfile) {
265            textureProvider->assignUniqueKeyToTexture(key, blurProfile);
266        }
267    }
268
269    return blurProfile;
270}
271
272GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor);
273
274const GrFragmentProcessor* GrCircleBlurFragmentProcessor::TestCreate(GrProcessorTestData* d) {
275    SkScalar wh = d->fRandom->nextRangeScalar(100.f, 1000.f);
276    SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f);
277    SkRect circle = SkRect::MakeWH(wh, wh);
278    return GrCircleBlurFragmentProcessor::Create(d->fContext->textureProvider(), circle, sigma);
279}
280
281#endif
282