Hard-assert on something that has bitten me too many times now.
[movit] / test_util.cpp
1 #include <assert.h>
2 #include <math.h>
3 #include <stdio.h>
4 #include <algorithm>
5 #include <epoxy/gl.h>
6 #include <gtest/gtest.h>
7 #include <gtest/gtest-message.h>
8
9 #include "flat_input.h"
10 #include "init.h"
11 #include "resource_pool.h"
12 #include "test_util.h"
13 #include "util.h"
14
15 using namespace std;
16
17 namespace movit {
18
19 class Input;
20
21 namespace {
22
23 // Not thread-safe, but this isn't a big problem for testing.
24 ResourcePool *get_static_pool()
25 {
26         static ResourcePool *resource_pool = NULL;
27         if (!resource_pool) {
28                 resource_pool = new ResourcePool();
29         }
30         return resource_pool;
31 }
32
33 // Flip upside-down to compensate for different origin.
34 template<class T>
35 void vertical_flip(T *data, unsigned width, unsigned height)
36 {
37         for (unsigned y = 0; y < height / 2; ++y) {
38                 unsigned flip_y = height - y - 1;
39                 for (unsigned x = 0; x < width; ++x) {
40                         swap(data[y * width + x], data[flip_y * width + x]);
41                 }
42         }
43 }
44
45 }  // namespace
46
47 EffectChainTester::EffectChainTester(const float *data, unsigned width, unsigned height,
48                                      MovitPixelFormat pixel_format, Colorspace color_space, GammaCurve gamma_curve,
49                                      GLenum framebuffer_format,
50                                      GLenum intermediate_format)
51         : chain(width, height, get_static_pool(), intermediate_format),
52           width(width),
53           height(height),
54           framebuffer_format(framebuffer_format),
55           output_added(false),
56           finalized(false)
57 {
58         CHECK(init_movit(".", MOVIT_DEBUG_OFF));
59
60         if (data != NULL) {
61                 add_input(data, pixel_format, color_space, gamma_curve);
62         }
63 }
64
65 EffectChainTester::~EffectChainTester()
66 {
67 }
68
69 Input *EffectChainTester::add_input(const float *data, MovitPixelFormat pixel_format, Colorspace color_space, GammaCurve gamma_curve, int input_width, int input_height)
70 {
71         ImageFormat format;
72         format.color_space = color_space;
73         format.gamma_curve = gamma_curve;
74
75         if (input_width == -1) {
76                 input_width = width;
77         }
78         if (input_height == -1) {
79                 input_height = height;
80         }
81
82         FlatInput *input = new FlatInput(format, pixel_format, GL_FLOAT, input_width, input_height);
83         input->set_pixel_data(data);
84         chain.add_input(input);
85         return input;
86 }
87
88 Input *EffectChainTester::add_input(const unsigned char *data, MovitPixelFormat pixel_format, Colorspace color_space, GammaCurve gamma_curve, int input_width, int input_height)
89 {
90         ImageFormat format;
91         format.color_space = color_space;
92         format.gamma_curve = gamma_curve;
93
94         if (input_width == -1) {
95                 input_width = width;
96         }
97         if (input_height == -1) {
98                 input_height = height;
99         }
100
101         FlatInput *input = new FlatInput(format, pixel_format, GL_UNSIGNED_BYTE, input_width, input_height);
102         input->set_pixel_data(data);
103         chain.add_input(input);
104         return input;
105 }
106
107 void EffectChainTester::run(float *out_data, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
108 {
109         internal_run<float>(out_data, NULL, NULL, GL_FLOAT, format, color_space, gamma_curve, alpha_format);
110 }
111
112 void EffectChainTester::run(float *out_data, float *out_data2, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
113 {
114         internal_run<float>(out_data, out_data2, NULL, GL_FLOAT, format, color_space, gamma_curve, alpha_format);
115 }
116
117 void EffectChainTester::run(float *out_data, float *out_data2, float *out_data3, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
118 {
119         internal_run(out_data, out_data2, out_data3, GL_FLOAT, format, color_space, gamma_curve, alpha_format);
120 }
121
122 void EffectChainTester::run(unsigned char *out_data, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
123 {
124         internal_run<unsigned char>(out_data, NULL, NULL, GL_UNSIGNED_BYTE, format, color_space, gamma_curve, alpha_format);
125 }
126
127 void EffectChainTester::run(unsigned char *out_data, unsigned char *out_data2, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
128 {
129         internal_run<unsigned char>(out_data, out_data2, NULL, GL_UNSIGNED_BYTE, format, color_space, gamma_curve, alpha_format);
130 }
131
132 void EffectChainTester::run(unsigned char *out_data, unsigned char *out_data2, unsigned char *out_data3, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
133 {
134         internal_run(out_data, out_data2, out_data3, GL_UNSIGNED_BYTE, format, color_space, gamma_curve, alpha_format);
135 }
136
137 template<class T>
138 void EffectChainTester::internal_run(T *out_data, T *out_data2, T *out_data3, GLenum internal_format, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
139 {
140         if (!finalized) {
141                 finalize_chain(color_space, gamma_curve, alpha_format);
142         }
143
144         GLuint type;
145         if (framebuffer_format == GL_RGBA8) {
146                 type = GL_UNSIGNED_BYTE;
147         } else if (framebuffer_format == GL_RGBA16F || framebuffer_format == GL_RGBA32F) {
148                 type = GL_FLOAT;
149         } else {
150                 // Add more here as needed.
151                 assert(false);
152         }
153
154         unsigned num_outputs;
155         if (out_data3 != NULL) {
156                 num_outputs = 3;
157         } else if (out_data2 != NULL) {
158                 num_outputs = 2;
159         } else {
160                 num_outputs = 1;
161         }
162
163         GLuint fbo, texnum[3];
164
165         glGenTextures(num_outputs, texnum);
166         check_error();
167         for (unsigned i = 0; i < num_outputs; ++i) {
168                 glBindTexture(GL_TEXTURE_2D, texnum[i]);
169                 check_error();
170                 glTexImage2D(GL_TEXTURE_2D, 0, framebuffer_format, width, height, 0, GL_RGBA, type, NULL);
171                 check_error();
172         }
173
174         glGenFramebuffers(1, &fbo);
175         check_error();
176         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
177         check_error();
178         for (unsigned i = 0; i < num_outputs; ++i) {
179                 glFramebufferTexture2D(
180                         GL_FRAMEBUFFER,
181                         GL_COLOR_ATTACHMENT0 + i,
182                         GL_TEXTURE_2D,
183                         texnum[i],
184                         0);
185                 check_error();
186         }
187
188         GLenum bufs[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
189         glDrawBuffers(num_outputs, bufs);
190
191         chain.render_to_fbo(fbo, width, height);
192
193         T *data[3] = { out_data, out_data2, out_data3 };
194
195         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
196         check_error();
197         for (unsigned i = 0; i < num_outputs; ++i) {
198                 T *ptr = data[i];
199                 glReadBuffer(GL_COLOR_ATTACHMENT0 + i);
200                 if (!epoxy_is_desktop_gl() && (format == GL_RED || format == GL_BLUE || format == GL_ALPHA)) {
201                         // GLES will only read GL_RGBA.
202                         T *temp = new T[width * height * 4];
203                         glReadPixels(0, 0, width, height, GL_RGBA, internal_format, temp);
204                         check_error();
205                         if (format == GL_ALPHA) {
206                                 for (unsigned i = 0; i < width * height; ++i) {
207                                         ptr[i] = temp[i * 4 + 3];
208                                 }
209                         } else if (format == GL_BLUE) {
210                                 for (unsigned i = 0; i < width * height; ++i) {
211                                         ptr[i] = temp[i * 4 + 2];
212                                 }
213                         } else {
214                                 for (unsigned i = 0; i < width * height; ++i) {
215                                         ptr[i] = temp[i * 4];
216                                 }
217                         }
218                         delete[] temp;
219                 } else {
220                         glReadPixels(0, 0, width, height, format, internal_format, ptr);
221                         check_error();
222                 }
223
224                 if (format == GL_RGBA) {
225                         vertical_flip(ptr, width * 4, height);
226                 } else {
227                         vertical_flip(ptr, width, height);
228                 }
229         }
230
231         glDeleteFramebuffers(1, &fbo);
232         check_error();
233         glDeleteTextures(num_outputs, texnum);
234         check_error();
235 }
236
237 void EffectChainTester::add_output(const ImageFormat &format, OutputAlphaFormat alpha_format)
238 {
239         chain.add_output(format, alpha_format);
240         output_added = true;
241 }
242
243 void EffectChainTester::add_ycbcr_output(const ImageFormat &format, OutputAlphaFormat alpha_format, const YCbCrFormat &ycbcr_format, YCbCrOutputSplitting output_splitting)
244 {
245         chain.add_ycbcr_output(format, alpha_format, ycbcr_format, output_splitting);
246         output_added = true;
247 }
248
249 void EffectChainTester::finalize_chain(Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
250 {
251         assert(!finalized);
252         if (!output_added) {
253                 ImageFormat image_format;
254                 image_format.color_space = color_space;
255                 image_format.gamma_curve = gamma_curve;
256                 chain.add_output(image_format, alpha_format);
257                 output_added = true;
258         }
259         chain.finalize();
260         finalized = true;
261 }
262
263 void expect_equal(const float *ref, const float *result, unsigned width, unsigned height, float largest_difference_limit, float rms_limit)
264 {
265         float largest_difference = -1.0f;
266         float squared_difference = 0.0f;
267         int largest_diff_x = -1, largest_diff_y = -1;
268
269         for (unsigned y = 0; y < height; ++y) {
270                 for (unsigned x = 0; x < width; ++x) {
271                         float diff = ref[y * width + x] - result[y * width + x];
272                         if (fabs(diff) > largest_difference) {
273                                 largest_difference = fabs(diff);
274                                 largest_diff_x = x;
275                                 largest_diff_y = y;
276                         }
277                         squared_difference += diff * diff;
278                 }
279         }
280
281         EXPECT_LT(largest_difference, largest_difference_limit)
282                 << "Largest difference is in x=" << largest_diff_x << ", y=" << largest_diff_y << ":\n"
283                 << "Reference: " << ref[largest_diff_y * width + largest_diff_x] << "\n"
284                 << "Result:    " << result[largest_diff_y * width + largest_diff_x];
285
286         float rms = sqrt(squared_difference) / (width * height);
287         EXPECT_LT(rms, rms_limit);
288
289         if (largest_difference >= largest_difference_limit || rms >= rms_limit) {
290                 fprintf(stderr, "Dumping matrices for easier debugging, since at least one test failed.\n");
291
292                 fprintf(stderr, "Reference:\n");
293                 for (unsigned y = 0; y < height; ++y) {
294                         for (unsigned x = 0; x < width; ++x) {
295                                 fprintf(stderr, "%7.4f ", ref[y * width + x]);
296                         }
297                         fprintf(stderr, "\n");
298                 }
299
300                 fprintf(stderr, "\nResult:\n");
301                 for (unsigned y = 0; y < height; ++y) {
302                         for (unsigned x = 0; x < width; ++x) {
303                                 fprintf(stderr, "%7.4f ", result[y * width + x]);
304                         }
305                         fprintf(stderr, "\n");
306                 }
307         }
308 }
309
310 void expect_equal(const unsigned char *ref, const unsigned char *result, unsigned width, unsigned height, unsigned largest_difference_limit, float rms_limit)
311 {
312         assert(width > 0);
313         assert(height > 0);
314
315         float *ref_float = new float[width * height];
316         float *result_float = new float[width * height];
317
318         for (unsigned y = 0; y < height; ++y) {
319                 for (unsigned x = 0; x < width; ++x) {
320                         ref_float[y * width + x] = ref[y * width + x];
321                         result_float[y * width + x] = result[y * width + x];
322                 }
323         }
324
325         expect_equal(ref_float, result_float, width, height, largest_difference_limit, rms_limit);
326
327         delete[] ref_float;
328         delete[] result_float;
329 }
330
331 void test_accuracy(const float *expected, const float *result, unsigned num_values, double absolute_error_limit, double relative_error_limit, double local_relative_error_limit, double rms_limit)
332 {
333         double squared_difference = 0.0;
334         for (unsigned i = 0; i < num_values; ++i) {
335                 double absolute_error = fabs(expected[i] - result[i]);
336                 squared_difference += absolute_error * absolute_error;
337                 EXPECT_LT(absolute_error, absolute_error_limit);
338
339                 if (expected[i] > 0.0) {
340                         double relative_error = fabs(absolute_error / expected[i]);
341
342                         EXPECT_LT(relative_error, relative_error_limit);
343                 }
344                 if (i < num_values - 1) {
345                         double delta = expected[i + 1] - expected[i];
346                         double local_relative_error = fabs(absolute_error / delta);
347                         EXPECT_LT(local_relative_error, local_relative_error_limit);
348                 }
349         }
350         double rms = sqrt(squared_difference) / num_values;
351         EXPECT_LT(rms, rms_limit);
352 }
353
354 double srgb_to_linear(double x)
355 {
356         // From the Wikipedia article on sRGB.
357         if (x < 0.04045) {
358                 return x / 12.92;
359         } else {
360                 return pow((x + 0.055) / 1.055, 2.4);
361         }
362 }
363
364 double linear_to_srgb(double x)
365 {
366         // From the Wikipedia article on sRGB.
367         if (x < 0.0031308) {
368                 return 12.92 * x;
369         } else {
370                 return 1.055 * pow(x, 1.0 / 2.4) - 0.055;
371         }
372 }
373
374 }  // namespace movit