Allow YCbCrInput to change input format after finalize.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 29 Apr 2017 19:59:38 +0000 (21:59 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 29 Apr 2017 19:59:38 +0000 (21:59 +0200)
The performance should be very nearly identical; only a little
possible constant folding, and that's it. There's only one
(documented) limitation, and it's both rare and should be easy
to work around.

version.h
ycbcr_input.cpp
ycbcr_input.frag
ycbcr_input.h
ycbcr_input_test.cpp

index cc87124..9935982 100644 (file)
--- a/version.h
+++ b/version.h
@@ -5,6 +5,6 @@
 // changes, even within git versions. There is no specific version
 // documentation outside the regular changelogs, though.
 
-#define MOVIT_VERSION 28
+#define MOVIT_VERSION 29
 
 #endif // !defined(_MOVIT_VERSION_H)
index 925bf2a..e424fca 100644 (file)
@@ -56,6 +56,10 @@ YCbCrInput::YCbCrInput(const ImageFormat &image_format,
        }
 
        register_int("needs_mipmaps", &needs_mipmaps);
+       register_uniform_mat3("inv_ycbcr_matrix", &uniform_ycbcr_matrix);
+       register_uniform_vec3("offset", uniform_offset);
+       register_uniform_vec2("cb_offset", (float *)&uniform_cb_offset);
+       register_uniform_vec2("cr_offset", (float *)&uniform_cr_offset);
 }
 
 YCbCrInput::~YCbCrInput()
