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