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