X-Git-Url: https://git.sesse.net/?p=movit;a=blobdiff_plain;f=effect_chain_test.cpp;h=63f574be615d2bb80ccbadfd9b56328173470db3;hp=5ffb105cdb75ccd805caa1f9f25bfd322dac6f1e;hb=beef8c4b242dd9e073f04b80539a10295417ced5;hpb=2d3b64d51be3e8f38a8c224f558de4ceb222e799 diff --git a/effect_chain_test.cpp b/effect_chain_test.cpp index 5ffb105..63f574b 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" @@ -135,7 +136,7 @@ public: template class RewritingEffect : public Effect { public: - RewritingEffect() : effect(new T()), replaced_node(NULL) {} + RewritingEffect() : effect(new T()), replaced_node(nullptr) {} virtual string effect_type_id() const { return "RewritingEffect[" + effect->effect_type_id() + "]"; } string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); } virtual void rewrite_graph(EffectChain *graph, Node *self) { @@ -187,7 +188,7 @@ TEST(EffectChainTest, RewritingWorksAndTexturesAreAskedForsRGB) { 0.0000f, 0.0000f, 0.0000f, 1.0000f }; float out_data[4 * 4]; - EffectChainTester tester(NULL, 1, 4); + EffectChainTester tester(nullptr, 1, 4); tester.add_input(data, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_sRGB); RewritingEffect *effect = new RewritingEffect(); tester.get_chain()->add_effect(effect); @@ -261,7 +262,7 @@ TEST(EffectChainTest, HandlesInputChangingColorspace) { }; float out_data[size]; - EffectChainTester tester(NULL, 4, 1, FORMAT_GRAYSCALE); + EffectChainTester tester(nullptr, 4, 1, FORMAT_GRAYSCALE); // First say that we have sRGB, linear input. ImageFormat format; @@ -351,7 +352,7 @@ TEST(EffectChainTest, IdentityThroughGPUsRGBConversions) { expected_data[i] = i / 255.0; }; float out_data[256]; - EffectChainTester tester(NULL, 256, 1); + EffectChainTester tester(nullptr, 256, 1); tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB); tester.get_chain()->add_effect(new IdentityEffect()); tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_sRGB); @@ -437,7 +438,7 @@ private: // which outputs blank alpha. class RewritingToBlueInput : public Input { public: - RewritingToBlueInput() : blue_node(NULL) { register_int("needs_mipmaps", &needs_mipmaps); } + RewritingToBlueInput() : blue_node(nullptr) { register_int("needs_mipmaps", &needs_mipmaps); } virtual string effect_type_id() const { return "RewritingToBlueInput"; } string output_fragment_shader() { EXPECT_TRUE(false); return read_file("identity.frag"); } virtual void rewrite_graph(EffectChain *graph, Node *self) { @@ -473,7 +474,7 @@ TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) { 0.0f, 0.0f, 1.0f, 1.0f, }; float out_data[4 * size]; - EffectChainTester tester(NULL, size, 1); + EffectChainTester tester(nullptr, size, 1); RewritingToBlueInput *input = new RewritingToBlueInput(); tester.get_chain()->add_input(input); tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_PREMULTIPLIED); @@ -502,7 +503,7 @@ TEST(EffectChainTest, NoAlphaConversionsWithBlankAlphaPreservingEffect) { 0.0f, 0.0f, 1.0f, 1.0f, }; float out_data[4 * size]; - EffectChainTester tester(NULL, size, 1); + EffectChainTester tester(nullptr, size, 1); tester.get_chain()->add_input(new BlueInput()); tester.get_chain()->add_effect(new BlankAlphaPreservingEffect()); RewritingEffect *effect = new RewritingEffect(); @@ -528,7 +529,7 @@ TEST(EffectChainTest, AlphaConversionsWithNonBlankAlphaPreservingEffect) { 0.0f, 0.0f, 1.0f, 1.0f, }; float out_data[4 * size]; - EffectChainTester tester(NULL, size, 1); + EffectChainTester tester(nullptr, size, 1); tester.get_chain()->add_input(new BlueInput()); tester.get_chain()->add_effect(new IdentityEffect()); // Not BlankAlphaPreservingEffect. RewritingEffect *effect = new RewritingEffect(); @@ -683,7 +684,7 @@ TEST(EffectChainTest, MipmapsWithNonMipmapCapableInput) { 0.25f, 0.25f, 0.25f, 0.25f, }; float out_data[4 * 16]; - EffectChainTester tester(NULL, 4, 16, FORMAT_GRAYSCALE); + EffectChainTester tester(nullptr, 4, 16, FORMAT_GRAYSCALE); ImageFormat format; format.color_space = COLORSPACE_sRGB; @@ -795,7 +796,7 @@ TEST(EffectChainTest, DiamondGraph) { MultiplyEffect *mul_two = new MultiplyEffect(); ASSERT_TRUE(mul_two->set_vec4("factor", two)); - EffectChainTester tester(NULL, 2, 2); + EffectChainTester tester(nullptr, 2, 2); ImageFormat format; format.color_space = COLORSPACE_sRGB; @@ -846,7 +847,7 @@ TEST(EffectChainTest, DiamondGraphWithOneInputUsedInTwoPhases) { BouncingIdentityEffect *bounce = new BouncingIdentityEffect(); - EffectChainTester tester(NULL, 2, 2); + EffectChainTester tester(nullptr, 2, 2); ImageFormat format; format.color_space = COLORSPACE_sRGB; @@ -876,7 +877,7 @@ TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneGammaConversion) { }; float out_data[2 * 2]; - EffectChainTester tester(NULL, 2, 2); + EffectChainTester tester(nullptr, 2, 2); tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_sRGB); // MirrorEffect does not get linear light, so the conversions will be @@ -909,7 +910,7 @@ TEST(EffectChainTest, EffectUsedTwiceOnlyGetsOneColorspaceConversion) { }; float out_data[2 * 2]; - EffectChainTester tester(NULL, 2, 2); + EffectChainTester tester(nullptr, 2, 2); tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_REC_601_625, GAMMA_LINEAR); // MirrorEffect does not get linear light, so the conversions will be @@ -953,7 +954,7 @@ TEST(EffectChainTest, SameInputsGiveSameOutputs) { }; float out_data[4 * 3]; - EffectChainTester tester(NULL, 4, 3); // Note non-square aspect. + EffectChainTester tester(nullptr, 4, 3); // Note non-square aspect. ImageFormat format; format.color_space = COLORSPACE_sRGB; @@ -998,7 +999,7 @@ TEST(EffectChainTest, AspectRatioConversion) { // (keep the height, round the width 9.333 to 9). float out_data[9 * 7]; - EffectChainTester tester(NULL, 4, 3); + EffectChainTester tester(nullptr, 4, 3); ImageFormat format; format.color_space = COLORSPACE_sRGB; @@ -1043,7 +1044,7 @@ TEST(EffectChainTest, FirstPhaseWithNoTextureCoordinates) { FlatInput *input = new FlatInput(format, FORMAT_GRAYSCALE, GL_FLOAT, 1, size); input->set_pixel_data(data); - EffectChainTester tester(NULL, 1, size); + EffectChainTester tester(nullptr, 1, size); tester.get_chain()->add_input(new BlueInput()); Effect *phase1_end = tester.get_chain()->add_effect(new BouncingIdentityEffect()); tester.get_chain()->add_input(input); @@ -1223,7 +1224,7 @@ TEST(EffectChainTest, IdentityWithOwnPool) { check_error(); glBindTexture(GL_TEXTURE_2D, texnum); check_error(); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, NULL); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr); check_error(); glGenFramebuffers(1, &fbo); @@ -1281,7 +1282,7 @@ TEST(EffectChainTest, StringStreamLocalesWork) { // setlocale() behind-the-scenes, and that might corrupt the returned // pointer, so we need to take our own copy of it here. char *saved_locale = setlocale(LC_ALL, "nb_NO.UTF_8"); - if (saved_locale == NULL) { + if (saved_locale == nullptr) { // The locale wasn't available. return; } @@ -1303,6 +1304,21 @@ TEST(EffectChainTest, StringStreamLocalesWork) { free(saved_locale); } +// An effect that does nothing, but as a compute shader. +class IdentityComputeEffect : public Effect { +public: + IdentityComputeEffect() {} + virtual string effect_type_id() const { return "IdentityComputeEffect"; } + virtual bool is_compute_shader() const { return true; } + string output_fragment_shader() { return read_file("identity.comp"); } +}; + +class WithAndWithoutComputeShaderTest : public testing::TestWithParam { +}; +INSTANTIATE_TEST_CASE_P(WithAndWithoutComputeShaderTest, + WithAndWithoutComputeShaderTest, + testing::Values("fragment", "compute")); + TEST(EffectChainTest, sRGBIntermediate) { float data[] = { 0.0f, 0.5f, 0.0f, 1.0f, @@ -1318,6 +1334,9 @@ TEST(EffectChainTest, sRGBIntermediate) { << "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. @@ -1363,11 +1382,11 @@ TEST(EffectChainTest, Linear10bitIntermediateAccuracy) { } // This maximum error is pretty bad; about 6.5 levels of a 10-bit sRGB - // framebuffer. - expect_equal(linear_data, out_data, size, 1, 7e-3, 2e-5); + // framebuffer. (Slightly more on NVIDIA cards.) + expect_equal(linear_data, out_data, size, 1, 7.5e-3, 2e-5); } -TEST(EffectChainTest, SquareRoot10bitIntermediateAccuracy) { +TEST_P(WithAndWithoutComputeShaderTest, 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 @@ -1382,7 +1401,11 @@ TEST(EffectChainTest, SquareRoot10bitIntermediateAccuracy) { 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()); + if (GetParam() == "compute") { + tester.get_chain()->add_effect(new IdentityComputeEffect()); + } else { + tester.get_chain()->add_effect(new IdentityEffect()); + } tester.get_chain()->add_effect(new BouncingIdentityEffect()); tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); @@ -1393,7 +1416,7 @@ TEST(EffectChainTest, SquareRoot10bitIntermediateAccuracy) { // 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, 7e-4, 5e-6); + expect_equal(linear_data, out_data, size, 1, 7.5e-4, 5e-6); } TEST(EffectChainTest, SquareRootIntermediateIsTurnedOffForNonLinearData) { @@ -1416,4 +1439,110 @@ TEST(EffectChainTest, SquareRootIntermediateIsTurnedOffForNonLinearData) { 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); +} + +TEST(ComputeShaderTest, Identity) { + 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); + if (!movit_compute_shaders_supported) { + fprintf(stderr, "Skipping test; no support for compile shaders.\n"); + return; + } + tester.get_chain()->add_effect(new IdentityComputeEffect()); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + expect_equal(data, out_data, 3, 2); +} + +// Like IdentityComputeEffect, but due to the alpha handling, this will be +// the very last effect in the chain, which means we can't output it directly +// to the screen. +class IdentityAlphaComputeEffect : public IdentityComputeEffect { + AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; } +}; + +TEST(ComputeShaderTest, LastEffectInChain) { + 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); + if (!movit_compute_shaders_supported) { + fprintf(stderr, "Skipping test; no support for compile shaders.\n"); + return; + } + tester.get_chain()->add_effect(new IdentityAlphaComputeEffect()); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + expect_equal(data, out_data, 3, 2); +} + +TEST(ComputeShaderTest, Render8BitTo8Bit) { + uint8_t data[] = { + 14, 200, 80, + 90, 100, 110, + }; + uint8_t out_data[6]; + EffectChainTester tester(nullptr, 3, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8); + if (!movit_compute_shaders_supported) { + fprintf(stderr, "Skipping test; no support for compile shaders.\n"); + return; + } + tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 3, 2); + tester.get_chain()->add_effect(new IdentityAlphaComputeEffect()); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + expect_equal(data, out_data, 3, 2); +} + } // namespace movit