@@ -67,6 +71,18 @@ YCbCrInput::~YCbCrInput()
 
 void YCbCrInput::set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num)
 {
+       compute_ycbcr_matrix(ycbcr_format, uniform_offset, &uniform_ycbcr_matrix, type);
+
+       uniform_cb_offset.x = compute_chroma_offset(
+               ycbcr_format.cb_x_position, ycbcr_format.chroma_subsampling_x, widths[1]);
+       uniform_cb_offset.y = compute_chroma_offset(
+               ycbcr_format.cb_y_position, ycbcr_format.chroma_subsampling_y, heights[1]);
+
+       uniform_cr_offset.x = compute_chroma_offset(
+               ycbcr_format.cr_x_position, ycbcr_format.chroma_subsampling_x, widths[2]);
+       uniform_cr_offset.y = compute_chroma_offset(
+               ycbcr_format.cr_y_position, ycbcr_format.chroma_subsampling_y, heights[2]);
+
        for (unsigned channel = 0; channel < num_channels; ++channel) {
                glActiveTexture(GL_TEXTURE0 + *sampler_num + channel);
                check_error();
@@ -151,31 +167,12 @@ void YCbCrInput::set_gl_state(GLuint glsl_program_num, const string& prefix, uns
 
 string YCbCrInput::output_fragment_shader()
 {
-       float offset[3];
-       Matrix3d ycbcr_to_rgb;
-       compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb, type);
-
        string frag_shader;
 
-       frag_shader = output_glsl_mat3("PREFIX(inv_ycbcr_matrix)", ycbcr_to_rgb);
-       frag_shader += output_glsl_vec3("PREFIX(offset)", offset[0], offset[1], offset[2]);
-
-       float cb_offset_x = compute_chroma_offset(
-               ycbcr_format.cb_x_position, ycbcr_format.chroma_subsampling_x, widths[1]);
-       float cb_offset_y = compute_chroma_offset(
-               ycbcr_format.cb_y_position, ycbcr_format.chroma_subsampling_y, heights[1]);
-       frag_shader += output_glsl_vec2("PREFIX(cb_offset)", cb_offset_x, cb_offset_y);
-
-       float cr_offset_x = compute_chroma_offset(
-               ycbcr_format.cr_x_position, ycbcr_format.chroma_subsampling_x, widths[2]);
-       float cr_offset_y = compute_chroma_offset(
-               ycbcr_format.cr_y_position, ycbcr_format.chroma_subsampling_y, heights[2]);
-       frag_shader += output_glsl_vec2("PREFIX(cr_offset)", cr_offset_x, cr_offset_y);
-
        if (ycbcr_input_splitting == YCBCR_INPUT_INTERLEAVED) {
                frag_shader += "#define Y_CB_CR_SAME_TEXTURE 1\n";
        } else if (ycbcr_input_splitting == YCBCR_INPUT_SPLIT_Y_AND_CBCR) {
-               bool cb_cr_offsets_equal =
+               cb_cr_offsets_equal =
                        (fabs(ycbcr_format.cb_x_position - ycbcr_format.cr_x_position) < 1e-6) &&
                        (fabs(ycbcr_format.cb_y_position - ycbcr_format.cr_y_position) < 1e-6);
                char buf[256];
@@ -190,6 +187,19 @@ string YCbCrInput::output_fragment_shader()
        return frag_shader;
 }
 
+void YCbCrInput::change_ycbcr_format(const YCbCrFormat &ycbcr_format)
+{
+       if (cb_cr_offsets_equal) {
+               assert((fabs(ycbcr_format.cb_x_position - ycbcr_format.cr_x_position) < 1e-6) &&
+                      (fabs(ycbcr_format.cb_y_position - ycbcr_format.cr_y_position) < 1e-6));
+       }
+       if (ycbcr_input_splitting == YCBCR_INPUT_INTERLEAVED) {
+               assert(ycbcr_format.chroma_subsampling_x == 1);
+               assert(ycbcr_format.chroma_subsampling_y == 1);
+       }
+       this->ycbcr_format = ycbcr_format;
+}
+
 void YCbCrInput::invalidate_pixel_data()
 {
        for (unsigned channel = 0; channel < 3; ++channel) {
index 29f809d..170e412 100644 (file)
@@ -3,6 +3,10 @@
 // uniform sampler2D PREFIX(tex_cbcr);  // If CB_CR_SAME_TEXTURE.
 // uniform sampler2D PREFIX(tex_cb);    // If not CB_CR_SAME_TEXTURE.
 // uniform sampler2D PREFIX(tex_cr);    // If not CB_CR_SAME_TEXTURE.
+// uniform mat3 PREFIX(ycbcr_matrix);
+// uniform vec3 PREFIX(offset);
+// uniform vec2 PREFIX(cb_offset);
+// uniform vec2 PREFIX(cr_offset);
 
 vec4 FUNCNAME(vec2 tc) {
        // OpenGL's origin is bottom-left, but most graphics software assumes
index 566f828..bb51049 100644 (file)
@@ -161,6 +161,16 @@ public:
                this->owns_texture[channel] = false;
        }
 
+       // You can change the Y'CbCr format freely, also after finalize,
+       // although with one limitation: If Cb and Cr come from the same
+       // texture and their offsets offsets are the same (ie., within 1e-6)
+       // when finalizing, they most continue to be so forever, as this
+       // optimization is compiled into the shader.
+       //
+       // If you change subsampling parameters, you'll need to call
+       // set_width() / set_height() again after this.
+       void change_ycbcr_format(const YCbCrFormat &ycbcr_format);
+
        virtual void inform_added(EffectChain *chain)
        {
                resource_pool = chain->get_resource_pool();
@@ -180,6 +190,10 @@ private:
        GLenum type;
        GLuint pbos[3], texture_num[3];
        GLint uniform_tex_y, uniform_tex_cb, uniform_tex_cr;
+       Eigen::Matrix3d uniform_ycbcr_matrix;
+       float uniform_offset[3];
+       Point2D uniform_cb_offset, uniform_cr_offset;
+       bool cb_cr_offsets_equal;
 
        unsigned width, height, widths[3], heights[3];
        const unsigned char *pixel_data[3];
index 413ac15..deece92 100644 (file)
@@ -285,6 +285,80 @@ TEST(YCbCrInputTest, Rec2020) {
        expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
 }
 
+// Very similar to Rec709.
+TEST(YCbCrInputTest, ChangeFormat) {
+       const int width = 1;
+       const int height = 5;
+
+       // Pure-color test inputs, calculated with the formulas in Rec. 709
+       // page 19, items 3.4 and 3.5.
+       unsigned char y[width * height] = {
+               16, 235, 63, 173, 32,
+       };
+       unsigned char cb[width * height] = {
+               128, 128, 102, 42, 240,
+       };
+       unsigned char cr[width * height] = {
+               128, 128, 240, 26, 118,
+       };
+       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;
+
+       // Basically all of these values will be changed after finalize.
+       YCbCrFormat initial_ycbcr_format;
+       initial_ycbcr_format.luma_coefficients = YCBCR_REC_601;
+       initial_ycbcr_format.full_range = true;
+       initial_ycbcr_format.num_levels = 1024;
+       initial_ycbcr_format.chroma_subsampling_x = 1;
+       initial_ycbcr_format.chroma_subsampling_y = 5;
+       initial_ycbcr_format.cb_x_position = 0.0f;
+       initial_ycbcr_format.cb_y_position = 0.5f;
+       initial_ycbcr_format.cr_x_position = 0.0f;
+       initial_ycbcr_format.cr_y_position = 0.5f;
+
+       YCbCrInput *input = new YCbCrInput(format, initial_ycbcr_format, width, height);
+       input->set_pixel_data(0, y);
+       input->set_pixel_data(1, cb);
+       input->set_pixel_data(2, cr);
+       tester.get_chain()->add_input(input);
+
+       tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
+
+       // Rerun with the right format.
+       YCbCrFormat ycbcr_format;
+       ycbcr_format.luma_coefficients = YCBCR_REC_709;
+       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;
+
+       input->change_ycbcr_format(ycbcr_format);
+       input->set_width(width);
+       input->set_height(height);
+
+       tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
+
+       // 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);
+}
+
 TEST(YCbCrInputTest, Subsampling420) {
        const int width = 4;
        const int height = 4;