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