From: Steinar H. Gunderson Date: Mon, 5 Oct 2015 17:49:51 +0000 (+0200) Subject: Make FlatInput and YCbCrInput support taking in external OpenGL textures. X-Git-Tag: 1.3.0~37 X-Git-Url: https://git.sesse.net/?p=movit;a=commitdiff_plain;h=84412c6d89fbe3563bab0b151274eb56c2ddd35c Make FlatInput and YCbCrInput support taking in external OpenGL textures. --- diff --git a/flat_input.cpp b/flat_input.cpp index 1795902..0f6f395 100644 --- a/flat_input.cpp +++ b/flat_input.cpp @@ -21,6 +21,7 @@ FlatInput::FlatInput(ImageFormat image_format, MovitPixelFormat pixel_format_in, width(width), height(height), pitch(width), + owns_texture(false), pixel_data(NULL), fixup_swap_rb(false), fixup_red_to_grayscale(false) @@ -57,9 +58,7 @@ FlatInput::FlatInput(ImageFormat image_format, MovitPixelFormat pixel_format_in, FlatInput::~FlatInput() { - if (texture_num != 0) { - resource_pool->release_2d_texture(texture_num); - } + possibly_release_texture(); } void FlatInput::set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num) @@ -163,6 +162,7 @@ void FlatInput::set_gl_state(GLuint glsl_program_num, const string& prefix, unsi check_error(); glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); check_error(); + owns_texture = true; } else { glBindTexture(GL_TEXTURE_2D, texture_num); check_error(); @@ -183,9 +183,15 @@ string FlatInput::output_fragment_shader() void FlatInput::invalidate_pixel_data() { - if (texture_num != 0) { + possibly_release_texture(); +} + +void FlatInput::possibly_release_texture() +{ + if (texture_num != 0 && owns_texture) { resource_pool->release_2d_texture(texture_num); texture_num = 0; + owns_texture = false; } } diff --git a/flat_input.h b/flat_input.h index 495a36c..14f8df1 100644 --- a/flat_input.h +++ b/flat_input.h @@ -112,18 +112,40 @@ public: invalidate_pixel_data(); } + // Tells the input to use the specific OpenGL texture as pixel data. + // This is useful if you want to share the same texture between multiple + // EffectChain instances, or if you somehow can get the data into a texture more + // efficiently than through a normal upload (e.g. a video codec decoding straight + // into a texture). Note that you are responsible for setting the right sampler + // parameters (e.g. clamp-to-edge) yourself, as well as generate any mipmaps + // if they are needed. + // + // NOTE: The input does not take ownership of this texture; you are responsible + // for releasing it yourself. In particular, if you call invalidate_pixel_data() + // or anything calling it, the texture will silently be removed from the input. + void set_texture_num(GLuint texture_num) + { + possibly_release_texture(); + this->texture_num = texture_num; + this->owns_texture = false; + } + virtual void inform_added(EffectChain *chain) { resource_pool = chain->get_resource_pool(); } private: + // Release the texture if we have any, and it is owned by us. + void possibly_release_texture(); + ImageFormat image_format; MovitPixelFormat pixel_format; GLenum type; GLuint pbo, texture_num; int output_linear_gamma, needs_mipmaps; unsigned width, height, pitch; + bool owns_texture; const void *pixel_data; ResourcePool *resource_pool; bool fixup_swap_rb, fixup_red_to_grayscale; diff --git a/flat_input_test.cpp b/flat_input_test.cpp index 397cae3..741b7c7 100644 --- a/flat_input_test.cpp +++ b/flat_input_test.cpp @@ -6,6 +6,7 @@ #include "effect_chain.h" #include "flat_input.h" #include "gtest/gtest.h" +#include "resource_pool.h" #include "test_util.h" #include "util.h" @@ -267,4 +268,56 @@ TEST(FlatInput, PBO) { glDeleteBuffers(1, &pbo); } +TEST(FlatInput, ExternalTexture) { + const int size = 5; + + float data[3 * size] = { + 0.0, 0.0, 0.0, + 0.5, 0.0, 0.0, + 0.0, 0.5, 0.0, + 0.0, 0.0, 0.7, + 0.0, 0.3, 0.7, + }; + float expected_data[4 * size] = { + 0.0, 0.0, 0.0, 1.0, + 0.5, 0.0, 0.0, 1.0, + 0.0, 0.5, 0.0, 1.0, + 0.0, 0.0, 0.7, 1.0, + 0.0, 0.3, 0.7, 1.0, + }; + float out_data[4 * size]; + + EffectChainTester tester(NULL, 1, size, FORMAT_RGB, COLORSPACE_sRGB, GAMMA_LINEAR); + + ImageFormat format; + format.color_space = COLORSPACE_sRGB; + format.gamma_curve = GAMMA_LINEAR; + + ResourcePool pool; + GLuint tex = pool.create_2d_texture(GL_RGB8, 1, size); + check_error(); + glBindTexture(GL_TEXTURE_2D, tex); + check_error(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + check_error(); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + check_error(); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, size, GL_RGB, GL_FLOAT, data); + check_error(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + check_error(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + check_error(); + + FlatInput *input = new FlatInput(format, FORMAT_RGB, GL_FLOAT, 1, size); + input->set_texture_num(tex); + tester.get_chain()->add_input(input); + + tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR); + + pool.release_2d_texture(tex); + + expect_equal(expected_data, out_data, 4, size); +} + } // namespace movit diff --git a/ycbcr_input.cpp b/ycbcr_input.cpp index 7f58f3f..4c824c6 100644 --- a/ycbcr_input.cpp +++ b/ycbcr_input.cpp @@ -41,6 +41,7 @@ YCbCrInput::YCbCrInput(const ImageFormat &image_format, heights[2] = height / ycbcr_format.chroma_subsampling_y; pixel_data[0] = pixel_data[1] = pixel_data[2] = NULL; + owns_texture[0] = owns_texture[1] = owns_texture[2] = false; register_uniform_sampler2d("tex_y", &uniform_tex_y); @@ -58,9 +59,7 @@ YCbCrInput::YCbCrInput(const ImageFormat &image_format, YCbCrInput::~YCbCrInput() { for (unsigned channel = 0; channel < num_channels; ++channel) { - if (texture_num[channel] != 0) { - resource_pool->release_2d_texture(texture_num[channel]); - } + possibly_release_texture(channel); } } @@ -100,6 +99,7 @@ void YCbCrInput::set_gl_state(GLuint glsl_program_num, const string& prefix, uns check_error(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); check_error(); + owns_texture[channel] = true; } else { glBindTexture(GL_TEXTURE_2D, texture_num[channel]); check_error(); @@ -158,10 +158,7 @@ string YCbCrInput::output_fragment_shader() void YCbCrInput::invalidate_pixel_data() { for (unsigned channel = 0; channel < 3; ++channel) { - if (texture_num[channel] != 0) { - resource_pool->release_2d_texture(texture_num[channel]); - texture_num[channel] = 0; - } + possibly_release_texture(channel); } } @@ -174,4 +171,13 @@ bool YCbCrInput::set_int(const std::string& key, int value) return Effect::set_int(key, value); } +void YCbCrInput::possibly_release_texture(unsigned channel) +{ + if (texture_num[channel] != 0 && owns_texture[channel]) { + resource_pool->release_2d_texture(texture_num[channel]); + texture_num[channel] = 0; + owns_texture[channel] = false; + } +} + } // namespace movit diff --git a/ycbcr_input.h b/ycbcr_input.h index 282b55a..6f4b66d 100644 --- a/ycbcr_input.h +++ b/ycbcr_input.h @@ -85,6 +85,16 @@ public: invalidate_pixel_data(); } + // Tells the input to use the specific OpenGL texture as pixel data for the given + // channel. The comments on FlatInput::set_texture_num() also apply here, except + // that this input generally does not use mipmaps. + void set_texture_num(unsigned channel, GLuint texture_num) + { + possibly_release_texture(channel); + this->texture_num[channel] = texture_num; + this->owns_texture[channel] = false; + } + virtual void inform_added(EffectChain *chain) { resource_pool = chain->get_resource_pool(); @@ -93,6 +103,9 @@ public: bool set_int(const std::string& key, int value); private: + // Release the texture in the given channel if we have any, and it is owned by us. + void possibly_release_texture(unsigned channel); + ImageFormat image_format; YCbCrFormat ycbcr_format; GLuint num_channels; @@ -103,6 +116,7 @@ private: unsigned width, height, widths[3], heights[3]; const unsigned char *pixel_data[3]; unsigned pitch[3]; + bool owns_texture[3]; ResourcePool *resource_pool; }; diff --git a/ycbcr_input_test.cpp b/ycbcr_input_test.cpp index 8b69e87..109178f 100644 --- a/ycbcr_input_test.cpp +++ b/ycbcr_input_test.cpp @@ -7,6 +7,7 @@ #include "gtest/gtest.h" #include "test_util.h" #include "util.h" +#include "resource_pool.h" #include "ycbcr_input.h" namespace movit { @@ -538,4 +539,77 @@ TEST(YCbCrInputTest, CombinedCbAndCr) { expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002); } +TEST(YCbCrInputTest, ExternalTexture) { + const int width = 1; + const int height = 5; + + // Pure-color test inputs, calculated with the formulas in Rec. 601 + // section 2.5.4. + unsigned char y[width * height] = { + 16, 235, 81, 145, 41, + }; + unsigned char cb[width * height] = { + 128, 128, 90, 54, 240, + }; + unsigned char cr[width * height] = { + 128, 128, 240, 34, 110, + }; + float expected_data[4 * width * height] = { + 0.0, 0.0, 0.0, 1.0, + 1.0, 1.0, 1.0, 1.0, + 1.0, 0.0, 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + 0.0, 0.0, 1.0, 1.0, + }; + float out_data[4 * width * height]; + + EffectChainTester tester(NULL, width, height); + + ImageFormat format; + format.color_space = COLORSPACE_sRGB; + format.gamma_curve = GAMMA_sRGB; + + YCbCrFormat ycbcr_format; + ycbcr_format.luma_coefficients = YCBCR_REC_601; + ycbcr_format.full_range = false; + ycbcr_format.num_levels = 256; + ycbcr_format.chroma_subsampling_x = 1; + ycbcr_format.chroma_subsampling_y = 1; + ycbcr_format.cb_x_position = 0.5f; + ycbcr_format.cb_y_position = 0.5f; + ycbcr_format.cr_x_position = 0.5f; + ycbcr_format.cr_y_position = 0.5f; + + // Make a texture for the Cb data; keep the others as regular uploads. + ResourcePool pool; + GLuint cb_tex = pool.create_2d_texture(GL_R8, width, height); + check_error(); + glBindTexture(GL_TEXTURE_2D, cb_tex); + check_error(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + check_error(); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + check_error(); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, cb); + check_error(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + check_error(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + check_error(); + + YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height); + input->set_pixel_data(0, y); + input->set_texture_num(1, cb_tex); + input->set_pixel_data(2, cr); + tester.get_chain()->add_input(input); + + tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB); + + pool.release_2d_texture(cb_tex); + + // Y'CbCr isn't 100% accurate (the input values are rounded), + // so we need some leeway. + expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002); +} + } // namespace movit