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