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