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