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