• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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