From: Steinar H. Gunderson Date: Sat, 29 Apr 2017 19:59:38 +0000 (+0200) Subject: Allow YCbCrInput to change input format after finalize. X-Git-Tag: 1.5.1~2 X-Git-Url: https://git.sesse.net/?p=movit;a=commitdiff_plain;h=6a176580b95002657b381acfaf3892b345e28364 Allow YCbCrInput to change input format after finalize. 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. --- diff --git a/version.h b/version.h index cc87124..9935982 100644 --- 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) diff --git a/ycbcr_input.cpp b/ycbcr_input.cpp index 925bf2a..e424fca 100644 --- a/ycbcr_input.cpp +++ b/ycbcr_input.cpp @@ -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) { diff --git a/ycbcr_input.frag b/ycbcr_input.frag index 29f809d..170e412 100644 --- a/ycbcr_input.frag +++ b/ycbcr_input.frag @@ -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 diff --git a/ycbcr_input.h b/ycbcr_input.h index 566f828..bb51049 100644 --- a/ycbcr_input.h +++ b/ycbcr_input.h @@ -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]; diff --git a/ycbcr_input_test.cpp b/ycbcr_input_test.cpp index 413ac15..deece92 100644 --- a/ycbcr_input_test.cpp +++ b/ycbcr_input_test.cpp @@ -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;