Add accuracy unit tests for GammaExpansionEffect.
[movit] / gamma_expansion_effect_test.cpp
1 // Unit tests for GammaExpansionEffect.
2
3 #include <math.h>
4 #include <GL/glew.h>
5
6 #include "gamma_expansion_effect.h"
7 #include "gtest/gtest.h"
8 #include "test_util.h"
9
10 TEST(GammaExpansionEffectTest, sRGB_KeyValues) {
11         float data[] = {
12                 0.0f, 1.0f,
13                 0.040f, 0.041f,  // On either side of the discontinuity.
14         };
15         float expected_data[] = {
16                 0.0f, 1.0f,
17                 0.00309f, 0.00317f, 
18         };
19         float out_data[4];
20         EffectChainTester tester(data, 2, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
21         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
22
23         expect_equal(expected_data, out_data, 2, 2);
24 }
25
26 TEST(GammaExpansionEffectTest, sRGB_RampAlwaysIncreases) {
27         float data[256], out_data[256];
28         for (unsigned i = 0; i < 256; ++i) {
29                 data[i] = i / 255.0f;
30         }
31         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
32         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
33
34         for (unsigned i = 1; i < 256; ++i) {
35                 EXPECT_GT(out_data[i], out_data[i - 1])
36                    << "No increase between " << i-1 << " and " << i;
37         }
38 }
39
40 TEST(GammaExpansionEffectTest, sRGB_AlphaIsUnchanged) {
41         float data[] = {
42                 0.0f, 0.0f, 0.0f, 0.0f,
43                 0.0f, 0.0f, 0.0f, 0.25f,
44                 0.0f, 0.0f, 0.0f, 0.5f,
45                 0.0f, 0.0f, 0.0f, 0.75f,
46                 0.0f, 0.0f, 0.0f, 1.0f,
47         };
48         float out_data[5 * 4];
49         EffectChainTester tester(data, 5, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_sRGB);
50         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
51
52         expect_equal(data, out_data, 5, 1);
53 }
54
55 TEST(GammaExpansionEffectTest, sRGB_Accuracy) {
56         float data[256], expected_data[256], out_data[256];
57
58         for (int i = 0; i < 256; ++i) {
59                 double x = i / 255.0;
60
61                 data[i] = x;
62
63                 // From the Wikipedia article on sRGB.
64                 if (x < 0.04045) {
65                         expected_data[i] = x / 12.92;
66                 } else {
67                         expected_data[i] = pow((x + 0.055) / 1.055, 2.4);
68                 }
69         }
70
71         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB, GL_RGBA32F);
72         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
73
74         // Accuracy limits; for comparison, limits for a straightforward ALU solution
75         // (using a branch and pow()) in parenthesis, used as a “high anchor” to
76         // indicate limitations of float arithmetic etc.:
77         //
78         //   Maximum absolute error: 0.1% of max energy (0.051%)
79         //   Maximum relative error: 2.5% of correct answer (0.093%)
80         //                           25% of difference to next pixel level (6.18%)
81         //   Allowed RMS error:      0.0001 (0.000010)
82         //
83         test_accuracy(expected_data, out_data, 256, 1e-3, 0.025, 0.25, 1e-4);
84 }
85
86 TEST(GammaExpansionEffectTest, Rec709_KeyValues) {
87         float data[] = {
88                 0.0f, 1.0f,
89                 0.080f, 0.082f,  // On either side of the discontinuity.
90         };
91         float expected_data[] = {
92                 0.0f, 1.0f,
93                 0.017778f, 0.018167f,
94         };
95         float out_data[4];
96         EffectChainTester tester(data, 2, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
97         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
98
99         expect_equal(expected_data, out_data, 2, 2);
100 }
101
102 TEST(GammaExpansionEffectTest, Rec709_RampAlwaysIncreases) {
103         float data[256], out_data[256];
104         for (unsigned i = 0; i < 256; ++i) {
105                 data[i] = i / 255.0f;
106         }
107         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
108         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
109
110         for (unsigned i = 1; i < 256; ++i) {
111                 EXPECT_GT(out_data[i], out_data[i - 1])
112                    << "No increase between " << i-1 << " and " << i;
113         }
114 }
115
116 TEST(GammaExpansionEffectTest, Rec709_AlphaIsUnchanged) {
117         float data[] = {
118                 0.0f, 0.0f, 0.0f, 0.0f,
119                 0.0f, 0.0f, 0.0f, 0.25f,
120                 0.0f, 0.0f, 0.0f, 0.5f,
121                 0.0f, 0.0f, 0.0f, 0.75f,
122                 0.0f, 0.0f, 0.0f, 1.0f,
123         };
124         float out_data[5 * 4];
125         EffectChainTester tester(data, 5, 1, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_REC_709);
126         tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
127
128         expect_equal(data, out_data, 5, 1);
129 }
130
131 TEST(GammaExpansionEffectTest, Rec709_Accuracy) {
132         float data[256], expected_data[256], out_data[256];
133
134         for (int i = 0; i < 256; ++i) {
135                 double x = i / 255.0;
136
137                 data[i] = x;
138
139                 // Rec. 2020, page 3.
140                 if (x < 0.018 * 4.5) {
141                         expected_data[i] = x / 4.5;
142                 } else {
143                         expected_data[i] = pow((x + 0.099) / 1.099, 1.0 / 0.45);
144                 }
145         }
146
147         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709, GL_RGBA32F);
148         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
149
150         // Accuracy limits; for comparison, limits for a straightforward ALU solution
151         // (using a branch and pow()) in parenthesis, used as a “high anchor” to
152         // indicate limitations of float arithmetic etc.:
153         //
154         //   Maximum absolute error: 0.1% of max energy (0.046%)
155         //   Maximum relative error: 1.0% of correct answer (0.080%)
156         //                           10% of difference to next pixel level (6.19%)
157         //   Allowed RMS error:      0.0001 (0.000010)
158         //
159         test_accuracy(expected_data, out_data, 256, 1e-3, 0.01, 0.1, 1e-4);
160 }
161
162 // This test tests the same gamma ramp as Rec709_Accuracy, but with 10-bit
163 // input range and somewhat looser error bounds. (One could claim that this is
164 // already on the limit of what we can reasonably do with fp16 input, if you
165 // look at the local relative error.)
166 TEST(GammaExpansionEffectTest, Rec2020_10Bit_Accuracy) {
167         float data[1024], expected_data[1024], out_data[1024];
168
169         for (int i = 0; i < 1024; ++i) {
170                 double x = i / 1023.0;
171
172                 data[i] = x;
173
174                 // Rec. 2020, page 3.
175                 if (x < 0.018 * 4.5) {
176                         expected_data[i] = x / 4.5;
177                 } else {
178                         expected_data[i] = pow((x + 0.099) / 1.099, 1.0 / 0.45);
179                 }
180         }
181
182         EffectChainTester tester(data, 1024, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_2020_10_BIT, GL_RGBA32F);
183         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
184
185         // Accuracy limits; for comparison, limits for a straightforward ALU solution
186         // (using a branch and pow()) in parenthesis, used as a “high anchor” to
187         // indicate limitations of float arithmetic etc.:
188         //
189         //   Maximum absolute error: 0.1% of max energy (0.036%)
190         //   Maximum relative error: 1.0% of correct answer (0.064%)
191         //                           30% of difference to next pixel level (24.9%)
192         //   Allowed RMS error:      0.0001 (0.000005)
193         //
194         test_accuracy(expected_data, out_data, 1024, 1e-3, 0.01, 0.30, 1e-4);
195 }
196
197 TEST(GammaExpansionEffectTest, Rec2020_12BitIsVeryCloseToRec709) {
198         float data[256];
199         for (unsigned i = 0; i < 256; ++i) {
200                 data[i] = i / 255.0f;
201         }
202         float out_data_709[256];
203         float out_data_2020[256];
204
205         EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
206         tester.run(out_data_709, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
207         EffectChainTester tester2(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_2020_12_BIT);
208         tester2.run(out_data_2020, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
209
210         double sqdiff = 0.0;
211         for (unsigned i = 0; i < 256; ++i) {
212                 EXPECT_NEAR(out_data_709[i], out_data_2020[i], 1e-3);
213                 sqdiff += (out_data_709[i] - out_data_2020[i]) * (out_data_709[i] - out_data_2020[i]);
214         }
215         EXPECT_GT(sqdiff, 1e-6);
216 }
217
218 // The fp16 _input_ provided by FlatInput is not enough to distinguish between
219 // all of the possible 12-bit input values (every other level translates to the
220 // same value). Thus, this test has extremely loose bounds; if we ever decide
221 // to start supporting fp32, we should re-run this and tighten them a lot.
222 TEST(GammaExpansionEffectTest, Rec2020_12Bit_Inaccuracy) {
223         float data[4096], expected_data[4096], out_data[4096];
224
225         for (int i = 0; i < 4096; ++i) {
226                 double x = i / 4095.0;
227
228                 data[i] = x;
229
230                 // Rec. 2020, page 3.
231                 if (x < 0.0181 * 4.5) {
232                         expected_data[i] = x / 4.5;
233                 } else {
234                         expected_data[i] = pow((x + 0.0993) / 1.0993, 1.0/0.45);
235                 }
236         }
237
238         EffectChainTester tester(data, 4096, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_2020_12_BIT, GL_RGBA32F);
239         tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
240
241         // Accuracy limits; for comparison, limits for a straightforward ALU solution
242         // (using a branch and pow()) in parenthesis, used as a “high anchor” to
243         // indicate limitations of float arithmetic etc.:
244         //
245         //   Maximum absolute error: 0.1% of max energy (0.050%)
246         //   Maximum relative error: 1.0% of correct answer (0.050%)
247         //                           250% of difference to next pixel level (100.00%)
248         //   Allowed RMS error:      0.0001 (0.000003)
249         //
250         test_accuracy(expected_data, out_data, 4096, 1e-3, 0.01, 2.50, 1e-4);
251 }