1 /*
2 * Copyright 2021 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 <tonemap/tonemap.h>
18
19 #include <algorithm>
20 #include <cstdint>
21 #include <mutex>
22 #include <type_traits>
23
24 namespace android::tonemap {
25
26 namespace {
27
28 // Flag containing the variant of tone map algorithm to use.
29 enum class ToneMapAlgorithm {
30 AndroidO, // Default algorithm in place since Android O,
31 Android13, // Algorithm used in Android 13.
32 };
33
34 static const constexpr auto kToneMapAlgorithm = ToneMapAlgorithm::Android13;
35
36 static const constexpr auto kTransferMask =
37 static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_MASK);
38 static const constexpr auto kTransferST2084 =
39 static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_ST2084);
40 static const constexpr auto kTransferHLG =
41 static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_HLG);
42
43 template <typename T, std::enable_if_t<std::is_trivially_copyable<T>::value, bool> = true>
buildUniformValue(T value)44 std::vector<uint8_t> buildUniformValue(T value) {
45 std::vector<uint8_t> result;
46 result.resize(sizeof(value));
47 std::memcpy(result.data(), &value, sizeof(value));
48 return result;
49 }
50
51 // Refer to BT2100-2
computeHlgGamma(float currentDisplayBrightnessNits)52 float computeHlgGamma(float currentDisplayBrightnessNits) {
53 // BT 2100-2's recommendation for taking into account the nominal max
54 // brightness of the display does not work when the current brightness is
55 // very low. For instance, the gamma becomes negative when the current
56 // brightness is between 1 and 2 nits, which would be a bad experience in a
57 // dark environment. Furthermore, BT2100-2 recommends applying
58 // channel^(gamma - 1) as its OOTF, which means that when the current
59 // brightness is lower than 335 nits then channel * channel^(gamma - 1) >
60 // channel, which makes dark scenes very bright. As a workaround for those
61 // problems, lower-bound the brightness to 500 nits.
62 constexpr float minBrightnessNits = 500.f;
63 currentDisplayBrightnessNits = std::max(minBrightnessNits, currentDisplayBrightnessNits);
64 return 1.2 + 0.42 * std::log10(currentDisplayBrightnessNits / 1000);
65 }
66
67 class ToneMapperO : public ToneMapper {
68 public:
generateTonemapGainShaderSkSL(aidl::android::hardware::graphics::common::Dataspace sourceDataspace,aidl::android::hardware::graphics::common::Dataspace destinationDataspace)69 std::string generateTonemapGainShaderSkSL(
70 aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
71 aidl::android::hardware::graphics::common::Dataspace destinationDataspace) override {
72 const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
73 const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace);
74
75 std::string program;
76 // Define required uniforms
77 program.append(R"(
78 uniform float in_libtonemap_displayMaxLuminance;
79 uniform float in_libtonemap_inputMaxLuminance;
80 )");
81 switch (sourceDataspaceInt & kTransferMask) {
82 case kTransferST2084:
83 case kTransferHLG:
84 switch (destinationDataspaceInt & kTransferMask) {
85 case kTransferST2084:
86 program.append(R"(
87 float libtonemap_ToneMapTargetNits(vec3 xyz) {
88 return xyz.y;
89 }
90 )");
91 break;
92 case kTransferHLG:
93 // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, so
94 // we'll clamp the luminance range in case we're mapping from PQ input to
95 // HLG output.
96 program.append(R"(
97 float libtonemap_ToneMapTargetNits(vec3 xyz) {
98 float nits = clamp(xyz.y, 0.0, 1000.0);
99 return nits * pow(nits / 1000.0, -0.2 / 1.2);
100 }
101 )");
102 break;
103 default:
104 // HLG follows BT2100, but this tonemapping version
105 // does not take into account current display brightness
106 if ((sourceDataspaceInt & kTransferMask) == kTransferHLG) {
107 program.append(R"(
108 float libtonemap_applyBaseOOTFGain(float nits) {
109 return pow(nits, 0.2);
110 }
111 )");
112 } else {
113 program.append(R"(
114 float libtonemap_applyBaseOOTFGain(float nits) {
115 return 1.0;
116 }
117 )");
118 }
119 // Here we're mapping from HDR to SDR content, so interpolate using a
120 // Hermitian polynomial onto the smaller luminance range.
121 program.append(R"(
122 float libtonemap_ToneMapTargetNits(vec3 xyz) {
123 float maxInLumi = in_libtonemap_inputMaxLuminance;
124 float maxOutLumi = in_libtonemap_displayMaxLuminance;
125
126 xyz = xyz * libtonemap_applyBaseOOTFGain(xyz.y);
127
128 float nits = xyz.y;
129
130 // if the max input luminance is less than what we can
131 // output then no tone mapping is needed as all color
132 // values will be in range.
133 if (maxInLumi <= maxOutLumi) {
134 return xyz.y;
135 } else {
136
137 // three control points
138 const float x0 = 10.0;
139 const float y0 = 17.0;
140 float x1 = maxOutLumi * 0.75;
141 float y1 = x1;
142 float x2 = x1 + (maxInLumi - x1) / 2.0;
143 float y2 = y1 + (maxOutLumi - y1) * 0.75;
144
145 // horizontal distances between the last three
146 // control points
147 float h12 = x2 - x1;
148 float h23 = maxInLumi - x2;
149 // tangents at the last three control points
150 float m1 = (y2 - y1) / h12;
151 float m3 = (maxOutLumi - y2) / h23;
152 float m2 = (m1 + m3) / 2.0;
153
154 if (nits < x0) {
155 // scale [0.0, x0] to [0.0, y0] linearly
156 float slope = y0 / x0;
157 return nits * slope;
158 } else if (nits < x1) {
159 // scale [x0, x1] to [y0, y1] linearly
160 float slope = (y1 - y0) / (x1 - x0);
161 nits = y0 + (nits - x0) * slope;
162 } else if (nits < x2) {
163 // scale [x1, x2] to [y1, y2] using Hermite interp
164 float t = (nits - x1) / h12;
165 nits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) *
166 (1.0 - t) * (1.0 - t) +
167 (y2 * (3.0 - 2.0 * t) +
168 h12 * m2 * (t - 1.0)) * t * t;
169 } else {
170 // scale [x2, maxInLumi] to [y2, maxOutLumi] using
171 // Hermite interp
172 float t = (nits - x2) / h23;
173 nits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) *
174 (1.0 - t) * (1.0 - t) + (maxOutLumi *
175 (3.0 - 2.0 * t) + h23 * m3 *
176 (t - 1.0)) * t * t;
177 }
178 }
179
180 return nits;
181 }
182 )");
183 break;
184 }
185 break;
186 default:
187 switch (destinationDataspaceInt & kTransferMask) {
188 case kTransferST2084:
189 case kTransferHLG:
190 // HLG follows BT2100, but this tonemapping version
191 // does not take into account current display brightness
192 if ((destinationDataspaceInt & kTransferMask) == kTransferHLG) {
193 program.append(R"(
194 float libtonemap_applyBaseOOTFGain(float nits) {
195 return pow(nits / 1000.0, -0.2 / 1.2);
196 }
197 )");
198 } else {
199 program.append(R"(
200 float libtonemap_applyBaseOOTFGain(float nits) {
201 return 1.0;
202 }
203 )");
204 }
205 // Map from SDR onto an HDR output buffer
206 // Here we use a polynomial curve to map from [0, displayMaxLuminance] onto
207 // [0, maxOutLumi] which is hard-coded to be 3000 nits.
208 program.append(R"(
209 float libtonemap_ToneMapTargetNits(vec3 xyz) {
210 const float maxOutLumi = 3000.0;
211
212 const float x0 = 5.0;
213 const float y0 = 2.5;
214 float x1 = in_libtonemap_displayMaxLuminance * 0.7;
215 float y1 = maxOutLumi * 0.15;
216 float x2 = in_libtonemap_displayMaxLuminance * 0.9;
217 float y2 = maxOutLumi * 0.45;
218 float x3 = in_libtonemap_displayMaxLuminance;
219 float y3 = maxOutLumi;
220
221 float c1 = y1 / 3.0;
222 float c2 = y2 / 2.0;
223 float c3 = y3 / 1.5;
224
225 float nits = xyz.y;
226
227 if (nits <= x0) {
228 // scale [0.0, x0] to [0.0, y0] linearly
229 float slope = y0 / x0;
230 nits = nits * slope;
231 } else if (nits <= x1) {
232 // scale [x0, x1] to [y0, y1] using a curve
233 float t = (nits - x0) / (x1 - x0);
234 nits = (1.0 - t) * (1.0 - t) * y0 +
235 2.0 * (1.0 - t) * t * c1 + t * t * y1;
236 } else if (nits <= x2) {
237 // scale [x1, x2] to [y1, y2] using a curve
238 float t = (nits - x1) / (x2 - x1);
239 nits = (1.0 - t) * (1.0 - t) * y1 +
240 2.0 * (1.0 - t) * t * c2 + t * t * y2;
241 } else {
242 // scale [x2, x3] to [y2, y3] using a curve
243 float t = (nits - x2) / (x3 - x2);
244 nits = (1.0 - t) * (1.0 - t) * y2 +
245 2.0 * (1.0 - t) * t * c3 + t * t * y3;
246 }
247
248 return nits * libtonemap_applyBaseOOTFGain(nits);
249 }
250 )");
251 break;
252 default:
253 // For completeness, this is tone-mapping from SDR to SDR, where this is
254 // just a no-op.
255 program.append(R"(
256 float libtonemap_ToneMapTargetNits(vec3 xyz) {
257 return xyz.y;
258 }
259 )");
260 break;
261 }
262 break;
263 }
264
265 program.append(R"(
266 float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz) {
267 if (xyz.y <= 0.0) {
268 return 1.0;
269 }
270 return libtonemap_ToneMapTargetNits(xyz) / xyz.y;
271 }
272 )");
273 return program;
274 }
275
generateShaderSkSLUniforms(const Metadata & metadata)276 std::vector<ShaderUniform> generateShaderSkSLUniforms(const Metadata& metadata) override {
277 std::vector<ShaderUniform> uniforms;
278
279 uniforms.reserve(2);
280
281 uniforms.push_back({.name = "in_libtonemap_displayMaxLuminance",
282 .value = buildUniformValue<float>(metadata.displayMaxLuminance)});
283 uniforms.push_back({.name = "in_libtonemap_inputMaxLuminance",
284 .value = buildUniformValue<float>(metadata.contentMaxLuminance)});
285 return uniforms;
286 }
287
lookupTonemapGain(aidl::android::hardware::graphics::common::Dataspace sourceDataspace,aidl::android::hardware::graphics::common::Dataspace destinationDataspace,const std::vector<Color> & colors,const Metadata & metadata)288 std::vector<Gain> lookupTonemapGain(
289 aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
290 aidl::android::hardware::graphics::common::Dataspace destinationDataspace,
291 const std::vector<Color>& colors, const Metadata& metadata) override {
292 std::vector<Gain> gains;
293 gains.reserve(colors.size());
294
295 for (const auto [_, xyz] : colors) {
296 if (xyz.y <= 0.0) {
297 gains.push_back(1.0);
298 continue;
299 }
300 const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
301 const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace);
302
303 double targetNits = 0.0;
304 switch (sourceDataspaceInt & kTransferMask) {
305 case kTransferST2084:
306 case kTransferHLG:
307 switch (destinationDataspaceInt & kTransferMask) {
308 case kTransferST2084:
309 targetNits = xyz.y;
310 break;
311 case kTransferHLG:
312 // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG,
313 // so we'll clamp the luminance range in case we're mapping from PQ
314 // input to HLG output.
315 targetNits = std::clamp(xyz.y, 0.0f, 1000.0f);
316 targetNits *= std::pow(targetNits / 1000.f, -0.2 / 1.2);
317 break;
318 default:
319 // Here we're mapping from HDR to SDR content, so interpolate using a
320 // Hermitian polynomial onto the smaller luminance range.
321
322 targetNits = xyz.y;
323
324 if ((sourceDataspaceInt & kTransferMask) == kTransferHLG) {
325 targetNits *= std::pow(targetNits, 0.2);
326 }
327 // if the max input luminance is less than what we can output then
328 // no tone mapping is needed as all color values will be in range.
329 if (metadata.contentMaxLuminance > metadata.displayMaxLuminance) {
330 // three control points
331 const double x0 = 10.0;
332 const double y0 = 17.0;
333 double x1 = metadata.displayMaxLuminance * 0.75;
334 double y1 = x1;
335 double x2 = x1 + (metadata.contentMaxLuminance - x1) / 2.0;
336 double y2 = y1 + (metadata.displayMaxLuminance - y1) * 0.75;
337
338 // horizontal distances between the last three control points
339 double h12 = x2 - x1;
340 double h23 = metadata.contentMaxLuminance - x2;
341 // tangents at the last three control points
342 double m1 = (y2 - y1) / h12;
343 double m3 = (metadata.displayMaxLuminance - y2) / h23;
344 double m2 = (m1 + m3) / 2.0;
345
346 if (targetNits < x0) {
347 // scale [0.0, x0] to [0.0, y0] linearly
348 double slope = y0 / x0;
349 targetNits *= slope;
350 } else if (targetNits < x1) {
351 // scale [x0, x1] to [y0, y1] linearly
352 double slope = (y1 - y0) / (x1 - x0);
353 targetNits = y0 + (targetNits - x0) * slope;
354 } else if (targetNits < x2) {
355 // scale [x1, x2] to [y1, y2] using Hermite interp
356 double t = (targetNits - x1) / h12;
357 targetNits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) * (1.0 - t) *
358 (1.0 - t) +
359 (y2 * (3.0 - 2.0 * t) + h12 * m2 * (t - 1.0)) * t * t;
360 } else {
361 // scale [x2, maxInLumi] to [y2, maxOutLumi] using Hermite
362 // interp
363 double t = (targetNits - x2) / h23;
364 targetNits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) * (1.0 - t) *
365 (1.0 - t) +
366 (metadata.displayMaxLuminance * (3.0 - 2.0 * t) +
367 h23 * m3 * (t - 1.0)) *
368 t * t;
369 }
370 }
371 break;
372 }
373 break;
374 default:
375 // source is SDR
376 switch (destinationDataspaceInt & kTransferMask) {
377 case kTransferST2084:
378 case kTransferHLG: {
379 // Map from SDR onto an HDR output buffer
380 // Here we use a polynomial curve to map from [0, displayMaxLuminance]
381 // onto [0, maxOutLumi] which is hard-coded to be 3000 nits.
382 const double maxOutLumi = 3000.0;
383
384 double x0 = 5.0;
385 double y0 = 2.5;
386 double x1 = metadata.displayMaxLuminance * 0.7;
387 double y1 = maxOutLumi * 0.15;
388 double x2 = metadata.displayMaxLuminance * 0.9;
389 double y2 = maxOutLumi * 0.45;
390 double x3 = metadata.displayMaxLuminance;
391 double y3 = maxOutLumi;
392
393 double c1 = y1 / 3.0;
394 double c2 = y2 / 2.0;
395 double c3 = y3 / 1.5;
396
397 targetNits = xyz.y;
398
399 if (targetNits <= x0) {
400 // scale [0.0, x0] to [0.0, y0] linearly
401 double slope = y0 / x0;
402 targetNits *= slope;
403 } else if (targetNits <= x1) {
404 // scale [x0, x1] to [y0, y1] using a curve
405 double t = (targetNits - x0) / (x1 - x0);
406 targetNits = (1.0 - t) * (1.0 - t) * y0 + 2.0 * (1.0 - t) * t * c1 +
407 t * t * y1;
408 } else if (targetNits <= x2) {
409 // scale [x1, x2] to [y1, y2] using a curve
410 double t = (targetNits - x1) / (x2 - x1);
411 targetNits = (1.0 - t) * (1.0 - t) * y1 + 2.0 * (1.0 - t) * t * c2 +
412 t * t * y2;
413 } else {
414 // scale [x2, x3] to [y2, y3] using a curve
415 double t = (targetNits - x2) / (x3 - x2);
416 targetNits = (1.0 - t) * (1.0 - t) * y2 + 2.0 * (1.0 - t) * t * c3 +
417 t * t * y3;
418 }
419
420 if ((destinationDataspaceInt & kTransferMask) == kTransferHLG) {
421 targetNits *= std::pow(targetNits / 1000.0, -0.2 / 1.2);
422 }
423 } break;
424 default:
425 // For completeness, this is tone-mapping from SDR to SDR, where this is
426 // just a no-op.
427 targetNits = xyz.y;
428 break;
429 }
430 }
431 gains.push_back(targetNits / xyz.y);
432 }
433 return gains;
434 }
435 };
436
437 class ToneMapper13 : public ToneMapper {
438 private:
OETF_ST2084(double nits)439 double OETF_ST2084(double nits) {
440 nits = nits / 10000.0;
441 double m1 = (2610.0 / 4096.0) / 4.0;
442 double m2 = (2523.0 / 4096.0) * 128.0;
443 double c1 = (3424.0 / 4096.0);
444 double c2 = (2413.0 / 4096.0) * 32.0;
445 double c3 = (2392.0 / 4096.0) * 32.0;
446
447 double tmp = std::pow(nits, m1);
448 tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
449 return std::pow(tmp, m2);
450 }
451
OETF_HLG(double nits)452 double OETF_HLG(double nits) {
453 nits = nits / 1000.0;
454 const double a = 0.17883277;
455 const double b = 0.28466892;
456 const double c = 0.55991073;
457 return nits <= 1.0 / 12.0 ? std::sqrt(3.0 * nits) : a * std::log(12.0 * nits - b) + c;
458 }
459
460 public:
generateTonemapGainShaderSkSL(aidl::android::hardware::graphics::common::Dataspace sourceDataspace,aidl::android::hardware::graphics::common::Dataspace destinationDataspace)461 std::string generateTonemapGainShaderSkSL(
462 aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
463 aidl::android::hardware::graphics::common::Dataspace destinationDataspace) override {
464 const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
465 const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace);
466
467 std::string program;
468 // Input uniforms
469 program.append(R"(
470 uniform float in_libtonemap_displayMaxLuminance;
471 uniform float in_libtonemap_inputMaxLuminance;
472 uniform float in_libtonemap_hlgGamma;
473 )");
474 switch (sourceDataspaceInt & kTransferMask) {
475 case kTransferST2084:
476 switch (destinationDataspaceInt & kTransferMask) {
477 case kTransferST2084:
478 program.append(R"(
479 float libtonemap_ToneMapTargetNits(float maxRGB) {
480 return maxRGB;
481 }
482 )");
483 break;
484 case kTransferHLG:
485 // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, so
486 // we'll clamp the luminance range in case we're mapping from PQ input to
487 // HLG output.
488 program.append(R"(
489 float libtonemap_ToneMapTargetNits(float maxRGB) {
490 float nits = clamp(maxRGB, 0.0, 1000.0);
491 float gamma = (1 - in_libtonemap_hlgGamma)
492 / in_libtonemap_hlgGamma;
493 return nits * pow(nits / 1000.0, gamma);
494 }
495 )");
496 break;
497
498 default:
499 program.append(R"(
500 float libtonemap_OETFTone(float channel) {
501 channel = channel / 10000.0;
502 float m1 = (2610.0 / 4096.0) / 4.0;
503 float m2 = (2523.0 / 4096.0) * 128.0;
504 float c1 = (3424.0 / 4096.0);
505 float c2 = (2413.0 / 4096.0) * 32.0;
506 float c3 = (2392.0 / 4096.0) * 32.0;
507
508 float tmp = pow(channel, float(m1));
509 tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
510 return pow(tmp, float(m2));
511 }
512
513 float libtonemap_ToneMapTargetNits(float maxRGB) {
514 float maxInLumi = in_libtonemap_inputMaxLuminance;
515 float maxOutLumi = in_libtonemap_displayMaxLuminance;
516
517 float nits = maxRGB;
518
519 float x1 = maxOutLumi * 0.65;
520 float y1 = x1;
521
522 float x3 = maxInLumi;
523 float y3 = maxOutLumi;
524
525 float x2 = x1 + (x3 - x1) * 4.0 / 17.0;
526 float y2 = maxOutLumi * 0.9;
527
528 float greyNorm1 = libtonemap_OETFTone(x1);
529 float greyNorm2 = libtonemap_OETFTone(x2);
530 float greyNorm3 = libtonemap_OETFTone(x3);
531
532 float slope1 = 0;
533 float slope2 = (y2 - y1) / (greyNorm2 - greyNorm1);
534 float slope3 = (y3 - y2 ) / (greyNorm3 - greyNorm2);
535
536 if (nits < x1) {
537 return nits;
538 }
539
540 if (nits > maxInLumi) {
541 return maxOutLumi;
542 }
543
544 float greyNits = libtonemap_OETFTone(nits);
545
546 if (greyNits <= greyNorm2) {
547 nits = (greyNits - greyNorm2) * slope2 + y2;
548 } else if (greyNits <= greyNorm3) {
549 nits = (greyNits - greyNorm3) * slope3 + y3;
550 } else {
551 nits = maxOutLumi;
552 }
553
554 return nits;
555 }
556 )");
557 break;
558 }
559 break;
560 case kTransferHLG:
561 switch (destinationDataspaceInt & kTransferMask) {
562 // HLG uses the OOTF from BT 2100.
563 case kTransferST2084:
564 program.append(R"(
565 float libtonemap_ToneMapTargetNits(float maxRGB) {
566 return maxRGB
567 * pow(maxRGB / 1000.0, in_libtonemap_hlgGamma - 1);
568 }
569 )");
570 break;
571 case kTransferHLG:
572 program.append(R"(
573 float libtonemap_ToneMapTargetNits(float maxRGB) {
574 return maxRGB;
575 }
576 )");
577 break;
578 default:
579 // Follow BT 2100 and renormalize to max display luminance if we're
580 // tone-mapping down to SDR, as libshaders normalizes all SDR output from
581 // [0, maxDisplayLumins] -> [0, 1]
582 program.append(R"(
583 float libtonemap_ToneMapTargetNits(float maxRGB) {
584 return maxRGB
585 * pow(maxRGB / 1000.0, in_libtonemap_hlgGamma - 1)
586 * in_libtonemap_displayMaxLuminance / 1000.0;
587 }
588 )");
589 break;
590 }
591 break;
592 default:
593 // Inverse tone-mapping and SDR-SDR mapping is not supported.
594 program.append(R"(
595 float libtonemap_ToneMapTargetNits(float maxRGB) {
596 return maxRGB;
597 }
598 )");
599 break;
600 }
601
602 program.append(R"(
603 float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz) {
604 float maxRGB = max(linearRGB.r, max(linearRGB.g, linearRGB.b));
605 if (maxRGB <= 0.0) {
606 return 1.0;
607 }
608 return libtonemap_ToneMapTargetNits(maxRGB) / maxRGB;
609 }
610 )");
611 return program;
612 }
613
generateShaderSkSLUniforms(const Metadata & metadata)614 std::vector<ShaderUniform> generateShaderSkSLUniforms(const Metadata& metadata) override {
615 // Hardcode the max content luminance to a "reasonable" level
616 static const constexpr float kContentMaxLuminance = 4000.f;
617 std::vector<ShaderUniform> uniforms;
618 uniforms.reserve(3);
619 uniforms.push_back({.name = "in_libtonemap_displayMaxLuminance",
620 .value = buildUniformValue<float>(metadata.displayMaxLuminance)});
621 uniforms.push_back({.name = "in_libtonemap_inputMaxLuminance",
622 .value = buildUniformValue<float>(kContentMaxLuminance)});
623 uniforms.push_back({.name = "in_libtonemap_hlgGamma",
624 .value = buildUniformValue<float>(
625 computeHlgGamma(metadata.currentDisplayLuminance))});
626 return uniforms;
627 }
628
lookupTonemapGain(aidl::android::hardware::graphics::common::Dataspace sourceDataspace,aidl::android::hardware::graphics::common::Dataspace destinationDataspace,const std::vector<Color> & colors,const Metadata & metadata)629 std::vector<Gain> lookupTonemapGain(
630 aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
631 aidl::android::hardware::graphics::common::Dataspace destinationDataspace,
632 const std::vector<Color>& colors, const Metadata& metadata) override {
633 std::vector<Gain> gains;
634 gains.reserve(colors.size());
635
636 // Precompute constants for HDR->SDR tonemapping parameters
637 constexpr double maxInLumi = 4000;
638 const double maxOutLumi = metadata.displayMaxLuminance;
639
640 const double x1 = maxOutLumi * 0.65;
641 const double y1 = x1;
642
643 const double x3 = maxInLumi;
644 const double y3 = maxOutLumi;
645
646 const double x2 = x1 + (x3 - x1) * 4.0 / 17.0;
647 const double y2 = maxOutLumi * 0.9;
648
649 const double greyNorm1 = OETF_ST2084(x1);
650 const double greyNorm2 = OETF_ST2084(x2);
651 const double greyNorm3 = OETF_ST2084(x3);
652
653 const double slope2 = (y2 - y1) / (greyNorm2 - greyNorm1);
654 const double slope3 = (y3 - y2) / (greyNorm3 - greyNorm2);
655
656 const double hlgGamma = computeHlgGamma(metadata.currentDisplayLuminance);
657
658 for (const auto [linearRGB, _] : colors) {
659 double maxRGB = std::max({linearRGB.r, linearRGB.g, linearRGB.b});
660
661 if (maxRGB <= 0.0) {
662 gains.push_back(1.0);
663 continue;
664 }
665
666 const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
667 const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace);
668
669 double targetNits = 0.0;
670 switch (sourceDataspaceInt & kTransferMask) {
671 case kTransferST2084:
672 switch (destinationDataspaceInt & kTransferMask) {
673 case kTransferST2084:
674 targetNits = maxRGB;
675 break;
676 case kTransferHLG:
677 // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG,
678 // so we'll clamp the luminance range in case we're mapping from PQ
679 // input to HLG output.
680 targetNits = std::clamp(maxRGB, 0.0, 1000.0);
681 targetNits *= pow(targetNits / 1000.0, (1 - hlgGamma) / (hlgGamma));
682 break;
683 default:
684 targetNits = maxRGB;
685 if (targetNits < x1) {
686 break;
687 }
688
689 if (targetNits > maxInLumi) {
690 targetNits = maxOutLumi;
691 break;
692 }
693
694 const double greyNits = OETF_ST2084(targetNits);
695
696 if (greyNits <= greyNorm2) {
697 targetNits = (greyNits - greyNorm2) * slope2 + y2;
698 } else if (greyNits <= greyNorm3) {
699 targetNits = (greyNits - greyNorm3) * slope3 + y3;
700 } else {
701 targetNits = maxOutLumi;
702 }
703 break;
704 }
705 break;
706 case kTransferHLG:
707 switch (destinationDataspaceInt & kTransferMask) {
708 case kTransferST2084:
709 targetNits = maxRGB * pow(maxRGB / 1000.0, hlgGamma - 1);
710 break;
711 case kTransferHLG:
712 targetNits = maxRGB;
713 break;
714 default:
715 targetNits = maxRGB * pow(maxRGB / 1000.0, hlgGamma - 1) *
716 metadata.displayMaxLuminance / 1000.0;
717 break;
718 }
719 break;
720 default:
721 targetNits = maxRGB;
722 break;
723 }
724
725 gains.push_back(targetNits / maxRGB);
726 }
727 return gains;
728 }
729 };
730
731 } // namespace
732
getToneMapper()733 ToneMapper* getToneMapper() {
734 static std::once_flag sOnce;
735 static std::unique_ptr<ToneMapper> sToneMapper;
736
737 std::call_once(sOnce, [&] {
738 switch (kToneMapAlgorithm) {
739 case ToneMapAlgorithm::AndroidO:
740 sToneMapper = std::unique_ptr<ToneMapper>(new ToneMapperO());
741 break;
742 case ToneMapAlgorithm::Android13:
743 sToneMapper = std::unique_ptr<ToneMapper>(new ToneMapper13());
744 }
745 });
746
747 return sToneMapper.get();
748 }
749 } // namespace android::tonemap
750