xref: /aosp_15_r20/frameworks/base/libs/hwui/tests/common/scenes/WindowBlurKawase.cpp (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
2  * Copyright (C) 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 #include <SkBitmap.h>
18 #include <SkBlendMode.h>
19 #include <SkCanvas.h>
20 #include <SkPaint.h>
21 #include <SkRefCnt.h>
22 #include <SkRuntimeEffect.h>
23 #include <SkSurface.h>
24 #include <include/gpu/ganesh/SkSurfaceGanesh.h>
25 #include <math.h>
26 
27 #include "SkImageFilters.h"
28 #include "TestSceneBase.h"
29 #include "include/gpu/GpuTypes.h"  // from Skia
30 #include "tests/common/BitmapAllocationTestUtils.h"
31 #include "utils/Color.h"
32 
33 class WindowBlurKawase;
34 
35 static TestScene::Registrar _WindowBlurKawase(TestScene::Info{
36         "windowblurkawase", "Draws window Kawase blur",
37         TestScene::simpleCreateScene<WindowBlurKawase>});
38 
39 /**
40  * Simulates the multi-pass Kawase blur algorithm in
41  * frameworks/native/libs/renderengine/skia/filters/WindowBlurKawaseFilter.cpp
42  */
43 class WindowBlurKawase : public TestScene {
44 private:
45     // Keep in sync with
46     // frameworks/native/libs/renderengine/skia/filters/KawaseBlurFilter.h
47     static constexpr uint32_t kMaxPasses = 4;
48     // Keep in sync with frameworks/native/libs/renderengine/skia/filters/BlurFilter.h
49     static constexpr float kInputScale = 0.25f;
50 
51     static constexpr uint32_t kLoopLength = 500;
52     static constexpr uint32_t kMaxBlurRadius = 300;
53     sk_sp<SkRuntimeEffect> mBlurEffect;
54 
55     sp<RenderNode> card;
56     sp<RenderNode> contentNode;
57 
58 public:
WindowBlurKawase()59     explicit WindowBlurKawase() {
60         SkString blurString(
61                 "uniform shader child;"
62                 "uniform float in_blurOffset;"
63 
64                 "half4 main(float2 xy) {"
65                 "half4 c = child.eval(xy);"
66                 "c += child.eval(xy + float2(+in_blurOffset, +in_blurOffset));"
67                 "c += child.eval(xy + float2(+in_blurOffset, -in_blurOffset));"
68                 "c += child.eval(xy + float2(-in_blurOffset, -in_blurOffset));"
69                 "c += child.eval(xy + float2(-in_blurOffset, +in_blurOffset));"
70                 "return half4(c.rgb * 0.2, 1.0);"
71                 "}");
72 
73         auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString);
74         if (!blurEffect) {
75             LOG_ALWAYS_FATAL("RuntimeShader error: %s", error.c_str());
76         }
77         mBlurEffect = std::move(blurEffect);
78     }
79 
createContent(int width,int height,Canvas & canvas)80     void createContent(int width, int height, Canvas& canvas) override {
81         contentNode = TestUtils::createNode(
82                 0, 0, width, height, [width, height](RenderProperties& props, Canvas& canvas) {
83                     canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
84                     Paint paint;
85                     paint.setColor(Color::Red_500);
86                     canvas.drawRect(0, 0, width / 2, height / 2, paint);
87                     paint.setColor(Color::Blue_500);
88                     canvas.drawRect(width / 2, height / 2, width, height, paint);
89                 });
90 
91         card = TestUtils::createNode(
92                 0, 0, width, height,
93                 [this](RenderProperties& props, Canvas& canvas) { blurFrame(canvas, 0); });
94         canvas.drawRenderNode(card.get());
95     }
96 
doFrame(int frameNr)97     void doFrame(int frameNr) override {
98         int curFrame = frameNr % kLoopLength;
99         float blurRadius =
100                 (sin((float)curFrame / kLoopLength * M_PI * 2) + 1) * 0.5 * kMaxBlurRadius;
101         TestUtils::recordNode(
102                 *card, [this, blurRadius](Canvas& canvas) { blurFrame(canvas, blurRadius); });
103     }
104 
blurFrame(Canvas & canvas,float blurRadius)105     void blurFrame(Canvas& canvas, float blurRadius) {
106         if (blurRadius == 0) {
107             canvas.drawRenderNode(contentNode.get());
108             return;
109         }
110 
111         int width = canvas.width();
112         int height = canvas.height();
113         float tmpRadius = (float)blurRadius / 2.0f;
114         uint32_t numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
115         float radiusByPasses = tmpRadius / (float)numberOfPasses;
116 
117         SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
118 
119         sp<RenderNode> node = contentNode;
120         for (int i = 0; i < numberOfPasses; i++) {
121             blurBuilder.uniform("in_blurOffset") = radiusByPasses * kInputScale * (i + 1);
122             sk_sp<SkImageFilter> blurFilter =
123                     SkImageFilters::RuntimeShader(blurBuilder, radiusByPasses, "child", nullptr);
124             // Also downsample the image in the first pass.
125             float canvasScale = i == 0 ? kInputScale : 1;
126 
127             // Apply the blur effect as an image filter.
128             node = TestUtils::createNode(
129                     0, 0, width * kInputScale, height * kInputScale,
130                     [node, blurFilter, canvasScale](RenderProperties& props, Canvas& canvas) {
131                         props.mutateLayerProperties().setImageFilter(blurFilter.get());
132                         canvas.scale(canvasScale, canvasScale);
133                         canvas.drawRenderNode(node.get());
134                     });
135         }
136 
137         // Finally upsample the image to its original size.
138         canvas.scale(1 / kInputScale, 1 / kInputScale);
139         canvas.drawRenderNode(node.get());
140     }
141 };
142