blob: 291dafc81acd8499903cbd8be0f30231f50ef701 [file] [log] [blame]
robertphillips30c4cae2015-09-15 10:20:55 -07001/*
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"
egdaniel7ea439b2015-12-03 09:20:44 -080013#include "GrInvariantOutput.h"
robertphillips30c4cae2015-09-15 10:20:55 -070014#include "GrTextureProvider.h"
15
egdaniel64c47282015-11-13 06:54:19 -080016#include "glsl/GrGLSLFragmentProcessor.h"
egdaniel2d721d32015-11-11 13:06:05 -080017#include "glsl/GrGLSLFragmentShaderBuilder.h"
egdaniel018fb622015-10-28 07:26:40 -070018#include "glsl/GrGLSLProgramDataManager.h"
egdaniel7ea439b2015-12-03 09:20:44 -080019#include "glsl/GrGLSLUniformHandler.h"
robertphillips30c4cae2015-09-15 10:20:55 -070020
benjaminwagner6c71e0a2016-04-07 08:49:31 -070021#include "SkFixed.h"
22
egdaniel64c47282015-11-13 06:54:19 -080023class GrGLCircleBlurFragmentProcessor : public GrGLSLFragmentProcessor {
robertphillips30c4cae2015-09-15 10:20:55 -070024public:
robertphillips30c4cae2015-09-15 10:20:55 -070025 void emitCode(EmitArgs&) override;
26
27protected:
egdaniel018fb622015-10-28 07:26:40 -070028 void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override;
robertphillips30c4cae2015-09-15 10:20:55 -070029
30private:
egdaniel018fb622015-10-28 07:26:40 -070031 GrGLSLProgramDataManager::UniformHandle fDataUniform;
robertphillips30c4cae2015-09-15 10:20:55 -070032
egdaniel64c47282015-11-13 06:54:19 -080033 typedef GrGLSLFragmentProcessor INHERITED;
robertphillips30c4cae2015-09-15 10:20:55 -070034};
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)
robertphillips4e567722015-12-10 13:29:14 -080043 // w - the inverse of the profile texture size
cdalton5e58cee2016-02-11 12:49:47 -080044 fDataUniform = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
egdaniel7ea439b2015-12-03 09:20:44 -080045 kVec4f_GrSLType,
46 kDefault_GrSLPrecision,
47 "data",
48 &dataName);
robertphillips30c4cae2015-09-15 10:20:55 -070049
cdalton85285412016-02-18 12:37:07 -080050 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
egdaniel4ca2e602015-11-18 08:01:26 -080051 const char *fragmentPos = fragBuilder->fragmentPosition();
robertphillips30c4cae2015-09-15 10:20:55 -070052
53 if (args.fInputColor) {
egdaniel4ca2e602015-11-18 08:01:26 -080054 fragBuilder->codeAppendf("vec4 src=%s;", args.fInputColor);
robertphillips30c4cae2015-09-15 10:20:55 -070055 } else {
egdaniel4ca2e602015-11-18 08:01:26 -080056 fragBuilder->codeAppendf("vec4 src=vec4(1);");
robertphillips30c4cae2015-09-15 10:20:55 -070057 }
58
robertphillips4e567722015-12-10 13:29:14 -080059 // We just want to compute "length(vec) - %s.z + 0.5) * %s.w" but need to rearrange
60 // for precision
halcanary9d524f22016-03-29 09:03:52 -070061 fragBuilder->codeAppendf("vec2 vec = vec2( (%s.x - %s.x) * %s.w , (%s.y - %s.y) * %s.w );",
robertphillips4e567722015-12-10 13:29:14 -080062 fragmentPos, dataName, dataName,
63 fragmentPos, dataName, dataName);
64 fragBuilder->codeAppendf("float dist = length(vec) + ( 0.5 - %s.z ) * %s.w;",
65 dataName, dataName);
robertphillips30c4cae2015-09-15 10:20:55 -070066
egdaniel4ca2e602015-11-18 08:01:26 -080067 fragBuilder->codeAppendf("float intensity = ");
68 fragBuilder->appendTextureLookup(args.fSamplers[0], "vec2(dist, 0.5)");
69 fragBuilder->codeAppend(".a;");
robertphillips30c4cae2015-09-15 10:20:55 -070070
egdaniel4ca2e602015-11-18 08:01:26 -080071 fragBuilder->codeAppendf("%s = src * intensity;\n", args.fOutputColor );
robertphillips30c4cae2015-09-15 10:20:55 -070072}
73
egdaniel018fb622015-10-28 07:26:40 -070074void GrGLCircleBlurFragmentProcessor::onSetData(const GrGLSLProgramDataManager& pdman,
robertphillips30c4cae2015-09-15 10:20:55 -070075 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)
robertphillips4e567722015-12-10 13:29:14 -080082 // w - the inverse of the profile texture size
robertphillips30c4cae2015-09-15 10:20:55 -070083 pdman.set4f(fDataUniform, circle.centerX(), circle.centerY(), cbfp.offset(),
robertphillips4e567722015-12-10 13:29:14 -080084 1.0f / cbfp.profileSize());
robertphillips30c4cae2015-09-15 10:20:55 -070085}
86
87///////////////////////////////////////////////////////////////////////////////
88
89GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(const SkRect& circle,
90 float sigma,
91 float offset,
halcanary9d524f22016-03-29 09:03:52 -070092 GrTexture* blurProfile)
robertphillips30c4cae2015-09-15 10:20:55 -070093 : 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
egdaniel57d3b032015-11-13 11:57:27 -0800102GrGLSLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLSLInstance() const {
robertphillips9cdb9922016-02-03 12:25:40 -0800103 return new GrGLCircleBlurFragmentProcessor;
robertphillips30c4cae2015-09-15 10:20:55 -0700104}
105
egdaniel57d3b032015-11-13 11:57:27 -0800106void GrCircleBlurFragmentProcessor::onGetGLSLProcessorKey(const GrGLSLCaps& caps,
107 GrProcessorKeyBuilder* b) const {
robertphillips30c4cae2015-09-15 10:20:55 -0700108 GrGLCircleBlurFragmentProcessor::GenKey(*this, caps, b);
109}
110
111void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput* inout) const {
112 inout->mulByUnknownSingleComponent();
113}
114
115// Evaluate an AA circle function centered at the origin with 'radius' at (x,y)
116static inline float disk(float x, float y, float radius) {
117 float distSq = x*x + y*y;
118 if (distSq <= (radius-0.5f)*(radius-0.5f)) {
119 return 1.0f;
120 } else if (distSq >= (radius+0.5f)*(radius+0.5f)) {
121 return 0.0f;
122 } else {
bsalomon0d705a42015-09-15 14:41:15 -0700123 float ramp = radius + 0.5f - sqrtf(distSq);
robertphillips30c4cae2015-09-15 10:20:55 -0700124 SkASSERT(ramp >= 0.0f && ramp <= 1.0f);
125 return ramp;
126 }
127}
128
129// Create the top half of an even-sized Gaussian kernel
130static void make_half_kernel(float* kernel, int kernelWH, float sigma) {
131 SkASSERT(!(kernelWH & 1));
132
133 const float kernelOff = (kernelWH-1)/2.0f;
134
135 float b = 1.0f / (2.0f * sigma * sigma);
136 // omit the scale term since we're just going to renormalize
137
138 float tot = 0.0f;
139 for (int y = 0; y < kernelWH/2; ++y) {
140 for (int x = 0; x < kernelWH/2; ++x) {
141 // TODO: use a cheap approximation of the 2D Guassian?
142 float x2 = (x-kernelOff) * (x-kernelOff);
143 float y2 = (y-kernelOff) * (y-kernelOff);
144 // The kernel is symmetric so only compute it once for both sides
bsalomon0d705a42015-09-15 14:41:15 -0700145 kernel[y*kernelWH+(kernelWH-x-1)] = kernel[y*kernelWH+x] = expf(-(x2 + y2) * b);
robertphillips30c4cae2015-09-15 10:20:55 -0700146 tot += 2.0f * kernel[y*kernelWH+x];
147 }
148 }
149 // Still normalize the half kernel to 1.0 (rather than 0.5) so we don't
150 // have to scale by 2.0 after convolution.
151 for (int y = 0; y < kernelWH/2; ++y) {
152 for (int x = 0; x < kernelWH; ++x) {
153 kernel[y*kernelWH+x] /= tot;
154 }
155 }
156}
157
158// Apply the half-kernel at 't' away from the center of the circle
159static uint8_t eval_at(float t, float halfWidth, float* halfKernel, int kernelWH) {
160 SkASSERT(!(kernelWH & 1));
161
162 const float kernelOff = (kernelWH-1)/2.0f;
163
164 float acc = 0;
165
166 for (int y = 0; y < kernelWH/2; ++y) {
167 if (kernelOff-y > halfWidth+0.5f) {
168 // All disk() samples in this row will be 0.0f
169 continue;
170 }
171
172 for (int x = 0; x < kernelWH; ++x) {
173 float image = disk(t - kernelOff + x, -kernelOff + y, halfWidth);
174 float kernel = halfKernel[y*kernelWH+x];
175 acc += kernel * image;
176 }
177 }
178
179 return SkUnitScalarClampToByte(acc);
180}
181
182static inline void compute_profile_offset_and_size(float halfWH, float sigma,
183 float* offset, int* size) {
184
185 if (3*sigma <= halfWH) {
186 // The circle is bigger than the Gaussian. In this case we know the interior of the
187 // blurred circle is solid.
188 *offset = halfWH - 3 * sigma; // This location maps to 0.5f in the weights texture.
robertphillips4e567722015-12-10 13:29:14 -0800189 // It should always be 255.
robertphillips30c4cae2015-09-15 10:20:55 -0700190 *size = SkScalarCeilToInt(6*sigma);
191 } else {
192 // The Gaussian is bigger than the circle.
193 *offset = 0.0f;
194 *size = SkScalarCeilToInt(halfWH + 3*sigma);
195 }
196}
197
198static uint8_t* create_profile(float halfWH, float sigma) {
199
200 int kernelWH = SkScalarCeilToInt(6.0f*sigma);
201 kernelWH = (kernelWH + 1) & ~1; // make it the next even number up
202
203 SkAutoTArray<float> halfKernel(kernelWH*kernelWH/2);
204
205 make_half_kernel(halfKernel.get(), kernelWH, sigma);
206
207 float offset;
208 int numSteps;
209
210 compute_profile_offset_and_size(halfWH, sigma, &offset, &numSteps);
211
212 uint8_t* weights = new uint8_t[numSteps];
benjaminwagner9d240232016-02-24 07:51:33 -0800213 for (int i = 0; i < numSteps - 1; ++i) {
robertphillips30c4cae2015-09-15 10:20:55 -0700214 weights[i] = eval_at(offset+i, halfWH, halfKernel.get(), kernelWH);
215 }
benjaminwagner9d240232016-02-24 07:51:33 -0800216 // Ensure the tail of the Gaussian goes to zero.
217 weights[numSteps-1] = 0;
robertphillips30c4cae2015-09-15 10:20:55 -0700218
219 return weights;
220}
221
222GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture(
223 GrTextureProvider* textureProvider,
224 const SkRect& circle,
225 float sigma,
226 float* offset) {
227 float halfWH = circle.width() / 2.0f;
228
229 int size;
230 compute_profile_offset_and_size(halfWH, sigma, offset, &size);
231
232 GrSurfaceDesc texDesc;
233 texDesc.fWidth = size;
234 texDesc.fHeight = 1;
235 texDesc.fConfig = kAlpha_8_GrPixelConfig;
236
237 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
238 GrUniqueKey key;
239 GrUniqueKey::Builder builder(&key, kDomain, 2);
240 // The profile curve varies with both the sigma of the Gaussian and the size of the
241 // disk. Quantizing to 16.16 should be close enough though.
242 builder[0] = SkScalarToFixed(sigma);
243 builder[1] = SkScalarToFixed(halfWH);
244 builder.finish();
245
246 GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key);
247
248 if (!blurProfile) {
249 SkAutoTDeleteArray<uint8_t> profile(create_profile(halfWH, sigma));
250
bsalomon5ec26ae2016-02-25 08:33:02 -0800251 blurProfile = textureProvider->createTexture(texDesc, SkBudgeted::kYes, profile.get(), 0);
robertphillips30c4cae2015-09-15 10:20:55 -0700252 if (blurProfile) {
253 textureProvider->assignUniqueKeyToTexture(key, blurProfile);
254 }
255 }
256
257 return blurProfile;
258}
259
260GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor);
261
262const GrFragmentProcessor* GrCircleBlurFragmentProcessor::TestCreate(GrProcessorTestData* d) {
263 SkScalar wh = d->fRandom->nextRangeScalar(100.f, 1000.f);
264 SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f);
265 SkRect circle = SkRect::MakeWH(wh, wh);
266 return GrCircleBlurFragmentProcessor::Create(d->fContext->textureProvider(), circle, sigma);
267}
268
269#endif