}
void EffectChain::add_ycbcr_output(const ImageFormat &format, OutputAlphaFormat alpha_format,
- const YCbCrFormat &ycbcr_format, YCbCrOutputSplitting output_splitting)
+ const YCbCrFormat &ycbcr_format, YCbCrOutputSplitting output_splitting,
+ GLenum output_type)
{
assert(!finalized);
assert(num_output_color_ycbcr < 2);
assert(output_ycbcr_format.num_levels == ycbcr_format.num_levels);
assert(output_ycbcr_format.chroma_subsampling_x == 1);
assert(output_ycbcr_format.chroma_subsampling_y == 1);
+ assert(output_ycbcr_type == output_type);
} else {
output_ycbcr_format = ycbcr_format;
+ output_ycbcr_type = output_type;
}
output_ycbcr_splitting[num_output_color_ycbcr++] = output_splitting;
return;
}
Node *output = find_output_node();
- ycbcr_conversion_effect_node = add_node(new YCbCrConversionEffect(output_ycbcr_format));
+ ycbcr_conversion_effect_node = add_node(new YCbCrConversionEffect(output_ycbcr_format, output_ycbcr_type));
connect_nodes(output, ycbcr_conversion_effect_node);
}
void add_output(const ImageFormat &format, OutputAlphaFormat alpha_format);
// Adds an YCbCr output. Note that you can only have at most two Y'CbCr
- // outputs, and they must have the same <ycbcr_format>.
+ // outputs, and they must have the same <ycbcr_format> and <type>.
// (This limitation may be lifted in the future, to allow e.g. simultaneous
// 8- and 10-bit output. Currently, multiple Y'CbCr outputs are only
// useful in some very limited circumstances, like if one texture goes
//
// Only 4:4:4 output is supported due to fragment shader limitations,
// so chroma_subsampling_x and chroma_subsampling_y must both be 1.
+ // <type> should match the data type of the FBO you are rendering to,
+ // so that if you use 16-bit output (GL_UNSIGNED_SHORT), you will get
+ // 8-, 10- or 12-bit output correctly as determined by <ycbcr_format.num_levels>.
+ // Using e.g. ycbcr_format.num_levels == 1024 with GL_UNSIGNED_BYTE is
+ // nonsensical and invokes undefined behavior.
//
// If you have both RGBA and Y'CbCr output(s), the RGBA output will come
// in the last draw buffer. Also, <format> and <alpha_format> must be
// identical between the two.
void add_ycbcr_output(const ImageFormat &format, OutputAlphaFormat alpha_format,
const YCbCrFormat &ycbcr_format,
- YCbCrOutputSplitting output_splitting = YCBCR_OUTPUT_INTERLEAVED);
+ YCbCrOutputSplitting output_splitting = YCBCR_OUTPUT_INTERLEAVED,
+ GLenum output_type = GL_UNSIGNED_BYTE);
// Change Y'CbCr output format. (This can be done also after finalize()).
// Note that you are not allowed to change subsampling parameters;
bool output_color_rgba;
int num_output_color_ycbcr; // Max 2.
YCbCrFormat output_ycbcr_format; // If num_output_color_ycbcr is > 0.
+ GLenum output_ycbcr_type; // If num_output_color_ycbcr is > 0.
YCbCrOutputSplitting output_ycbcr_splitting[2]; // If num_output_color_ycbcr is > N.
std::vector<Node *> nodes;
internal_run(out_data, out_data2, out_data3, out_data4, GL_UNSIGNED_BYTE, format, color_space, gamma_curve, alpha_format);
}
+void EffectChainTester::run(uint16_t *out_data, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
+{
+ internal_run<uint16_t>(out_data, NULL, NULL, NULL, GL_UNSIGNED_SHORT, format, color_space, gamma_curve, alpha_format);
+}
+
void EffectChainTester::run_10_10_10_2(uint32_t *out_data, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
{
internal_run<uint32_t>(out_data, NULL, NULL, NULL, GL_UNSIGNED_INT_2_10_10_10_REV, format, color_space, gamma_curve, alpha_format);
GLuint type;
if (framebuffer_format == GL_RGBA8) {
type = GL_UNSIGNED_BYTE;
+ } else if (framebuffer_format == GL_RGBA16) {
+ type = GL_UNSIGNED_SHORT;
} else if (framebuffer_format == GL_RGBA16F || framebuffer_format == GL_RGBA32F) {
type = GL_FLOAT;
} else if (framebuffer_format == GL_RGB10_A2) {
check_error();
}
- if (format == GL_RGBA && (type == GL_UNSIGNED_BYTE || type == GL_FLOAT)) {
+ if (format == GL_RGBA && (type == GL_UNSIGNED_BYTE || type == GL_UNSIGNED_SHORT || type == GL_FLOAT)) {
vertical_flip(ptr, width * 4, height);
} else {
vertical_flip(ptr, width, height);
output_added = true;
}
-void EffectChainTester::add_ycbcr_output(const ImageFormat &format, OutputAlphaFormat alpha_format, const YCbCrFormat &ycbcr_format, YCbCrOutputSplitting output_splitting)
+void EffectChainTester::add_ycbcr_output(const ImageFormat &format, OutputAlphaFormat alpha_format, const YCbCrFormat &ycbcr_format, YCbCrOutputSplitting output_splitting, GLenum type)
{
- chain.add_ycbcr_output(format, alpha_format, ycbcr_format, output_splitting);
+ chain.add_ycbcr_output(format, alpha_format, ycbcr_format, output_splitting, type);
output_added = true;
}
delete[] result_float;
}
+void expect_equal(const uint16_t *ref, const uint16_t *result, unsigned width, unsigned height, unsigned largest_difference_limit, float rms_limit)
+{
+ assert(width > 0);
+ assert(height > 0);
+
+ float *ref_float = new float[width * height];
+ float *result_float = new float[width * height];
+
+ for (unsigned y = 0; y < height; ++y) {
+ for (unsigned x = 0; x < width; ++x) {
+ ref_float[y * width + x] = ref[y * width + x];
+ result_float[y * width + x] = result[y * width + x];
+ }
+ }
+
+ expect_equal(ref_float, result_float, width, height, largest_difference_limit, rms_limit);
+
+ delete[] ref_float;
+ delete[] result_float;
+}
+
void expect_equal(const int *ref, const int *result, unsigned width, unsigned height, unsigned largest_difference_limit, float rms_limit)
{
assert(width > 0);
void run(unsigned char *out_data, unsigned char *out_data2, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format = OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
void run(unsigned char *out_data, unsigned char *out_data2, unsigned char *out_data3, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format = OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
void run(unsigned char *out_data, unsigned char *out_data2, unsigned char *out_data3, unsigned char *out_data4, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format = OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
+ void run(uint16_t *out_data, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format = OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
void run_10_10_10_2(uint32_t *out_data, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format = OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
void add_output(const ImageFormat &format, OutputAlphaFormat alpha_format);
- void add_ycbcr_output(const ImageFormat &format, OutputAlphaFormat alpha_format, const YCbCrFormat &ycbcr_format, YCbCrOutputSplitting output_splitting = YCBCR_OUTPUT_INTERLEAVED);
+ void add_ycbcr_output(const ImageFormat &format, OutputAlphaFormat alpha_format, const YCbCrFormat &ycbcr_format, YCbCrOutputSplitting output_splitting = YCBCR_OUTPUT_INTERLEAVED, GLenum output_type = GL_UNSIGNED_BYTE);
private:
void finalize_chain(Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format);
void expect_equal(const float *ref, const float *result, unsigned width, unsigned height, float largest_difference_limit = 1.5 / 255.0, float rms_limit = 0.2 / 255.0);
void expect_equal(const unsigned char *ref, const unsigned char *result, unsigned width, unsigned height, unsigned largest_difference_limit = 1, float rms_limit = 0.2);
+void expect_equal(const uint16_t *ref, const uint16_t *result, unsigned width, unsigned height, unsigned largest_difference_limit = 1, float rms_limit = 0.2);
void expect_equal(const int *ref, const int *result, unsigned width, unsigned height, unsigned largest_difference_limit = 1, float rms_limit = 0.2);
void test_accuracy(const float *expected, const float *result, unsigned num_values, double absolute_error_limit, double relative_error_limit, double local_relative_error_limit, double rms_limit);
// changes, even within git versions. There is no specific version
// documentation outside the regular changelogs, though.
-#define MOVIT_VERSION 27
+#define MOVIT_VERSION 28
#endif // !defined(_MOVIT_VERSION_H)
// Given <ycbcr_format>, compute the values needed to turn Y'CbCr into R'G'B';
// first subtract the returned offset, then left-multiply the returned matrix
// (the scaling is already folded into it).
-void compute_ycbcr_matrix(YCbCrFormat ycbcr_format, float* offset, Matrix3d* ycbcr_to_rgb)
+void compute_ycbcr_matrix(YCbCrFormat ycbcr_format, float* offset, Matrix3d* ycbcr_to_rgb, GLenum type, double *scale_factor)
{
double coeff[3], scale[3];
// Fold in the scaling.
*ycbcr_to_rgb *= Map<const Vector3d>(scale).asDiagonal();
+
+ if (type == GL_UNSIGNED_SHORT) {
+ // For 10-bit or 12-bit packed into 16-bit, we need to scale the values
+ // so that the max value goes from 1023 (or 4095) to 65535. We do this
+ // by folding the scaling into the conversion matrix, so it comes essentially
+ // for free. However, the offset is before the scaling (and thus assumes
+ // correctly scaled values), so we need to adjust that the other way.
+ double scale = 65535.0 / (ycbcr_format.num_levels - 1);
+ offset[0] /= scale;
+ offset[1] /= scale;
+ offset[2] /= scale;
+ *ycbcr_to_rgb *= scale;
+ if (scale_factor != NULL) {
+ *scale_factor = scale;
+ }
+ } else if (scale_factor != NULL) {
+ *scale_factor = 1.0;
+ }
}
} // namespace movit
#include "image_format.h"
+#include <epoxy/gl.h>
#include <Eigen/Core>
namespace movit {
// Given <ycbcr_format>, compute the values needed to turn Y'CbCr into R'G'B';
// first subtract the returned offset, then left-multiply the returned matrix
// (the scaling is already folded into it).
-void compute_ycbcr_matrix(YCbCrFormat ycbcr_format, float *offset, Eigen::Matrix3d *ycbcr_to_rgb);
+//
+// <type> is the data type you're rendering from; normally, it would should match
+// <ycbcr_format.num_levels>, but for the special case of 10- and 12-bit Y'CbCr,
+// we support storing it in 16-bit formats, which incurs extra scaling factors.
+// You can get that scaling factor in <scale> if you want.
+void compute_ycbcr_matrix(YCbCrFormat ycbcr_format, float *offset, Eigen::Matrix3d *ycbcr_to_rgb,
+ GLenum type = GL_UNSIGNED_BYTE, double *scale_factor = NULL);
} // namespace movit
namespace movit {
-YCbCrConversionEffect::YCbCrConversionEffect(const YCbCrFormat &ycbcr_format)
- : ycbcr_format(ycbcr_format)
+YCbCrConversionEffect::YCbCrConversionEffect(const YCbCrFormat &ycbcr_format, GLenum type)
+ : ycbcr_format(ycbcr_format), type(type)
{
register_uniform_mat3("ycbcr_matrix", &uniform_ycbcr_matrix);
register_uniform_vec3("offset", uniform_offset);
Effect::set_gl_state(glsl_program_num, prefix, sampler_num);
Matrix3d ycbcr_to_rgb;
- compute_ycbcr_matrix(ycbcr_format, uniform_offset, &ycbcr_to_rgb);
+ double scale_factor;
+ compute_ycbcr_matrix(ycbcr_format, uniform_offset, &ycbcr_to_rgb, type, &scale_factor);
uniform_ycbcr_matrix = ycbcr_to_rgb.inverse();
} else {
assert(false);
}
+ uniform_ycbcr_min[0] /= scale_factor;
+ uniform_ycbcr_min[1] /= scale_factor;
+ uniform_ycbcr_min[2] /= scale_factor;
}
}
private:
// Should not be instantiated by end users;
// call EffectChain::add_ycbcr_output() instead.
- YCbCrConversionEffect(const YCbCrFormat &ycbcr_format);
+ YCbCrConversionEffect(const YCbCrFormat &ycbcr_format, GLenum type);
friend class EffectChain;
public:
private:
YCbCrFormat ycbcr_format;
+ GLenum type;
Eigen::Matrix3d uniform_ycbcr_matrix;
float uniform_offset[3];
expect_equal(expected_data, expanded_out_data, 4 * width, height);
}
+TEST(YCbCrConversionEffectTest, TenBitOutputInSixteen) {
+ const int width = 1;
+ const int height = 5;
+
+ // Same test inputs and outputs as TenBitOutput, except that alpha
+ // is 16 bits instead of two.
+ float data[width * height * 4] = {
+ 0.0f, 0.0f, 0.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 0.0f, 0.0f, 1.0f,
+ 0.0f, 1.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f, 1.0f,
+ };
+ uint16_t out_data[width * height * 4];
+ uint16_t expected_data[width * height * 4] = {
+ 64, 512, 512, 65535,
+ 940, 512, 512, 65535,
+ 250, 409, 960, 65535,
+ 691, 167, 105, 65535,
+ 127, 960, 471, 65535,
+ };
+
+ EffectChainTester tester(NULL, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA16);
+ tester.add_input(data, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_sRGB);
+
+ 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;
+ 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;
+
+ tester.add_ycbcr_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format, YCBCR_OUTPUT_INTERLEAVED, GL_UNSIGNED_SHORT);
+ tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
+
+ expect_equal(expected_data, out_data, 4 * width, height);
+}
+
} // namespace movit
{
float offset[3];
Matrix3d ycbcr_to_rgb;
- compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb);
-
- if (type == GL_UNSIGNED_SHORT) {
- // For 10-bit or 12-bit packed into 16-bit, we need to scale the values
- // so that the max value goes from 1023 (or 4095) to 65535. We do this
- // by folding the scaling into the conversion matrix, so it comes essentially
- // for free. However, the offset is before the scaling (and thus assumes
- // correctly scaled values), so we need to adjust that the other way.
- double scale = 65535.0 / (ycbcr_format.num_levels - 1);
- offset[0] /= scale;
- offset[1] /= scale;
- offset[2] /= scale;
- ycbcr_to_rgb *= scale;
- }
+ compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb, type);
string frag_shader;