GrCircleBlurFragmentProcessor.cpp revision 64c4728c70001ed074fecf5c4e083781987b12e9
1
2/*
3 * Copyright 2015 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 "GrCircleBlurFragmentProcessor.h"
10
11#if SK_SUPPORT_GPU
12
13#include "GrContext.h"
14#include "GrTextureProvider.h"
15
16#include "glsl/GrGLSLFragmentProcessor.h"
17#include "glsl/GrGLSLFragmentShaderBuilder.h"
18#include "glsl/GrGLSLProgramBuilder.h"
19#include "glsl/GrGLSLProgramDataManager.h"
20
21class GrGLCircleBlurFragmentProcessor : public GrGLSLFragmentProcessor {
22public:
23    GrGLCircleBlurFragmentProcessor(const GrProcessor&) {}
24    void emitCode(EmitArgs&) override;
25
26protected:
27    void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override;
28
29private:
30    GrGLSLProgramDataManager::UniformHandle fDataUniform;
31
32    typedef GrGLSLFragmentProcessor INHERITED;
33};
34
35void GrGLCircleBlurFragmentProcessor::emitCode(EmitArgs& args) {
36
37    const char *dataName;
38
39    // The data is formatted as:
40    // x,y  - the center of the circle
41    // z    - the distance at which the intensity starts falling off (e.g., the start of the table)
42    // w    - the size of the profile texture
43    fDataUniform = args.fBuilder->addUniform(GrGLSLProgramBuilder::kFragment_Visibility,
44                                             kVec4f_GrSLType,
45                                             kDefault_GrSLPrecision,
46                                             "data",
47                                             &dataName);
48
49    GrGLSLFragmentBuilder* fsBuilder = args.fBuilder->getFragmentShaderBuilder();
50    const char *fragmentPos = fsBuilder->fragmentPosition();
51
52    if (args.fInputColor) {
53        fsBuilder->codeAppendf("vec4 src=%s;", args.fInputColor);
54    } else {
55        fsBuilder->codeAppendf("vec4 src=vec4(1);");
56    }
57
58    fsBuilder->codeAppendf("vec2 vec = %s.xy - %s.xy;", fragmentPos, dataName);
59    fsBuilder->codeAppendf("float dist = (length(vec) - %s.z + 0.5) / %s.w;", dataName, dataName);
60
61    fsBuilder->codeAppendf("float intensity = ");
62    fsBuilder->appendTextureLookup(args.fSamplers[0], "vec2(dist, 0.5)");
63    fsBuilder->codeAppend(".a;");
64
65    fsBuilder->codeAppendf("%s = src * intensity;\n", args.fOutputColor );
66}
67
68void GrGLCircleBlurFragmentProcessor::onSetData(const GrGLSLProgramDataManager& pdman,
69                                                const GrProcessor& proc) {
70    const GrCircleBlurFragmentProcessor& cbfp = proc.cast<GrCircleBlurFragmentProcessor>();
71    const SkRect& circle = cbfp.circle();
72
73    // The data is formatted as:
74    // x,y  - the center of the circle
75    // z    - the distance at which the intensity starts falling off (e.g., the start of the table)
76    // w    - the size of the profile texture
77    pdman.set4f(fDataUniform, circle.centerX(), circle.centerY(), cbfp.offset(),
78                SkIntToScalar(cbfp.profileSize()));
79}
80
81///////////////////////////////////////////////////////////////////////////////
82
83GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(const SkRect& circle,
84                                                             float sigma,
85                                                             float offset,
86                                                             GrTexture* blurProfile)
87    : fCircle(circle)
88    , fSigma(sigma)
89    , fOffset(offset)
90    , fBlurProfileAccess(blurProfile, GrTextureParams::kBilerp_FilterMode) {
91    this->initClassID<GrCircleBlurFragmentProcessor>();
92    this->addTextureAccess(&fBlurProfileAccess);
93    this->setWillReadFragmentPosition();
94}
95
96GrGLSLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLInstance() const {
97    return new GrGLCircleBlurFragmentProcessor(*this);
98}
99
100void GrCircleBlurFragmentProcessor::onGetGLProcessorKey(const GrGLSLCaps& caps,
101                                                        GrProcessorKeyBuilder* b) const {
102    GrGLCircleBlurFragmentProcessor::GenKey(*this, caps, b);
103}
104
105void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput* inout) const {
106    inout->mulByUnknownSingleComponent();
107}
108
109// Evaluate an AA circle function centered at the origin with 'radius' at (x,y)
110static inline float disk(float x, float y, float radius) {
111    float distSq = x*x + y*y;
112    if (distSq <= (radius-0.5f)*(radius-0.5f)) {
113        return 1.0f;
114    } else if (distSq >= (radius+0.5f)*(radius+0.5f)) {
115        return 0.0f;
116    } else {
117        float ramp = radius + 0.5f - sqrtf(distSq);
118        SkASSERT(ramp >= 0.0f && ramp <= 1.0f);
119        return ramp;
120    }
121}
122
123// Create the top half of an even-sized Gaussian kernel
124static void make_half_kernel(float* kernel, int kernelWH, float sigma) {
125    SkASSERT(!(kernelWH & 1));
126
127    const float kernelOff = (kernelWH-1)/2.0f;
128
129    float b = 1.0f / (2.0f * sigma * sigma);
130    // omit the scale term since we're just going to renormalize
131
132    float tot = 0.0f;
133    for (int y = 0; y < kernelWH/2; ++y) {
134        for (int x = 0; x < kernelWH/2; ++x) {
135            // TODO: use a cheap approximation of the 2D Guassian?
136            float x2 = (x-kernelOff) * (x-kernelOff);
137            float y2 = (y-kernelOff) * (y-kernelOff);
138            // The kernel is symmetric so only compute it once for both sides
139            kernel[y*kernelWH+(kernelWH-x-1)] = kernel[y*kernelWH+x] = expf(-(x2 + y2) * b);
140            tot += 2.0f * kernel[y*kernelWH+x];
141        }
142    }
143    // Still normalize the half kernel to 1.0 (rather than 0.5) so we don't
144    // have to scale by 2.0 after convolution.
145    for (int y = 0; y < kernelWH/2; ++y) {
146        for (int x = 0; x < kernelWH; ++x) {
147            kernel[y*kernelWH+x] /= tot;
148        }
149    }
150}
151
152// Apply the half-kernel at 't' away from the center of the circle
153static uint8_t eval_at(float t, float halfWidth, float* halfKernel, int kernelWH) {
154    SkASSERT(!(kernelWH & 1));
155
156    const float kernelOff = (kernelWH-1)/2.0f;
157
158    float acc = 0;
159
160    for (int y = 0; y < kernelWH/2; ++y) {
161        if (kernelOff-y > halfWidth+0.5f) {
162            // All disk() samples in this row will be 0.0f
163            continue;
164        }
165
166        for (int x = 0; x < kernelWH; ++x) {
167            float image = disk(t - kernelOff + x, -kernelOff + y, halfWidth);
168            float kernel = halfKernel[y*kernelWH+x];
169            acc += kernel * image;
170        }
171    }
172
173    return SkUnitScalarClampToByte(acc);
174}
175
176static inline void compute_profile_offset_and_size(float halfWH, float sigma,
177                                                   float* offset, int* size) {
178
179    if (3*sigma <= halfWH) {
180        // The circle is bigger than the Gaussian. In this case we know the interior of the
181        // blurred circle is solid.
182        *offset = halfWH - 3 * sigma; // This location maps to 0.5f in the weights texture.
183                                     // It should always be 255.
184        *size = SkScalarCeilToInt(6*sigma);
185    } else {
186        // The Gaussian is bigger than the circle.
187        *offset = 0.0f;
188        *size = SkScalarCeilToInt(halfWH + 3*sigma);
189    }
190}
191
192static uint8_t* create_profile(float halfWH, float sigma) {
193
194    int kernelWH = SkScalarCeilToInt(6.0f*sigma);
195    kernelWH = (kernelWH + 1) & ~1; // make it the next even number up
196
197    SkAutoTArray<float> halfKernel(kernelWH*kernelWH/2);
198
199    make_half_kernel(halfKernel.get(), kernelWH, sigma);
200
201    float offset;
202    int numSteps;
203
204    compute_profile_offset_and_size(halfWH, sigma, &offset, &numSteps);
205
206    uint8_t* weights = new uint8_t[numSteps];
207    for (int i = 0; i < numSteps; ++i) {
208        weights[i] = eval_at(offset+i, halfWH, halfKernel.get(), kernelWH);
209    }
210
211    return weights;
212}
213
214GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture(
215                                                                GrTextureProvider* textureProvider,
216                                                                const SkRect& circle,
217                                                                float sigma,
218                                                                float* offset) {
219    float halfWH = circle.width() / 2.0f;
220
221    int size;
222    compute_profile_offset_and_size(halfWH, sigma, offset, &size);
223
224    GrSurfaceDesc texDesc;
225    texDesc.fWidth = size;
226    texDesc.fHeight = 1;
227    texDesc.fConfig = kAlpha_8_GrPixelConfig;
228
229    static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
230    GrUniqueKey key;
231    GrUniqueKey::Builder builder(&key, kDomain, 2);
232    // The profile curve varies with both the sigma of the Gaussian and the size of the
233    // disk. Quantizing to 16.16 should be close enough though.
234    builder[0] = SkScalarToFixed(sigma);
235    builder[1] = SkScalarToFixed(halfWH);
236    builder.finish();
237
238    GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key);
239
240    if (!blurProfile) {
241        SkAutoTDeleteArray<uint8_t> profile(create_profile(halfWH, sigma));
242
243        blurProfile = textureProvider->createTexture(texDesc, true, profile.get(), 0);
244        if (blurProfile) {
245            textureProvider->assignUniqueKeyToTexture(key, blurProfile);
246        }
247    }
248
249    return blurProfile;
250}
251
252GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor);
253
254const GrFragmentProcessor* GrCircleBlurFragmentProcessor::TestCreate(GrProcessorTestData* d) {
255    SkScalar wh = d->fRandom->nextRangeScalar(100.f, 1000.f);
256    SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f);
257    SkRect circle = SkRect::MakeWH(wh, wh);
258    return GrCircleBlurFragmentProcessor::Create(d->fContext->textureProvider(), circle, sigma);
259}
260
261#endif
262