1 /*
2  * Copyright 2020 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 "LinearEffect.h"
18 
19 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
20 
21 #include <SkString.h>
22 #include <utils/Trace.h>
23 
24 #include <optional>
25 
26 #include "log/log.h"
27 #include "math/mat4.h"
28 #include "system/graphics-base-v1.0.h"
29 #include "ui/ColorSpace.h"
30 
31 namespace android {
32 namespace renderengine {
33 namespace skia {
34 
generateEOTF(ui::Dataspace dataspace,SkString & shader)35 static void generateEOTF(ui::Dataspace dataspace, SkString& shader) {
36     switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) {
37         case HAL_DATASPACE_TRANSFER_ST2084:
38             shader.append(R"(
39 
40                 float3 EOTF(float3 color) {
41                     float m1 = (2610.0 / 4096.0) / 4.0;
42                     float m2 = (2523.0 / 4096.0) * 128.0;
43                     float c1 = (3424.0 / 4096.0);
44                     float c2 = (2413.0 / 4096.0) * 32.0;
45                     float c3 = (2392.0 / 4096.0) * 32.0;
46 
47                     float3 tmp = pow(clamp(color, 0.0, 1.0), 1.0 / float3(m2));
48                     tmp = max(tmp - c1, 0.0) / (c2 - c3 * tmp);
49                     return pow(tmp, 1.0 / float3(m1));
50                 }
51             )");
52             break;
53         case HAL_DATASPACE_TRANSFER_HLG:
54             shader.append(R"(
55                 float EOTF_channel(float channel) {
56                     const float a = 0.17883277;
57                     const float b = 0.28466892;
58                     const float c = 0.55991073;
59                     return channel <= 0.5 ? channel * channel / 3.0 :
60                             (exp((channel - c) / a) + b) / 12.0;
61                 }
62 
63                 float3 EOTF(float3 color) {
64                     return float3(EOTF_channel(color.r), EOTF_channel(color.g),
65                             EOTF_channel(color.b));
66                 }
67             )");
68             break;
69         case HAL_DATASPACE_TRANSFER_LINEAR:
70             shader.append(R"(
71                 float3 EOTF(float3 color) {
72                     return color;
73                 }
74             )");
75             break;
76         case HAL_DATASPACE_TRANSFER_SRGB:
77         default:
78             shader.append(R"(
79 
80                 float EOTF_sRGB(float srgb) {
81                     return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4);
82                 }
83 
84                 float3 EOTF_sRGB(float3 srgb) {
85                     return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
86                 }
87 
88                 float3 EOTF(float3 srgb) {
89                     return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
90                 }
91             )");
92             break;
93     }
94 }
95 
generateXYZTransforms(SkString & shader)96 static void generateXYZTransforms(SkString& shader) {
97     shader.append(R"(
98         uniform float4x4 in_rgbToXyz;
99         uniform float4x4 in_xyzToRgb;
100         float3 ToXYZ(float3 rgb) {
101             return clamp((in_rgbToXyz * float4(rgb, 1.0)).rgb, 0.0, 1.0);
102         }
103 
104         float3 ToRGB(float3 xyz) {
105             return clamp((in_xyzToRgb * float4(xyz, 1.0)).rgb, 0.0, 1.0);
106         }
107     )");
108 }
109 
110 // Conversion from relative light to absolute light (maps from [0, 1] to [0, maxNits])
generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace,SkString & shader)111 static void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace, SkString& shader) {
112     switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
113         case HAL_DATASPACE_TRANSFER_ST2084:
114             shader.append(R"(
115                     float3 ScaleLuminance(float3 xyz) {
116                         return xyz * 10000.0;
117                     }
118                 )");
119             break;
120         case HAL_DATASPACE_TRANSFER_HLG:
121             shader.append(R"(
122                     float3 ScaleLuminance(float3 xyz) {
123                         return xyz * 1000.0 * pow(xyz.y, 0.2);
124                     }
125                 )");
126             break;
127         default:
128             shader.append(R"(
129                     float3 ScaleLuminance(float3 xyz) {
130                         return xyz * in_inputMaxLuminance;
131                     }
132                 )");
133             break;
134     }
135 }
136 
generateToneMapInterpolation(ui::Dataspace inputDataspace,ui::Dataspace outputDataspace,SkString & shader)137 static void generateToneMapInterpolation(ui::Dataspace inputDataspace,
138                                          ui::Dataspace outputDataspace, SkString& shader) {
139     switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
140         case HAL_DATASPACE_TRANSFER_ST2084:
141         case HAL_DATASPACE_TRANSFER_HLG:
142             switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
143                 case HAL_DATASPACE_TRANSFER_ST2084:
144                     shader.append(R"(
145                             float3 ToneMap(float3 xyz) {
146                                 return xyz;
147                             }
148                         )");
149                     break;
150                 case HAL_DATASPACE_TRANSFER_HLG:
151                     // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, so
152                     // we'll clamp the luminance range in case we're mapping from PQ input to HLG
153                     // output.
154                     shader.append(R"(
155                             float3 ToneMap(float3 xyz) {
156                                 return clamp(xyz, 0.0, 1000.0);
157                             }
158                         )");
159                     break;
160                 default:
161                     // Here we're mapping from HDR to SDR content, so interpolate using a Hermitian
162                     // polynomial onto the smaller luminance range.
163                     shader.append(R"(
164                             float3 ToneMap(float3 xyz) {
165                                 float maxInLumi = in_inputMaxLuminance;
166                                 float maxOutLumi = in_displayMaxLuminance;
167 
168                                 float nits = xyz.y;
169 
170                                 // if the max input luminance is less than what we can output then
171                                 // no tone mapping is needed as all color values will be in range.
172                                 if (maxInLumi <= maxOutLumi) {
173                                     return xyz;
174                                 } else {
175 
176                                     // three control points
177                                     const float x0 = 10.0;
178                                     const float y0 = 17.0;
179                                     float x1 = maxOutLumi * 0.75;
180                                     float y1 = x1;
181                                     float x2 = x1 + (maxInLumi - x1) / 2.0;
182                                     float y2 = y1 + (maxOutLumi - y1) * 0.75;
183 
184                                     // horizontal distances between the last three control points
185                                     float h12 = x2 - x1;
186                                     float h23 = maxInLumi - x2;
187                                     // tangents at the last three control points
188                                     float m1 = (y2 - y1) / h12;
189                                     float m3 = (maxOutLumi - y2) / h23;
190                                     float m2 = (m1 + m3) / 2.0;
191 
192                                     if (nits < x0) {
193                                         // scale [0.0, x0] to [0.0, y0] linearly
194                                         float slope = y0 / x0;
195                                         return xyz * slope;
196                                     } else if (nits < x1) {
197                                         // scale [x0, x1] to [y0, y1] linearly
198                                         float slope = (y1 - y0) / (x1 - x0);
199                                         nits = y0 + (nits - x0) * slope;
200                                     } else if (nits < x2) {
201                                         // scale [x1, x2] to [y1, y2] using Hermite interp
202                                         float t = (nits - x1) / h12;
203                                         nits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) * (1.0 - t) * (1.0 - t) +
204                                                 (y2 * (3.0 - 2.0 * t) + h12 * m2 * (t - 1.0)) * t * t;
205                                     } else {
206                                         // scale [x2, maxInLumi] to [y2, maxOutLumi] using Hermite interp
207                                         float t = (nits - x2) / h23;
208                                         nits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) * (1.0 - t) * (1.0 - t) +
209                                                 (maxOutLumi * (3.0 - 2.0 * t) + h23 * m3 * (t - 1.0)) * t * t;
210                                     }
211                                 }
212 
213                                 // color.y is greater than x0 and is thus non-zero
214                                 return xyz * (nits / xyz.y);
215                             }
216                         )");
217                     break;
218             }
219             break;
220         default:
221             switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
222                 case HAL_DATASPACE_TRANSFER_ST2084:
223                 case HAL_DATASPACE_TRANSFER_HLG:
224                     // Map from SDR onto an HDR output buffer
225                     // Here we use a polynomial curve to map from [0, displayMaxLuminance] onto
226                     // [0, maxOutLumi] which is hard-coded to be 3000 nits.
227                     shader.append(R"(
228                             float3 ToneMap(float3 xyz) {
229                                 const float maxOutLumi = 3000.0;
230 
231                                 const float x0 = 5.0;
232                                 const float y0 = 2.5;
233                                 float x1 = in_displayMaxLuminance * 0.7;
234                                 float y1 = maxOutLumi * 0.15;
235                                 float x2 = in_displayMaxLuminance * 0.9;
236                                 float y2 = maxOutLumi * 0.45;
237                                 float x3 = in_displayMaxLuminance;
238                                 float y3 = maxOutLumi;
239 
240                                 float c1 = y1 / 3.0;
241                                 float c2 = y2 / 2.0;
242                                 float c3 = y3 / 1.5;
243 
244                                 float nits = xyz.y;
245 
246                                 if (nits <= x0) {
247                                     // scale [0.0, x0] to [0.0, y0] linearly
248                                     float slope = y0 / x0;
249                                     return xyz * slope;
250                                 } else if (nits <= x1) {
251                                     // scale [x0, x1] to [y0, y1] using a curve
252                                     float t = (nits - x0) / (x1 - x0);
253                                     nits = (1.0 - t) * (1.0 - t) * y0 + 2.0 * (1.0 - t) * t * c1 + t * t * y1;
254                                 } else if (nits <= x2) {
255                                     // scale [x1, x2] to [y1, y2] using a curve
256                                     float t = (nits - x1) / (x2 - x1);
257                                     nits = (1.0 - t) * (1.0 - t) * y1 + 2.0 * (1.0 - t) * t * c2 + t * t * y2;
258                                 } else {
259                                     // scale [x2, x3] to [y2, y3] using a curve
260                                     float t = (nits - x2) / (x3 - x2);
261                                     nits = (1.0 - t) * (1.0 - t) * y2 + 2.0 * (1.0 - t) * t * c3 + t * t * y3;
262                                 }
263 
264                                 // xyz.y is greater than x0 and is thus non-zero
265                                 return xyz * (nits / xyz.y);
266                             }
267                         )");
268                     break;
269                 default:
270                     // For completeness, this is tone-mapping from SDR to SDR, where this is just a
271                     // no-op.
272                     shader.append(R"(
273                             float3 ToneMap(float3 xyz) {
274                                 return xyz;
275                             }
276                         )");
277                     break;
278             }
279             break;
280     }
281 }
282 
283 // Normalizes from absolute light back to relative light (maps from [0, maxNits] back to [0, 1])
generateLuminanceNormalizationForOOTF(ui::Dataspace outputDataspace,SkString & shader)284 static void generateLuminanceNormalizationForOOTF(ui::Dataspace outputDataspace, SkString& shader) {
285     switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
286         case HAL_DATASPACE_TRANSFER_ST2084:
287             shader.append(R"(
288                     float3 NormalizeLuminance(float3 xyz) {
289                         return xyz / 10000.0;
290                     }
291                 )");
292             break;
293         case HAL_DATASPACE_TRANSFER_HLG:
294             shader.append(R"(
295                     float3 NormalizeLuminance(float3 xyz) {
296                         return xyz / 1000.0 * pow(xyz.y / 1000.0, -0.2 / 1.2);
297                     }
298                 )");
299             break;
300         default:
301             shader.append(R"(
302                     float3 NormalizeLuminance(float3 xyz) {
303                         return xyz / in_displayMaxLuminance;
304                     }
305                 )");
306             break;
307     }
308 }
309 
generateOOTF(ui::Dataspace inputDataspace,ui::Dataspace outputDataspace,SkString & shader)310 static void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace,
311                          SkString& shader) {
312     // Input uniforms
313     shader.append(R"(
314             uniform float in_displayMaxLuminance;
315             uniform float in_inputMaxLuminance;
316         )");
317 
318     generateLuminanceScalesForOOTF(inputDataspace, shader);
319     generateToneMapInterpolation(inputDataspace, outputDataspace, shader);
320     generateLuminanceNormalizationForOOTF(outputDataspace, shader);
321 
322     shader.append(R"(
323             float3 OOTF(float3 xyz) {
324                 return NormalizeLuminance(ToneMap(ScaleLuminance(xyz)));
325             }
326         )");
327 }
328 
generateOETF(ui::Dataspace dataspace,SkString & shader)329 static void generateOETF(ui::Dataspace dataspace, SkString& shader) {
330     switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) {
331         case HAL_DATASPACE_TRANSFER_ST2084:
332             shader.append(R"(
333 
334                 float3 OETF(float3 xyz) {
335                     float m1 = (2610.0 / 4096.0) / 4.0;
336                     float m2 = (2523.0 / 4096.0) * 128.0;
337                     float c1 = (3424.0 / 4096.0);
338                     float c2 = (2413.0 / 4096.0) * 32.0;
339                     float c3 = (2392.0 / 4096.0) * 32.0;
340 
341                     float3 tmp = pow(xyz, float3(m1));
342                     tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
343                     return pow(tmp, float3(m2));
344                 }
345             )");
346             break;
347         case HAL_DATASPACE_TRANSFER_HLG:
348             shader.append(R"(
349                 float OETF_channel(float channel) {
350                     const float a = 0.17883277;
351                     const float b = 0.28466892;
352                     const float c = 0.55991073;
353                     return channel <= 1.0 / 12.0 ? sqrt(3.0 * channel) :
354                             a * log(12.0 * channel - b) + c;
355                 }
356 
357                 float3 OETF(float3 linear) {
358                     return float3(OETF_channel(linear.r), OETF_channel(linear.g),
359                             OETF_channel(linear.b));
360                 }
361             )");
362             break;
363         case HAL_DATASPACE_TRANSFER_LINEAR:
364             shader.append(R"(
365                 float3 OETF(float3 linear) {
366                     return linear;
367                 }
368             )");
369             break;
370         case HAL_DATASPACE_TRANSFER_SRGB:
371         default:
372             shader.append(R"(
373                 float OETF_sRGB(float linear) {
374                     return linear <= 0.0031308 ?
375                             linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055;
376                 }
377 
378                 float3 OETF_sRGB(float3 linear) {
379                     return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
380                 }
381 
382                 float3 OETF(float3 linear) {
383                     return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
384                 }
385             )");
386             break;
387     }
388 }
389 
generateEffectiveOOTF(bool undoPremultipliedAlpha,SkString & shader)390 static void generateEffectiveOOTF(bool undoPremultipliedAlpha, SkString& shader) {
391     shader.append(R"(
392         uniform shader input;
393         half4 main(float2 xy) {
394             float4 c = float4(sample(input, xy));
395     )");
396     if (undoPremultipliedAlpha) {
397         shader.append(R"(
398             c.rgb = c.rgb / (c.a + 0.0019);
399         )");
400     }
401     shader.append(R"(
402         c.rgb = OETF(ToRGB(OOTF(ToXYZ(EOTF(c.rgb)))));
403     )");
404     if (undoPremultipliedAlpha) {
405         shader.append(R"(
406             c.rgb = c.rgb * (c.a + 0.0019);
407         )");
408     }
409     shader.append(R"(
410             return c;
411         }
412     )");
413 }
toColorSpace(ui::Dataspace dataspace)414 static ColorSpace toColorSpace(ui::Dataspace dataspace) {
415     switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
416         case HAL_DATASPACE_STANDARD_BT709:
417             return ColorSpace::sRGB();
418             break;
419         case HAL_DATASPACE_STANDARD_DCI_P3:
420             return ColorSpace::DisplayP3();
421             break;
422         case HAL_DATASPACE_STANDARD_BT2020:
423             return ColorSpace::BT2020();
424             break;
425         default:
426             return ColorSpace::sRGB();
427             break;
428     }
429 }
430 
buildRuntimeEffect(const LinearEffect & linearEffect)431 sk_sp<SkRuntimeEffect> buildRuntimeEffect(const LinearEffect& linearEffect) {
432     ATRACE_CALL();
433     SkString shaderString;
434     generateEOTF(linearEffect.inputDataspace, shaderString);
435     generateXYZTransforms(shaderString);
436     generateOOTF(linearEffect.inputDataspace, linearEffect.outputDataspace, shaderString);
437     generateOETF(linearEffect.outputDataspace, shaderString);
438     generateEffectiveOOTF(linearEffect.undoPremultipliedAlpha, shaderString);
439 
440     auto [shader, error] = SkRuntimeEffect::MakeForShader(shaderString);
441     if (!shader) {
442         LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str());
443     }
444     return shader;
445 }
446 
createLinearEffectShader(sk_sp<SkShader> shader,const LinearEffect & linearEffect,sk_sp<SkRuntimeEffect> runtimeEffect,const mat4 & colorTransform,float maxDisplayLuminance,float maxLuminance)447 sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader, const LinearEffect& linearEffect,
448                                          sk_sp<SkRuntimeEffect> runtimeEffect,
449                                          const mat4& colorTransform, float maxDisplayLuminance,
450                                          float maxLuminance) {
451     ATRACE_CALL();
452     SkRuntimeShaderBuilder effectBuilder(runtimeEffect);
453 
454     effectBuilder.child("input") = shader;
455 
456     if (linearEffect.inputDataspace == linearEffect.outputDataspace) {
457         effectBuilder.uniform("in_rgbToXyz") = mat4();
458         effectBuilder.uniform("in_xyzToRgb") = colorTransform;
459     } else {
460         ColorSpace inputColorSpace = toColorSpace(linearEffect.inputDataspace);
461         ColorSpace outputColorSpace = toColorSpace(linearEffect.outputDataspace);
462 
463         effectBuilder.uniform("in_rgbToXyz") = mat4(inputColorSpace.getRGBtoXYZ());
464         effectBuilder.uniform("in_xyzToRgb") =
465                 colorTransform * mat4(outputColorSpace.getXYZtoRGB());
466     }
467 
468     effectBuilder.uniform("in_displayMaxLuminance") = maxDisplayLuminance;
469     // If the input luminance is unknown, use display luminance (aka, no-op any luminance changes)
470     // This will be the case for eg screenshots in addition to uncalibrated displays
471     effectBuilder.uniform("in_inputMaxLuminance") =
472             maxLuminance > 0 ? maxLuminance : maxDisplayLuminance;
473     return effectBuilder.makeShader(nullptr, false);
474 }
475 
476 } // namespace skia
477 } // namespace renderengine
478 } // namespace android