1 // Unit tests for EffectChain.
3 // Note that this also contains the tests for some of the simpler effects.
5 #include "effect_chain.h"
6 #include "flat_input.h"
7 #include "gtest/gtest.h"
8 #include "mirror_effect.h"
9 #include "resize_effect.h"
11 #include "test_util.h"
13 TEST(EffectChainTest, EmptyChain) {
19 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
20 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
22 expect_equal(data, out_data, 3, 2);
25 // An effect that does nothing.
26 class IdentityEffect : public Effect {
29 virtual std::string effect_type_id() const { return "IdentityEffect"; }
30 std::string output_fragment_shader() { return read_file("identity.frag"); }
33 TEST(EffectChainTest, Identity) {
39 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
40 tester.get_chain()->add_effect(new IdentityEffect());
41 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
43 expect_equal(data, out_data, 3, 2);
46 // An effect that does nothing, but requests texture bounce.
47 class BouncingIdentityEffect : public Effect {
49 BouncingIdentityEffect() {}
50 virtual std::string effect_type_id() const { return "IdentityEffect"; }
51 std::string output_fragment_shader() { return read_file("identity.frag"); }
52 bool needs_texture_bounce() const { return true; }
55 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
61 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
62 tester.get_chain()->add_effect(new BouncingIdentityEffect());
63 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
65 expect_equal(data, out_data, 3, 2);
68 TEST(MirrorTest, BasicTest) {
73 float expected_data[6] = {
78 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
79 tester.get_chain()->add_effect(new MirrorEffect());
80 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
82 expect_equal(expected_data, out_data, 3, 2);
85 // A dummy effect that inverts its input.
86 class InvertEffect : public Effect {
89 virtual std::string effect_type_id() const { return "InvertEffect"; }
90 std::string output_fragment_shader() { return read_file("invert_effect.frag"); }
93 // Like IdentityEffect, but rewrites itself out of the loop,
94 // splicing in a InvertEffect instead. Also stores the new node,
95 // so we later can check that there are gamma conversion effects
97 class RewritingToInvertEffect : public Effect {
99 RewritingToInvertEffect() {}
100 virtual std::string effect_type_id() const { return "RewritingToInvertEffect"; }
101 std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
102 virtual void rewrite_graph(EffectChain *graph, Node *self) {
103 Node *invert_node = graph->add_node(new InvertEffect());
104 graph->replace_receiver(self, invert_node);
105 graph->replace_sender(self, invert_node);
107 self->disabled = true;
108 this->invert_node = invert_node;
114 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
119 float expected_data[6] = {
120 1.0f, 0.9771f, 0.9673f,
124 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
125 RewritingToInvertEffect *effect = new RewritingToInvertEffect();
126 tester.get_chain()->add_effect(effect);
127 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
129 Node *node = effect->invert_node;
130 ASSERT_EQ(1, node->incoming_links.size());
131 ASSERT_EQ(1, node->outgoing_links.size());
132 EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id());
133 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
135 expect_equal(expected_data, out_data, 3, 2);
138 TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) {
139 unsigned char data[] = {
143 float expected_data[4] = {
148 EffectChainTester tester(NULL, 2, 2);
149 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
150 RewritingToInvertEffect *effect = new RewritingToInvertEffect();
151 tester.get_chain()->add_effect(effect);
152 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
154 Node *node = effect->invert_node;
155 ASSERT_EQ(1, node->incoming_links.size());
156 ASSERT_EQ(1, node->outgoing_links.size());
157 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
158 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
160 expect_equal(expected_data, out_data, 2, 2);
163 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
168 float expected_data[6] = {
173 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
174 RewritingToInvertEffect *effect = new RewritingToInvertEffect();
175 tester.get_chain()->add_effect(effect);
176 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
178 Node *node = effect->invert_node;
179 ASSERT_EQ(1, node->incoming_links.size());
180 ASSERT_EQ(1, node->outgoing_links.size());
181 EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
182 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
184 expect_equal(expected_data, out_data, 3, 2);
187 // Like RewritingToInvertEffect, but splicing in a MirrorEffect instead,
188 // which does not need linear light or sRGB primaries.
189 class RewritingToMirrorEffect : public Effect {
191 RewritingToMirrorEffect() {}
192 virtual std::string effect_type_id() const { return "RewritingToMirrorEffect"; }
193 std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
194 virtual void rewrite_graph(EffectChain *graph, Node *self) {
195 Node *mirror_node = graph->add_node(new MirrorEffect());
196 graph->replace_receiver(self, mirror_node);
197 graph->replace_sender(self, mirror_node);
199 self->disabled = true;
200 this->mirror_node = mirror_node;
206 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
211 float expected_data[6] = {
216 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
217 RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
218 tester.get_chain()->add_effect(effect);
219 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
221 Node *node = effect->mirror_node;
222 ASSERT_EQ(1, node->incoming_links.size());
223 EXPECT_EQ(0, node->outgoing_links.size());
224 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
226 expect_equal(expected_data, out_data, 3, 2);
229 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
234 float expected_data[6] = {
239 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
240 RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
241 tester.get_chain()->add_effect(effect);
242 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
244 Node *node = effect->mirror_node;
245 ASSERT_EQ(1, node->incoming_links.size());
246 EXPECT_EQ(0, node->outgoing_links.size());
247 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
249 expect_equal(expected_data, out_data, 3, 2);
252 // The identity effect needs linear light, and thus will get conversions on both sides.
253 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
254 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
256 for (unsigned i = 0; i < 256; ++i) {
260 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
261 tester.get_chain()->add_effect(new IdentityEffect());
262 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
264 expect_equal(data, out_data, 256, 1);
267 // Same, but uses the forward sRGB table from the GPU.
268 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
269 unsigned char data[256];
270 float expected_data[256];
271 for (unsigned i = 0; i < 256; ++i) {
273 expected_data[i] = i / 255.0;
276 EffectChainTester tester(NULL, 256, 1);
277 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
278 tester.get_chain()->add_effect(new IdentityEffect());
279 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
281 expect_equal(expected_data, out_data, 256, 1);
284 // Same, for the Rec. 601/709 gamma curve.
285 TEST(EffectChainTest, IdentityThroughRec709) {
287 for (unsigned i = 0; i < 256; ++i) {
291 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
292 tester.get_chain()->add_effect(new IdentityEffect());
293 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
295 expect_equal(data, out_data, 256, 1);
298 // Effectively scales down its input linearly by 4x (and repeating it),
299 // which is not attainable without mipmaps.
300 class MipmapNeedingEffect : public Effect {
302 MipmapNeedingEffect() {}
303 virtual bool needs_mipmaps() const { return true; }
304 virtual std::string effect_type_id() const { return "MipmapNeedingEffect"; }
305 std::string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
306 void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num)
308 glActiveTexture(GL_TEXTURE0);
310 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
312 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
317 TEST(EffectChainTest, MipmapGenerationWorks) {
318 float data[] = { // In 4x4 blocks.
319 1.0f, 0.0f, 0.0f, 0.0f,
320 0.0f, 0.0f, 0.0f, 0.0f,
321 0.0f, 0.0f, 0.0f, 0.0f,
322 0.0f, 0.0f, 0.0f, 1.0f,
324 0.0f, 0.0f, 0.0f, 0.0f,
325 0.0f, 0.5f, 0.0f, 0.0f,
326 0.0f, 0.0f, 1.0f, 0.0f,
327 0.0f, 0.0f, 0.0f, 0.0f,
329 1.0f, 1.0f, 1.0f, 1.0f,
330 1.0f, 1.0f, 1.0f, 1.0f,
331 1.0f, 1.0f, 1.0f, 1.0f,
332 1.0f, 1.0f, 1.0f, 1.0f,
334 0.0f, 0.0f, 0.0f, 0.0f,
335 0.0f, 1.0f, 1.0f, 0.0f,
336 0.0f, 1.0f, 1.0f, 0.0f,
337 0.0f, 0.0f, 0.0f, 0.0f,
339 float expected_data[] = { // Repeated four times each way.
340 0.125f, 0.125f, 0.125f, 0.125f,
341 0.09375f, 0.09375f, 0.09375f, 0.09375f,
342 1.0f, 1.0f, 1.0f, 1.0f,
343 0.25f, 0.25f, 0.25f, 0.25f,
345 0.125f, 0.125f, 0.125f, 0.125f,
346 0.09375f, 0.09375f, 0.09375f, 0.09375f,
347 1.0f, 1.0f, 1.0f, 1.0f,
348 0.25f, 0.25f, 0.25f, 0.25f,
350 0.125f, 0.125f, 0.125f, 0.125f,
351 0.09375f, 0.09375f, 0.09375f, 0.09375f,
352 1.0f, 1.0f, 1.0f, 1.0f,
353 0.25f, 0.25f, 0.25f, 0.25f,
355 0.125f, 0.125f, 0.125f, 0.125f,
356 0.09375f, 0.09375f, 0.09375f, 0.09375f,
357 1.0f, 1.0f, 1.0f, 1.0f,
358 0.25f, 0.25f, 0.25f, 0.25f,
360 float out_data[4 * 16];
361 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
362 tester.get_chain()->add_effect(new MipmapNeedingEffect());
363 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
365 expect_equal(expected_data, out_data, 4, 16);
368 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
369 float data[] = { // In 4x4 blocks.
370 1.0f, 0.0f, 0.0f, 0.0f,
371 0.0f, 0.0f, 0.0f, 0.0f,
372 0.0f, 0.0f, 0.0f, 0.0f,
373 0.0f, 0.0f, 0.0f, 1.0f,
375 0.0f, 0.0f, 0.0f, 0.0f,
376 0.0f, 0.5f, 0.0f, 0.0f,
377 0.0f, 0.0f, 1.0f, 0.0f,
378 0.0f, 0.0f, 0.0f, 0.0f,
380 1.0f, 1.0f, 1.0f, 1.0f,
381 1.0f, 1.0f, 1.0f, 1.0f,
382 1.0f, 1.0f, 1.0f, 1.0f,
383 1.0f, 1.0f, 1.0f, 1.0f,
385 0.0f, 0.0f, 0.0f, 0.0f,
386 0.0f, 1.0f, 1.0f, 0.0f,
387 0.0f, 1.0f, 1.0f, 0.0f,
388 0.0f, 0.0f, 0.0f, 0.0f,
390 float expected_data[] = { // Repeated four times horizontaly, interpolated vertically.
391 0.1250f, 0.1250f, 0.1250f, 0.1250f,
392 0.1250f, 0.1250f, 0.1250f, 0.1250f,
393 0.1211f, 0.1211f, 0.1211f, 0.1211f,
394 0.1133f, 0.1133f, 0.1133f, 0.1133f,
395 0.1055f, 0.1055f, 0.1055f, 0.1055f,
396 0.0977f, 0.0977f, 0.0977f, 0.0977f,
397 0.2070f, 0.2070f, 0.2070f, 0.2070f,
398 0.4336f, 0.4336f, 0.4336f, 0.4336f,
399 0.6602f, 0.6602f, 0.6602f, 0.6602f,
400 0.8867f, 0.8867f, 0.8867f, 0.8867f,
401 0.9062f, 0.9062f, 0.9062f, 0.9062f,
402 0.7188f, 0.7188f, 0.7188f, 0.7188f,
403 0.5312f, 0.5312f, 0.5312f, 0.5312f,
404 0.3438f, 0.3438f, 0.3438f, 0.3438f,
405 0.2500f, 0.2500f, 0.2500f, 0.2500f,
406 0.2500f, 0.2500f, 0.2500f, 0.2500f,
408 float out_data[4 * 16];
410 ResizeEffect *downscale = new ResizeEffect();
411 ASSERT_TRUE(downscale->set_int("width", 1));
412 ASSERT_TRUE(downscale->set_int("height", 4));
414 ResizeEffect *upscale = new ResizeEffect();
415 ASSERT_TRUE(upscale->set_int("width", 4));
416 ASSERT_TRUE(upscale->set_int("height", 16));
418 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
419 tester.get_chain()->add_effect(downscale);
420 tester.get_chain()->add_effect(upscale);
421 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
423 expect_equal(expected_data, out_data, 4, 16);