• 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/ganesh/effects/GrGaussianConvolutionFragmentProcessor.h"
9 
10 #include "src/core/SkGpuBlurUtils.h"
11 #include "src/gpu/KeyBuilder.h"
12 #include "src/gpu/ganesh/GrCaps.h"
13 #include "src/gpu/ganesh/GrShaderCaps.h"
14 #include "src/gpu/ganesh/GrTexture.h"
15 #include "src/gpu/ganesh/GrTextureProxy.h"
16 #include "src/gpu/ganesh/effects/GrTextureEffect.h"
17 #include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h"
18 #include "src/gpu/ganesh/glsl/GrGLSLProgramDataManager.h"
19 #include "src/gpu/ganesh/glsl/GrGLSLUniformHandler.h"
20 
21 // For brevity
22 using UniformHandle = GrGLSLProgramDataManager::UniformHandle;
23 using Direction = GrGaussianConvolutionFragmentProcessor::Direction;
24 
25 class GrGaussianConvolutionFragmentProcessor::Impl : public ProgramImpl {
26 public:
27     void emitCode(EmitArgs&) override;
28 
29 private:
30     void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override;
31 
32     UniformHandle fOffsetsAndKernelUni;
33     UniformHandle fKernelWidthUni;
34     UniformHandle fIncrementUni;
35 };
36 
should_use_variable_length_loop(const GrShaderCaps & caps)37 static bool should_use_variable_length_loop(const GrShaderCaps& caps) {
38     // If we're in reduced-shader mode, and we can use variable length loops, then use a uniform to
39     // limit the number of iterations, so we don't need a code variation for each width.
40     return (caps.fGLSLGeneration >= SkSL::GLSLGeneration::k300es && caps.fReducedShaderMode);
41 }
42 
emitCode(EmitArgs & args)43 void GrGaussianConvolutionFragmentProcessor::Impl::emitCode(EmitArgs& args) {
44     const GrGaussianConvolutionFragmentProcessor& ce =
45             args.fFp.cast<GrGaussianConvolutionFragmentProcessor>();
46 
47     GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
48 
49     const char* increment;
50     fIncrementUni = uniformHandler->addUniform(&ce, kFragment_GrShaderFlag, SkSLType::kHalf2,
51                                                "Increment", &increment);
52 
53     // For variable-length loops, size the kernel uniform for the maximum width so we can reuse the
54     // same code for any kernel width.
55     bool variableLengthLoop = should_use_variable_length_loop(*args.fShaderCaps);
56     int width = SkGpuBlurUtils::LinearKernelWidth(ce.fRadius);
57     int arrayCount = variableLengthLoop ? SkGpuBlurUtils::LinearKernelWidth(kMaxKernelRadius)
58                                         : width;
59 
60     const char* offsetsAndKernel;
61     fOffsetsAndKernelUni = uniformHandler->addUniformArray(&ce, kFragment_GrShaderFlag,
62                                                            SkSLType::kHalf2, "OffsetsAndKernel",
63                                                            arrayCount, &offsetsAndKernel);
64 
65     GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
66 
67     // Create a "Smooth" helper function that computes one sample from the child using the kernel.
68     SkString smoothFuncName = fragBuilder->getMangledFunctionName("Smooth");
69     const GrShaderVar smoothArgs[] = {{args.fInputColor,  SkSLType::kHalf4},
70                                       {"coord",           SkSLType::kFloat2},
71                                       {"offsetAndKernel", SkSLType::kHalf2}};
72 
73     std::string childCoord = SkSL::String::printf("(coord + offsetAndKernel.x * %s)", increment);
74     SkString sample = this->invokeChild(/*childIndex=*/0, args, childCoord);
75 
76     std::string smoothBody = SkSL::String::printf("return %s * offsetAndKernel.y;", sample.c_str());
77     fragBuilder->emitFunction(SkSLType::kHalf4, smoothFuncName.c_str(),
78                               {smoothArgs, std::size(smoothArgs)},
79                               smoothBody.c_str());
80 
81     // Implement the main() function.
82     fragBuilder->codeAppendf("half4 color = half4(0);"
83                              "float2 coord = %s;", args.fSampleCoord);
84     if (variableLengthLoop) {
85         const char* kernelWidth;
86         fKernelWidthUni = uniformHandler->addUniform(&ce, kFragment_GrShaderFlag, SkSLType::kInt,
87                                                      "KernelWidth", &kernelWidth);
88         fragBuilder->codeAppendf("for (int i=0; i<%s; ++i) {"
89                                  "    color += %s(%s, coord, %s[i]);"
90                                  "}",
91                                  kernelWidth, smoothFuncName.c_str(),
92                                  args.fInputColor, offsetsAndKernel);
93     } else {
94         fragBuilder->codeAppendf("for (int i=0; i<%d; ++i) {"
95                                  "    color += %s(%s, coord, %s[i]);"
96                                  "}",
97                                  width, smoothFuncName.c_str(),
98                                  args.fInputColor, offsetsAndKernel);
99     }
100     fragBuilder->codeAppendf("return color;\n");
101 }
102 
onSetData(const GrGLSLProgramDataManager & pdman,const GrFragmentProcessor & processor)103 void GrGaussianConvolutionFragmentProcessor::Impl::onSetData(const GrGLSLProgramDataManager& pdman,
104                                                              const GrFragmentProcessor& processor) {
105     const auto& conv = processor.cast<GrGaussianConvolutionFragmentProcessor>();
106 
107     float increment[2] = {};
108     increment[static_cast<int>(conv.fDirection)] = 1;
109     pdman.set2fv(fIncrementUni, 1, increment);
110 
111     int kernelWidth = SkGpuBlurUtils::LinearKernelWidth(conv.fRadius);
112     SkASSERT(kernelWidth <= kMaxKernelWidth);
113     pdman.set2fv(fOffsetsAndKernelUni, kernelWidth, conv.fOffsetsAndKernel[0].ptr());
114     if (fKernelWidthUni.isValid()) {
115         pdman.set1i(fKernelWidthUni, kernelWidth);
116     }
117 }
118 
119 ///////////////////////////////////////////////////////////////////////////////
120 
Make(GrSurfaceProxyView view,SkAlphaType alphaType,Direction dir,int halfWidth,float gaussianSigma,GrSamplerState::WrapMode wm,const SkIRect & subset,const SkIRect * pixelDomain,const GrCaps & caps)121 std::unique_ptr<GrFragmentProcessor> GrGaussianConvolutionFragmentProcessor::Make(
122         GrSurfaceProxyView view,
123         SkAlphaType alphaType,
124         Direction dir,
125         int halfWidth,
126         float gaussianSigma,
127         GrSamplerState::WrapMode wm,
128         const SkIRect& subset,
129         const SkIRect* pixelDomain,
130         const GrCaps& caps) {
131     std::unique_ptr<GrFragmentProcessor> child;
132     bool is_zero_sigma = SkGpuBlurUtils::IsEffectivelyZeroSigma(gaussianSigma);
133     // We should sample as nearest if there will be no shader to preserve existing behaviour, but
134     // the linear blur requires a linear sample.
135     GrSamplerState::Filter filter = is_zero_sigma ?
136         GrSamplerState::Filter::kNearest : GrSamplerState::Filter::kLinear;
137     GrSamplerState sampler(wm, filter);
138     if (is_zero_sigma) {
139         halfWidth = 0;
140     }
141     // It's pretty common to blur a subset of an input texture. In reduced shader mode we always
142     // apply the wrap mode in the shader.
143     bool alwaysUseShaderTileMode = caps.reducedShaderMode();
144     if (pixelDomain && !alwaysUseShaderTileMode) {
145         // Inset because we expect to be invoked at pixel centers.
146         SkRect domain = SkRect::Make(*pixelDomain).makeInset(0.5, 0.5f);
147         switch (dir) {
148             case Direction::kX: domain.outset(halfWidth, 0); break;
149             case Direction::kY: domain.outset(0, halfWidth); break;
150         }
151         child = GrTextureEffect::MakeSubset(std::move(view),
152                                             alphaType,
153                                             SkMatrix::I(),
154                                             sampler,
155                                             SkRect::Make(subset),
156                                             domain,
157                                             caps,
158                                             GrTextureEffect::kDefaultBorder);
159     } else {
160         child = GrTextureEffect::MakeSubset(std::move(view),
161                                             alphaType,
162                                             SkMatrix::I(),
163                                             sampler,
164                                             SkRect::Make(subset),
165                                             caps,
166                                             GrTextureEffect::kDefaultBorder,
167                                             alwaysUseShaderTileMode);
168     }
169 
170     if (is_zero_sigma) {
171         return child;
172     }
173     return std::unique_ptr<GrFragmentProcessor>(new GrGaussianConvolutionFragmentProcessor(
174             std::move(child), dir, halfWidth, gaussianSigma));
175 }
176 
GrGaussianConvolutionFragmentProcessor(std::unique_ptr<GrFragmentProcessor> child,Direction direction,int radius,float gaussianSigma)177 GrGaussianConvolutionFragmentProcessor::GrGaussianConvolutionFragmentProcessor(
178         std::unique_ptr<GrFragmentProcessor> child,
179         Direction direction,
180         int radius,
181         float gaussianSigma)
182         : INHERITED(kGrGaussianConvolutionFragmentProcessor_ClassID,
183                     ProcessorOptimizationFlags(child.get()))
184         , fRadius(radius)
185         , fDirection(direction) {
186     this->registerChild(std::move(child), SkSL::SampleUsage::Explicit());
187     SkASSERT(radius <= kMaxKernelRadius);
188     this->setUsesSampleCoordsDirectly();
189 
190     // Assemble a gaussian kernel and offset list.
191     float kernel[kMaxKernelWidth] = {};
192     float offsets[kMaxKernelWidth] = {};
193     SkGpuBlurUtils::Compute1DLinearGaussianKernel(kernel, offsets, gaussianSigma, fRadius);
194 
195     // Interleave the kernel and offset values into an array of SkV2s.
196     for (int index = 0; index < kMaxKernelWidth; ++index) {
197         fOffsetsAndKernel[index] = {offsets[index], kernel[index]};
198     }
199 }
200 
GrGaussianConvolutionFragmentProcessor(const GrGaussianConvolutionFragmentProcessor & that)201 GrGaussianConvolutionFragmentProcessor::GrGaussianConvolutionFragmentProcessor(
202         const GrGaussianConvolutionFragmentProcessor& that)
203         : INHERITED(that)
204         , fRadius(that.fRadius)
205         , fDirection(that.fDirection) {
206     memcpy(fOffsetsAndKernel, that.fOffsetsAndKernel, sizeof(fOffsetsAndKernel));
207 }
208 
onAddToKey(const GrShaderCaps & shaderCaps,skgpu::KeyBuilder * b) const209 void GrGaussianConvolutionFragmentProcessor::onAddToKey(const GrShaderCaps& shaderCaps,
210                                                         skgpu::KeyBuilder* b) const {
211     if (!should_use_variable_length_loop(shaderCaps)) {
212         b->add32(fRadius);
213     }
214 }
215 
216 std::unique_ptr<GrFragmentProcessor::ProgramImpl>
onMakeProgramImpl() const217 GrGaussianConvolutionFragmentProcessor::onMakeProgramImpl() const {
218     return std::make_unique<Impl>();
219 }
220 
onIsEqual(const GrFragmentProcessor & sBase) const221 bool GrGaussianConvolutionFragmentProcessor::onIsEqual(const GrFragmentProcessor& sBase) const {
222     const auto& that = sBase.cast<GrGaussianConvolutionFragmentProcessor>();
223     return fRadius == that.fRadius && fDirection == that.fDirection &&
224            std::equal(fOffsetsAndKernel,
225                       fOffsetsAndKernel + SkGpuBlurUtils::LinearKernelWidth(fRadius),
226                       that.fOffsetsAndKernel);
227 }
228 
229 ///////////////////////////////////////////////////////////////////////////////
230 
GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrGaussianConvolutionFragmentProcessor)231 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrGaussianConvolutionFragmentProcessor)
232 
233 #if GR_TEST_UTILS
234 std::unique_ptr<GrFragmentProcessor> GrGaussianConvolutionFragmentProcessor::TestCreate(
235         GrProcessorTestData* d) {
236     auto [view, ct, at] = d->randomView();
237 
238     Direction dir = d->fRandom->nextBool() ? Direction::kY : Direction::kX;
239     SkIRect subset{
240             static_cast<int>(d->fRandom->nextRangeU(0, view.width()  - 1)),
241             static_cast<int>(d->fRandom->nextRangeU(0, view.height() - 1)),
242             static_cast<int>(d->fRandom->nextRangeU(0, view.width()  - 1)),
243             static_cast<int>(d->fRandom->nextRangeU(0, view.height() - 1)),
244     };
245     subset.sort();
246 
247     auto wm = static_cast<GrSamplerState::WrapMode>(
248             d->fRandom->nextULessThan(GrSamplerState::kWrapModeCount));
249     int radius = d->fRandom->nextRangeU(1, kMaxKernelRadius);
250     float sigma = radius / 3.f;
251     SkIRect temp;
252     SkIRect* domain = nullptr;
253     if (d->fRandom->nextBool()) {
254         temp = {
255                 static_cast<int>(d->fRandom->nextRangeU(0, view.width()  - 1)),
256                 static_cast<int>(d->fRandom->nextRangeU(0, view.height() - 1)),
257                 static_cast<int>(d->fRandom->nextRangeU(0, view.width()  - 1)),
258                 static_cast<int>(d->fRandom->nextRangeU(0, view.height() - 1)),
259         };
260         temp.sort();
261         domain = &temp;
262     }
263 
264     return GrGaussianConvolutionFragmentProcessor::Make(std::move(view), at, dir, radius, sigma, wm,
265                                                         subset, domain, *d->caps());
266 }
267 #endif
268