xref: /aosp_15_r20/frameworks/native/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp (revision 38e8c45f13ce32b0dcecb25141ffecaf386fa17f)
1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
18 
19 #include "KawaseBlurDualFilter.h"
20 #include <SkAlphaType.h>
21 #include <SkBlendMode.h>
22 #include <SkCanvas.h>
23 #include <SkData.h>
24 #include <SkPaint.h>
25 #include <SkRRect.h>
26 #include <SkRuntimeEffect.h>
27 #include <SkShader.h>
28 #include <SkSize.h>
29 #include <SkString.h>
30 #include <SkSurface.h>
31 #include <SkTileMode.h>
32 #include <include/gpu/GpuTypes.h>
33 #include <include/gpu/ganesh/SkSurfaceGanesh.h>
34 #include <log/log.h>
35 #include <utils/Trace.h>
36 
37 namespace android {
38 namespace renderengine {
39 namespace skia {
40 
KawaseBlurDualFilter()41 KawaseBlurDualFilter::KawaseBlurDualFilter() : BlurFilter() {
42     // A shader to sample each vertex of a unit regular heptagon
43     // plus the original fragment coordinate.
44     SkString blurString(R"(
45         uniform shader child;
46         uniform float in_blurOffset;
47         uniform float in_crossFade;
48 
49         const float2 STEP_0 = float2( 1.0, 0.0);
50         const float2 STEP_1 = float2( 0.623489802,  0.781831482);
51         const float2 STEP_2 = float2(-0.222520934,  0.974927912);
52         const float2 STEP_3 = float2(-0.900968868,  0.433883739);
53         const float2 STEP_4 = float2( 0.900968868, -0.433883739);
54         const float2 STEP_5 = float2(-0.222520934, -0.974927912);
55         const float2 STEP_6 = float2(-0.623489802, -0.781831482);
56 
57         half4 main(float2 xy) {
58             half3 c = child.eval(xy).rgb;
59 
60             c += child.eval(xy + STEP_0 * in_blurOffset).rgb;
61             c += child.eval(xy + STEP_1 * in_blurOffset).rgb;
62             c += child.eval(xy + STEP_2 * in_blurOffset).rgb;
63             c += child.eval(xy + STEP_3 * in_blurOffset).rgb;
64             c += child.eval(xy + STEP_4 * in_blurOffset).rgb;
65             c += child.eval(xy + STEP_5 * in_blurOffset).rgb;
66             c += child.eval(xy + STEP_6 * in_blurOffset).rgb;
67 
68             return half4(c * 0.125 * in_crossFade, in_crossFade);
69         }
70     )");
71 
72     auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString);
73     LOG_ALWAYS_FATAL_IF(!blurEffect, "RuntimeShader error: %s", error.c_str());
74     mBlurEffect = std::move(blurEffect);
75 }
76 
makeSurface(SkiaGpuContext * context,const SkRect & origRect,int scale)77 static sk_sp<SkSurface> makeSurface(SkiaGpuContext* context, const SkRect& origRect, int scale) {
78     SkImageInfo scaledInfo =
79             SkImageInfo::MakeN32Premul(ceil(static_cast<float>(origRect.width()) / scale),
80                                        ceil(static_cast<float>(origRect.height()) / scale));
81     return context->createRenderTarget(scaledInfo);
82 }
83 
blurInto(const sk_sp<SkSurface> & drawSurface,const sk_sp<SkImage> & readImage,const float radius,const float alpha) const84 void KawaseBlurDualFilter::blurInto(const sk_sp<SkSurface>& drawSurface,
85                                     const sk_sp<SkImage>& readImage, const float radius,
86                                     const float alpha) const {
87     const float scale = static_cast<float>(drawSurface->width()) / readImage->width();
88     SkMatrix blurMatrix = SkMatrix::Scale(scale, scale);
89     blurInto(drawSurface,
90              readImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
91                                    SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone),
92                                    blurMatrix),
93              readImage->width() / static_cast<float>(drawSurface->width()), radius, alpha);
94 }
95 
blurInto(const sk_sp<SkSurface> & drawSurface,sk_sp<SkShader> input,const float inverseScale,const float radius,const float alpha) const96 void KawaseBlurDualFilter::blurInto(const sk_sp<SkSurface>& drawSurface, sk_sp<SkShader> input,
97                                     const float inverseScale, const float radius,
98                                     const float alpha) const {
99     SkPaint paint;
100     if (radius == 0) {
101         paint.setShader(std::move(input));
102         paint.setAlphaf(alpha);
103     } else {
104         SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
105         blurBuilder.child("child") = std::move(input);
106         blurBuilder.uniform("in_blurOffset") = radius;
107         blurBuilder.uniform("in_crossFade") = alpha;
108         paint.setShader(blurBuilder.makeShader(nullptr));
109     }
110     paint.setBlendMode(alpha == 1.0f ? SkBlendMode::kSrc : SkBlendMode::kSrcOver);
111     drawSurface->getCanvas()->drawPaint(paint);
112 }
113 
generate(SkiaGpuContext * context,const uint32_t blurRadius,const sk_sp<SkImage> input,const SkRect & blurRect) const114 sk_sp<SkImage> KawaseBlurDualFilter::generate(SkiaGpuContext* context, const uint32_t blurRadius,
115                                               const sk_sp<SkImage> input,
116                                               const SkRect& blurRect) const {
117     // Apply a conversion factor of (1 / sqrt(3)) to match Skia's built-in blur as used by
118     // RenderEffect. See the comment in SkBlurMask.cpp for reasoning behind this.
119     const float radius = blurRadius * 0.57735f;
120 
121     // Use a variable number of blur passes depending on the radius. The non-integer part of this
122     // calculation is used to mix the final pass into the second-last with an alpha blend.
123     constexpr int kMaxSurfaces = 3;
124     const float filterDepth = std::min(kMaxSurfaces - 1.0f, radius * kInputScale / 2.5f);
125     const int filterPasses = std::min(kMaxSurfaces - 1, static_cast<int>(ceil(filterDepth)));
126 
127     // Render into surfaces downscaled by 1x, 2x, and 4x from the initial downscale.
128     sk_sp<SkSurface> surfaces[kMaxSurfaces] =
129             {filterPasses >= 0 ? makeSurface(context, blurRect, 1 * kInverseInputScale) : nullptr,
130              filterPasses >= 1 ? makeSurface(context, blurRect, 2 * kInverseInputScale) : nullptr,
131              filterPasses >= 2 ? makeSurface(context, blurRect, 4 * kInverseInputScale) : nullptr};
132 
133     // These weights for scaling offsets per-pass are handpicked to look good at 1 <= radius <= 250.
134     static const float kWeights[5] = {
135             1.0f, // 1st downsampling pass
136             1.0f, // 2nd downsampling pass
137             1.0f, // 3rd downsampling pass
138             0.0f, // 1st upscaling pass. Set to zero to upscale without blurring for performance.
139             1.0f, // 2nd upscaling pass
140     };
141 
142     // Kawase is an approximation of Gaussian, but behaves differently because it is made up of many
143     // simpler blurs. A transformation is required to approximate the same effect as Gaussian.
144     float sumSquaredR = powf(kWeights[0], 2.0f);
145     for (int i = 0; i < filterPasses; i++) {
146         const float alpha = std::min(1.0f, filterDepth - i);
147         sumSquaredR += powf(powf(2.0f, i) * alpha * kWeights[1 + i] / kInputScale, 2.0f);
148         sumSquaredR += powf(powf(2.0f, i + 1) * alpha * kWeights[4 - i] / kInputScale, 2.0f);
149     }
150     // Solve for R = sqrt(sum(r_i^2)).
151     const float step = radius * sqrt(1.0f / sumSquaredR);
152 
153     // Start by downscaling and doing the first blur pass.
154     {
155         // For sampling Skia's API expects the inverse of what logically seems appropriate. In this
156         // case one may expect Translate(blurRect.fLeft, blurRect.fTop) * Scale(kInverseInputScale)
157         // but instead we must do the inverse.
158         SkMatrix blurMatrix = SkMatrix::Translate(-blurRect.fLeft, -blurRect.fTop);
159         blurMatrix.postScale(kInputScale, kInputScale);
160         const auto sourceShader =
161                 input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
162                                   SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone),
163                                   blurMatrix);
164         blurInto(surfaces[0], std::move(sourceShader), kInputScale, kWeights[0] * step, 1.0f);
165     }
166     // Next the remaining downscale blur passes.
167     for (int i = 0; i < filterPasses; i++) {
168         blurInto(surfaces[i + 1], surfaces[i]->makeImageSnapshot(), kWeights[1 + i] * step, 1.0f);
169     }
170     // Finally blur+upscale back to our original size.
171     for (int i = filterPasses - 1; i >= 0; i--) {
172         blurInto(surfaces[i], surfaces[i + 1]->makeImageSnapshot(), kWeights[4 - i] * step,
173                  std::min(1.0f, filterDepth - i));
174     }
175     return surfaces[0]->makeImageSnapshot();
176 }
177 
178 } // namespace skia
179 } // namespace renderengine
180 } // namespace android
181