OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2015 Google Inc. | 2 * Copyright 2015 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "GrCircleBlurFragmentProcessor.h" | 8 #include "GrCircleBlurFragmentProcessor.h" |
9 | 9 |
10 #if SK_SUPPORT_GPU | 10 #if SK_SUPPORT_GPU |
11 | 11 |
12 #include "GrContext.h" | 12 #include "GrContext.h" |
13 #include "GrInvariantOutput.h" | 13 #include "GrInvariantOutput.h" |
14 #include "GrTextureProvider.h" | 14 #include "GrTextureProvider.h" |
15 | 15 |
16 #include "glsl/GrGLSLFragmentProcessor.h" | 16 #include "glsl/GrGLSLFragmentProcessor.h" |
17 #include "glsl/GrGLSLFragmentShaderBuilder.h" | 17 #include "glsl/GrGLSLFragmentShaderBuilder.h" |
18 #include "glsl/GrGLSLProgramDataManager.h" | 18 #include "glsl/GrGLSLProgramDataManager.h" |
19 #include "glsl/GrGLSLUniformHandler.h" | 19 #include "glsl/GrGLSLUniformHandler.h" |
20 | 20 |
21 #include "SkFixed.h" | 21 #include "SkFixed.h" |
22 | 22 |
23 class GrGLCircleBlurFragmentProcessor : public GrGLSLFragmentProcessor { | 23 class GrCircleBlurFragmentProcessor::GLSLProcessor : public GrGLSLFragmentProces
sor { |
24 public: | 24 public: |
25 void emitCode(EmitArgs&) override; | 25 void emitCode(EmitArgs&) override; |
26 | 26 |
27 protected: | 27 protected: |
28 void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override
; | 28 void onSetData(const GrGLSLProgramDataManager&, const GrProcessor&) override
; |
29 | 29 |
30 private: | 30 private: |
31 GrGLSLProgramDataManager::UniformHandle fDataUniform; | 31 GrGLSLProgramDataManager::UniformHandle fDataUniform; |
32 | 32 |
33 typedef GrGLSLFragmentProcessor INHERITED; | 33 typedef GrGLSLFragmentProcessor INHERITED; |
34 }; | 34 }; |
35 | 35 |
36 void GrGLCircleBlurFragmentProcessor::emitCode(EmitArgs& args) { | 36 void GrCircleBlurFragmentProcessor::GLSLProcessor::emitCode(EmitArgs& args) { |
37 | 37 |
38 const char *dataName; | 38 const char *dataName; |
39 | 39 |
40 // The data is formatted as: | 40 // The data is formatted as: |
41 // x,y - the center of the circle | 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) | 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 | 43 // w - the inverse of the distance over which the texture is stretched. |
44 fDataUniform = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, | 44 fDataUniform = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, |
45 kVec4f_GrSLType, | 45 kVec4f_GrSLType, |
46 kDefault_GrSLPrecision, | 46 kDefault_GrSLPrecision, |
47 "data", | 47 "data", |
48 &dataName); | 48 &dataName); |
49 | 49 |
50 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; | 50 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; |
51 const char *fragmentPos = fragBuilder->fragmentPosition(); | 51 const char *fragmentPos = fragBuilder->fragmentPosition(); |
52 | 52 |
53 if (args.fInputColor) { | 53 if (args.fInputColor) { |
(...skipping 10 matching lines...) Expand all Loading... |
64 fragBuilder->codeAppendf("float dist = length(vec) + ( 0.5 - %s.z ) * %s.w;"
, | 64 fragBuilder->codeAppendf("float dist = length(vec) + ( 0.5 - %s.z ) * %s.w;"
, |
65 dataName, dataName); | 65 dataName, dataName); |
66 | 66 |
67 fragBuilder->codeAppendf("float intensity = "); | 67 fragBuilder->codeAppendf("float intensity = "); |
68 fragBuilder->appendTextureLookup(args.fTexSamplers[0], "vec2(dist, 0.5)"); | 68 fragBuilder->appendTextureLookup(args.fTexSamplers[0], "vec2(dist, 0.5)"); |
69 fragBuilder->codeAppend(".a;"); | 69 fragBuilder->codeAppend(".a;"); |
70 | 70 |
71 fragBuilder->codeAppendf("%s = src * intensity;\n", args.fOutputColor ); | 71 fragBuilder->codeAppendf("%s = src * intensity;\n", args.fOutputColor ); |
72 } | 72 } |
73 | 73 |
74 void GrGLCircleBlurFragmentProcessor::onSetData(const GrGLSLProgramDataManager&
pdman, | 74 void GrCircleBlurFragmentProcessor::GLSLProcessor::onSetData(const GrGLSLProgram
DataManager& pdman, |
75 const GrProcessor& proc) { | 75 const GrProcessor&
proc) { |
76 const GrCircleBlurFragmentProcessor& cbfp = proc.cast<GrCircleBlurFragmentPr
ocessor>(); | 76 const GrCircleBlurFragmentProcessor& cbfp = proc.cast<GrCircleBlurFragmentPr
ocessor>(); |
77 const SkRect& circle = cbfp.circle(); | 77 const SkRect& circle = cbfp.fCircle; |
78 | 78 |
79 // The data is formatted as: | 79 // The data is formatted as: |
80 // x,y - the center of the circle | 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) | 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 | 82 // w - the inverse of the distance over which the profile texture is stre
tched. |
83 pdman.set4f(fDataUniform, circle.centerX(), circle.centerY(), cbfp.offset(), | 83 pdman.set4f(fDataUniform, circle.centerX(), circle.centerY(), cbfp.fSolidRad
ius, |
84 1.0f / cbfp.profileSize()); | 84 1.f / cbfp.fTextureRadius); |
85 } | 85 } |
86 | 86 |
87 /////////////////////////////////////////////////////////////////////////////// | 87 /////////////////////////////////////////////////////////////////////////////// |
88 | 88 |
89 GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(const SkRect& circl
e, | 89 GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(const SkRect& circl
e, |
90 float sigma, | 90 float sigma, |
91 float offset, | 91 float solidRadius, |
| 92 float textureRadius
, |
92 GrTexture* blurProf
ile) | 93 GrTexture* blurProf
ile) |
93 : fCircle(circle) | 94 : fCircle(circle) |
94 , fSigma(sigma) | 95 , fSigma(sigma) |
95 , fOffset(offset) | 96 , fSolidRadius(solidRadius) |
| 97 , fTextureRadius(textureRadius) |
96 , fBlurProfileAccess(blurProfile, GrTextureParams::kBilerp_FilterMode) { | 98 , fBlurProfileAccess(blurProfile, GrTextureParams::kBilerp_FilterMode) { |
97 this->initClassID<GrCircleBlurFragmentProcessor>(); | 99 this->initClassID<GrCircleBlurFragmentProcessor>(); |
98 this->addTextureAccess(&fBlurProfileAccess); | 100 this->addTextureAccess(&fBlurProfileAccess); |
99 this->setWillReadFragmentPosition(); | 101 this->setWillReadFragmentPosition(); |
100 } | 102 } |
101 | 103 |
102 GrGLSLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLSLInstance() c
onst { | 104 GrGLSLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLSLInstance() c
onst { |
103 return new GrGLCircleBlurFragmentProcessor; | 105 return new GLSLProcessor; |
104 } | 106 } |
105 | 107 |
106 void GrCircleBlurFragmentProcessor::onGetGLSLProcessorKey(const GrGLSLCaps& caps
, | 108 void GrCircleBlurFragmentProcessor::onGetGLSLProcessorKey(const GrGLSLCaps& caps
, |
107 GrProcessorKeyBuilder*
b) const { | 109 GrProcessorKeyBuilder*
b) const { |
108 GrGLCircleBlurFragmentProcessor::GenKey(*this, caps, b); | 110 // The code for this processor is always the same so there is nothing to add
to the key. |
| 111 return; |
109 } | 112 } |
110 | 113 |
111 void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput*
inout) const { | 114 void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput*
inout) const { |
112 inout->mulByUnknownSingleComponent(); | 115 inout->mulByUnknownSingleComponent(); |
113 } | 116 } |
114 | 117 |
115 // Create a Gaussian half-kernel and a summed area table given a sigma and numbe
r of discrete | 118 // Create a Gaussian half-kernel and a summed area table given a sigma and numbe
r of discrete |
116 // steps. The half kernel is normalized to sum to 0.5. | 119 // steps. The half kernel is normalized to sum to 0.5. |
117 static void make_half_kernel_and_summed_table(float* halfKernel, float* summedHa
lfKernel, | 120 static void make_half_kernel_and_summed_table(float* halfKernel, float* summedHa
lfKernel, |
118 int halfKernelSize, float sigma) { | 121 int halfKernelSize, float sigma) { |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
186 continue; | 189 continue; |
187 } | 190 } |
188 float verticalEval = yKernelEvaluations[i + halfKernelSize]; | 191 float verticalEval = yKernelEvaluations[i + halfKernelSize]; |
189 acc += verticalEval * halfKernel[i]; | 192 acc += verticalEval * halfKernel[i]; |
190 } | 193 } |
191 // Since we applied a half kernel in y we multiply acc by 2 (the circle is s
ymmetric about the | 194 // Since we applied a half kernel in y we multiply acc by 2 (the circle is s
ymmetric about the |
192 // x axis). | 195 // x axis). |
193 return SkUnitScalarClampToByte(2.f * acc); | 196 return SkUnitScalarClampToByte(2.f * acc); |
194 } | 197 } |
195 | 198 |
196 static inline void compute_profile_offset_and_size(float circleR, float sigma, | |
197 float* offset, int* size) { | |
198 if (3*sigma <= circleR) { | |
199 // The circle is bigger than the Gaussian. In this case we know the inte
rior of the | |
200 // blurred circle is solid. | |
201 *offset = circleR - 3 * sigma; // This location maps to 0.5f in the weig
hts texture. | |
202 // It should always be 255. | |
203 *size = SkScalarCeilToInt(6*sigma); | |
204 } else { | |
205 // The Gaussian is bigger than the circle. | |
206 *offset = 0.0f; | |
207 *size = SkScalarCeilToInt(circleR + 3*sigma); | |
208 } | |
209 } | |
210 | |
211 // This function creates a profile of a blurred circle. It does this by computin
g a kernel for | 199 // This function creates a profile of a blurred circle. It does this by computin
g a kernel for |
212 // half the Gaussian and a matching summed area table. The summed area table is
used to compute | 200 // half the Gaussian and a matching summed area table. The summed area table is
used to compute |
213 // an array of vertical applications of the half kernel to the circle along the
x axis. The table | 201 // an array of vertical applications of the half kernel to the circle along the
x axis. The table |
214 // of y evaluations has 2 * k + n entries where k is the size of the half kernel
and n is the size | 202 // of y evaluations has 2 * k + n entries where k is the size of the half kernel
and n is the size |
215 // of the profile being computed. Then for each of the n profile entries we walk
out k steps in each | 203 // of the profile being computed. Then for each of the n profile entries we walk
out k steps in each |
216 // horizontal direction multiplying the corresponding y evaluation by the half k
ernel entry and | 204 // horizontal direction multiplying the corresponding y evaluation by the half k
ernel entry and |
217 // sum these values to compute the profile entry. | 205 // sum these values to compute the profile entry. |
218 static uint8_t* create_profile(float circleR, float sigma) { | 206 static uint8_t* create_profile(float sigma, float circleR, float offset, int pro
fileTextureWidth) { |
219 float offset; | 207 const int numSteps = profileTextureWidth; |
220 int numSteps; | |
221 compute_profile_offset_and_size(circleR, sigma, &offset, &numSteps); | |
222 | |
223 uint8_t* weights = new uint8_t[numSteps]; | 208 uint8_t* weights = new uint8_t[numSteps]; |
224 | 209 |
225 // The full kernel is 6 sigmas wide. | 210 // The full kernel is 6 sigmas wide. |
226 int halfKernelSize = SkScalarCeilToInt(6.0f*sigma); | 211 int halfKernelSize = SkScalarCeilToInt(6.0f*sigma); |
227 // round up to next multiple of 2 and then divide by 2 | 212 // round up to next multiple of 2 and then divide by 2 |
228 halfKernelSize = ((halfKernelSize + 1) & ~1) >> 1; | 213 halfKernelSize = ((halfKernelSize + 1) & ~1) >> 1; |
229 | 214 |
230 // Number of x steps at which to apply kernel in y to cover all the profile
samples in x. | 215 // Number of x steps at which to apply kernel in y to cover all the profile
samples in x. |
231 int numYSteps = numSteps + 2 * halfKernelSize; | 216 int numYSteps = numSteps + 2 * halfKernelSize; |
232 | 217 |
233 SkAutoTArray<float> bulkAlloc(halfKernelSize + halfKernelSize + numYSteps); | 218 SkAutoTArray<float> bulkAlloc(halfKernelSize + halfKernelSize + numYSteps); |
234 float* halfKernel = bulkAlloc.get(); | 219 float* halfKernel = bulkAlloc.get(); |
235 float* summedKernel = bulkAlloc.get() + halfKernelSize; | 220 float* summedKernel = bulkAlloc.get() + halfKernelSize; |
236 float* yEvals = bulkAlloc.get() + 2 * halfKernelSize; | 221 float* yEvals = bulkAlloc.get() + 2 * halfKernelSize; |
237 make_half_kernel_and_summed_table(halfKernel, summedKernel, halfKernelSize,
sigma); | 222 make_half_kernel_and_summed_table(halfKernel, summedKernel, halfKernelSize,
sigma); |
238 | 223 |
239 float firstX = offset - halfKernelSize + 0.5f; | 224 float firstX = offset - halfKernelSize + 0.5f; |
240 apply_kernel_in_y(yEvals, numYSteps, firstX, circleR, halfKernelSize, summed
Kernel); | 225 apply_kernel_in_y(yEvals, numYSteps, firstX, circleR, halfKernelSize, summed
Kernel); |
241 | 226 |
242 for (int i = 0; i < numSteps - 1; ++i) { | 227 for (int i = 0; i < numSteps - 1; ++i) { |
243 float evalX = offset + i + 0.5f; | 228 float evalX = offset + i + 0.5f; |
244 weights[i] = eval_at(evalX, circleR, halfKernel, halfKernelSize, yEvals
+ i); | 229 weights[i] = eval_at(evalX, circleR, halfKernel, halfKernelSize, yEvals
+ i); |
245 } | 230 } |
246 // Ensure the tail of the Gaussian goes to zero. | 231 // Ensure the tail of the Gaussian goes to zero. |
247 weights[numSteps - 1] = 0; | 232 weights[numSteps - 1] = 0; |
248 return weights; | 233 return weights; |
249 } | 234 } |
250 | 235 |
| 236 static int next_pow2_16bits(int x) { |
| 237 SkASSERT(x > 0); |
| 238 SkASSERT(x <= SK_MaxS16); |
| 239 x--; |
| 240 x |= x >> 1; |
| 241 x |= x >> 2; |
| 242 x |= x >> 4; |
| 243 x |= x >> 8; |
| 244 return x + 1; |
| 245 } |
| 246 |
251 GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture( | 247 GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture( |
252 GrTextureProvide
r* textureProvider, | 248 GrTextureProvide
r* textureProvider, |
253 const SkRect& ci
rcle, | 249 const SkRect& ci
rcle, |
254 float sigma, | 250 float sigma, |
255 float* offset) { | 251 float* solidRadi
us, |
| 252 float* textureRa
dius) { |
256 float circleR = circle.width() / 2.0f; | 253 float circleR = circle.width() / 2.0f; |
| 254 // Profile textures are cached by the ratio of sigma to circle radius and by
the size of the |
| 255 // profile texture (binned by powers of 2). |
| 256 SkScalar sigmaToCircleRRatio = sigma / circleR; |
| 257 // When sigma is really small this becomes a equivalent to convolving a Gaus
sian with a half- |
| 258 // plane. We could do that simpler computation. However, right now we're jus
t using a lower |
| 259 // bound off the ratio. Similarly, in the extreme high ratio cases circle be
comes a point WRT to |
| 260 // the Guassian and the profile texture is a just a Gaussian evaluation. |
| 261 sigmaToCircleRRatio = SkTPin(sigmaToCircleRRatio, 0.05f, 8.f); |
| 262 // Convert to fixed point for the key. |
| 263 SkFixed sigmaToCircleRRatioFixed = SkScalarToFixed(sigmaToCircleRRatio); |
| 264 // We shave off some bits to reduce the number of unique entries. We could p
robably shave off |
| 265 // more than we do. |
| 266 sigmaToCircleRRatioFixed &= ~0xff; |
| 267 // From the circle center to solidRadius is all 1s and represented by the le
ftmost pixel (with |
| 268 // value 255) in the profile texture. If it is zero then there is no solid c
enter to the |
| 269 // blurred circle. |
| 270 if (3*sigma <= circleR) { |
| 271 // The circle is bigger than the Gaussian. In this case we know the inte
rior of the |
| 272 // blurred circle is solid. |
| 273 *solidRadius = circleR - 3 * sigma; // This location maps to 0.5f in the
weights texture. |
| 274 // It should always be 255. |
| 275 *textureRadius = SkScalarCeilToScalar(6 * sigma); |
| 276 } else { |
| 277 // The Gaussian is bigger than the circle. |
| 278 *solidRadius = 0.0f; |
| 279 *textureRadius = SkScalarCeilToScalar(circleR + 3 * sigma); |
| 280 } |
| 281 int profileTextureWidth = SkScalarCeilToInt(*textureRadius); |
| 282 profileTextureWidth = (profileTextureWidth >= 1024) ? 1024 : |
| 283 next_pow2_16bits(profileTextureWidth); |
257 | 284 |
258 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); | 285 static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); |
259 GrUniqueKey key; | 286 GrUniqueKey key; |
260 GrUniqueKey::Builder builder(&key, kDomain, 2); | 287 GrUniqueKey::Builder builder(&key, kDomain, 2); |
261 // The profile curve varies with both the sigma of the Gaussian and the size
of the | 288 builder[0] = sigmaToCircleRRatioFixed; |
262 // disk. Quantizing to 16.16 should be close enough though. | 289 builder[1] = profileTextureWidth; |
263 builder[0] = SkScalarToFixed(sigma); | |
264 builder[1] = SkScalarToFixed(circleR); | |
265 builder.finish(); | 290 builder.finish(); |
266 | 291 |
267 GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key); | 292 GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key); |
268 | 293 |
269 int profileSize; | |
270 compute_profile_offset_and_size(circleR, sigma, offset, &profileSize); | |
271 | |
272 if (!blurProfile) { | 294 if (!blurProfile) { |
273 | |
274 GrSurfaceDesc texDesc; | 295 GrSurfaceDesc texDesc; |
275 texDesc.fWidth = profileSize; | 296 texDesc.fWidth = profileTextureWidth; |
276 texDesc.fHeight = 1; | 297 texDesc.fHeight = 1; |
277 texDesc.fConfig = kAlpha_8_GrPixelConfig; | 298 texDesc.fConfig = kAlpha_8_GrPixelConfig; |
278 | 299 |
279 SkAutoTDeleteArray<uint8_t> profile(create_profile(circleR, sigma)); | 300 // Rescale params to the size of the texture we're creating. |
| 301 SkScalar scale = profileTextureWidth / *textureRadius; |
| 302 SkAutoTDeleteArray<uint8_t> profile(create_profile(sigma * scale, circle
R * scale, |
| 303 *solidRadius * scale, |
| 304 profileTextureWidth))
; |
280 | 305 |
281 blurProfile = textureProvider->createTexture(texDesc, SkBudgeted::kYes,
profile.get(), 0); | 306 blurProfile = textureProvider->createTexture(texDesc, SkBudgeted::kYes,
profile.get(), 0); |
282 if (blurProfile) { | 307 if (blurProfile) { |
283 textureProvider->assignUniqueKeyToTexture(key, blurProfile); | 308 textureProvider->assignUniqueKeyToTexture(key, blurProfile); |
284 } | 309 } |
285 } | 310 } |
286 | 311 |
287 return blurProfile; | 312 return blurProfile; |
288 } | 313 } |
289 | 314 |
290 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor); | 315 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor); |
291 | 316 |
292 sk_sp<GrFragmentProcessor> GrCircleBlurFragmentProcessor::TestCreate(GrProcessor
TestData* d) { | 317 sk_sp<GrFragmentProcessor> GrCircleBlurFragmentProcessor::TestCreate(GrProcessor
TestData* d) { |
293 SkScalar wh = d->fRandom->nextRangeScalar(100.f, 1000.f); | 318 SkScalar wh = d->fRandom->nextRangeScalar(100.f, 1000.f); |
294 SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f); | 319 SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f); |
295 SkRect circle = SkRect::MakeWH(wh, wh); | 320 SkRect circle = SkRect::MakeWH(wh, wh); |
296 return GrCircleBlurFragmentProcessor::Make(d->fContext->textureProvider(), c
ircle, sigma); | 321 return GrCircleBlurFragmentProcessor::Make(d->fContext->textureProvider(), c
ircle, sigma); |
297 } | 322 } |
298 | 323 |
299 #endif | 324 #endif |
OLD | NEW |