Index: src/effects/GrCircleBlurFragmentProcessor.cpp |
diff --git a/src/effects/GrCircleBlurFragmentProcessor.cpp b/src/effects/GrCircleBlurFragmentProcessor.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c5aafdce0eb94e263b349841d6b7454cec38b7e2 |
--- /dev/null |
+++ b/src/effects/GrCircleBlurFragmentProcessor.cpp |
@@ -0,0 +1,259 @@ |
+ |
+/* |
+ * Copyright 2015 Google Inc. |
+ * |
+ * Use of this source code is governed by a BSD-style license that can be |
+ * found in the LICENSE file. |
+ */ |
+ |
+#include "GrCircleBlurFragmentProcessor.h" |
+ |
+#if SK_SUPPORT_GPU |
+ |
+#include "GrContext.h" |
+#include "GrTextureProvider.h" |
+ |
+#include "gl/GrGLFragmentProcessor.h" |
+#include "gl/builders/GrGLProgramBuilder.h" |
+ |
+class GrGLCircleBlurFragmentProcessor : public GrGLFragmentProcessor { |
+public: |
+ GrGLCircleBlurFragmentProcessor(const GrProcessor&) {} |
+ void emitCode(EmitArgs&) override; |
+ |
+protected: |
+ void onSetData(const GrGLProgramDataManager&, const GrProcessor&) override; |
+ |
+private: |
+ GrGLProgramDataManager::UniformHandle fDataUniform; |
+ |
+ typedef GrGLFragmentProcessor INHERITED; |
+}; |
+ |
+void GrGLCircleBlurFragmentProcessor::emitCode(EmitArgs& args) { |
+ |
+ const char *dataName; |
+ |
+ // The data is formatted as: |
+ // x,y - the center of the circle |
+ // z - the distance at which the intensity starts falling off (e.g., the start of the table) |
+ // w - the size of the profile texture |
+ fDataUniform = args.fBuilder->addUniform(GrGLProgramBuilder::kFragment_Visibility, |
+ kVec4f_GrSLType, |
+ kDefault_GrSLPrecision, |
+ "data", |
+ &dataName); |
+ |
+ GrGLFragmentBuilder* fsBuilder = args.fBuilder->getFragmentShaderBuilder(); |
+ const char *fragmentPos = fsBuilder->fragmentPosition(); |
+ |
+ if (args.fInputColor) { |
+ fsBuilder->codeAppendf("vec4 src=%s;", args.fInputColor); |
+ } else { |
+ fsBuilder->codeAppendf("vec4 src=vec4(1);"); |
+ } |
+ |
+ fsBuilder->codeAppendf("vec2 vec = %s.xy - %s.xy;", fragmentPos, dataName); |
+ fsBuilder->codeAppendf("float dist = (length(vec) - %s.z + 0.5) / %s.w;", dataName, dataName); |
+ |
+ fsBuilder->codeAppendf("float intensity = "); |
+ fsBuilder->appendTextureLookup(args.fSamplers[0], "vec2(dist, 0.5)"); |
+ fsBuilder->codeAppend(".a;"); |
+ |
+ fsBuilder->codeAppendf("%s = src * intensity;\n", args.fOutputColor ); |
+} |
+ |
+void GrGLCircleBlurFragmentProcessor::onSetData(const GrGLProgramDataManager& pdman, |
+ const GrProcessor& proc) { |
+ const GrCircleBlurFragmentProcessor& cbfp = proc.cast<GrCircleBlurFragmentProcessor>(); |
+ const SkRect& circle = cbfp.circle(); |
+ |
+ // The data is formatted as: |
+ // x,y - the center of the circle |
+ // z - the distance at which the intensity starts falling off (e.g., the start of the table) |
+ // w - the size of the profile texture |
+ pdman.set4f(fDataUniform, circle.centerX(), circle.centerY(), cbfp.offset(), |
+ SkIntToScalar(cbfp.profileSize())); |
+} |
+ |
+/////////////////////////////////////////////////////////////////////////////// |
+ |
+GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(const SkRect& circle, |
+ float sigma, |
+ float offset, |
+ GrTexture* blurProfile) |
+ : fCircle(circle) |
+ , fSigma(sigma) |
+ , fOffset(offset) |
+ , fBlurProfileAccess(blurProfile, GrTextureParams::kBilerp_FilterMode) { |
+ this->initClassID<GrCircleBlurFragmentProcessor>(); |
+ this->addTextureAccess(&fBlurProfileAccess); |
+ this->setWillReadFragmentPosition(); |
+} |
+ |
+GrGLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLInstance() const { |
+ return new GrGLCircleBlurFragmentProcessor(*this); |
+} |
+ |
+void GrCircleBlurFragmentProcessor::onGetGLProcessorKey(const GrGLSLCaps& caps, |
+ GrProcessorKeyBuilder* b) const { |
+ GrGLCircleBlurFragmentProcessor::GenKey(*this, caps, b); |
+} |
+ |
+void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput* inout) const { |
+ inout->mulByUnknownSingleComponent(); |
+} |
+ |
+// Evaluate an AA circle function centered at the origin with 'radius' at (x,y) |
+static inline float disk(float x, float y, float radius) { |
+ float distSq = x*x + y*y; |
+ if (distSq <= (radius-0.5f)*(radius-0.5f)) { |
+ return 1.0f; |
+ } else if (distSq >= (radius+0.5f)*(radius+0.5f)) { |
+ return 0.0f; |
+ } else { |
+ float ramp = radius + 0.5f - sqrt(distSq); |
+ SkASSERT(ramp >= 0.0f && ramp <= 1.0f); |
+ return ramp; |
+ } |
+} |
+ |
+// Create the top half of an even-sized Gaussian kernel |
+static void make_half_kernel(float* kernel, int kernelWH, float sigma) { |
+ SkASSERT(!(kernelWH & 1)); |
+ |
+ const float kernelOff = (kernelWH-1)/2.0f; |
+ |
+ float b = 1.0f / (2.0f * sigma * sigma); |
+ // omit the scale term since we're just going to renormalize |
+ |
+ float tot = 0.0f; |
+ for (int y = 0; y < kernelWH/2; ++y) { |
+ for (int x = 0; x < kernelWH/2; ++x) { |
+ // TODO: use a cheap approximation of the 2D Guassian? |
+ float x2 = (x-kernelOff) * (x-kernelOff); |
+ float y2 = (y-kernelOff) * (y-kernelOff); |
+ // The kernel is symmetric so only compute it once for both sides |
+ kernel[y*kernelWH+(kernelWH-x-1)] = kernel[y*kernelWH+x] = exp(-(x2 + y2) * b); |
+ tot += 2.0f * kernel[y*kernelWH+x]; |
+ } |
+ } |
+ // Still normalize the half kernel to 1.0 (rather than 0.5) so we don't |
+ // have to scale by 2.0 after convolution. |
+ for (int y = 0; y < kernelWH/2; ++y) { |
+ for (int x = 0; x < kernelWH; ++x) { |
+ kernel[y*kernelWH+x] /= tot; |
+ } |
+ } |
+} |
+ |
+// Apply the half-kernel at 't' away from the center of the circle |
+static uint8_t eval_at(float t, float halfWidth, float* halfKernel, int kernelWH) { |
+ SkASSERT(!(kernelWH & 1)); |
+ |
+ const float kernelOff = (kernelWH-1)/2.0f; |
+ |
+ float acc = 0; |
+ |
+ for (int y = 0; y < kernelWH/2; ++y) { |
+ if (kernelOff-y > halfWidth+0.5f) { |
+ // All disk() samples in this row will be 0.0f |
+ continue; |
+ } |
+ |
+ for (int x = 0; x < kernelWH; ++x) { |
+ float image = disk(t - kernelOff + x, -kernelOff + y, halfWidth); |
+ float kernel = halfKernel[y*kernelWH+x]; |
+ acc += kernel * image; |
+ } |
+ } |
+ |
+ return SkUnitScalarClampToByte(acc); |
+} |
+ |
+static inline void compute_profile_offset_and_size(float halfWH, float sigma, |
+ float* offset, int* size) { |
+ |
+ if (3*sigma <= halfWH) { |
+ // The circle is bigger than the Gaussian. In this case we know the interior of the |
+ // blurred circle is solid. |
+ *offset = halfWH - 3 * sigma; // This location maps to 0.5f in the weights texture. |
+ // It should always be 255. |
+ *size = SkScalarCeilToInt(6*sigma); |
+ } else { |
+ // The Gaussian is bigger than the circle. |
+ *offset = 0.0f; |
+ *size = SkScalarCeilToInt(halfWH + 3*sigma); |
+ } |
+} |
+ |
+static uint8_t* create_profile(float halfWH, float sigma) { |
+ |
+ int kernelWH = SkScalarCeilToInt(6.0f*sigma); |
+ kernelWH = (kernelWH + 1) & ~1; // make it the next even number up |
+ |
+ SkAutoTArray<float> halfKernel(kernelWH*kernelWH/2); |
+ |
+ make_half_kernel(halfKernel.get(), kernelWH, sigma); |
+ |
+ float offset; |
+ int numSteps; |
+ |
+ compute_profile_offset_and_size(halfWH, sigma, &offset, &numSteps); |
+ |
+ uint8_t* weights = new uint8_t[numSteps]; |
+ for (int i = 0; i < numSteps; ++i) { |
+ weights[i] = eval_at(offset+i, halfWH, halfKernel.get(), kernelWH); |
+ } |
+ |
+ return weights; |
+} |
+ |
+GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture( |
+ GrTextureProvider* textureProvider, |
+ const SkRect& circle, |
+ float sigma, |
+ float* offset) { |
+ float halfWH = circle.width() / 2.0f; |
+ |
+ int size; |
+ compute_profile_offset_and_size(halfWH, sigma, offset, &size); |
+ |
+ GrSurfaceDesc texDesc; |
+ texDesc.fWidth = size; |
+ texDesc.fHeight = 1; |
+ texDesc.fConfig = kAlpha_8_GrPixelConfig; |
+ |
+ static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain(); |
+ GrUniqueKey key; |
+ GrUniqueKey::Builder builder(&key, kDomain, 2); |
+ // The profile curve varies with both the sigma of the Gaussian and the size of the |
+ // disk. Quantizing to 16.16 should be close enough though. |
+ builder[0] = SkScalarToFixed(sigma); |
+ builder[1] = SkScalarToFixed(halfWH); |
+ builder.finish(); |
+ |
+ GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key); |
+ |
+ if (!blurProfile) { |
+ SkAutoTDeleteArray<uint8_t> profile(create_profile(halfWH, sigma)); |
+ |
+ blurProfile = textureProvider->createTexture(texDesc, true, profile.get(), 0); |
+ if (blurProfile) { |
+ textureProvider->assignUniqueKeyToTexture(key, blurProfile); |
+ } |
+ } |
+ |
+ return blurProfile; |
+} |
+ |
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor); |
+ |
+const GrFragmentProcessor* GrCircleBlurFragmentProcessor::TestCreate(GrProcessorTestData* d) { |
+ SkScalar wh = d->fRandom->nextRangeScalar(100.f, 1000.f); |
+ SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f); |
+ SkRect circle = SkRect::MakeWH(wh, wh); |
+ return GrCircleBlurFragmentProcessor::Create(d->fContext->textureProvider(), circle, sigma); |
+} |
+ |
+#endif |