blob: 39e3947049e039d54f8923ad74baa6de5ba04b10 [file] [log] [blame]
robertphillips30c4cae2015-09-15 10:20:55 -07001
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"
egdaniel7ea439b2015-12-03 09:20:44 -080014#include "GrInvariantOutput.h"
robertphillips30c4cae2015-09-15 10:20:55 -070015#include "GrTextureProvider.h"
16
egdaniel64c47282015-11-13 06:54:19 -080017#include "glsl/GrGLSLFragmentProcessor.h"
egdaniel2d721d32015-11-11 13:06:05 -080018#include "glsl/GrGLSLFragmentShaderBuilder.h"
egdaniel018fb622015-10-28 07:26:40 -070019#include "glsl/GrGLSLProgramDataManager.h"
egdaniel7ea439b2015-12-03 09:20:44 -080020#include "glsl/GrGLSLUniformHandler.h"
robertphillips30c4cae2015-09-15 10:20:55 -070021
egdaniel64c47282015-11-13 06:54:19 -080022class GrGLCircleBlurFragmentProcessor : public GrGLSLFragmentProcessor {
robertphillips30c4cae2015-09-15 10:20:55 -070023public:
robertphillips30c4cae2015-09-15 10:20:55 -070024 void emitCode(EmitArgs&) override;
25
26protected:
egdaniel018fb622015-10-28 07:26:40 -070027 void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override;
robertphillips30c4cae2015-09-15 10:20:55 -070028
29private:
egdaniel018fb622015-10-28 07:26:40 -070030 GrGLSLProgramDataManager::UniformHandle fDataUniform;
robertphillips30c4cae2015-09-15 10:20:55 -070031
egdaniel64c47282015-11-13 06:54:19 -080032 typedef GrGLSLFragmentProcessor INHERITED;
robertphillips30c4cae2015-09-15 10:20:55 -070033};
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)
robertphillips4e567722015-12-10 13:29:14 -080042 // w - the inverse of the profile texture size
cdalton5e58cee2016-02-11 12:49:47 -080043 fDataUniform = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
egdaniel7ea439b2015-12-03 09:20:44 -080044 kVec4f_GrSLType,
45 kDefault_GrSLPrecision,
46 "data",
47 &dataName);
robertphillips30c4cae2015-09-15 10:20:55 -070048
cdalton85285412016-02-18 12:37:07 -080049 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
egdaniel4ca2e602015-11-18 08:01:26 -080050 const char *fragmentPos = fragBuilder->fragmentPosition();
robertphillips30c4cae2015-09-15 10:20:55 -070051
52 if (args.fInputColor) {
egdaniel4ca2e602015-11-18 08:01:26 -080053 fragBuilder->codeAppendf("vec4 src=%s;", args.fInputColor);
robertphillips30c4cae2015-09-15 10:20:55 -070054 } else {
egdaniel4ca2e602015-11-18 08:01:26 -080055 fragBuilder->codeAppendf("vec4 src=vec4(1);");
robertphillips30c4cae2015-09-15 10:20:55 -070056 }
57
robertphillips4e567722015-12-10 13:29:14 -080058 // 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);
robertphillips30c4cae2015-09-15 10:20:55 -070065
egdaniel4ca2e602015-11-18 08:01:26 -080066 fragBuilder->codeAppendf("float intensity = ");
67 fragBuilder->appendTextureLookup(args.fSamplers[0], "vec2(dist, 0.5)");
68 fragBuilder->codeAppend(".a;");
robertphillips30c4cae2015-09-15 10:20:55 -070069
egdaniel4ca2e602015-11-18 08:01:26 -080070 fragBuilder->codeAppendf("%s = src * intensity;\n", args.fOutputColor );
robertphillips30c4cae2015-09-15 10:20:55 -070071}
72
egdaniel018fb622015-10-28 07:26:40 -070073void GrGLCircleBlurFragmentProcessor::onSetData(const GrGLSLProgramDataManager& pdman,
robertphillips30c4cae2015-09-15 10:20:55 -070074 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)
robertphillips4e567722015-12-10 13:29:14 -080081 // w - the inverse of the profile texture size
robertphillips30c4cae2015-09-15 10:20:55 -070082 pdman.set4f(fDataUniform, circle.centerX(), circle.centerY(), cbfp.offset(),
robertphillips4e567722015-12-10 13:29:14 -080083 1.0f / cbfp.profileSize());
robertphillips30c4cae2015-09-15 10:20:55 -070084}
85
86///////////////////////////////////////////////////////////////////////////////
87
88GrCircleBlurFragmentProcessor::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
egdaniel57d3b032015-11-13 11:57:27 -0800101GrGLSLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLSLInstance() const {
robertphillips9cdb9922016-02-03 12:25:40 -0800102 return new GrGLCircleBlurFragmentProcessor;
robertphillips30c4cae2015-09-15 10:20:55 -0700103}
104
egdaniel57d3b032015-11-13 11:57:27 -0800105void GrCircleBlurFragmentProcessor::onGetGLSLProcessorKey(const GrGLSLCaps& caps,
106 GrProcessorKeyBuilder* b) const {
robertphillips30c4cae2015-09-15 10:20:55 -0700107 GrGLCircleBlurFragmentProcessor::GenKey(*this, caps, b);
108}
109
110void 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)
115static 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 {
bsalomon0d705a42015-09-15 14:41:15 -0700122 float ramp = radius + 0.5f - sqrtf(distSq);
robertphillips30c4cae2015-09-15 10:20:55 -0700123 SkASSERT(ramp >= 0.0f && ramp <= 1.0f);
124 return ramp;
125 }
126}
127
128// Create the top half of an even-sized Gaussian kernel
129static 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
bsalomon0d705a42015-09-15 14:41:15 -0700144 kernel[y*kernelWH+(kernelWH-x-1)] = kernel[y*kernelWH+x] = expf(-(x2 + y2) * b);
robertphillips30c4cae2015-09-15 10:20:55 -0700145 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
158static 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
181static 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.
robertphillips4e567722015-12-10 13:29:14 -0800188 // It should always be 255.
robertphillips30c4cae2015-09-15 10:20:55 -0700189 *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
197static 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];
benjaminwagner9d240232016-02-24 07:51:33 -0800212 for (int i = 0; i < numSteps - 1; ++i) {
robertphillips30c4cae2015-09-15 10:20:55 -0700213 weights[i] = eval_at(offset+i, halfWH, halfKernel.get(), kernelWH);
214 }
benjaminwagner9d240232016-02-24 07:51:33 -0800215 // Ensure the tail of the Gaussian goes to zero.
216 weights[numSteps-1] = 0;
robertphillips30c4cae2015-09-15 10:20:55 -0700217
218 return weights;
219}
220
221GrTexture* 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, true, profile.get(), 0);
251 if (blurProfile) {
252 textureProvider->assignUniqueKeyToTexture(key, blurProfile);
253 }
254 }
255
256 return blurProfile;
257}
258
259GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor);
260
261const 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