xref: /aosp_15_r20/frameworks/base/libs/hwui/effects/GainmapRenderer.cpp (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
2  * Copyright (C) 2023 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 "GainmapRenderer.h"
18 
19 #include <SkGainmapShader.h>
20 
21 #include "Gainmap.h"
22 #include "Rect.h"
23 #include "utils/Trace.h"
24 
25 #ifdef __ANDROID__
26 #include "include/core/SkColorSpace.h"
27 #include "include/core/SkImage.h"
28 #include "include/core/SkShader.h"
29 #include "include/effects/SkRuntimeEffect.h"
30 #include "include/private/SkGainmapInfo.h"
31 #include "renderthread/CanvasContext.h"
32 #include "src/core/SkColorFilterPriv.h"
33 #include "src/core/SkImageInfoPriv.h"
34 #include "src/core/SkRuntimeEffectPriv.h"
35 
36 #include <cmath>
37 #endif
38 
39 namespace android::uirenderer {
40 
41 using namespace renderthread;
42 
getTargetHdrSdrRatio(const SkColorSpace * destColorspace)43 float getTargetHdrSdrRatio(const SkColorSpace* destColorspace) {
44     // We should always have a known destination colorspace. If we don't we must be in some
45     // legacy mode where we're lost and also definitely not going to HDR
46     if (destColorspace == nullptr) {
47         return 1.f;
48     }
49 
50     constexpr float GenericSdrWhiteNits = 203.f;
51     constexpr float maxPQLux = 10000.f;
52     constexpr float maxHLGLux = 1000.f;
53     skcms_TransferFunction destTF;
54     destColorspace->transferFn(&destTF);
55     if (skcms_TransferFunction_isPQish(&destTF)) {
56         return maxPQLux / GenericSdrWhiteNits;
57     } else if (skcms_TransferFunction_isHLGish(&destTF)) {
58         return maxHLGLux / GenericSdrWhiteNits;
59 #ifdef __ANDROID__
60     } else if (RenderThread::isCurrent()) {
61         CanvasContext* context = CanvasContext::getActiveContext();
62         return context ? context->targetSdrHdrRatio() : 1.f;
63 #endif
64     }
65     return 1.f;
66 }
67 
DrawGainmapBitmap(SkCanvas * c,const sk_sp<const SkImage> & image,const SkRect & src,const SkRect & dst,const SkSamplingOptions & sampling,const SkPaint * paint,SkCanvas::SrcRectConstraint constraint,const sk_sp<const SkImage> & gainmapImage,const SkGainmapInfo & gainmapInfo)68 void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkRect& src,
69                        const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint* paint,
70                        SkCanvas::SrcRectConstraint constraint,
71                        const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo) {
72     ATRACE_CALL();
73 #ifdef __ANDROID__
74     auto destColorspace = c->imageInfo().refColorSpace();
75     float targetSdrHdrRatio = getTargetHdrSdrRatio(destColorspace.get());
76     const bool baseImageHdr = gainmapInfo.fBaseImageType == SkGainmapInfo::BaseImageType::kHDR;
77     if (gainmapImage && ((baseImageHdr && targetSdrHdrRatio < gainmapInfo.fDisplayRatioHdr) ||
78                          (!baseImageHdr && targetSdrHdrRatio > gainmapInfo.fDisplayRatioSdr))) {
79         SkPaint gainmapPaint = *paint;
80         float sX = gainmapImage->width() / (float)image->width();
81         float sY = gainmapImage->height() / (float)image->height();
82         SkRect gainmapSrc = src;
83         // TODO: Tweak rounding?
84         gainmapSrc.fLeft *= sX;
85         gainmapSrc.fRight *= sX;
86         gainmapSrc.fTop *= sY;
87         gainmapSrc.fBottom *= sY;
88         auto shader =
89                 SkGainmapShader::Make(image, src, sampling, gainmapImage, gainmapSrc, sampling,
90                                       gainmapInfo, dst, targetSdrHdrRatio, destColorspace);
91         gainmapPaint.setShader(shader);
92         c->drawRect(dst, gainmapPaint);
93     } else
94 #endif
95         c->drawImageRect(image.get(), src, dst, sampling, paint, constraint);
96 }
97 
98 #ifdef __ANDROID__
99 
100 static constexpr char gGainmapSKSL[] = R"SKSL(
101     uniform shader linearBase;
102     uniform shader base;
103     uniform shader gainmap;
104     uniform colorFilter workingSpaceToLinearSrgb;
105     uniform half4 logRatioMin;
106     uniform half4 logRatioMax;
107     uniform half4 gainmapGamma;
108     uniform half4 epsilonSdr;
109     uniform half4 epsilonHdr;
110     uniform half W;
111     uniform int gainmapIsAlpha;
112     uniform int gainmapIsRed;
113     uniform int singleChannel;
114     uniform int noGamma;
115 
116     half4 toDest(half4 working) {
117         half4 ls = workingSpaceToLinearSrgb.eval(working);
118         vec3 dest = fromLinearSrgb(ls.rgb);
119         return half4(dest.r, dest.g, dest.b, ls.a);
120     }
121 
122     half4 main(float2 coord) {
123         if (W == 0.0) {
124             return base.eval(coord);
125         }
126 
127         half4 S = linearBase.eval(coord);
128         half4 G = gainmap.eval(coord);
129         if (gainmapIsAlpha == 1) {
130             G = half4(G.a, G.a, G.a, 1.0);
131         }
132         if (gainmapIsRed == 1) {
133             G = half4(G.r, G.r, G.r, 1.0);
134         }
135         if (singleChannel == 1) {
136             half L;
137             if (noGamma == 1) {
138                 L = mix(logRatioMin.r, logRatioMax.r, G.r);
139             } else {
140                 L = mix(logRatioMin.r, logRatioMax.r, pow(G.r, gainmapGamma.r));
141             }
142             half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;
143             return toDest(half4(H.r, H.g, H.b, S.a));
144         } else {
145             half3 L;
146             if (noGamma == 1) {
147                 L = mix(logRatioMin.rgb, logRatioMax.rgb, G.rgb);
148             } else {
149                 L = mix(logRatioMin.rgb, logRatioMax.rgb, pow(G.rgb, gainmapGamma.rgb));
150             }
151             half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;
152             return toDest(half4(H.r, H.g, H.b, S.a));
153         }
154     }
155 )SKSL";
156 
gainmap_apply_effect()157 static sk_sp<SkRuntimeEffect> gainmap_apply_effect() {
158     static const SkRuntimeEffect* effect = []() -> SkRuntimeEffect* {
159         auto buildResult = SkRuntimeEffect::MakeForShader(SkString(gGainmapSKSL), {});
160         if (buildResult.effect) {
161             return buildResult.effect.release();
162         } else {
163             LOG_ALWAYS_FATAL("Failed to build gainmap shader: %s", buildResult.errorText.c_str());
164         }
165     }();
166     SkASSERT(effect);
167     return sk_ref_sp(effect);
168 }
169 
all_channels_equal(const SkColor4f & c)170 static bool all_channels_equal(const SkColor4f& c) {
171     return c.fR == c.fG && c.fR == c.fB;
172 }
173 
174 class DeferredGainmapShader {
175 private:
176     sk_sp<SkRuntimeEffect> mShader{gainmap_apply_effect()};
177     SkRuntimeShaderBuilder mBuilder{mShader};
178     SkGainmapInfo mGainmapInfo;
179     std::mutex mUniformGuard;
180 
setupChildren(const sk_sp<const SkImage> & baseImage,const sk_sp<const SkImage> & gainmapImage,SkTileMode tileModeX,SkTileMode tileModeY,const SkSamplingOptions & samplingOptions)181     void setupChildren(const sk_sp<const SkImage>& baseImage,
182                        const sk_sp<const SkImage>& gainmapImage, SkTileMode tileModeX,
183                        SkTileMode tileModeY, const SkSamplingOptions& samplingOptions) {
184         sk_sp<SkColorSpace> baseColorSpace =
185                 baseImage->colorSpace() ? baseImage->refColorSpace() : SkColorSpace::MakeSRGB();
186 
187         // Determine the color space in which the gainmap math is to be applied.
188         sk_sp<SkColorSpace> gainmapMathColorSpace =
189                 mGainmapInfo.fGainmapMathColorSpace
190                         ? mGainmapInfo.fGainmapMathColorSpace->makeLinearGamma()
191                         : baseColorSpace->makeLinearGamma();
192 
193         // Create a color filter to transform from the base image's color space to the color space
194         // in which the gainmap is to be applied.
195         auto colorXformSdrToGainmap =
196                 SkColorFilterPriv::MakeColorSpaceXform(baseColorSpace, gainmapMathColorSpace);
197 
198         // The base image shader will convert into the color space in which the gainmap is applied.
199         auto linearBaseImageShader = baseImage->makeRawShader(tileModeX, tileModeY, samplingOptions)
200                                              ->makeWithColorFilter(colorXformSdrToGainmap);
201 
202         auto baseImageShader = baseImage->makeShader(tileModeX, tileModeY, samplingOptions);
203 
204         // The gainmap image shader will ignore any color space that the gainmap has.
205         const SkMatrix gainmapRectToDstRect =
206                 SkMatrix::RectToRect(SkRect::MakeWH(gainmapImage->width(), gainmapImage->height()),
207                                      SkRect::MakeWH(baseImage->width(), baseImage->height()));
208         auto gainmapImageShader = gainmapImage->makeRawShader(tileModeX, tileModeY, samplingOptions,
209                                                               &gainmapRectToDstRect);
210 
211         // Create a color filter to transform from the color space in which the gainmap is applied
212         // to the intermediate destination color space.
213         auto colorXformGainmapToDst = SkColorFilterPriv::MakeColorSpaceXform(
214                 gainmapMathColorSpace, SkColorSpace::MakeSRGBLinear());
215 
216         mBuilder.child("linearBase") = std::move(linearBaseImageShader);
217         mBuilder.child("base") = std::move(baseImageShader);
218         mBuilder.child("gainmap") = std::move(gainmapImageShader);
219         mBuilder.child("workingSpaceToLinearSrgb") = std::move(colorXformGainmapToDst);
220     }
221 
setupGenericUniforms(const sk_sp<const SkImage> & gainmapImage,const SkGainmapInfo & gainmapInfo)222     void setupGenericUniforms(const sk_sp<const SkImage>& gainmapImage,
223                               const SkGainmapInfo& gainmapInfo) {
224         const SkColor4f logRatioMin({std::log(gainmapInfo.fGainmapRatioMin.fR),
225                                      std::log(gainmapInfo.fGainmapRatioMin.fG),
226                                      std::log(gainmapInfo.fGainmapRatioMin.fB), 1.f});
227         const SkColor4f logRatioMax({std::log(gainmapInfo.fGainmapRatioMax.fR),
228                                      std::log(gainmapInfo.fGainmapRatioMax.fG),
229                                      std::log(gainmapInfo.fGainmapRatioMax.fB), 1.f});
230         const int noGamma = gainmapInfo.fGainmapGamma.fR == 1.f &&
231                             gainmapInfo.fGainmapGamma.fG == 1.f &&
232                             gainmapInfo.fGainmapGamma.fB == 1.f;
233         const uint32_t colorTypeFlags = SkColorTypeChannelFlags(gainmapImage->colorType());
234         const int gainmapIsAlpha = colorTypeFlags == kAlpha_SkColorChannelFlag;
235         const int gainmapIsRed = colorTypeFlags == kRed_SkColorChannelFlag;
236         const int singleChannel = all_channels_equal(gainmapInfo.fGainmapGamma) &&
237                                   all_channels_equal(gainmapInfo.fGainmapRatioMin) &&
238                                   all_channels_equal(gainmapInfo.fGainmapRatioMax) &&
239                                   (colorTypeFlags == kGray_SkColorChannelFlag ||
240                                    colorTypeFlags == kAlpha_SkColorChannelFlag ||
241                                    colorTypeFlags == kRed_SkColorChannelFlag);
242         mBuilder.uniform("logRatioMin") = logRatioMin;
243         mBuilder.uniform("logRatioMax") = logRatioMax;
244         mBuilder.uniform("gainmapGamma") = gainmapInfo.fGainmapGamma;
245         mBuilder.uniform("epsilonSdr") = gainmapInfo.fEpsilonSdr;
246         mBuilder.uniform("epsilonHdr") = gainmapInfo.fEpsilonHdr;
247         mBuilder.uniform("noGamma") = noGamma;
248         mBuilder.uniform("singleChannel") = singleChannel;
249         mBuilder.uniform("gainmapIsAlpha") = gainmapIsAlpha;
250         mBuilder.uniform("gainmapIsRed") = gainmapIsRed;
251     }
252 
build(float targetHdrSdrRatio)253     sk_sp<const SkData> build(float targetHdrSdrRatio) {
254         sk_sp<const SkData> uniforms;
255         {
256             // If we are called concurrently from multiple threads, we need to guard the call
257             // to writableUniforms() which mutates mUniform. This is otherwise safe because
258             // writeableUniforms() will make a copy if it's not unique before mutating
259             // This can happen if a BitmapShader is used on multiple canvas', such as a
260             // software + hardware canvas, which is otherwise valid as SkShader is "immutable"
261             std::lock_guard _lock(mUniformGuard);
262             // Compute the weight parameter that will be used to blend between the images.
263             float W = 0.f;
264             if (targetHdrSdrRatio > mGainmapInfo.fDisplayRatioSdr) {
265                 if (targetHdrSdrRatio < mGainmapInfo.fDisplayRatioHdr) {
266                     W = (std::log(targetHdrSdrRatio) -
267                          std::log(mGainmapInfo.fDisplayRatioSdr)) /
268                         (std::log(mGainmapInfo.fDisplayRatioHdr) -
269                          std::log(mGainmapInfo.fDisplayRatioSdr));
270                 } else {
271                     W = 1.f;
272                 }
273             }
274 
275             if (mGainmapInfo.fBaseImageType == SkGainmapInfo::BaseImageType::kHDR) {
276                 W -= 1.f;
277             }
278             mBuilder.uniform("W") = W;
279             uniforms = mBuilder.uniforms();
280         }
281         return uniforms;
282     }
283 
284 public:
DeferredGainmapShader(const sk_sp<const SkImage> & image,const sk_sp<const SkImage> & gainmapImage,const SkGainmapInfo & gainmapInfo,SkTileMode tileModeX,SkTileMode tileModeY,const SkSamplingOptions & sampling)285     explicit DeferredGainmapShader(const sk_sp<const SkImage>& image,
286                                    const sk_sp<const SkImage>& gainmapImage,
287                                    const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
288                                    SkTileMode tileModeY, const SkSamplingOptions& sampling) {
289         mGainmapInfo = gainmapInfo;
290         setupChildren(image, gainmapImage, tileModeX, tileModeY, sampling);
291         setupGenericUniforms(gainmapImage, gainmapInfo);
292     }
293 
Make(const sk_sp<const SkImage> & image,const sk_sp<const SkImage> & gainmapImage,const SkGainmapInfo & gainmapInfo,SkTileMode tileModeX,SkTileMode tileModeY,const SkSamplingOptions & sampling)294     static sk_sp<SkShader> Make(const sk_sp<const SkImage>& image,
295                                 const sk_sp<const SkImage>& gainmapImage,
296                                 const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
297                                 SkTileMode tileModeY, const SkSamplingOptions& sampling) {
298         auto deferredHandler = std::make_shared<DeferredGainmapShader>(
299                 image, gainmapImage, gainmapInfo, tileModeX, tileModeY, sampling);
300         auto callback =
301                 [deferredHandler](const SkRuntimeEffectPriv::UniformsCallbackContext& renderContext)
302                 -> sk_sp<const SkData> {
303             return deferredHandler->build(getTargetHdrSdrRatio(renderContext.fDstColorSpace));
304         };
305         return SkRuntimeEffectPriv::MakeDeferredShader(deferredHandler->mShader.get(), callback,
306                                                        deferredHandler->mBuilder.children());
307     }
308 };
309 
MakeGainmapShader(const sk_sp<const SkImage> & image,const sk_sp<const SkImage> & gainmapImage,const SkGainmapInfo & gainmapInfo,SkTileMode tileModeX,SkTileMode tileModeY,const SkSamplingOptions & sampling)310 sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
311                                   const sk_sp<const SkImage>& gainmapImage,
312                                   const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
313                                   SkTileMode tileModeY, const SkSamplingOptions& sampling) {
314     return DeferredGainmapShader::Make(image, gainmapImage, gainmapInfo, tileModeX, tileModeY,
315                                        sampling);
316 }
317 
318 #else  // __ANDROID__
319 
MakeGainmapShader(const sk_sp<const SkImage> & image,const sk_sp<const SkImage> & gainmapImage,const SkGainmapInfo & gainmapInfo,SkTileMode tileModeX,SkTileMode tileModeY,const SkSamplingOptions & sampling)320 sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
321                                   const sk_sp<const SkImage>& gainmapImage,
322                                   const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
323                                   SkTileMode tileModeY, const SkSamplingOptions& sampling) {
324         return nullptr;
325 }
326 
327 #endif  // __ANDROID__
328 
329 }  // namespace android::uirenderer
330