blob: 350bca427cf189365de462bbe5c932c9de224b7c [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 {
29 AndroidO, // Default algorithm in place since Android O,
30};
31
32static const constexpr auto kToneMapAlgorithm = ToneMapAlgorithm::AndroidO;
33
34static const constexpr auto kTransferMask =
35 static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_MASK);
36static const constexpr auto kTransferST2084 =
37 static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_ST2084);
38static const constexpr auto kTransferHLG =
39 static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_HLG);
40
41template <typename T, std::enable_if_t<std::is_trivially_copyable<T>::value, bool> = true>
42std::vector<uint8_t> buildUniformValue(T value) {
43 std::vector<uint8_t> result;
44 result.resize(sizeof(value));
45 std::memcpy(result.data(), &value, sizeof(value));
46 return result;
47}
48
49class ToneMapperO : public ToneMapper {
50public:
51 std::string generateTonemapGainShaderSkSL(
52 aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
53 aidl::android::hardware::graphics::common::Dataspace destinationDataspace) override {
54 const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
55 const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace);
56
57 std::string program;
58 // Define required uniforms
59 program.append(R"(
60 uniform float in_libtonemap_displayMaxLuminance;
61 uniform float in_libtonemap_inputMaxLuminance;
62 )");
63 switch (sourceDataspaceInt & kTransferMask) {
64 case kTransferST2084:
65 case kTransferHLG:
66 switch (destinationDataspaceInt & kTransferMask) {
67 case kTransferST2084:
68 program.append(R"(
69 float libtonemap_ToneMapTargetNits(vec3 xyz) {
70 return xyz.y;
71 }
72 )");
73 break;
74 case kTransferHLG:
75 // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, so
76 // we'll clamp the luminance range in case we're mapping from PQ input to
77 // HLG output.
78 program.append(R"(
79 float libtonemap_ToneMapTargetNits(vec3 xyz) {
80 return clamp(xyz.y, 0.0, 1000.0);
81 }
82 )");
83 break;
84 default:
85 // Here we're mapping from HDR to SDR content, so interpolate using a
86 // Hermitian polynomial onto the smaller luminance range.
87 program.append(R"(
88 float libtonemap_ToneMapTargetNits(vec3 xyz) {
89 float maxInLumi = in_libtonemap_inputMaxLuminance;
90 float maxOutLumi = in_libtonemap_displayMaxLuminance;
91
92 float nits = xyz.y;
93
94 // if the max input luminance is less than what we can
95 // output then no tone mapping is needed as all color
96 // values will be in range.
97 if (maxInLumi <= maxOutLumi) {
98 return xyz.y;
99 } else {
100
101 // three control points
102 const float x0 = 10.0;
103 const float y0 = 17.0;
104 float x1 = maxOutLumi * 0.75;
105 float y1 = x1;
106 float x2 = x1 + (maxInLumi - x1) / 2.0;
107 float y2 = y1 + (maxOutLumi - y1) * 0.75;
108
109 // horizontal distances between the last three
110 // control points
111 float h12 = x2 - x1;
112 float h23 = maxInLumi - x2;
113 // tangents at the last three control points
114 float m1 = (y2 - y1) / h12;
115 float m3 = (maxOutLumi - y2) / h23;
116 float m2 = (m1 + m3) / 2.0;
117
118 if (nits < x0) {
119 // scale [0.0, x0] to [0.0, y0] linearly
120 float slope = y0 / x0;
121 return nits * slope;
122 } else if (nits < x1) {
123 // scale [x0, x1] to [y0, y1] linearly
124 float slope = (y1 - y0) / (x1 - x0);
125 nits = y0 + (nits - x0) * slope;
126 } else if (nits < x2) {
127 // scale [x1, x2] to [y1, y2] using Hermite interp
128 float t = (nits - x1) / h12;
129 nits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) *
130 (1.0 - t) * (1.0 - t) +
131 (y2 * (3.0 - 2.0 * t) +
132 h12 * m2 * (t - 1.0)) * t * t;
133 } else {
134 // scale [x2, maxInLumi] to [y2, maxOutLumi] using
135 // Hermite interp
136 float t = (nits - x2) / h23;
137 nits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) *
138 (1.0 - t) * (1.0 - t) + (maxOutLumi *
139 (3.0 - 2.0 * t) + h23 * m3 *
140 (t - 1.0)) * t * t;
141 }
142 }
143
144 return nits;
145 }
146 )");
147 break;
148 }
149 break;
150 default:
151 switch (destinationDataspaceInt & kTransferMask) {
152 case kTransferST2084:
153 case kTransferHLG:
154 // Map from SDR onto an HDR output buffer
155 // Here we use a polynomial curve to map from [0, displayMaxLuminance] onto
156 // [0, maxOutLumi] which is hard-coded to be 3000 nits.
157 program.append(R"(
158 float libtonemap_ToneMapTargetNits(vec3 xyz) {
159 const float maxOutLumi = 3000.0;
160
161 const float x0 = 5.0;
162 const float y0 = 2.5;
163 float x1 = in_libtonemap_displayMaxLuminance * 0.7;
164 float y1 = maxOutLumi * 0.15;
165 float x2 = in_libtonemap_displayMaxLuminance * 0.9;
166 float y2 = maxOutLumi * 0.45;
167 float x3 = in_libtonemap_displayMaxLuminance;
168 float y3 = maxOutLumi;
169
170 float c1 = y1 / 3.0;
171 float c2 = y2 / 2.0;
172 float c3 = y3 / 1.5;
173
174 float nits = xyz.y;
175
176 if (nits <= x0) {
177 // scale [0.0, x0] to [0.0, y0] linearly
178 float slope = y0 / x0;
179 return nits * slope;
180 } else if (nits <= x1) {
181 // scale [x0, x1] to [y0, y1] using a curve
182 float t = (nits - x0) / (x1 - x0);
183 nits = (1.0 - t) * (1.0 - t) * y0 +
184 2.0 * (1.0 - t) * t * c1 + t * t * y1;
185 } else if (nits <= x2) {
186 // scale [x1, x2] to [y1, y2] using a curve
187 float t = (nits - x1) / (x2 - x1);
188 nits = (1.0 - t) * (1.0 - t) * y1 +
189 2.0 * (1.0 - t) * t * c2 + t * t * y2;
190 } else {
191 // scale [x2, x3] to [y2, y3] using a curve
192 float t = (nits - x2) / (x3 - x2);
193 nits = (1.0 - t) * (1.0 - t) * y2 +
194 2.0 * (1.0 - t) * t * c3 + t * t * y3;
195 }
196
197 return nits;
198 }
199 )");
200 break;
201 default:
202 // For completeness, this is tone-mapping from SDR to SDR, where this is
203 // just a no-op.
204 program.append(R"(
205 float libtonemap_ToneMapTargetNits(vec3 xyz) {
206 return xyz.y;
207 }
208 )");
209 break;
210 }
211 break;
212 }
213
214 program.append(R"(
215 float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz) {
216 if (xyz.y <= 0.0) {
217 return 1.0;
218 }
219 return libtonemap_ToneMapTargetNits(xyz) / xyz.y;
220 }
221 )");
222 return program;
223 }
224
225 std::vector<ShaderUniform> generateShaderSkSLUniforms(const Metadata& metadata) override {
226 std::vector<ShaderUniform> uniforms;
227
228 uniforms.reserve(2);
229
230 uniforms.push_back({.name = "in_libtonemap_displayMaxLuminance",
231 .value = buildUniformValue<float>(metadata.displayMaxLuminance)});
232 uniforms.push_back({.name = "in_libtonemap_inputMaxLuminance",
233 .value = buildUniformValue<float>(metadata.contentMaxLuminance)});
234
235 return uniforms;
236 }
237};
238
239} // namespace
240
241ToneMapper* getToneMapper() {
242 static std::once_flag sOnce;
243 static std::unique_ptr<ToneMapper> sToneMapper;
244
245 std::call_once(sOnce, [&] {
246 switch (kToneMapAlgorithm) {
247 case ToneMapAlgorithm::AndroidO:
248 sToneMapper = std::unique_ptr<ToneMapper>(new ToneMapperO());
249 break;
250 }
251 });
252
253 return sToneMapper.get();
254}
255
256} // namespace android::tonemap