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