1 // Unit tests for FlatInput.
2
3 #include <epoxy/gl.h>
4 #include <stddef.h>
5
6 #include "effect_chain.h"
7 #include "flat_input.h"
8 #include "gtest/gtest.h"
9 #include "resource_pool.h"
10 #include "test_util.h"
11 #include "util.h"
12
13 using namespace std;
14
15 namespace movit {
16
TEST(FlatInput,SimpleGrayscale)17 TEST(FlatInput, SimpleGrayscale) {
18 const int size = 4;
19
20 float data[size] = {
21 0.0,
22 0.5,
23 0.7,
24 1.0,
25 };
26 float expected_data[4 * size] = {
27 0.0, 0.0, 0.0, 1.0,
28 0.5, 0.5, 0.5, 1.0,
29 0.7, 0.7, 0.7, 1.0,
30 1.0, 1.0, 1.0, 1.0,
31 };
32 float out_data[4 * size];
33
34 EffectChainTester tester(data, 1, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
35 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
36
37 expect_equal(expected_data, out_data, 4, size);
38 }
39
TEST(FlatInput,RGB)40 TEST(FlatInput, RGB) {
41 const int size = 5;
42
43 float data[3 * size] = {
44 0.0, 0.0, 0.0,
45 0.5, 0.0, 0.0,
46 0.0, 0.5, 0.0,
47 0.0, 0.0, 0.7,
48 0.0, 0.3, 0.7,
49 };
50 float expected_data[4 * size] = {
51 0.0, 0.0, 0.0, 1.0,
52 0.5, 0.0, 0.0, 1.0,
53 0.0, 0.5, 0.0, 1.0,
54 0.0, 0.0, 0.7, 1.0,
55 0.0, 0.3, 0.7, 1.0,
56 };
57 float out_data[4 * size];
58
59 EffectChainTester tester(data, 1, size, FORMAT_RGB, COLORSPACE_sRGB, GAMMA_LINEAR);
60 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
61
62 expect_equal(expected_data, out_data, 4, size);
63 }
64
TEST(FlatInput,RGBA)65 TEST(FlatInput, RGBA) {
66 const int size = 5;
67
68 float data[4 * size] = {
69 0.0, 0.0, 0.0, 1.0,
70 0.5, 0.0, 0.0, 0.3,
71 0.0, 0.5, 0.0, 0.7,
72 0.0, 0.0, 0.7, 1.0,
73 0.0, 0.3, 0.7, 0.2,
74 };
75 float expected_data[4 * size] = {
76 0.0, 0.0, 0.0, 1.0,
77 0.5, 0.0, 0.0, 0.3,
78 0.0, 0.5, 0.0, 0.7,
79 0.0, 0.0, 0.7, 1.0,
80 0.0, 0.3, 0.7, 0.2,
81 };
82 float out_data[4 * size];
83
84 EffectChainTester tester(data, 1, size, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
85 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
86
87 expect_equal(expected_data, out_data, 4, size);
88 }
89
90 // Note: The sRGB conversion itself is tested in EffectChainTester,
91 // since it also wants to test the chain building itself.
92 // Here, we merely test that alpha is left alone; the test will usually
93 // run using the sRGB OpenGL extension, but might be run with a
94 // GammaExpansionEffect if the card/driver happens not to support that.
TEST(FlatInput,AlphaIsNotModifiedBySRGBConversion)95 TEST(FlatInput, AlphaIsNotModifiedBySRGBConversion) {
96 const int size = 5;
97
98 unsigned char data[4 * size] = {
99 0, 0, 0, 0,
100 0, 0, 0, 63,
101 0, 0, 0, 127,
102 0, 0, 0, 191,
103 0, 0, 0, 255,
104 };
105 float expected_data[4 * size] = {
106 0, 0, 0, 0.0 / 255.0,
107 0, 0, 0, 63.0 / 255.0,
108 0, 0, 0, 127.0 / 255.0,
109 0, 0, 0, 191.0 / 255.0,
110 0, 0, 0, 255.0 / 255.0,
111 };
112 float out_data[4 * size];
113
114 EffectChainTester tester(nullptr, 1, size);
115 tester.add_input(data, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_sRGB);
116 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
117
118 expect_equal(expected_data, out_data, 4, size);
119 }
120
TEST(FlatInput,BGR)121 TEST(FlatInput, BGR) {
122 const int size = 5;
123
124 float data[3 * size] = {
125 0.0, 0.0, 0.0,
126 0.5, 0.0, 0.0,
127 0.0, 0.5, 0.0,
128 0.0, 0.0, 0.7,
129 0.0, 0.3, 0.7,
130 };
131 float expected_data[4 * size] = {
132 0.0, 0.0, 0.0, 1.0,
133 0.0, 0.0, 0.5, 1.0,
134 0.0, 0.5, 0.0, 1.0,
135 0.7, 0.0, 0.0, 1.0,
136 0.7, 0.3, 0.0, 1.0,
137 };
138 float out_data[4 * size];
139
140 EffectChainTester tester(data, 1, size, FORMAT_BGR, COLORSPACE_sRGB, GAMMA_LINEAR);
141 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
142
143 expect_equal(expected_data, out_data, 4, size);
144 }
145
TEST(FlatInput,BGRA)146 TEST(FlatInput, BGRA) {
147 const int size = 5;
148
149 float data[4 * size] = {
150 0.0, 0.0, 0.0, 1.0,
151 0.5, 0.0, 0.0, 0.3,
152 0.0, 0.5, 0.0, 0.7,
153 0.0, 0.0, 0.7, 1.0,
154 0.0, 0.3, 0.7, 0.2,
155 };
156 float expected_data[4 * size] = {
157 0.0, 0.0, 0.0, 1.0,
158 0.0, 0.0, 0.5, 0.3,
159 0.0, 0.5, 0.0, 0.7,
160 0.7, 0.0, 0.0, 1.0,
161 0.7, 0.3, 0.0, 0.2,
162 };
163 float out_data[4 * size];
164
165 EffectChainTester tester(data, 1, size, FORMAT_BGRA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR);
166 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
167
168 expect_equal(expected_data, out_data, 4, size);
169 }
170
TEST(FlatInput,Pitch)171 TEST(FlatInput, Pitch) {
172 const int pitch = 3;
173 const int width = 2;
174 const int height = 4;
175
176 float data[pitch * height] = {
177 0.0, 1.0, 999.0f,
178 0.5, 0.5, 999.0f,
179 0.7, 0.2, 999.0f,
180 1.0, 0.6, 999.0f,
181 };
182 float expected_data[4 * width * height] = {
183 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0,
184 0.5, 0.5, 0.5, 1.0, 0.5, 0.5, 0.5, 1.0,
185 0.7, 0.7, 0.7, 1.0, 0.2, 0.2, 0.2, 1.0,
186 1.0, 1.0, 1.0, 1.0, 0.6, 0.6, 0.6, 1.0,
187 };
188 float out_data[4 * width * height];
189
190 EffectChainTester tester(nullptr, width, height);
191
192 ImageFormat format;
193 format.color_space = COLORSPACE_sRGB;
194 format.gamma_curve = GAMMA_LINEAR;
195
196 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
197 input->set_pitch(pitch);
198 input->set_pixel_data(data);
199 tester.get_chain()->add_input(input);
200
201 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
202 expect_equal(expected_data, out_data, 4 * width, height);
203 }
204
TEST(FlatInput,UpdatedData)205 TEST(FlatInput, UpdatedData) {
206 const int width = 2;
207 const int height = 4;
208
209 float data[width * height] = {
210 0.0, 1.0,
211 0.5, 0.5,
212 0.7, 0.2,
213 1.0, 0.6,
214 };
215 float out_data[width * height];
216
217 EffectChainTester tester(nullptr, width, height);
218
219 ImageFormat format;
220 format.color_space = COLORSPACE_sRGB;
221 format.gamma_curve = GAMMA_LINEAR;
222
223 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
224 input->set_pixel_data(data);
225 tester.get_chain()->add_input(input);
226
227 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
228 expect_equal(data, out_data, width, height);
229
230 data[6] = 0.3;
231 input->invalidate_pixel_data();
232
233 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
234 expect_equal(data, out_data, width, height);
235 }
236
TEST(FlatInput,PBO)237 TEST(FlatInput, PBO) {
238 const int width = 3;
239 const int height = 2;
240
241 float data[width * height] = {
242 0.0, 1.0, 0.5,
243 0.5, 0.5, 0.2,
244 };
245 float expected_data[4 * width * height] = {
246 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.5, 0.5, 1.0,
247 0.5, 0.5, 0.5, 1.0, 0.5, 0.5, 0.5, 1.0, 0.2, 0.2, 0.2, 1.0,
248 };
249 float out_data[4 * width * height];
250
251 GLuint pbo;
252 glGenBuffers(1, &pbo);
253 glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo);
254 glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width * height * sizeof(float), data, GL_STREAM_DRAW);
255 glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
256
257 EffectChainTester tester(nullptr, width, height);
258
259 ImageFormat format;
260 format.color_space = COLORSPACE_sRGB;
261 format.gamma_curve = GAMMA_LINEAR;
262
263 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
264 input->set_pixel_data((float *)BUFFER_OFFSET(0), pbo);
265 tester.get_chain()->add_input(input);
266
267 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
268 expect_equal(expected_data, out_data, 4 * width, height);
269
270 glDeleteBuffers(1, &pbo);
271 }
272
TEST(FlatInput,ExternalTexture)273 TEST(FlatInput, ExternalTexture) {
274 const int size = 5;
275
276 float data[3 * size] = {
277 0.0, 0.0, 0.0,
278 0.5, 0.0, 0.0,
279 0.0, 0.5, 0.0,
280 0.0, 0.0, 0.7,
281 0.0, 0.3, 0.7,
282 };
283 float expected_data[4 * size] = {
284 0.0, 0.0, 0.0, 1.0,
285 0.5, 0.0, 0.0, 1.0,
286 0.0, 0.5, 0.0, 1.0,
287 0.0, 0.0, 0.7, 1.0,
288 0.0, 0.3, 0.7, 1.0,
289 };
290 float out_data[4 * size];
291
292 EffectChainTester tester(nullptr, 1, size, FORMAT_RGB, COLORSPACE_sRGB, GAMMA_LINEAR);
293
294 ImageFormat format;
295 format.color_space = COLORSPACE_sRGB;
296 format.gamma_curve = GAMMA_LINEAR;
297
298 ResourcePool pool;
299 GLuint tex = pool.create_2d_texture(GL_RGB8, 1, size);
300 check_error();
301 glBindTexture(GL_TEXTURE_2D, tex);
302 check_error();
303 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
304 check_error();
305 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
306 check_error();
307 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, size, GL_RGB, GL_FLOAT, data);
308 check_error();
309 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
310 check_error();
311 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
312 check_error();
313
314 FlatInput *input = new FlatInput(format, FORMAT_RGB, GL_FLOAT, 1, size);
315 input->set_texture_num(tex);
316 tester.get_chain()->add_input(input);
317
318 tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR);
319
320 pool.release_2d_texture(tex);
321
322 expect_equal(expected_data, out_data, 4, size);
323 }
324
325 // Just an IdentityEffect, but marks as needing mipmaps, so that we can use it
326 // for downscaling to verify mipmaps were used.
327 class MipmapNeedingEffect : public Effect {
328 public:
MipmapNeedingEffect()329 MipmapNeedingEffect() {}
needs_mipmaps() const330 MipmapRequirements needs_mipmaps() const override { return NEEDS_MIPMAPS; }
331
effect_type_id() const332 string effect_type_id() const override { return "MipmapNeedingEffect"; }
output_fragment_shader()333 string output_fragment_shader() override { return read_file("identity.frag"); }
334 };
335
TEST(FlatInput,ExternalTextureMipmapState)336 TEST(FlatInput, ExternalTextureMipmapState) {
337 const int width = 4;
338 const int height = 4;
339
340 float data[width * height] = {
341 1.0, 0.0, 0.0, 0.0,
342 0.0, 0.0, 0.0, 0.0,
343 0.0, 0.0, 0.0, 0.0,
344 0.0, 0.0, 0.0, 0.0,
345 };
346 float expected_data[] = {
347 0.0625,
348 };
349 float out_data[1];
350
351 EffectChainTester tester(nullptr, 1, 1, FORMAT_RGB, COLORSPACE_sRGB, GAMMA_LINEAR);
352
353 ImageFormat format;
354 format.color_space = COLORSPACE_sRGB;
355 format.gamma_curve = GAMMA_LINEAR;
356
357 ResourcePool pool;
358 GLuint tex = pool.create_2d_texture(GL_R8, width, height);
359 check_error();
360 glBindTexture(GL_TEXTURE_2D, tex);
361 check_error();
362 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
363 check_error();
364 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
365 check_error();
366 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_FLOAT, data);
367 check_error();
368 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
369 check_error();
370 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
371 check_error();
372 glGenerateMipmap(GL_TEXTURE_2D);
373 check_error();
374
375 // Turn off mipmaps, so that we verify that Movit turns it back on.
376 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
377 check_error();
378
379 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
380 input->set_texture_num(tex);
381 tester.get_chain()->add_input(input);
382 tester.get_chain()->add_effect(new MipmapNeedingEffect);
383
384 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
385
386 pool.release_2d_texture(tex);
387
388 expect_equal(expected_data, out_data, 1, 1);
389 }
390
TEST(FlatInput,NoData)391 TEST(FlatInput, NoData) {
392 const int width = 2;
393 const int height = 4;
394
395 float out_data[width * height];
396
397 EffectChainTester tester(nullptr, width, height);
398
399 ImageFormat format;
400 format.color_space = COLORSPACE_sRGB;
401 format.gamma_curve = GAMMA_LINEAR;
402
403 FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, width, height);
404 tester.get_chain()->add_input(input);
405
406 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
407
408 // Don't care what the output was, just that it does not crash.
409 }
410
411 } // namespace movit
412