xref: /aosp_15_r20/external/skia/gm/kawase_blur_rt.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2020 Google LLC
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 "gm/gm.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkData.h"
11 #include "include/core/SkPaint.h"
12 #include "include/core/SkSize.h"
13 #include "include/core/SkString.h"
14 #include "include/core/SkSurface.h"
15 #include "include/effects/SkRuntimeEffect.h"
16 #include "tools/DecodeUtils.h"
17 #include "tools/Resources.h"
18 
19 class KawaseBlurFilter {
20 public:
21     // Downsample scale factor used to improve performance
22     static constexpr float kInputScale = 0.25f;
23     // Downsample scale factor used to improve performance
24     static constexpr float kInverseInputScale = 1.0f / kInputScale;
25     // Maximum number of render passes
26     static constexpr uint32_t kMaxPasses = 4;
27     // To avoid downscaling artifacts, we interpolate the blurred fbo with the full composited
28     // image, up to this radius.
29     static constexpr float kMaxCrossFadeRadius = 30.0f;
30 
KawaseBlurFilter()31     KawaseBlurFilter() {
32         SkString blurString(R"(
33             uniform shader src;
34             uniform float in_inverseScale;
35             uniform float2 in_blurOffset;
36 
37             half4 main(float2 xy) {
38                 float2 scaled_xy = float2(xy.x * in_inverseScale, xy.y * in_inverseScale);
39 
40                 half4 c = src.eval(scaled_xy);
41                 c += src.eval(scaled_xy + float2( in_blurOffset.x,  in_blurOffset.y));
42                 c += src.eval(scaled_xy + float2( in_blurOffset.x, -in_blurOffset.y));
43                 c += src.eval(scaled_xy + float2(-in_blurOffset.x,  in_blurOffset.y));
44                 c += src.eval(scaled_xy + float2(-in_blurOffset.x, -in_blurOffset.y));
45 
46                 return half4(c.rgb * 0.2, 1.0);
47             }
48         )");
49 
50         SkString mixString(R"(
51             uniform shader in_blur;
52             uniform shader in_original;
53             uniform float in_inverseScale;
54             uniform float in_mix;
55 
56             half4 main(float2 xy) {
57                 float2 scaled_xy = float2(xy.x * in_inverseScale, xy.y * in_inverseScale);
58 
59                 half4 blurred = in_blur.eval(scaled_xy);
60                 half4 composition = in_original.eval(xy);
61                 return mix(composition, blurred, in_mix);
62             }
63         )");
64 
65         auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString);
66         if (!blurEffect) {
67             SkDEBUGFAILF("RuntimeShader error: %s\n", error.c_str());
68         }
69         fBlurEffect = std::move(blurEffect);
70 
71         auto [mixEffect, error2] = SkRuntimeEffect::MakeForShader(mixString);
72         if (!mixEffect) {
73             SkDEBUGFAILF("RuntimeShader error: %s\n", error2.c_str());
74         }
75         fMixEffect = std::move(mixEffect);
76     }
77 
MakeSurface(SkCanvas * canvas,const SkImageInfo & info)78     static sk_sp<SkSurface> MakeSurface(SkCanvas* canvas, const SkImageInfo& info) {
79         if (sk_sp<SkSurface> surface = canvas->makeSurface(info)) {
80             return surface;
81         }
82         // serialize-8888 returns null from makeSurface; fallback to a raster surface.
83         return SkSurfaces::Raster(info);
84     }
85 
draw(SkCanvas * canvas,sk_sp<SkImage> input,int blurRadius)86     void draw(SkCanvas* canvas, sk_sp<SkImage> input, int blurRadius) {
87         // NOTE: this is only experimental and the current blur params cause points to be sampled
88         // beyond the input blur radius.
89 
90         // Kawase is an approximation of Gaussian, but it behaves differently from it.
91         // A radius transformation is required for approximating them, and also to introduce
92         // non-integer steps, necessary to smoothly interpolate large radii.
93         float tmpRadius = (float)blurRadius / 6.0f;
94         float numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
95         float radiusByPasses = tmpRadius / (float)numberOfPasses;
96 
97         SkImageInfo scaledInfo = SkImageInfo::MakeN32Premul((float)input->width() * kInputScale,
98                                                             (float)input->height() * kInputScale);
99         auto drawSurface = MakeSurface(canvas, scaledInfo);
100 
101         const float stepX = radiusByPasses;
102         const float stepY = radiusByPasses;
103 
104         // start by drawing and downscaling and doing the first blur pass
105         SkRuntimeShaderBuilder blurBuilder(fBlurEffect);
106         blurBuilder.child("src") = input->makeShader(SkSamplingOptions(SkFilterMode::kLinear));
107         blurBuilder.uniform("in_inverseScale") = kInverseInputScale;
108         blurBuilder.uniform("in_blurOffset") = SkV2{stepX * kInverseInputScale,
109                                                     stepY * kInverseInputScale};
110         SkPaint paint;
111         paint.setShader(blurBuilder.makeShader());
112         drawSurface->getCanvas()->drawIRect(scaledInfo.bounds(), paint);
113 
114         // DEBUG draw each of the stages
115         canvas->save();
116         canvas->drawImage(drawSurface->makeImageSnapshot(), input->width() / 4, 0,
117                           SkSamplingOptions());
118         canvas->translate(input->width() / 4, input->height() * 0.75);
119 
120         // And now we'll ping pong between our surfaces, to accumulate the result of various
121         // offsets.
122         auto lastDrawTarget = drawSurface;
123         if (numberOfPasses > 1) {
124             auto readSurface = drawSurface;
125             drawSurface = MakeSurface(canvas, scaledInfo);
126 
127             for (auto i = 1; i < numberOfPasses; i++) {
128                 const float stepScale = (float)i * kInputScale;
129 
130                 blurBuilder.child("src") = readSurface->makeImageSnapshot()->makeShader(
131                         SkSamplingOptions(SkFilterMode::kLinear));
132                 blurBuilder.uniform("in_inverseScale") = 1.0f;
133                 blurBuilder.uniform("in_blurOffset") = SkV2{stepX * stepScale , stepY * stepScale};
134 
135                 paint.setShader(blurBuilder.makeShader());
136                 drawSurface->getCanvas()->drawIRect(scaledInfo.bounds(), paint);
137 
138                 // DEBUG draw each of the stages
139                 canvas->drawImage(drawSurface->makeImageSnapshot(), 0, 0, SkSamplingOptions());
140                 canvas->translate(0, input->height() * 0.75);
141 
142                 // Swap buffers for next iteration
143                 auto tmp = drawSurface;
144                 drawSurface = readSurface;
145                 readSurface = tmp;
146             }
147             lastDrawTarget = readSurface;
148         }
149 
150         // restore translations done for debug and offset
151         canvas->restore();
152         SkAutoCanvasRestore acr(canvas, true);
153         canvas->translate(input->width(), 0);
154 
155         // do the final composition and when we scale our blur up. It will be interpolated
156         // with the larger composited texture to hide downscaling artifacts.
157         SkRuntimeShaderBuilder mixBuilder(fMixEffect);
158         mixBuilder.child("in_blur") = lastDrawTarget->makeImageSnapshot()->makeShader(
159                 SkSamplingOptions(SkFilterMode::kLinear));
160         mixBuilder.child("in_original") =
161                 input->makeShader(SkSamplingOptions(SkFilterMode::kLinear));
162         mixBuilder.uniform("in_inverseScale") = kInputScale;
163         mixBuilder.uniform("in_mix") = std::min(1.0f, (float)blurRadius / kMaxCrossFadeRadius);
164 
165         paint.setShader(mixBuilder.makeShader());
166         canvas->drawIRect(input->bounds(), paint);
167     }
168 
169 private:
170     sk_sp<SkRuntimeEffect> fBlurEffect;
171     sk_sp<SkRuntimeEffect> fMixEffect;
172 };
173 
174 class KawaseBlurRT : public skiagm::GM {
175 public:
KawaseBlurRT()176     KawaseBlurRT() {}
getName() const177     SkString getName() const override { return SkString("kawase_blur_rt"); }
getISize()178     SkISize getISize() override { return {1280, 768}; }
179 
onOnceBeforeDraw()180     void onOnceBeforeDraw() override {
181         fMandrill = ToolUtils::GetResourceAsImage("images/mandrill_256.png");
182     }
183 
onDraw(SkCanvas * canvas)184     void onDraw(SkCanvas* canvas) override {
185         canvas->drawImage(fMandrill, 0, 0);
186         canvas->translate(256, 0);
187         KawaseBlurFilter blurFilter;
188         blurFilter.draw(canvas, fMandrill, 45);
189         canvas->translate(512, 0);
190         blurFilter.draw(canvas, fMandrill, 55);
191     }
192 
193 private:
194     sk_sp<SkImage> fMandrill;
195 };
196 DEF_GM(return new KawaseBlurRT;)
197