• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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