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