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