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 * 16 + 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 * 8 + 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     vec4 main(vec2 xy) {
82         float localMax = lux.eval(xy * scaleFactor).r;
83         float4 rgba = image.eval(xy);
84         float3 linear = toLinearSrgb(rgba.rgb) * hdrSdrRatio;
85 
86         if (localMax <= 1.0) {
87             return float4(fromLinearSrgb(linear), 1.0);
88         }
89 
90         float maxRGB = max(linear.r, max(linear.g, linear.b));
91         localMax = max(localMax, maxRGB);
92         float gain = (1 + maxRGB / (localMax * localMax)) / (1 + maxRGB);
93         return float4(fromLinearSrgb(linear * gain), 1.0);
94     }
95 )");
96 
97 // Draws the given runtime shader on a GPU surface and returns the result as an SkImage.
makeImage(SkSurface * surface,const SkRuntimeShaderBuilder & builder)98 sk_sp<SkImage> makeImage(SkSurface* surface, const SkRuntimeShaderBuilder& builder) {
99     sk_sp<SkShader> shader = builder.makeShader(nullptr);
100     LOG_ALWAYS_FATAL_IF(!shader, "%s, Failed to make shader!", __func__);
101     SkPaint paint;
102     paint.setShader(std::move(shader));
103     paint.setBlendMode(SkBlendMode::kSrc);
104     surface->getCanvas()->drawPaint(paint);
105     return surface->makeImageSnapshot();
106 }
107 
108 } // namespace
109 
MouriMap()110 MouriMap::MouriMap()
111       : mCrosstalkAndChunk16x16(makeEffect(kCrosstalkAndChunk16x16)),
112         mChunk8x8(makeEffect(kChunk8x8)),
113         mBlur(makeEffect(kBlur)),
114         mTonemap(makeEffect(kTonemap)) {}
115 
mouriMap(SkiaGpuContext * context,sk_sp<SkShader> input,float hdrSdrRatio)116 sk_sp<SkShader> MouriMap::mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input,
117                                    float hdrSdrRatio) {
118     auto downchunked = downchunk(context, input, hdrSdrRatio);
119     auto localLux = blur(context, downchunked.get());
120     return tonemap(input, localLux.get(), hdrSdrRatio);
121 }
122 
downchunk(SkiaGpuContext * context,sk_sp<SkShader> input,float hdrSdrRatio) const123 sk_sp<SkImage> MouriMap::downchunk(SkiaGpuContext* context, sk_sp<SkShader> input,
124                                    float hdrSdrRatio) const {
125     SkMatrix matrix;
126     SkImage* image = input->isAImage(&matrix, (SkTileMode*)nullptr);
127     SkRuntimeShaderBuilder crosstalkAndChunk16x16Builder(mCrosstalkAndChunk16x16);
128     crosstalkAndChunk16x16Builder.child("bitmap") = input;
129     crosstalkAndChunk16x16Builder.uniform("hdrSdrRatio") = hdrSdrRatio;
130     // TODO: fp16 might be overkill. Most practical surfaces use 8-bit RGB for HDR UI and 10-bit YUV
131     // for HDR video. These downsample operations compute log2(max(linear RGB, 1.0)). So we don't
132     // care about LDR precision since they all resolve to LDR-max. For appropriately mastered HDR
133     // content that follows BT. 2408, 25% of the bit range for HLG and 42% of the bit range for PQ
134     // are reserved for HDR. This means that we can fit the entire HDR range for 10-bit HLG inside
135     // of 8 bits. We can also fit about half of the range for PQ, but most content does not fill the
136     // entire 10k nit range for PQ. Furthermore, we blur all of this later on anyways, so we might
137     // not need to be so precise. So, it's possible that we could use A8 or R8 instead. If we want
138     // to be really conservative we can try to use R16 or even RGBA1010102 to fake an R10 surface,
139     // which would cut write bandwidth significantly.
140     static constexpr auto kFirstDownscaleAmount = 16;
141     sk_sp<SkSurface> firstDownsampledSurface = context->createRenderTarget(
142             image->imageInfo()
143                     .makeWH(std::max(1, image->width() / kFirstDownscaleAmount),
144                             std::max(1, image->height() / kFirstDownscaleAmount))
145                     .makeColorType(kRGBA_F16_SkColorType));
146     LOG_ALWAYS_FATAL_IF(!firstDownsampledSurface, "%s: Failed to create surface!", __func__);
147     auto firstDownsampledImage =
148             makeImage(firstDownsampledSurface.get(), crosstalkAndChunk16x16Builder);
149     SkRuntimeShaderBuilder chunk8x8Builder(mChunk8x8);
150     chunk8x8Builder.child("bitmap") =
151             firstDownsampledImage->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp,
152                                                  SkSamplingOptions());
153     static constexpr auto kSecondDownscaleAmount = 8;
154     sk_sp<SkSurface> secondDownsampledSurface = context->createRenderTarget(
155             firstDownsampledImage->imageInfo()
156                     .makeWH(std::max(1, firstDownsampledImage->width() / kSecondDownscaleAmount),
157                             std::max(1, firstDownsampledImage->height() / kSecondDownscaleAmount)));
158     LOG_ALWAYS_FATAL_IF(!secondDownsampledSurface, "%s: Failed to create surface!", __func__);
159     return makeImage(secondDownsampledSurface.get(), chunk8x8Builder);
160 }
blur(SkiaGpuContext * context,SkImage * input) const161 sk_sp<SkImage> MouriMap::blur(SkiaGpuContext* context, SkImage* input) const {
162     SkRuntimeShaderBuilder blurBuilder(mBlur);
163     blurBuilder.child("bitmap") =
164             input->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions());
165     sk_sp<SkSurface> blurSurface = context->createRenderTarget(input->imageInfo());
166     LOG_ALWAYS_FATAL_IF(!blurSurface, "%s: Failed to create surface!", __func__);
167     return makeImage(blurSurface.get(), blurBuilder);
168 }
tonemap(sk_sp<SkShader> input,SkImage * localLux,float hdrSdrRatio) const169 sk_sp<SkShader> MouriMap::tonemap(sk_sp<SkShader> input, SkImage* localLux,
170                                   float hdrSdrRatio) const {
171     static constexpr float kScaleFactor = 1.0f / 128.0f;
172     SkRuntimeShaderBuilder tonemapBuilder(mTonemap);
173     tonemapBuilder.child("image") = input;
174     tonemapBuilder.child("lux") =
175             localLux->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp,
176                                     SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone));
177     tonemapBuilder.uniform("scaleFactor") = kScaleFactor;
178     tonemapBuilder.uniform("hdrSdrRatio") = hdrSdrRatio;
179     return tonemapBuilder.makeShader();
180 }
181 } // namespace skia
182 } // namespace renderengine
183 } // namespace android