From: Steinar H. Gunderson Date: Tue, 14 Feb 2017 17:50:07 +0000 (+0100) Subject: Add input support for packed 10-bit Y'CbCr. X-Git-Tag: 1.5.0~20 X-Git-Url: https://git.sesse.net/?p=movit;a=commitdiff_plain;h=98f458e22ce732e6c50a9856d7fd636dca49b4c3 Add input support for packed 10-bit Y'CbCr. --- diff --git a/ycbcr_input.cpp b/ycbcr_input.cpp index 4a0ec0e..f8df0c1 100644 --- a/ycbcr_input.cpp +++ b/ycbcr_input.cpp @@ -19,10 +19,12 @@ namespace movit { YCbCrInput::YCbCrInput(const ImageFormat &image_format, const YCbCrFormat &ycbcr_format, unsigned width, unsigned height, - YCbCrInputSplitting ycbcr_input_splitting) + YCbCrInputSplitting ycbcr_input_splitting, + GLenum type) : image_format(image_format), ycbcr_format(ycbcr_format), ycbcr_input_splitting(ycbcr_input_splitting), + type(type), width(width), height(height), resource_pool(NULL) @@ -69,12 +71,20 @@ void YCbCrInput::set_gl_state(GLuint glsl_program_num, const string& prefix, uns if (texture_num[channel] == 0 && (pbos[channel] != 0 || pixel_data[channel] != NULL)) { GLenum format, internal_format; if (channel == 0 && ycbcr_input_splitting == YCBCR_INPUT_INTERLEAVED) { - format = GL_RGB; - internal_format = GL_RGB8; + if (type == GL_UNSIGNED_INT_2_10_10_10_REV) { + format = GL_RGBA; + internal_format = GL_RGB10_A2; + } else { + assert(type == GL_UNSIGNED_BYTE); + format = GL_RGB; + internal_format = GL_RGB8; + } } else if (channel == 1 && ycbcr_input_splitting == YCBCR_INPUT_SPLIT_Y_AND_CBCR) { + assert(type == GL_UNSIGNED_BYTE); format = GL_RG; internal_format = GL_RG8; } else { + assert(type == GL_UNSIGNED_BYTE); format = GL_RED; internal_format = GL_R8; } @@ -91,7 +101,7 @@ void YCbCrInput::set_gl_state(GLuint glsl_program_num, const string& prefix, uns check_error(); glPixelStorei(GL_UNPACK_ROW_LENGTH, pitch[channel]); check_error(); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, widths[channel], heights[channel], format, GL_UNSIGNED_BYTE, pixel_data[channel]); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, widths[channel], heights[channel], format, type, pixel_data[channel]); check_error(); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); check_error(); diff --git a/ycbcr_input.h b/ycbcr_input.h index 22208e0..31ba42b 100644 --- a/ycbcr_input.h +++ b/ycbcr_input.h @@ -1,10 +1,20 @@ #ifndef _MOVIT_YCBCR_INPUT_H #define _MOVIT_YCBCR_INPUT_H 1 -// YCbCrInput is for handling planar or 4:4:4 interleaved 8-bit Y'CbCr -// (also sometimes, usually rather imprecisely, called “YUV”), which is typically -// what you get from a video decoder. It upsamples planes as needed, using the -// default linear upsampling OpenGL gives you. +// YCbCrInput is for handling Y'CbCr (also sometimes, usually rather +// imprecisely, called “YUV”), which is typically what you get from a video +// decoder. It supports these formats: +// +// * 8-bit planar Y'CbCr, possibly subsampled (e.g. 4:2:0). +// * 8-bit semiplanar Y'CbCr (Y' in one plane, CbCr in another), +// possibly subsampled. +// * 8-bit interleaved (chunked) Y'CbCr, no subsampling (4:4:4 only). +// * 10-bit interleaved (chunked) Y'CbCr packed into 32-bit words +// (10:10:10:2), no subsampling (4:4:4 only). +// +// For the former case, it upsamples planes as needed, using the default linear +// upsampling OpenGL gives you. Note that YCbCr422InterleavedInput supports the +// important special case of 8-bit 4:2:2 interleaved. #include #include @@ -20,9 +30,7 @@ namespace movit { class ResourcePool; -// Whether the data is planar (Y', Cb and Cr in one texture each) -// or not. Note that YCbCr422InterleavedInput supports the important special -// case of 4:2:2 interleaved. +// Whether the data is planar (Y', Cb and Cr in one texture each) or not. enum YCbCrInputSplitting { // The standard, default case; Y', Cb and Cr in one texture each. YCBCR_INPUT_PLANAR, @@ -41,10 +49,13 @@ enum YCbCrInputSplitting { class YCbCrInput : public Input { public: + // Type can be GL_UNSIGNED_BYTE for 8-bit, or GL_UNSIGNED_INT_2_10_10_10_REV + // for 10-bit (YCBCR_INPUT_INTERLEAVED only). YCbCrInput(const ImageFormat &image_format, const YCbCrFormat &ycbcr_format, unsigned width, unsigned height, - YCbCrInputSplitting ycbcr_input_splitting = YCBCR_INPUT_PLANAR); + YCbCrInputSplitting ycbcr_input_splitting = YCBCR_INPUT_PLANAR, + GLenum type = GL_UNSIGNED_BYTE); ~YCbCrInput(); virtual std::string effect_type_id() const { return "YCbCrInput"; } @@ -75,12 +86,22 @@ public: // the pointer (and PBO, if set) has to be valid at the time of the render call. void set_pixel_data(unsigned channel, const unsigned char *pixel_data, GLuint pbo = 0) { + assert(type == GL_UNSIGNED_BYTE || type == GL_UNSIGNED_INT_2_10_10_10_REV); assert(channel >= 0 && channel < num_channels); this->pixel_data[channel] = pixel_data; this->pbos[channel] = pbo; invalidate_pixel_data(); } + void set_pixel_data(unsigned channel, const uint32_t *pixel_data, GLuint pbo = 0) + { + assert(type == GL_UNSIGNED_INT_2_10_10_10_REV); + assert(channel == 0); + this->pixel_data[channel] = reinterpret_cast(pixel_data); + this->pbos[channel] = pbo; + invalidate_pixel_data(); + } + void invalidate_pixel_data(); // Note: Sets pitch to width, so even if your pitch is unchanged, @@ -142,6 +163,7 @@ private: YCbCrFormat ycbcr_format; GLuint num_channels; YCbCrInputSplitting ycbcr_input_splitting; + GLenum type; GLuint pbos[3], texture_num[3]; GLint uniform_tex_y, uniform_tex_cb, uniform_tex_cr; diff --git a/ycbcr_input_test.cpp b/ycbcr_input_test.cpp index 019cc07..7792b28 100644 --- a/ycbcr_input_test.cpp +++ b/ycbcr_input_test.cpp @@ -798,4 +798,64 @@ TEST(YCbCrInputTest, NoData) { // Don't care what the output was, just that it does not crash. } +TEST(YCbCrInputTest, TenBitInterleaved) { + const int width = 1; + const int height = 5; + + // Pure-color inputs, calculated using formulas 3.2, 3.3 and 3.4 from + // Rec. 709. (Except the first two, which are obvious given the 64–940 + // range of luminance.) + unsigned expanded_data[width * height * 3] = { + 64, 512, 512, + 940, 512, 512, + 250, 409, 960, + 691, 167, 105, + 127, 960, 471, + }; + 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]; + + // Pack 32:32:32 to 10:10:10:2. + uint32_t data[width * height]; + for (unsigned i = 0; i < width * height; ++i) { + data[i] = + expanded_data[i * 3 + 0] | + (expanded_data[i * 3 + 1] << 10) | + (expanded_data[i * 3 + 2] << 20); + } + + 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_709; + ycbcr_format.full_range = false; + ycbcr_format.num_levels = 1024; // 10-bit. + 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; + + YCbCrInput *input = new YCbCrInput(format, ycbcr_format, width, height, YCBCR_INPUT_INTERLEAVED, GL_UNSIGNED_INT_2_10_10_10_REV); + input->set_pixel_data(0, data); + tester.get_chain()->add_input(input); + + tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB); + + // We can set much tighter limits on this than 8-bit Y'CbCr; + // even tighter than the default limits. + expect_equal(expected_data, out_data, 4 * width, height, 0.002, 0.0003); +} + } // namespace movit