xref: /aosp_15_r20/frameworks/native/libs/renderengine/skia/filters/MouriMap.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 #include "MouriMap.h"
17 #include <SkCanvas.h>
18 #include <SkColorType.h>
19 #include <SkPaint.h>
20 #include <SkTileMode.h>
21 
22 namespace android {
23 namespace renderengine {
24 namespace skia {
25 namespace {
makeEffect(const SkString & sksl)26 sk_sp<SkRuntimeEffect> makeEffect(const SkString& sksl) {
27     auto [effect, error] = SkRuntimeEffect::MakeForShader(sksl);
28     LOG_ALWAYS_FATAL_IF(!effect, "RuntimeShader error: %s", error.c_str());
29     return effect;
30 }
31 const SkString kCrosstalkAndChunk16x16(R"(
32     uniform shader bitmap;
33     uniform float hdrSdrRatio;
34     vec4 main(vec2 xy) {
35         float maximum = 0.0;
36         for (int y = 0; y < 16; y++) {
37             for (int x = 0; x < 16; x++) {
38                 float3 linear = toLinearSrgb(bitmap.eval((xy - 0.5) * 16 + 0.5 + vec2(x, y)).rgb) * hdrSdrRatio;
39                 float maxRGB = max(linear.r, max(linear.g, linear.b));
40                 maximum = max(maximum, log2(max(maxRGB, 1.0)));
41             }
42         }
43         return float4(float3(maximum), 1.0);
44     }
45 )");
46 const SkString kChunk8x8(R"(
47     uniform shader bitmap;
48     vec4 main(vec2 xy) {
49         float maximum = 0.0;
50         for (int y = 0; y < 8; y++) {
51             for (int x = 0; x < 8; x++) {
52                 maximum = max(maximum, bitmap.eval((xy - 0.5) * 8 + 0.5 + vec2(x, y)).r);
53             }
54         }
55         return float4(float3(maximum), 1.0);
56     }
57 )");
58 const SkString kBlur(R"(
59     uniform shader bitmap;
60     vec4 main(vec2 xy) {
61         float C[5];
62         C[0] = 1.0 / 16.0;
63         C[1] = 4.0 / 16.0;
64         C[2] = 6.0 / 16.0;
65         C[3] = 4.0 / 16.0;
66         C[4] = 1.0 / 16.0;
67         float result = 0.0;
68         for (int y = -2; y <= 2; y++) {
69             for (int x = -2; x <= 2; x++) {
70                 result += C[y + 2] * C[x + 2] * bitmap.eval(xy + vec2(x, y)).r;
71             }
72         }
73         return float4(float3(exp2(result)), 1.0);
74     }
75 )");
76 const SkString kTonemap(R"(
77     uniform shader image;
78     uniform shader lux;
79     uniform float scaleFactor;
80     uniform float hdrSdrRatio;
81     uniform float targetHdrSdrRatio;
82     vec4 main(vec2 xy) {
83         float localMax = lux.eval(xy * scaleFactor).r;
84         float4 rgba = image.eval(xy);
85         float3 linear = toLinearSrgb(rgba.rgb) * hdrSdrRatio;
86 
87         if (localMax <= targetHdrSdrRatio) {
88             return float4(fromLinearSrgb(linear), rgba.a);
89         }
90 
91         float maxRGB = max(linear.r, max(linear.g, linear.b));
92         localMax = max(localMax, maxRGB);
93         float gain = (1 + maxRGB * (targetHdrSdrRatio / (localMax * localMax)))
94                 / (1 + maxRGB / targetHdrSdrRatio);
95         return float4(fromLinearSrgb(linear * gain), rgba.a);
96     }
97 )");
98 
99 // Draws the given runtime shader on a GPU surface and returns the result as an SkImage.
makeImage(SkSurface * surface,const SkRuntimeShaderBuilder & builder)100 sk_sp<SkImage> makeImage(SkSurface* surface, const SkRuntimeShaderBuilder& builder) {
101     sk_sp<SkShader> shader = builder.makeShader(nullptr);
102     LOG_ALWAYS_FATAL_IF(!shader, "%s, Failed to make shader!", __func__);
103     SkPaint paint;
104     paint.setShader(std::move(shader));
105     paint.setBlendMode(SkBlendMode::kSrc);
106     surface->getCanvas()->drawPaint(paint);
107     return surface->makeImageSnapshot();
108 }
109 
110 } // namespace
111 
MouriMap()112 MouriMap::MouriMap()
113       : mCrosstalkAndChunk16x16(makeEffect(kCrosstalkAndChunk16x16)),
114         mChunk8x8(makeEffect(kChunk8x8)),
115         mBlur(makeEffect(kBlur)),
116         mTonemap(makeEffect(kTonemap)) {}
117 
mouriMap(SkiaGpuContext * context,sk_sp<SkShader> input,float hdrSdrRatio,float targetHdrSdrRatio)118 sk_sp<SkShader> MouriMap::mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input,
119                                    float hdrSdrRatio, float targetHdrSdrRatio) {
120     auto downchunked = downchunk(context, input, hdrSdrRatio);
121     auto localLux = blur(context, downchunked.get());
122     return tonemap(input, localLux.get(), hdrSdrRatio, targetHdrSdrRatio);
123 }
124 
downchunk(SkiaGpuContext * context,sk_sp<SkShader> input,float hdrSdrRatio) const125 sk_sp<SkImage> MouriMap::downchunk(SkiaGpuContext* context, sk_sp<SkShader> input,
126                                    float hdrSdrRatio) const {
127     SkMatrix matrix;
128     SkImage* image = input->isAImage(&matrix, (SkTileMode*)nullptr);
129     SkRuntimeShaderBuilder crosstalkAndChunk16x16Builder(mCrosstalkAndChunk16x16);
130     crosstalkAndChunk16x16Builder.child("bitmap") = input;
131     crosstalkAndChunk16x16Builder.uniform("hdrSdrRatio") = hdrSdrRatio;
132     // TODO: fp16 might be overkill. Most practical surfaces use 8-bit RGB for HDR UI and 10-bit YUV
133     // for HDR video. These downsample operations compute log2(max(linear RGB, 1.0)). So we don't
134     // care about LDR precision since they all resolve to LDR-max. For appropriately mastered HDR
135     // content that follows BT. 2408, 25% of the bit range for HLG and 42% of the bit range for PQ
136     // are reserved for HDR. This means that we can fit the entire HDR range for 10-bit HLG inside
137     // of 8 bits. We can also fit about half of the range for PQ, but most content does not fill the
138     // entire 10k nit range for PQ. Furthermore, we blur all of this later on anyways, so we might
139     // not need to be so precise. So, it's possible that we could use A8 or R8 instead. If we want
140     // to be really conservative we can try to use R16 or even RGBA1010102 to fake an R10 surface,
141     // which would cut write bandwidth significantly.
142     static constexpr auto kFirstDownscaleAmount = 16;
143     sk_sp<SkSurface> firstDownsampledSurface = context->createRenderTarget(
144             image->imageInfo()
145                     .makeWH(std::max(1, image->width() / kFirstDownscaleAmount),
146                             std::max(1, image->height() / kFirstDownscaleAmount))
147                     .makeColorType(kRGBA_F16_SkColorType));
148     LOG_ALWAYS_FATAL_IF(!firstDownsampledSurface, "%s: Failed to create surface!", __func__);
149     auto firstDownsampledImage =
150             makeImage(firstDownsampledSurface.get(), crosstalkAndChunk16x16Builder);
151     SkRuntimeShaderBuilder chunk8x8Builder(mChunk8x8);
152     chunk8x8Builder.child("bitmap") =
153             firstDownsampledImage->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp,
154                                                  SkSamplingOptions());
155     static constexpr auto kSecondDownscaleAmount = 8;
156     sk_sp<SkSurface> secondDownsampledSurface = context->createRenderTarget(
157             firstDownsampledImage->imageInfo()
158                     .makeWH(std::max(1, firstDownsampledImage->width() / kSecondDownscaleAmount),
159                             std::max(1, firstDownsampledImage->height() / kSecondDownscaleAmount)));
160     LOG_ALWAYS_FATAL_IF(!secondDownsampledSurface, "%s: Failed to create surface!", __func__);
161     return makeImage(secondDownsampledSurface.get(), chunk8x8Builder);
162 }
blur(SkiaGpuContext * context,SkImage * input) const163 sk_sp<SkImage> MouriMap::blur(SkiaGpuContext* context, SkImage* input) const {
164     SkRuntimeShaderBuilder blurBuilder(mBlur);
165     blurBuilder.child("bitmap") =
166             input->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions());
167     sk_sp<SkSurface> blurSurface = context->createRenderTarget(input->imageInfo());
168     LOG_ALWAYS_FATAL_IF(!blurSurface, "%s: Failed to create surface!", __func__);
169     return makeImage(blurSurface.get(), blurBuilder);
170 }
tonemap(sk_sp<SkShader> input,SkImage * localLux,float hdrSdrRatio,float targetHdrSdrRatio) const171 sk_sp<SkShader> MouriMap::tonemap(sk_sp<SkShader> input, SkImage* localLux, float hdrSdrRatio,
172                                   float targetHdrSdrRatio) const {
173     static constexpr float kScaleFactor = 1.0f / 128.0f;
174     SkRuntimeShaderBuilder tonemapBuilder(mTonemap);
175     tonemapBuilder.child("image") = input;
176     tonemapBuilder.child("lux") =
177             localLux->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp,
178                                     SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone));
179     tonemapBuilder.uniform("scaleFactor") = kScaleFactor;
180     tonemapBuilder.uniform("hdrSdrRatio") = hdrSdrRatio;
181     tonemapBuilder.uniform("targetHdrSdrRatio") = targetHdrSdrRatio;
182     return tonemapBuilder.makeShader();
183 }
184 } // namespace skia
185 } // namespace renderengine
186 } // namespace android
187