1/*
2 * Copyright 2012 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 "src/gpu/effects/GrGaussianConvolutionFragmentProcessor.h"
9
10#include "src/gpu/GrTexture.h"
11#include "src/gpu/GrTextureProxy.h"
12#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
13#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
14#include "src/gpu/glsl/GrGLSLProgramDataManager.h"
15#include "src/gpu/glsl/GrGLSLUniformHandler.h"
16
17// For brevity
18using UniformHandle = GrGLSLProgramDataManager::UniformHandle;
19using Direction = GrGaussianConvolutionFragmentProcessor::Direction;
20
21class GrGLConvolutionEffect : public GrGLSLFragmentProcessor {
22public:
23 void emitCode(EmitArgs&) override;
24
25 static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*);
26
27protected:
28 void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override;
29
30private:
31 UniformHandle fKernelUni;
32 UniformHandle fImageIncrementUni;
33 UniformHandle fBoundsUni;
34
35 typedef GrGLSLFragmentProcessor INHERITED;
36};
37
38void GrGLConvolutionEffect::emitCode(EmitArgs& args) {
39 const GrGaussianConvolutionFragmentProcessor& ce =
40 args.fFp.cast<GrGaussianConvolutionFragmentProcessor>();
41
42 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
43 fImageIncrementUni = uniformHandler->addUniform(&ce, kFragment_GrShaderFlag, kHalf2_GrSLType,
44 "ImageIncrement");
45 if (ce.useBounds()) {
46 fBoundsUni = uniformHandler->addUniform(&ce, kFragment_GrShaderFlag, kHalf2_GrSLType,
47 "Bounds");
48 }
49
50 int width = ce.width();
51
52 int arrayCount = (width + 3) / 4;
53 SkASSERT(4 * arrayCount >= width);
54
55 fKernelUni = uniformHandler->addUniformArray(&ce, kFragment_GrShaderFlag, kHalf4_GrSLType,
56 "Kernel", arrayCount);
57
58 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
59 SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
60
61 fragBuilder->codeAppendf("%s = half4(0, 0, 0, 0);", args.fOutputColor);
62
63 const GrShaderVar& kernel = uniformHandler->getUniformVariable(fKernelUni);
64 const char* imgInc = uniformHandler->getUniformCStr(fImageIncrementUni);
65
66 fragBuilder->codeAppendf("float2 coord = %s - %d.0 * %s;", coords2D.c_str(), ce.radius(), imgInc);
67 fragBuilder->codeAppend("float2 coordSampled = half2(0, 0);");
68
69 // Manually unroll loop because some drivers don't; yields 20-30% speedup.
70 const char* kVecSuffix[4] = {".x", ".y", ".z", ".w"};
71 for (int i = 0; i < width; i++) {
72 SkString index;
73 SkString kernelIndex;
74 index.appendS32(i / 4);
75 kernel.appendArrayAccess(index.c_str(), &kernelIndex);
76 kernelIndex.append(kVecSuffix[i & 0x3]);
77
78 fragBuilder->codeAppend("coordSampled = coord;");
79 if (ce.useBounds()) {
80 // We used to compute a bool indicating whether we're in bounds or not, cast it to a
81 // float, and then mul weight*texture_sample by the float. However, the Adreno 430 seems
82 // to have a bug that caused corruption.
83 const char* bounds = uniformHandler->getUniformCStr(fBoundsUni);
84 const char* component = ce.direction() == Direction::kY ? "y" : "x";
85
86 switch (ce.mode()) {
87 case GrTextureDomain::kClamp_Mode: {
88 fragBuilder->codeAppendf("coordSampled.%s = clamp(coord.%s, %s.x, %s.y);\n",
89 component, component, bounds, bounds);
90 break;
91 }
92 // Deferring implementing kMirrorRepeat until we use DomainEffects as
93 // child processors. Fallback to Repeat.
94 case GrTextureDomain::kMirrorRepeat_Mode:
95 case GrTextureDomain::kRepeat_Mode: {
96 fragBuilder->codeAppendf("coordSampled.%s = "
97 "mod(coord.%s - %s.x, %s.y - %s.x) + %s.x;\n",
98 component, component, bounds, bounds, bounds, bounds);
99 break;
100 }
101 case GrTextureDomain::kDecal_Mode: {
102 fragBuilder->codeAppendf("if (coord.%s >= %s.x && coord.%s <= %s.y) {",
103 component, bounds, component, bounds);
104 break;
105 }
106 default: {
107 SK_ABORT("Unsupported operation.");
108 }
109 }
110 }
111 fragBuilder->codeAppendf("%s += ", args.fOutputColor);
112 fragBuilder->appendTextureLookup(args.fTexSamplers[0], "coordSampled");
113 fragBuilder->codeAppendf(" * %s;\n", kernelIndex.c_str());
114 if (GrTextureDomain::kDecal_Mode == ce.mode()) {
115 fragBuilder->codeAppend("}");
116 }
117 fragBuilder->codeAppendf("coord += %s;\n", imgInc);
118 }
119 fragBuilder->codeAppendf("%s *= %s;\n", args.fOutputColor, args.fInputColor);
120}
121
122void GrGLConvolutionEffect::onSetData(const GrGLSLProgramDataManager& pdman,
123 const GrFragmentProcessor& processor) {
124 const GrGaussianConvolutionFragmentProcessor& conv =
125 processor.cast<GrGaussianConvolutionFragmentProcessor>();
126 const auto& view = conv.textureSampler(0).view();
127 GrSurfaceProxy* proxy = view.proxy();
128 GrTexture& texture = *proxy->peekTexture();
129
130 float imageIncrement[2] = {0};
131 float ySign = view.origin() != kTopLeft_GrSurfaceOrigin ? 1.0f : -1.0f;
132 switch (conv.direction()) {
133 case Direction::kX:
134 imageIncrement[0] = 1.0f / texture.width();
135 break;
136 case Direction::kY:
137 imageIncrement[1] = ySign / texture.height();
138 break;
139 default:
140 SK_ABORT("Unknown filter direction.");
141 }
142 pdman.set2fv(fImageIncrementUni, 1, imageIncrement);
143 if (conv.useBounds()) {
144 float bounds[2] = {0};
145 bounds[0] = conv.bounds()[0];
146 bounds[1] = conv.bounds()[1];
147 if (GrTextureDomain::kClamp_Mode == conv.mode()) {
148 bounds[0] += SK_ScalarHalf;
149 bounds[1] -= SK_ScalarHalf;
150 }
151 if (Direction::kX == conv.direction()) {
152 SkScalar inv = SkScalarInvert(SkIntToScalar(texture.width()));
153 bounds[0] *= inv;
154 bounds[1] *= inv;
155 } else {
156 SkScalar inv = SkScalarInvert(SkIntToScalar(texture.height()));
157 if (view.origin() != kTopLeft_GrSurfaceOrigin) {
158 float tmp = bounds[0];
159 bounds[0] = 1.0f - (inv * bounds[1]);
160 bounds[1] = 1.0f - (inv * tmp);
161 } else {
162 bounds[0] *= inv;
163 bounds[1] *= inv;
164 }
165 }
166
167 SkASSERT(bounds[0] <= bounds[1]);
168 pdman.set2f(fBoundsUni, bounds[0], bounds[1]);
169 }
170 int width = conv.width();
171
172 int arrayCount = (width + 3) / 4;
173 SkASSERT(4 * arrayCount >= width);
174 pdman.set4fv(fKernelUni, arrayCount, conv.kernel());
175}
176
177void GrGLConvolutionEffect::GenKey(const GrProcessor& processor, const GrShaderCaps&,
178 GrProcessorKeyBuilder* b) {
179 const GrGaussianConvolutionFragmentProcessor& conv =
180 processor.cast<GrGaussianConvolutionFragmentProcessor>();
181 uint32_t key = conv.radius();
182 key <<= 3;
183 if (conv.useBounds()) {
184 key |= Direction::kY == conv.direction() ? 0x4 : 0x0;
185 }
186 key |= static_cast<uint32_t>(conv.mode());
187 b->add32(key);
188}
189
190///////////////////////////////////////////////////////////////////////////////
191static void fill_in_1D_gaussian_kernel(float* kernel, int width, float gaussianSigma, int radius) {
192 const float twoSigmaSqrd = 2.0f * gaussianSigma * gaussianSigma;
193 if (SkScalarNearlyZero(twoSigmaSqrd, SK_ScalarNearlyZero)) {
194 for (int i = 0; i < width; ++i) {
195 kernel[i] = 0.0f;
196 }
197 return;
198 }
199
200 const float denom = 1.0f / twoSigmaSqrd;
201
202 float sum = 0.0f;
203 for (int i = 0; i < width; ++i) {
204 float x = static_cast<float>(i - radius);
205 // Note that the constant term (1/(sqrt(2*pi*sigma^2)) of the Gaussian
206 // is dropped here, since we renormalize the kernel below.
207 kernel[i] = sk_float_exp(-x * x * denom);
208 sum += kernel[i];
209 }
210 // Normalize the kernel
211 float scale = 1.0f / sum;
212 for (int i = 0; i < width; ++i) {
213 kernel[i] *= scale;
214 }
215}
216
217GrGaussianConvolutionFragmentProcessor::GrGaussianConvolutionFragmentProcessor(
218 GrSurfaceProxyView view,
219 SkAlphaType alphaType,
220 Direction direction,
221 int radius,
222 float gaussianSigma,
223 GrTextureDomain::Mode mode,
224 int bounds[2])
225 : INHERITED(kGrGaussianConvolutionFragmentProcessor_ClassID,
226 ModulateForSamplerOptFlags(alphaType, mode == GrTextureDomain::kDecal_Mode))
227 , fCoordTransform(view.proxy(), view.origin())
228 , fTextureSampler(std::move(view))
229 , fRadius(radius)
230 , fDirection(direction)
231 , fMode(mode) {
232 this->addCoordTransform(&fCoordTransform);
233 this->setTextureSamplerCnt(1);
234 SkASSERT(radius <= kMaxKernelRadius);
235
236 fill_in_1D_gaussian_kernel(fKernel, this->width(), gaussianSigma, this->radius());
237 // SkGpuBlurUtils is not as aggressive as it once was about avoiding domains. So we check
238 // here if we can omit the domain. TODO: remove this when this effect uses a child to
239 // sample the texture.
240 auto samplerProxy = fTextureSampler.proxy();
241 if (!samplerProxy->isFullyLazy()) {
242 int wh = (fDirection == Direction::kX) ? samplerProxy->backingStoreDimensions().width()
243 : samplerProxy->backingStoreDimensions().height();
244 if (bounds[0] == 0 && bounds[1] == wh) {
245 bool useSampler = false;
246 GrSamplerState::WrapMode samplerMode = GrSamplerState::WrapMode::kClamp;
247 switch (fMode) {
248 case GrTextureDomain::kClamp_Mode:
249 case GrTextureDomain::kIgnore_Mode:
250 useSampler = true;
251 break;
252 case GrTextureDomain::kRepeat_Mode:
253 useSampler = true;
254 samplerMode = GrSamplerState::WrapMode::kRepeat;
255 break;
256 case GrTextureDomain::kMirrorRepeat_Mode:
257 useSampler = true;
258 samplerMode = GrSamplerState::WrapMode::kMirrorRepeat;
259 break;
260 case GrTextureDomain::kDecal_Mode:
261 // Not sure if we support this in HW without having GrCaps here.
262 // Just wait until we replace this with GrTextureEffect.
263 break;
264 }
265 if (useSampler) {
266 fMode = GrTextureDomain::kIgnore_Mode;
267 if (fDirection == Direction::kX) {
268 fTextureSampler.samplerState().setWrapModeX(samplerMode);
269 } else {
270 fTextureSampler.samplerState().setWrapModeY(samplerMode);
271 }
272 }
273 }
274 }
275 memcpy(fBounds, bounds, sizeof(fBounds));
276}
277
278GrGaussianConvolutionFragmentProcessor::GrGaussianConvolutionFragmentProcessor(
279 const GrGaussianConvolutionFragmentProcessor& that)
280 : INHERITED(kGrGaussianConvolutionFragmentProcessor_ClassID, that.optimizationFlags())
281 , fCoordTransform(that.fCoordTransform)
282 , fTextureSampler(that.fTextureSampler)
283 , fRadius(that.fRadius)
284 , fDirection(that.fDirection)
285 , fMode(that.fMode) {
286 this->addCoordTransform(&fCoordTransform);
287 this->setTextureSamplerCnt(1);
288 memcpy(fKernel, that.fKernel, that.width() * sizeof(float));
289 memcpy(fBounds, that.fBounds, sizeof(fBounds));
290}
291
292void GrGaussianConvolutionFragmentProcessor::onGetGLSLProcessorKey(const GrShaderCaps& caps,
293 GrProcessorKeyBuilder* b) const {
294 GrGLConvolutionEffect::GenKey(*this, caps, b);
295}
296
297GrGLSLFragmentProcessor* GrGaussianConvolutionFragmentProcessor::onCreateGLSLInstance() const {
298 return new GrGLConvolutionEffect;
299}
300
301bool GrGaussianConvolutionFragmentProcessor::onIsEqual(const GrFragmentProcessor& sBase) const {
302 const GrGaussianConvolutionFragmentProcessor& s =
303 sBase.cast<GrGaussianConvolutionFragmentProcessor>();
304 return (this->radius() == s.radius() && this->direction() == s.direction() &&
305 this->mode() == s.mode() &&
306 0 == memcmp(fBounds, s.fBounds, sizeof(fBounds)) &&
307 0 == memcmp(fKernel, s.fKernel, this->width() * sizeof(float)));
308}
309
310///////////////////////////////////////////////////////////////////////////////
311
312GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrGaussianConvolutionFragmentProcessor);
313
314#if GR_TEST_UTILS
315std::unique_ptr<GrFragmentProcessor> GrGaussianConvolutionFragmentProcessor::TestCreate(
316 GrProcessorTestData* d) {
317 auto [view, ct, at] = d->randomView();
318
319 int bounds[2];
320 int modeIdx = d->fRandom->nextRangeU(0, GrTextureDomain::kModeCount-1);
321
322 Direction dir;
323 if (d->fRandom->nextBool()) {
324 dir = Direction::kX;
325 bounds[0] = d->fRandom->nextRangeU(0, view.width()-2);
326 bounds[1] = d->fRandom->nextRangeU(bounds[0]+1, view.width()-1);
327 } else {
328 dir = Direction::kY;
329 bounds[0] = d->fRandom->nextRangeU(0, view.height()-2);
330 bounds[1] = d->fRandom->nextRangeU(bounds[0]+1, view.height()-1);
331 }
332
333 int radius = d->fRandom->nextRangeU(1, kMaxKernelRadius);
334 float sigma = radius / 3.f;
335
336 return GrGaussianConvolutionFragmentProcessor::Make(std::move(view), at, dir, radius, sigma,
337 static_cast<GrTextureDomain::Mode>(modeIdx),
338 bounds);
339}
340#endif
341