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, RewritingWorksAndColorspaceConversionsAreInserted) {
143 float expected_data[6] = {
148 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
149 RewritingToInvertEffect *effect = new RewritingToInvertEffect();
150 tester.get_chain()->add_effect(effect);
151 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
153 Node *node = effect->invert_node;
154 ASSERT_EQ(1, node->incoming_links.size());
155 ASSERT_EQ(1, node->outgoing_links.size());
156 EXPECT_EQ("ColorSpaceConversionEffect", node->incoming_links[0]->effect->effect_type_id());
157 EXPECT_EQ("ColorSpaceConversionEffect", node->outgoing_links[0]->effect->effect_type_id());
159 expect_equal(expected_data, out_data, 3, 2);
162 // Like RewritingToInvertEffect, but splicing in a MirrorEffect instead,
163 // which does not need linear light or sRGB primaries.
164 class RewritingToMirrorEffect : public Effect {
166 RewritingToMirrorEffect() {}
167 virtual std::string effect_type_id() const { return "RewritingToMirrorEffect"; }
168 std::string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); }
169 virtual void rewrite_graph(EffectChain *graph, Node *self) {
170 Node *mirror_node = graph->add_node(new MirrorEffect());
171 graph->replace_receiver(self, mirror_node);
172 graph->replace_sender(self, mirror_node);
174 self->disabled = true;
175 this->mirror_node = mirror_node;
181 TEST(EffectChainTest, NoGammaConversionsWhenLinearLightNotNeeded) {
186 float expected_data[6] = {
191 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
192 RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
193 tester.get_chain()->add_effect(effect);
194 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
196 Node *node = effect->mirror_node;
197 ASSERT_EQ(1, node->incoming_links.size());
198 EXPECT_EQ(0, node->outgoing_links.size());
199 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
201 expect_equal(expected_data, out_data, 3, 2);
204 TEST(EffectChainTest, NoColorspaceConversionsWhensRGBPrimariesNotNeeded) {
209 float expected_data[6] = {
214 EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_REC_601_525, GAMMA_LINEAR);
215 RewritingToMirrorEffect *effect = new RewritingToMirrorEffect();
216 tester.get_chain()->add_effect(effect);
217 tester.run(out_data, GL_RED, COLORSPACE_REC_601_525, GAMMA_LINEAR);
219 Node *node = effect->mirror_node;
220 ASSERT_EQ(1, node->incoming_links.size());
221 EXPECT_EQ(0, node->outgoing_links.size());
222 EXPECT_EQ("FlatInput", node->incoming_links[0]->effect->effect_type_id());
224 expect_equal(expected_data, out_data, 3, 2);
227 // The identity effect needs linear light, and thus will get conversions on both sides.
228 // Verify that sRGB data is properly converted to and from linear light for the entire ramp.
229 TEST(EffectChainTest, IdentityThroughsRGBConversions) {
231 for (unsigned i = 0; i < 256; ++i) {
235 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB);
236 tester.get_chain()->add_effect(new IdentityEffect());
237 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB);
239 expect_equal(data, out_data, 256, 1);
242 // Same, for the Rec. 601/709 gamma curve.
243 TEST(EffectChainTest, IdentityThroughRec709) {
245 for (unsigned i = 0; i < 256; ++i) {
249 EffectChainTester tester(data, 256, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_709);
250 tester.get_chain()->add_effect(new IdentityEffect());
251 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_709);
253 expect_equal(data, out_data, 256, 1);
256 // Effectively scales down its input linearly by 4x (and repeating it),
257 // which is not attainable without mipmaps.
258 class MipmapNeedingEffect : public Effect {
260 MipmapNeedingEffect() {}
261 virtual bool needs_mipmaps() const { return true; }
262 virtual std::string effect_type_id() const { return "MipmapNeedingEffect"; }
263 std::string output_fragment_shader() { return read_file("mipmap_needing_effect.frag"); }
264 void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num)
266 glActiveTexture(GL_TEXTURE0);
268 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
270 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
275 TEST(EffectChainTest, MipmapGenerationWorks) {
276 float data[] = { // In 4x4 blocks.
277 1.0f, 0.0f, 0.0f, 0.0f,
278 0.0f, 0.0f, 0.0f, 0.0f,
279 0.0f, 0.0f, 0.0f, 0.0f,
280 0.0f, 0.0f, 0.0f, 1.0f,
282 0.0f, 0.0f, 0.0f, 0.0f,
283 0.0f, 0.5f, 0.0f, 0.0f,
284 0.0f, 0.0f, 1.0f, 0.0f,
285 0.0f, 0.0f, 0.0f, 0.0f,
287 1.0f, 1.0f, 1.0f, 1.0f,
288 1.0f, 1.0f, 1.0f, 1.0f,
289 1.0f, 1.0f, 1.0f, 1.0f,
290 1.0f, 1.0f, 1.0f, 1.0f,
292 0.0f, 0.0f, 0.0f, 0.0f,
293 0.0f, 1.0f, 1.0f, 0.0f,
294 0.0f, 1.0f, 1.0f, 0.0f,
295 0.0f, 0.0f, 0.0f, 0.0f,
297 float expected_data[] = { // Repeated four times each way.
298 0.125f, 0.125f, 0.125f, 0.125f,
299 0.09375f, 0.09375f, 0.09375f, 0.09375f,
300 1.0f, 1.0f, 1.0f, 1.0f,
301 0.25f, 0.25f, 0.25f, 0.25f,
303 0.125f, 0.125f, 0.125f, 0.125f,
304 0.09375f, 0.09375f, 0.09375f, 0.09375f,
305 1.0f, 1.0f, 1.0f, 1.0f,
306 0.25f, 0.25f, 0.25f, 0.25f,
308 0.125f, 0.125f, 0.125f, 0.125f,
309 0.09375f, 0.09375f, 0.09375f, 0.09375f,
310 1.0f, 1.0f, 1.0f, 1.0f,
311 0.25f, 0.25f, 0.25f, 0.25f,
313 0.125f, 0.125f, 0.125f, 0.125f,
314 0.09375f, 0.09375f, 0.09375f, 0.09375f,
315 1.0f, 1.0f, 1.0f, 1.0f,
316 0.25f, 0.25f, 0.25f, 0.25f,
318 float out_data[4 * 16];
319 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
320 tester.get_chain()->add_effect(new MipmapNeedingEffect());
321 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
323 expect_equal(expected_data, out_data, 4, 16);
326 TEST(EffectChainTest, ResizeDownByFourThenUpByFour) {
327 float data[] = { // In 4x4 blocks.
328 1.0f, 0.0f, 0.0f, 0.0f,
329 0.0f, 0.0f, 0.0f, 0.0f,
330 0.0f, 0.0f, 0.0f, 0.0f,
331 0.0f, 0.0f, 0.0f, 1.0f,
333 0.0f, 0.0f, 0.0f, 0.0f,
334 0.0f, 0.5f, 0.0f, 0.0f,
335 0.0f, 0.0f, 1.0f, 0.0f,
336 0.0f, 0.0f, 0.0f, 0.0f,
338 1.0f, 1.0f, 1.0f, 1.0f,
339 1.0f, 1.0f, 1.0f, 1.0f,
340 1.0f, 1.0f, 1.0f, 1.0f,
341 1.0f, 1.0f, 1.0f, 1.0f,
343 0.0f, 0.0f, 0.0f, 0.0f,
344 0.0f, 1.0f, 1.0f, 0.0f,
345 0.0f, 1.0f, 1.0f, 0.0f,
346 0.0f, 0.0f, 0.0f, 0.0f,
348 float expected_data[] = { // Repeated four times horizontaly, interpolated vertically.
349 0.1250f, 0.1250f, 0.1250f, 0.1250f,
350 0.1250f, 0.1250f, 0.1250f, 0.1250f,
351 0.1211f, 0.1211f, 0.1211f, 0.1211f,
352 0.1133f, 0.1133f, 0.1133f, 0.1133f,
353 0.1055f, 0.1055f, 0.1055f, 0.1055f,
354 0.0977f, 0.0977f, 0.0977f, 0.0977f,
355 0.2070f, 0.2070f, 0.2070f, 0.2070f,
356 0.4336f, 0.4336f, 0.4336f, 0.4336f,
357 0.6602f, 0.6602f, 0.6602f, 0.6602f,
358 0.8867f, 0.8867f, 0.8867f, 0.8867f,
359 0.9062f, 0.9062f, 0.9062f, 0.9062f,
360 0.7188f, 0.7188f, 0.7188f, 0.7188f,
361 0.5312f, 0.5312f, 0.5312f, 0.5312f,
362 0.3438f, 0.3438f, 0.3438f, 0.3438f,
363 0.2500f, 0.2500f, 0.2500f, 0.2500f,
364 0.2500f, 0.2500f, 0.2500f, 0.2500f,
366 float out_data[4 * 16];
368 ResizeEffect *downscale = new ResizeEffect();
369 ASSERT_TRUE(downscale->set_int("width", 1));
370 ASSERT_TRUE(downscale->set_int("height", 4));
372 ResizeEffect *upscale = new ResizeEffect();
373 ASSERT_TRUE(upscale->set_int("width", 4));
374 ASSERT_TRUE(upscale->set_int("height", 16));
376 EffectChainTester tester(data, 4, 16, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR);
377 tester.get_chain()->add_effect(downscale);
378 tester.get_chain()->add_effect(upscale);
379 tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR);
381 expect_equal(expected_data, out_data, 4, 16);