blob: 2cec773eb36c984a1efc4e4e4510e9a330125821 [file] [log] [blame]
Alec Mouri465b2962021-10-08 16:22:21 -07001/*
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 <cstdint>
20#include <mutex>
21#include <type_traits>
22
23namespace android::tonemap {
24
25namespace {
26
27// Flag containing the variant of tone map algorithm to use.
28enum class ToneMapAlgorithm {
Alec Mouri5184f412021-10-14 18:13:49 -070029 AndroidO, // Default algorithm in place since Android O,
30 Android13, // Algorithm used in Android 13.
Alec Mouri465b2962021-10-08 16:22:21 -070031};
32
Alec Mouri5184f412021-10-14 18:13:49 -070033static const constexpr auto kToneMapAlgorithm = ToneMapAlgorithm::Android13;
Alec Mouri465b2962021-10-08 16:22:21 -070034
35static const constexpr auto kTransferMask =
36 static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_MASK);
37static const constexpr auto kTransferST2084 =
38 static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_ST2084);
39static const constexpr auto kTransferHLG =
40 static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_HLG);
41
42template <typename T, std::enable_if_t<std::is_trivially_copyable<T>::value, bool> = true>
43std::vector<uint8_t> buildUniformValue(T value) {
44 std::vector<uint8_t> result;
45 result.resize(sizeof(value));
46 std::memcpy(result.data(), &value, sizeof(value));
47 return result;
48}
49
50class ToneMapperO : public ToneMapper {
51public:
52 std::string generateTonemapGainShaderSkSL(
53 aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
54 aidl::android::hardware::graphics::common::Dataspace destinationDataspace) override {
55 const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
56 const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace);
57
58 std::string program;
59 // Define required uniforms
60 program.append(R"(
61 uniform float in_libtonemap_displayMaxLuminance;
62 uniform float in_libtonemap_inputMaxLuminance;
63 )");
64 switch (sourceDataspaceInt & kTransferMask) {
65 case kTransferST2084:
66 case kTransferHLG:
67 switch (destinationDataspaceInt & kTransferMask) {
68 case kTransferST2084:
69 program.append(R"(
70 float libtonemap_ToneMapTargetNits(vec3 xyz) {
71 return xyz.y;
72 }
73 )");
74 break;
75 case kTransferHLG:
76 // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, so
77 // we'll clamp the luminance range in case we're mapping from PQ input to
78 // HLG output.
79 program.append(R"(
80 float libtonemap_ToneMapTargetNits(vec3 xyz) {
81 return clamp(xyz.y, 0.0, 1000.0);
82 }
83 )");
84 break;
85 default:
86 // Here we're mapping from HDR to SDR content, so interpolate using a
87 // Hermitian polynomial onto the smaller luminance range.
88 program.append(R"(
89 float libtonemap_ToneMapTargetNits(vec3 xyz) {
90 float maxInLumi = in_libtonemap_inputMaxLuminance;
91 float maxOutLumi = in_libtonemap_displayMaxLuminance;
92
93 float nits = xyz.y;
94
95 // if the max input luminance is less than what we can
96 // output then no tone mapping is needed as all color
97 // values will be in range.
98 if (maxInLumi <= maxOutLumi) {
99 return xyz.y;
100 } else {
101
102 // three control points
103 const float x0 = 10.0;
104 const float y0 = 17.0;
105 float x1 = maxOutLumi * 0.75;
106 float y1 = x1;
107 float x2 = x1 + (maxInLumi - x1) / 2.0;
108 float y2 = y1 + (maxOutLumi - y1) * 0.75;
109
110 // horizontal distances between the last three
111 // control points
112 float h12 = x2 - x1;
113 float h23 = maxInLumi - x2;
114 // tangents at the last three control points
115 float m1 = (y2 - y1) / h12;
116 float m3 = (maxOutLumi - y2) / h23;
117 float m2 = (m1 + m3) / 2.0;
118
119 if (nits < x0) {
120 // scale [0.0, x0] to [0.0, y0] linearly
121 float slope = y0 / x0;
122 return nits * slope;
123 } else if (nits < x1) {
124 // scale [x0, x1] to [y0, y1] linearly
125 float slope = (y1 - y0) / (x1 - x0);
126 nits = y0 + (nits - x0) * slope;
127 } else if (nits < x2) {
128 // scale [x1, x2] to [y1, y2] using Hermite interp
129 float t = (nits - x1) / h12;
130 nits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) *
131 (1.0 - t) * (1.0 - t) +
132 (y2 * (3.0 - 2.0 * t) +
133 h12 * m2 * (t - 1.0)) * t * t;
134 } else {
135 // scale [x2, maxInLumi] to [y2, maxOutLumi] using
136 // Hermite interp
137 float t = (nits - x2) / h23;
138 nits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) *
139 (1.0 - t) * (1.0 - t) + (maxOutLumi *
140 (3.0 - 2.0 * t) + h23 * m3 *
141 (t - 1.0)) * t * t;
142 }
143 }
144
145 return nits;
146 }
147 )");
148 break;
149 }
150 break;
151 default:
152 switch (destinationDataspaceInt & kTransferMask) {
153 case kTransferST2084:
154 case kTransferHLG:
155 // Map from SDR onto an HDR output buffer
156 // Here we use a polynomial curve to map from [0, displayMaxLuminance] onto
157 // [0, maxOutLumi] which is hard-coded to be 3000 nits.
158 program.append(R"(
159 float libtonemap_ToneMapTargetNits(vec3 xyz) {
160 const float maxOutLumi = 3000.0;
161
162 const float x0 = 5.0;
163 const float y0 = 2.5;
164 float x1 = in_libtonemap_displayMaxLuminance * 0.7;
165 float y1 = maxOutLumi * 0.15;
166 float x2 = in_libtonemap_displayMaxLuminance * 0.9;
167 float y2 = maxOutLumi * 0.45;
168 float x3 = in_libtonemap_displayMaxLuminance;
169 float y3 = maxOutLumi;
170
171 float c1 = y1 / 3.0;
172 float c2 = y2 / 2.0;
173 float c3 = y3 / 1.5;
174
175 float nits = xyz.y;
176
177 if (nits <= x0) {
178 // scale [0.0, x0] to [0.0, y0] linearly
179 float slope = y0 / x0;
180 return nits * slope;
181 } else if (nits <= x1) {
182 // scale [x0, x1] to [y0, y1] using a curve
183 float t = (nits - x0) / (x1 - x0);
184 nits = (1.0 - t) * (1.0 - t) * y0 +
185 2.0 * (1.0 - t) * t * c1 + t * t * y1;
186 } else if (nits <= x2) {
187 // scale [x1, x2] to [y1, y2] using a curve
188 float t = (nits - x1) / (x2 - x1);
189 nits = (1.0 - t) * (1.0 - t) * y1 +
190 2.0 * (1.0 - t) * t * c2 + t * t * y2;
191 } else {
192 // scale [x2, x3] to [y2, y3] using a curve
193 float t = (nits - x2) / (x3 - x2);
194 nits = (1.0 - t) * (1.0 - t) * y2 +
195 2.0 * (1.0 - t) * t * c3 + t * t * y3;
196 }
197
198 return nits;
199 }
200 )");
201 break;
202 default:
203 // For completeness, this is tone-mapping from SDR to SDR, where this is
204 // just a no-op.
205 program.append(R"(
206 float libtonemap_ToneMapTargetNits(vec3 xyz) {
207 return xyz.y;
208 }
209 )");
210 break;
211 }
212 break;
213 }
214
215 program.append(R"(
216 float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz) {
217 if (xyz.y <= 0.0) {
218 return 1.0;
219 }
220 return libtonemap_ToneMapTargetNits(xyz) / xyz.y;
221 }
222 )");
223 return program;
224 }
225
226 std::vector<ShaderUniform> generateShaderSkSLUniforms(const Metadata& metadata) override {
227 std::vector<ShaderUniform> uniforms;
228
229 uniforms.reserve(2);
230
231 uniforms.push_back({.name = "in_libtonemap_displayMaxLuminance",
232 .value = buildUniformValue<float>(metadata.displayMaxLuminance)});
233 uniforms.push_back({.name = "in_libtonemap_inputMaxLuminance",
234 .value = buildUniformValue<float>(metadata.contentMaxLuminance)});
Alec Mouri5184f412021-10-14 18:13:49 -0700235 return uniforms;
236 }
237};
Alec Mouri465b2962021-10-08 16:22:21 -0700238
Alec Mouri5184f412021-10-14 18:13:49 -0700239class ToneMapper13 : public ToneMapper {
240public:
241 std::string generateTonemapGainShaderSkSL(
242 aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
243 aidl::android::hardware::graphics::common::Dataspace destinationDataspace) override {
244 const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
245 const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace);
246
247 std::string program;
248 // Input uniforms
249 program.append(R"(
250 uniform float in_libtonemap_displayMaxLuminance;
251 uniform float in_libtonemap_inputMaxLuminance;
252 )");
253 switch (sourceDataspaceInt & kTransferMask) {
254 case kTransferST2084:
255 case kTransferHLG:
256 switch (destinationDataspaceInt & kTransferMask) {
257 case kTransferST2084:
258 program.append(R"(
259 float libtonemap_ToneMapTargetNits(float maxRGB) {
260 return maxRGB;
261 }
262 )");
263 break;
264 case kTransferHLG:
265 // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, so
266 // we'll clamp the luminance range in case we're mapping from PQ input to
267 // HLG output.
268 program.append(R"(
269 float libtonemap_ToneMapTargetNits(float maxRGB) {
270 return clamp(maxRGB, 0.0, 1000.0);
271 }
272 )");
273 break;
274
275 default:
276 switch (sourceDataspaceInt & kTransferMask) {
277 case kTransferST2084:
278 program.append(R"(
279 float libtonemap_OETFTone(float channel) {
280 channel = channel / 10000.0;
281 float m1 = (2610.0 / 4096.0) / 4.0;
282 float m2 = (2523.0 / 4096.0) * 128.0;
283 float c1 = (3424.0 / 4096.0);
284 float c2 = (2413.0 / 4096.0) * 32.0;
285 float c3 = (2392.0 / 4096.0) * 32.0;
286
287 float tmp = pow(channel, float(m1));
288 tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
289 return pow(tmp, float(m2));
290 }
291 )");
292 break;
293 case kTransferHLG:
294 program.append(R"(
295 float libtonemap_OETFTone(float channel) {
296 channel = channel / 1000.0;
297 const float a = 0.17883277;
298 const float b = 0.28466892;
299 const float c = 0.55991073;
300 return channel <= 1.0 / 12.0 ? sqrt(3.0 * channel) :
301 a * log(12.0 * channel - b) + c;
302 }
303 )");
304 break;
305 }
306 // Here we're mapping from HDR to SDR content, so interpolate using a
307 // Hermitian polynomial onto the smaller luminance range.
308 program.append(R"(
309 float libtonemap_ToneMapTargetNits(float maxRGB) {
310 float maxInLumi = in_libtonemap_inputMaxLuminance;
311 float maxOutLumi = in_libtonemap_displayMaxLuminance;
312
313 float nits = maxRGB;
314
315 float x1 = maxOutLumi * 0.65;
316 float y1 = x1;
317
318 float x3 = maxInLumi;
319 float y3 = maxOutLumi;
320
321 float x2 = x1 + (x3 - x1) * 4.0 / 17.0;
322 float y2 = maxOutLumi * 0.9;
323
324 float greyNorm1 = libtonemap_OETFTone(x1);
325 float greyNorm2 = libtonemap_OETFTone(x2);
326 float greyNorm3 = libtonemap_OETFTone(x3);
327
328 float slope1 = 0;
329 float slope2 = (y2 - y1) / (greyNorm2 - greyNorm1);
330 float slope3 = (y3 - y2 ) / (greyNorm3 - greyNorm2);
331
332 if (nits < x1) {
333 return nits;
334 }
335
336 if (nits > maxInLumi) {
337 return maxOutLumi;
338 }
339
340 float greyNits = libtonemap_OETFTone(nits);
341
342 if (greyNits <= greyNorm2) {
343 nits = (greyNits - greyNorm2) * slope2 + y2;
344 } else if (greyNits <= greyNorm3) {
345 nits = (greyNits - greyNorm3) * slope3 + y3;
346 } else {
347 nits = maxOutLumi;
348 }
349
350 return nits;
351 }
352 )");
353 break;
354 }
355 break;
356 default:
357 // Inverse tone-mapping and SDR-SDR mapping is not supported.
358 program.append(R"(
359 float libtonemap_ToneMapTargetNits(float maxRGB) {
360 return maxRGB;
361 }
362 )");
363 break;
364 }
365
366 program.append(R"(
367 float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz) {
368 float maxRGB = max(linearRGB.r, max(linearRGB.g, linearRGB.b));
369 if (maxRGB <= 0.0) {
370 return 1.0;
371 }
372 return libtonemap_ToneMapTargetNits(maxRGB) / maxRGB;
373 }
374 )");
375 return program;
376 }
377
378 std::vector<ShaderUniform> generateShaderSkSLUniforms(const Metadata& metadata) override {
379 // Hardcode the max content luminance to a "reasonable" level
380 static const constexpr float kContentMaxLuminance = 4000.f;
381 std::vector<ShaderUniform> uniforms;
382 uniforms.reserve(2);
383 uniforms.push_back({.name = "in_libtonemap_displayMaxLuminance",
384 .value = buildUniformValue<float>(metadata.displayMaxLuminance)});
385 uniforms.push_back({.name = "in_libtonemap_inputMaxLuminance",
386 .value = buildUniformValue<float>(kContentMaxLuminance)});
Alec Mouri465b2962021-10-08 16:22:21 -0700387 return uniforms;
388 }
389};
390
391} // namespace
392
393ToneMapper* getToneMapper() {
394 static std::once_flag sOnce;
395 static std::unique_ptr<ToneMapper> sToneMapper;
396
397 std::call_once(sOnce, [&] {
398 switch (kToneMapAlgorithm) {
399 case ToneMapAlgorithm::AndroidO:
400 sToneMapper = std::unique_ptr<ToneMapper>(new ToneMapperO());
401 break;
Alec Mouri5184f412021-10-14 18:13:49 -0700402 case ToneMapAlgorithm::Android13:
403 sToneMapper = std::unique_ptr<ToneMapper>(new ToneMapper13());
Alec Mouri465b2962021-10-08 16:22:21 -0700404 }
405 });
406
407 return sToneMapper.get();
408}
409
410} // namespace android::tonemap