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