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