1 // Unit tests for EffectChain.
3 // Note that this also contains the tests for some of the simpler effects.
7 #include "effect_chain.h"
8 #include "flat_input.h"
9 #include "gtest/gtest.h"
10 #include "mirror_effect.h"
11 #include "resize_effect.h"
12 #include "test_util.h"
14 TEST(EffectChainTest, EmptyChain) {
20 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
21 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
23 expect_equal(data, out_data, 3, 2);
26 // An effect that does nothing.
27 class IdentityEffect : public Effect {
30 virtual std::string effect_type_id() const { return "IdentityEffect"; }
31 std::string output_fragment_shader() { return read_file("identity.frag"); }
34 TEST(EffectChainTest, Identity) {
40 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
41 tester.get_chain()->add_effect(new IdentityEffect());
42 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
44 expect_equal(data, out_data, 3, 2);
47 // An effect that does nothing, but requests texture bounce.
48 class BouncingIdentityEffect : public Effect {
50 BouncingIdentityEffect() {}
51 virtual std::string effect_type_id() const { return "IdentityEffect"; }
52 std::string output_fragment_shader() { return read_file("identity.frag"); }
53 bool needs_texture_bounce() const { return true; }
56 TEST(EffectChainTest, TextureBouncePreservesIdentity) {
62 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
63 tester.get_chain()->add_effect(new BouncingIdentityEffect());
64 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
66 expect_equal(data, out_data, 3, 2);
69 TEST(MirrorTest, BasicTest) {
74 float expected_data[6] = {
79 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
80 tester.get_chain()->add_effect(new MirrorEffect());
81 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
83 expect_equal(expected_data, out_data, 3, 2);
86 // A dummy effect that inverts its input.
87 class InvertEffect : public Effect {
90 virtual std::string effect_type_id() const { return "InvertEffect"; }
91 std::string output_fragment_shader() { return read_file("invert_effect.frag"); }
94 // Like IdentityEffect, but rewrites itself out of the loop,
95 // splicing in a InvertEffect instead. Also stores the new node,
96 // so we later can check that there are gamma conversion effects
98 class RewritingToInvertEffect : public Effect {
100 RewritingToInvertEffect() {}
101 virtual std::string effect_type_id() const { return "RewritingToInvertEffect"; }
102 std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
103 virtual void rewrite_graph(EffectChain *graph, Node *self) {
104 Node *invert_node = graph->add_node(new InvertEffect());
105 graph->replace_receiver(self, invert_node);
106 graph->replace_sender(self, invert_node);
108 self->disabled = true;
109 this->invert_node = invert_node;
115 TEST(EffectChainTest, RewritingWorksAndGammaConversionsAreInserted) {
120 float expected_data[6] = {
121 1.0f, 0.9771f, 0.9673f,
125 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
126 RewritingToInvertEffect *effect = new RewritingToInvertEffect();
127 tester.get_chain()->add_effect(effect);
128 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
130 Node *node = effect->invert_node;
131 ASSERT_EQ(1, node->incoming_links.size());
132 ASSERT_EQ(1, node->outgoing_links.size());
133 EXPECT_EQ("GammaExpansionEffect", node->incoming_links[0]->effect->effect_type_id());
134 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
136 expect_equal(expected_data, out_data, 3, 2);
139 TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) {
140 unsigned char data[] = {
144 float expected_data[4] = {
149 EffectChainTester tester(NULL, 2, 2);
150 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
151 RewritingToInvertEffect *effect = new RewritingToInvertEffect();
152 tester.get_chain()->add_effect(effect);
153 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
155 Node *node = effect->invert_node;
156 ASSERT_EQ(1, node->incoming_links.size());
157 ASSERT_EQ(1, node->outgoing_links.size());
158 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
159 EXPECT_EQ("GammaCompressionEffect", node->outgoing_links[0]->effect->effect_type_id());
161 expect_equal(expected_data, out_data, 2, 2);
164 TEST(EffectChainTest, RewritingWorksAndColorspaceConversionsAreInserted) {
169 float expected_data[6] = {
174 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
175 RewritingToInvertEffect *effect = new RewritingToInvertEffect();
176 tester.get_chain()->add_effect(effect);
177 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
179 Node *node = effect->invert_node;
180 ASSERT_EQ(1, node->incoming_links.size());
181 ASSERT_EQ(1, node->outgoing_links.size());
182 EXPECT_EQ("ColorspaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
183 EXPECT_EQ("ColorspaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
185 expect_equal(expected_data, out_data, 3, 2);
188 // Like RewritingToInvertEffect, but splicing in a MirrorEffect instead,
189 // which does not need linear light or sRGB primaries.
190 class RewritingToMirrorEffect : public Effect {
192 RewritingToMirrorEffect() {}
193 virtual std::string effect_type_id() const { return "RewritingToMirrorEffect"; }
194 std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
195 virtual void rewrite_graph(EffectChain *graph, Node *self) {
196 Node *mirror_node = graph->add_node(new MirrorEffect());
197 graph->replace_receiver(self, mirror_node);
198 graph->replace_sender(self, mirror_node);
200 self->disabled = true;
201 this->mirror_node = mirror_node;
207 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
212 float expected_data[6] = {
217 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
218 RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
219 tester.get_chain()->add_effect(effect);
220 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
222 Node *node = effect->mirror_node;
223 ASSERT_EQ(1, node->incoming_links.size());
224 EXPECT_EQ(0, node->outgoing_links.size());
225 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
227 expect_equal(expected_data, out_data, 3, 2);
230 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
235 float expected_data[6] = {
240 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
241 RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
242 tester.get_chain()->add_effect(effect);
243 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
245 Node *node = effect->mirror_node;
246 ASSERT_EQ(1, node->incoming_links.size());
247 EXPECT_EQ(0, node->outgoing_links.size());
248 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
250 expect_equal(expected_data, out_data, 3, 2);
253 // The identity effect needs linear light, and thus will get conversions on both sides.
254 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
255 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
257 for (unsigned i = 0; i < 256; ++i) {
261 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
262 tester.get_chain()->add_effect(new IdentityEffect());
263 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
265 expect_equal(data, out_data, 256, 1);
268 // Same, but uses the forward sRGB table from the GPU.
269 TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) {
270 unsigned char data[256];
271 float expected_data[256];
272 for (unsigned i = 0; i < 256; ++i) {
274 expected_data[i] = i / 255.0;
277 EffectChainTester tester(NULL, 256, 1);
278 tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
279 tester.get_chain()->add_effect(new IdentityEffect());
280 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
282 expect_equal(expected_data, out_data, 256, 1);
285 // Same, for the Rec. 601/709 gamma curve.
286 TEST(EffectChainTest, IdentityThroughRec709) {
288 for (unsigned i = 0; i < 256; ++i) {
292 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
293 tester.get_chain()->add_effect(new IdentityEffect());
294 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
296 expect_equal(data, out_data, 256, 1);
299 // Effectively scales down its input linearly by 4x (and repeating it),
300 // which is not attainable without mipmaps.
301 class MipmapNeedingEffect : public Effect {
303 MipmapNeedingEffect() {}
304 virtual bool needs_mipmaps() const { return true; }
305 virtual std::string effect_type_id() const { return "MipmapNeedingEffect"; }
306 std::string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
307 void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num)
309 glActiveTexture(GL_TEXTURE0);
311 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
313 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
318 TEST(EffectChainTest, MipmapGenerationWorks) {
319 float data[] = { // In 4x4 blocks.
320 1.0f, 0.0f, 0.0f, 0.0f,
321 0.0f, 0.0f, 0.0f, 0.0f,
322 0.0f, 0.0f, 0.0f, 0.0f,
323 0.0f, 0.0f, 0.0f, 1.0f,
325 0.0f, 0.0f, 0.0f, 0.0f,
326 0.0f, 0.5f, 0.0f, 0.0f,
327 0.0f, 0.0f, 1.0f, 0.0f,
328 0.0f, 0.0f, 0.0f, 0.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,
333 1.0f, 1.0f, 1.0f, 1.0f,
335 0.0f, 0.0f, 0.0f, 0.0f,
336 0.0f, 1.0f, 1.0f, 0.0f,
337 0.0f, 1.0f, 1.0f, 0.0f,
338 0.0f, 0.0f, 0.0f, 0.0f,
340 float expected_data[] = { // Repeated four times each way.
341 0.125f, 0.125f, 0.125f, 0.125f,
342 0.09375f, 0.09375f, 0.09375f, 0.09375f,
343 1.0f, 1.0f, 1.0f, 1.0f,
344 0.25f, 0.25f, 0.25f, 0.25f,
346 0.125f, 0.125f, 0.125f, 0.125f,
347 0.09375f, 0.09375f, 0.09375f, 0.09375f,
348 1.0f, 1.0f, 1.0f, 1.0f,
349 0.25f, 0.25f, 0.25f, 0.25f,
351 0.125f, 0.125f, 0.125f, 0.125f,
352 0.09375f, 0.09375f, 0.09375f, 0.09375f,
353 1.0f, 1.0f, 1.0f, 1.0f,
354 0.25f, 0.25f, 0.25f, 0.25f,
356 0.125f, 0.125f, 0.125f, 0.125f,
357 0.09375f, 0.09375f, 0.09375f, 0.09375f,
358 1.0f, 1.0f, 1.0f, 1.0f,
359 0.25f, 0.25f, 0.25f, 0.25f,
361 float out_data[4 * 16];
362 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
363 tester.get_chain()->add_effect(new MipmapNeedingEffect());
364 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
366 expect_equal(expected_data, out_data, 4, 16);
369 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
370 float data[] = { // In 4x4 blocks.
371 1.0f, 0.0f, 0.0f, 0.0f,
372 0.0f, 0.0f, 0.0f, 0.0f,
373 0.0f, 0.0f, 0.0f, 0.0f,
374 0.0f, 0.0f, 0.0f, 1.0f,
376 0.0f, 0.0f, 0.0f, 0.0f,
377 0.0f, 0.5f, 0.0f, 0.0f,
378 0.0f, 0.0f, 1.0f, 0.0f,
379 0.0f, 0.0f, 0.0f, 0.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,
384 1.0f, 1.0f, 1.0f, 1.0f,
386 0.0f, 0.0f, 0.0f, 0.0f,
387 0.0f, 1.0f, 1.0f, 0.0f,
388 0.0f, 1.0f, 1.0f, 0.0f,
389 0.0f, 0.0f, 0.0f, 0.0f,
391 float expected_data[] = { // Repeated four times horizontaly, interpolated vertically.
392 0.1250f, 0.1250f, 0.1250f, 0.1250f,
393 0.1250f, 0.1250f, 0.1250f, 0.1250f,
394 0.1211f, 0.1211f, 0.1211f, 0.1211f,
395 0.1133f, 0.1133f, 0.1133f, 0.1133f,
396 0.1055f, 0.1055f, 0.1055f, 0.1055f,
397 0.0977f, 0.0977f, 0.0977f, 0.0977f,
398 0.2070f, 0.2070f, 0.2070f, 0.2070f,
399 0.4336f, 0.4336f, 0.4336f, 0.4336f,
400 0.6602f, 0.6602f, 0.6602f, 0.6602f,
401 0.8867f, 0.8867f, 0.8867f, 0.8867f,
402 0.9062f, 0.9062f, 0.9062f, 0.9062f,
403 0.7188f, 0.7188f, 0.7188f, 0.7188f,
404 0.5312f, 0.5312f, 0.5312f, 0.5312f,
405 0.3438f, 0.3438f, 0.3438f, 0.3438f,
406 0.2500f, 0.2500f, 0.2500f, 0.2500f,
407 0.2500f, 0.2500f, 0.2500f, 0.2500f,
409 float out_data[4 * 16];
411 ResizeEffect *downscale = new ResizeEffect();
412 ASSERT_TRUE(downscale->set_int("width", 1));
413 ASSERT_TRUE(downscale->set_int("height", 4));
415 ResizeEffect *upscale = new ResizeEffect();
416 ASSERT_TRUE(upscale->set_int("width", 4));
417 ASSERT_TRUE(upscale->set_int("height", 16));
419 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
420 tester.get_chain()->add_effect(downscale);
421 tester.get_chain()->add_effect(upscale);
422 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
424 expect_equal(expected_data, out_data, 4, 16);