X-Git-Url: https://git.sesse.net/?p=movit;a=blobdiff_plain;f=effect_chain_test.cpp;h=97b90ed5d331386cbef60725725c8a5d9a49e09b;hp=6ab874d2e4e32507beb5cc9eeb546fac69e9f546;hb=90ac46cdc5845432df13385f946c63b5496c685e;hpb=54d47f65e9abac77229636fbaaefea8caf34a4d4 diff --git a/effect_chain_test.cpp b/effect_chain_test.cpp index 6ab874d..97b90ed 100644 --- a/effect_chain_test.cpp +++ b/effect_chain_test.cpp @@ -18,6 +18,7 @@ #include "mirror_effect.h" #include "multiply_effect.h" #include "resize_effect.h" +#include "resource_pool.h" #include "test_util.h" #include "util.h" @@ -1303,5 +1304,167 @@ TEST(EffectChainTest, StringStreamLocalesWork) { free(saved_locale); } +TEST(EffectChainTest, sRGBIntermediate) { + float data[] = { + 0.0f, 0.5f, 0.0f, 1.0f, + }; + float out_data[4]; + EffectChainTester tester(data, 1, 1, FORMAT_RGBA_PREMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_LINEAR); + tester.get_chain()->set_intermediate_format(GL_SRGB8); + tester.get_chain()->add_effect(new IdentityEffect()); + tester.get_chain()->add_effect(new BouncingIdentityEffect()); + tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR); + + EXPECT_GE(fabs(out_data[1] - data[1]), 1e-3) + << "Expected sRGB not to be able to represent 0.5 exactly (got " << out_data[1] << ")"; + EXPECT_LT(fabs(out_data[1] - data[1]), 0.1f) + << "Expected sRGB to be able to represent 0.5 approximately (got " << out_data[1] << ")"; + + // This state should have been preserved. + EXPECT_FALSE(glIsEnabled(GL_FRAMEBUFFER_SRGB)); +} + +// An effect that is like IdentityEffect, but also does not require linear light. +class PassThroughEffect : public IdentityEffect { +public: + PassThroughEffect() {} + virtual string effect_type_id() const { return "PassThroughEffect"; } + virtual bool needs_linear_light() const { return false; } + AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; } +}; + +// Same, just also bouncing. +class BouncingPassThroughEffect : public BouncingIdentityEffect { +public: + BouncingPassThroughEffect() {} + virtual string effect_type_id() const { return "BouncingPassThroughEffect"; } + virtual bool needs_linear_light() const { return false; } + bool needs_texture_bounce() const { return true; } + AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; } +}; + +TEST(EffectChainTest, Linear10bitIntermediateAccuracy) { + // Note that we do the comparison in sRGB space, which is what we + // typically would want; however, we do the sRGB conversion ourself + // to avoid compounding errors from shader conversions into the + // analysis. + const int size = 4096; // 12-bit. + float linear_data[size], data[size], out_data[size]; + + for (int i = 0; i < size; ++i) { + linear_data[i] = i / double(size - 1); + data[i] = srgb_to_linear(linear_data[i]); + } + + EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F); + tester.get_chain()->set_intermediate_format(GL_RGB10_A2); + tester.get_chain()->add_effect(new IdentityEffect()); + tester.get_chain()->add_effect(new BouncingIdentityEffect()); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + for (int i = 0; i < size; ++i) { + out_data[i] = linear_to_srgb(out_data[i]); + } + + // This maximum error is pretty bad; about 6.5 levels of a 10-bit sRGB + // framebuffer. (Slightly more on NVIDIA cards.) + expect_equal(linear_data, out_data, size, 1, 7.5e-3, 2e-5); +} + +TEST(EffectChainTest, SquareRoot10bitIntermediateAccuracy) { + // Note that we do the comparison in sRGB space, which is what we + // typically would want; however, we do the sRGB conversion ourself + // to avoid compounding errors from shader conversions into the + // analysis. + const int size = 4096; // 12-bit. + float linear_data[size], data[size], out_data[size]; + + for (int i = 0; i < size; ++i) { + linear_data[i] = i / double(size - 1); + data[i] = srgb_to_linear(linear_data[i]); + } + + EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA32F); + tester.get_chain()->set_intermediate_format(GL_RGB10_A2, SQUARE_ROOT_FRAMEBUFFER_TRANSFORMATION); + tester.get_chain()->add_effect(new IdentityEffect()); + tester.get_chain()->add_effect(new BouncingIdentityEffect()); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + for (int i = 0; i < size; ++i) { + out_data[i] = linear_to_srgb(out_data[i]); + } + + // This maximum error is much better; about 0.7 levels of a 10-bit sRGB + // framebuffer (ideal would be 0.5). That is an order of magnitude better + // than in the linear test above. The RMS error is much better, too. + expect_equal(linear_data, out_data, size, 1, 7.5e-4, 5e-6); +} + +TEST(EffectChainTest, SquareRootIntermediateIsTurnedOffForNonLinearData) { + const int size = 256; // 8-bit. + float data[size], out_data[size]; + + for (int i = 0; i < size; ++i) { + data[i] = i / double(size - 1); + } + + EffectChainTester tester(data, size, 1, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_REC_601, GL_RGBA32F); + tester.get_chain()->set_intermediate_format(GL_RGB8, SQUARE_ROOT_FRAMEBUFFER_TRANSFORMATION); + tester.get_chain()->add_effect(new PassThroughEffect()); + tester.get_chain()->add_effect(new BouncingPassThroughEffect()); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_REC_601); + + // The data should be passed through nearly exactly, since there is no effect + // on the path that requires linear light. (Actually, it _is_ exact modulo + // fp32 errors, but the error bounds is strictly _less than_, not zero.) + expect_equal(data, out_data, size, 1, 1e-6, 1e-6); +} + +// An effect that stores which program number was last run under. +class RecordingIdentityEffect : public Effect { +public: + RecordingIdentityEffect() {} + virtual string effect_type_id() const { return "RecordingIdentityEffect"; } + string output_fragment_shader() { return read_file("identity.frag"); } + + GLuint last_glsl_program_num; + void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num) + { + last_glsl_program_num = glsl_program_num; + } +}; + +TEST(EffectChainTest, ProgramsAreClonedForMultipleThreads) { + float data[] = { + 0.0f, 0.25f, 0.3f, + 0.75f, 1.0f, 1.0f, + }; + float out_data[6]; + EffectChainTester tester(data, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR); + RecordingIdentityEffect *effect = new RecordingIdentityEffect(); + tester.get_chain()->add_effect(effect); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + expect_equal(data, out_data, 3, 2); + + ASSERT_NE(0, effect->last_glsl_program_num); + + // Now pretend some other effect is using this program number; + // ResourcePool will then need to clone it. + ResourcePool *resource_pool = tester.get_chain()->get_resource_pool(); + GLuint master_program_num = resource_pool->use_glsl_program(effect->last_glsl_program_num); + EXPECT_EQ(effect->last_glsl_program_num, master_program_num); + + // Re-run should still give the correct data, but it should have run + // with a different program. + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + expect_equal(data, out_data, 3, 2); + EXPECT_NE(effect->last_glsl_program_num, master_program_num); + + // Release the program, and check one final time. + resource_pool->unuse_glsl_program(master_program_num); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + expect_equal(data, out_data, 3, 2); +} } // namespace movit