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