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