Make FlatInput and YCbCrInput support taking in external OpenGL textures.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 5 Oct 2015 17:49:51 +0000 (19:49 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 5 Oct 2015 17:49:51 +0000 (19:49 +0200)
flat_input.cpp
flat_input.h
flat_input_test.cpp
ycbcr_input.cpp
ycbcr_input.h
ycbcr_input_test.cpp

index 1795902..0f6f395 100644 (file)
@@ -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;
        }
 }
 
index 495a36c..14f8df1 100644 (file)
@@ -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;
index 397cae3..741b7c7 100644 (file)
@@ -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
index 7f58f3f..4c824c6 100644 (file)
@@ -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
index 282b55a..6f4b66d 100644 (file)
@@ -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;
 };
 
index 8b69e87..109178f 100644 (file)
@@ -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