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/core/SkGpuBlurUtils.h"
11 #include "src/gpu/GrTexture.h"
12 #include "src/gpu/GrTextureProxy.h"
13 #include "src/gpu/KeyBuilder.h"
14 #include "src/gpu/effects/GrTextureEffect.h"
15 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
16 #include "src/gpu/glsl/GrGLSLProgramDataManager.h"
17 #include "src/gpu/glsl/GrGLSLUniformHandler.h"
18 #include "src/sksl/dsl/priv/DSLFPs.h"
19
20 // For brevity
21 using UniformHandle = GrGLSLProgramDataManager::UniformHandle;
22 using Direction = GrGaussianConvolutionFragmentProcessor::Direction;
23
24 class GrGaussianConvolutionFragmentProcessor::Impl : public ProgramImpl {
25 public:
26 void emitCode(EmitArgs&) override;
27
28 private:
29 void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override;
30
31 UniformHandle fKernelUni;
32 UniformHandle fOffsetsUni;
33 UniformHandle fKernelWidthUni;
34 UniformHandle fIncrementUni;
35 };
36
37 enum class LoopType {
38 kUnrolled,
39 kFixedLength,
40 kVariableLength,
41 };
42
loop_type(const GrShaderCaps & caps)43 static LoopType loop_type(const GrShaderCaps& caps) {
44 // This checks that bitwise integer operations and array indexing by non-consts are allowed.
45 if (caps.generation() < SkSL::GLSLGeneration::k130) {
46 return LoopType::kUnrolled;
47 }
48 // If we're in reduced shader mode and we can have a loop then use a uniform to limit the
49 // number of iterations so we don't need a code variation for each width.
50 return caps.reducedShaderMode() ? LoopType::kVariableLength : LoopType::kFixedLength;
51 }
52
emitCode(EmitArgs & args)53 void GrGaussianConvolutionFragmentProcessor::Impl::emitCode(EmitArgs& args) {
54 const GrGaussianConvolutionFragmentProcessor& ce =
55 args.fFp.cast<GrGaussianConvolutionFragmentProcessor>();
56
57 using namespace SkSL::dsl;
58 StartFragmentProcessor(this, &args);
59 GlobalVar increment(kUniform_Modifier, kHalf2_Type, "Increment");
60 Declare(increment);
61 fIncrementUni = VarUniformHandle(increment);
62
63 int width = SkGpuBlurUtils::LinearKernelWidth(ce.fRadius);
64
65 LoopType loopType = loop_type(*args.fShaderCaps);
66
67 int arrayCount;
68 if (loopType == LoopType::kVariableLength) {
69 // Size the kernel uniform for the maximum width.
70 arrayCount = (SkGpuBlurUtils::LinearKernelWidth(kMaxKernelRadius) + 3) / 4;
71 } else {
72 arrayCount = (width + 3) / 4;
73 SkASSERT(4 * arrayCount >= width);
74 }
75
76 GlobalVar kernel(kUniform_Modifier, Array(kHalf4_Type, arrayCount), "Kernel");
77 Declare(kernel);
78 fKernelUni = VarUniformHandle(kernel);
79
80
81 GlobalVar offsets(kUniform_Modifier, Array(kHalf4_Type, arrayCount), "Offsets");
82 Declare(offsets);
83 fOffsetsUni = VarUniformHandle(offsets);
84
85 Var color(kHalf4_Type, "color", Half4(0));
86 Declare(color);
87
88 Var coord(kFloat2_Type, "coord", sk_SampleCoord());
89 Declare(coord);
90
91 switch (loopType) {
92 case LoopType::kUnrolled:
93 for (int i = 0; i < width; i++) {
94 color += SampleChild(/*index=*/0, coord + offsets[i / 4][i & 3] * increment) *
95 kernel[i / 4][i & 0x3];
96 }
97 break;
98 case LoopType::kFixedLength: {
99 Var i(kInt_Type, "i", 0);
100 For(Declare(i), i < width, i++,
101 color += SampleChild(/*index=*/0, coord + offsets[i / 4][i & 3] * increment) *
102 kernel[i / 4][i & 0x3]);
103 break;
104 }
105 case LoopType::kVariableLength: {
106 GlobalVar kernelWidth(kUniform_Modifier, kInt_Type, "kernelWidth");
107 Declare(kernelWidth);
108 fKernelWidthUni = VarUniformHandle(kernelWidth);
109 Var i(kInt_Type, "i", 0);
110 For(Declare(i), i < kernelWidth, i++,
111 color += SampleChild(/*index=*/0, coord + offsets[i / 4][i & 3] * increment) *
112 kernel[i / 4][i & 0x3]);
113 break;
114 }
115 }
116
117 Return(color);
118 EndFragmentProcessor();
119 }
120
onSetData(const GrGLSLProgramDataManager & pdman,const GrFragmentProcessor & processor)121 void GrGaussianConvolutionFragmentProcessor::Impl::onSetData(const GrGLSLProgramDataManager& pdman,
122 const GrFragmentProcessor& processor) {
123 const auto& conv = processor.cast<GrGaussianConvolutionFragmentProcessor>();
124
125 float increment[2] = {};
126 increment[static_cast<int>(conv.fDirection)] = 1;
127 pdman.set2fv(fIncrementUni, 1, increment);
128
129 int width = SkGpuBlurUtils::LinearKernelWidth(conv.fRadius);
130 int arrayCount = (width + 3)/4;
131 SkDEBUGCODE(size_t arraySize = 4*arrayCount;)
132 SkASSERT(arraySize >= static_cast<size_t>(width));
133 SkASSERT(arraySize <= SK_ARRAY_COUNT(GrGaussianConvolutionFragmentProcessor::fKernel));
134 pdman.set4fv(fKernelUni, arrayCount, conv.fKernel);
135 pdman.set4fv(fOffsetsUni, arrayCount, conv.fOffsets);
136 if (fKernelWidthUni.isValid()) {
137 pdman.set1i(fKernelWidthUni, width);
138 }
139 }
140
141 ///////////////////////////////////////////////////////////////////////////////
142
Make(GrSurfaceProxyView view,SkAlphaType alphaType,Direction dir,int halfWidth,float gaussianSigma,GrSamplerState::WrapMode wm,const SkIRect & subset,const SkIRect * pixelDomain,const GrCaps & caps)143 std::unique_ptr<GrFragmentProcessor> GrGaussianConvolutionFragmentProcessor::Make(
144 GrSurfaceProxyView view,
145 SkAlphaType alphaType,
146 Direction dir,
147 int halfWidth,
148 float gaussianSigma,
149 GrSamplerState::WrapMode wm,
150 const SkIRect& subset,
151 const SkIRect* pixelDomain,
152 const GrCaps& caps) {
153 std::unique_ptr<GrFragmentProcessor> child;
154 bool is_zero_sigma = SkGpuBlurUtils::IsEffectivelyZeroSigma(gaussianSigma);
155 // We should sample as nearest if there will be no shader to preserve existing behaviour, but
156 // the linear blur requires a linear sample.
157 GrSamplerState::Filter filter = is_zero_sigma ?
158 GrSamplerState::Filter::kNearest : GrSamplerState::Filter::kLinear;
159 GrSamplerState sampler(wm, filter);
160 if (is_zero_sigma) {
161 halfWidth = 0;
162 }
163 // It's pretty common to blur a subset of an input texture. In reduced shader mode we always
164 // apply the wrap mode in the shader.
165 bool alwaysUseShaderTileMode = caps.reducedShaderMode();
166 if (pixelDomain && !alwaysUseShaderTileMode) {
167 // Inset because we expect to be invoked at pixel centers.
168 SkRect domain = SkRect::Make(*pixelDomain).makeInset(0.5, 0.5f);
169 switch (dir) {
170 case Direction::kX: domain.outset(halfWidth, 0); break;
171 case Direction::kY: domain.outset(0, halfWidth); break;
172 }
173 child = GrTextureEffect::MakeSubset(std::move(view),
174 alphaType,
175 SkMatrix::I(),
176 sampler,
177 SkRect::Make(subset),
178 domain,
179 caps,
180 GrTextureEffect::kDefaultBorder);
181 } else {
182 child = GrTextureEffect::MakeSubset(std::move(view),
183 alphaType,
184 SkMatrix::I(),
185 sampler,
186 SkRect::Make(subset),
187 caps,
188 GrTextureEffect::kDefaultBorder,
189 alwaysUseShaderTileMode);
190 }
191
192 if (is_zero_sigma) {
193 return child;
194 }
195 return std::unique_ptr<GrFragmentProcessor>(new GrGaussianConvolutionFragmentProcessor(
196 std::move(child), dir, halfWidth, gaussianSigma));
197 }
198
GrGaussianConvolutionFragmentProcessor(std::unique_ptr<GrFragmentProcessor> child,Direction direction,int radius,float gaussianSigma)199 GrGaussianConvolutionFragmentProcessor::GrGaussianConvolutionFragmentProcessor(
200 std::unique_ptr<GrFragmentProcessor> child,
201 Direction direction,
202 int radius,
203 float gaussianSigma)
204 : INHERITED(kGrGaussianConvolutionFragmentProcessor_ClassID,
205 ProcessorOptimizationFlags(child.get()))
206 , fRadius(radius)
207 , fDirection(direction) {
208 this->registerChild(std::move(child), SkSL::SampleUsage::Explicit());
209 SkASSERT(radius <= kMaxKernelRadius);
210 SkGpuBlurUtils::Compute1DLinearGaussianKernel(fKernel, fOffsets, gaussianSigma, fRadius);
211 this->setUsesSampleCoordsDirectly();
212 }
213
GrGaussianConvolutionFragmentProcessor(const GrGaussianConvolutionFragmentProcessor & that)214 GrGaussianConvolutionFragmentProcessor::GrGaussianConvolutionFragmentProcessor(
215 const GrGaussianConvolutionFragmentProcessor& that)
216 : INHERITED(that)
217 , fRadius(that.fRadius)
218 , fDirection(that.fDirection) {
219 memcpy(fKernel, that.fKernel, SkGpuBlurUtils::LinearKernelWidth(fRadius) * sizeof(float));
220 memcpy(fOffsets, that.fOffsets, SkGpuBlurUtils::LinearKernelWidth(fRadius) * sizeof(float));
221 }
222
onAddToKey(const GrShaderCaps & shaderCaps,skgpu::KeyBuilder * b) const223 void GrGaussianConvolutionFragmentProcessor::onAddToKey(const GrShaderCaps& shaderCaps,
224 skgpu::KeyBuilder* b) const {
225 if (loop_type(shaderCaps) != LoopType::kVariableLength) {
226 b->add32(fRadius);
227 }
228 }
229
230 std::unique_ptr<GrFragmentProcessor::ProgramImpl>
onMakeProgramImpl() const231 GrGaussianConvolutionFragmentProcessor::onMakeProgramImpl() const {
232 return std::make_unique<Impl>();
233 }
234
onIsEqual(const GrFragmentProcessor & sBase) const235 bool GrGaussianConvolutionFragmentProcessor::onIsEqual(const GrFragmentProcessor& sBase) const {
236 const auto& that = sBase.cast<GrGaussianConvolutionFragmentProcessor>();
237 return fRadius == that.fRadius && fDirection == that.fDirection &&
238 std::equal(fKernel, fKernel + SkGpuBlurUtils::LinearKernelWidth(fRadius), that.fKernel) &&
239 std::equal(fOffsets, fOffsets + SkGpuBlurUtils::LinearKernelWidth(fRadius), that.fOffsets);
240 }
241
242 ///////////////////////////////////////////////////////////////////////////////
243
244 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrGaussianConvolutionFragmentProcessor);
245
246 #if GR_TEST_UTILS
TestCreate(GrProcessorTestData * d)247 std::unique_ptr<GrFragmentProcessor> GrGaussianConvolutionFragmentProcessor::TestCreate(
248 GrProcessorTestData* d) {
249 auto [view, ct, at] = d->randomView();
250
251 Direction dir = d->fRandom->nextBool() ? Direction::kY : Direction::kX;
252 SkIRect subset{
253 static_cast<int>(d->fRandom->nextRangeU(0, view.width() - 1)),
254 static_cast<int>(d->fRandom->nextRangeU(0, view.height() - 1)),
255 static_cast<int>(d->fRandom->nextRangeU(0, view.width() - 1)),
256 static_cast<int>(d->fRandom->nextRangeU(0, view.height() - 1)),
257 };
258 subset.sort();
259
260 auto wm = static_cast<GrSamplerState::WrapMode>(
261 d->fRandom->nextULessThan(GrSamplerState::kWrapModeCount));
262 int radius = d->fRandom->nextRangeU(1, kMaxKernelRadius);
263 float sigma = radius / 3.f;
264 SkIRect temp;
265 SkIRect* domain = nullptr;
266 if (d->fRandom->nextBool()) {
267 temp = {
268 static_cast<int>(d->fRandom->nextRangeU(0, view.width() - 1)),
269 static_cast<int>(d->fRandom->nextRangeU(0, view.height() - 1)),
270 static_cast<int>(d->fRandom->nextRangeU(0, view.width() - 1)),
271 static_cast<int>(d->fRandom->nextRangeU(0, view.height() - 1)),
272 };
273 temp.sort();
274 domain = &temp;
275 }
276
277 return GrGaussianConvolutionFragmentProcessor::Make(std::move(view), at, dir, radius, sigma, wm,
278 subset, domain, *d->caps());
279 }
280 #endif
281