: aspect_nom(aspect_nom),
aspect_denom(aspect_denom),
output_color_rgba(false),
- output_color_ycbcr(false),
+ num_output_color_ycbcr(0),
dither_effect(NULL),
ycbcr_conversion_effect_node(NULL),
intermediate_format(GL_RGBA16F),
const YCbCrFormat &ycbcr_format, YCbCrOutputSplitting output_splitting)
{
assert(!finalized);
- assert(!output_color_ycbcr);
+ assert(num_output_color_ycbcr < 2);
output_format = format;
output_alpha_format = alpha_format;
- output_color_ycbcr = true;
- output_ycbcr_format = ycbcr_format;
- output_ycbcr_splitting = output_splitting;
+
+ if (num_output_color_ycbcr == 1) {
+ // Check that the format is the same.
+ assert(output_ycbcr_format.luma_coefficients == ycbcr_format.luma_coefficients);
+ assert(output_ycbcr_format.full_range == ycbcr_format.full_range);
+ assert(output_ycbcr_format.num_levels == ycbcr_format.num_levels);
+ assert(output_ycbcr_format.chroma_subsampling_x == ycbcr_format.chroma_subsampling_x);
+ assert(output_ycbcr_format.chroma_subsampling_y == ycbcr_format.chroma_subsampling_y);
+ assert(fabs(output_ycbcr_format.cb_x_position - ycbcr_format.cb_x_position) < 1e-3);
+ assert(fabs(output_ycbcr_format.cb_y_position - ycbcr_format.cb_y_position) < 1e-3);
+ assert(fabs(output_ycbcr_format.cr_x_position - ycbcr_format.cr_x_position) < 1e-3);
+ assert(fabs(output_ycbcr_format.cr_y_position - ycbcr_format.cr_y_position) < 1e-3);
+ } else {
+ output_ycbcr_format = ycbcr_format;
+ }
+ output_ycbcr_splitting[num_output_color_ycbcr++] = output_splitting;
assert(ycbcr_format.chroma_subsampling_x == 1);
assert(ycbcr_format.chroma_subsampling_y == 1);
void EffectChain::change_ycbcr_output_format(const YCbCrFormat &ycbcr_format)
{
- assert(output_color_ycbcr);
+ assert(num_output_color_ycbcr > 0);
assert(output_ycbcr_format.chroma_subsampling_x == ycbcr_format.chroma_subsampling_x);
assert(output_ycbcr_format.chroma_subsampling_y == ycbcr_format.chroma_subsampling_y);
assert(fabs(output_ycbcr_format.cb_x_position - ycbcr_format.cb_x_position) < 1e-3);
// If we're the last phase, add the right #defines for Y'CbCr multi-output as needed.
vector<string> frag_shader_outputs; // In order.
- if (phase->output_node->outgoing_links.empty() && output_color_ycbcr) {
- switch (output_ycbcr_splitting) {
+ if (phase->output_node->outgoing_links.empty() && num_output_color_ycbcr > 0) {
+ switch (output_ycbcr_splitting[0]) {
case YCBCR_OUTPUT_INTERLEAVED:
// No #defines set.
frag_shader_outputs.push_back("FragColor");
assert(false);
}
+ if (num_output_color_ycbcr > 1) {
+ switch (output_ycbcr_splitting[1]) {
+ case YCBCR_OUTPUT_INTERLEAVED:
+ frag_shader += "#define SECOND_YCBCR_OUTPUT_INTERLEAVED 1\n";
+ frag_shader_outputs.push_back("YCbCr2");
+ break;
+ case YCBCR_OUTPUT_SPLIT_Y_AND_CBCR:
+ frag_shader += "#define SECOND_YCBCR_OUTPUT_SPLIT_Y_AND_CBCR 1\n";
+ frag_shader_outputs.push_back("Y2");
+ frag_shader_outputs.push_back("Chroma2");
+ break;
+ case YCBCR_OUTPUT_PLANAR:
+ frag_shader += "#define SECOND_YCBCR_OUTPUT_PLANAR 1\n";
+ frag_shader_outputs.push_back("Y2");
+ frag_shader_outputs.push_back("Cb2");
+ frag_shader_outputs.push_back("Cr2");
+ break;
+ default:
+ assert(false);
+ }
+ }
+
if (output_color_rgba) {
// Note: Needs to come in the header, because not only the
// output needs to see it (YCbCrConversionEffect and DitherEffect
// gamma-encoded data.
void EffectChain::add_ycbcr_conversion_if_needed()
{
- assert(output_color_rgba || output_color_ycbcr);
- if (!output_color_ycbcr) {
+ assert(output_color_rgba || num_output_color_ycbcr > 0);
+ if (num_output_color_ycbcr == 0) {
return;
}
Node *output = find_output_node();
}
Effect *add_effect(Effect *effect, const std::vector<Effect *> &inputs);
- // Adds an RGBA output. Note that you can have at most one RGBA output and one
- // Y'CbCr output (see below for details).
+ // Adds an RGBA output. Note that you can have at most one RGBA output and two
+ // Y'CbCr outputs (see below for details).
void add_output(const ImageFormat &format, OutputAlphaFormat alpha_format);
- // Adds an YCbCr output. Note that you can only have one Y'CbCr output.
- // Currently, only 4:4:4 output is supported, so chroma_subsampling_x
- // and chroma_subsampling_y must both be 1.
+ // Adds an YCbCr output. Note that you can only have at most two Y'CbCr
+ // outputs, and they must have the same <ycbcr_format>.
+ // (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
+ // to some place you cannot easily read from later.)
//
- // If you have both RGBA and Y'CbCr output, the RGBA output will come
+ // Only 4:4:4 output is supported due to fragment shader limitations,
+ // so chroma_subsampling_x and chroma_subsampling_y must both be 1.
+ //
+ // 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,
ImageFormat output_format;
OutputAlphaFormat output_alpha_format;
- bool output_color_rgba, output_color_ycbcr;
- YCbCrFormat output_ycbcr_format; // If output_color_ycbcr is true.
- YCbCrOutputSplitting output_ycbcr_splitting; // If output_color_ycbcr is true.
+ bool output_color_rgba;
+ int num_output_color_ycbcr; // Max 2.
+ YCbCrFormat output_ycbcr_format; // If num_output_color_ycbcr is > 0.
+ YCbCrOutputSplitting output_ycbcr_splitting[2]; // If num_output_color_ycbcr is > N.
std::vector<Node *> nodes;
std::map<Effect *, Node *> node_map;
#define YCBCR_OUTPUT_SPLIT_Y_AND_CBCR 0
#endif
+#ifndef SECOND_YCBCR_OUTPUT_PLANAR
+#define SECOND_YCBCR_OUTPUT_PLANAR 0
+#endif
+
+#ifndef SECOND_YCBCR_OUTPUT_SPLIT_Y_AND_CBCR
+#define SECOND_YCBCR_OUTPUT_SPLIT_Y_AND_CBCR 0
+#endif
+
+#ifndef SECOND_YCBCR_OUTPUT_INTERLEAVED
+#define SECOND_YCBCR_OUTPUT_INTERLEAVED 0
+#endif
+
#ifndef YCBCR_ALSO_OUTPUT_RGBA
#define YCBCR_ALSO_OUTPUT_RGBA 0
#endif
#endif
#if YCBCR_OUTPUT_PLANAR
-out vec4 Y;
-out vec4 Cb;
-out vec4 Cr;
+out vec4 Y, Cb, Cr;
#elif YCBCR_OUTPUT_SPLIT_Y_AND_CBCR
-out vec4 Y;
-out vec4 Chroma;
+out vec4 Y, Chroma;
#else
-out vec4 FragColor;
+out vec4 FragColor; // Y'CbCr or RGBA.
+#endif
+
+#if SECOND_YCBCR_OUTPUT_PLANAR
+out vec4 Y2, Cb2, Cr2;
+#elif SECOND_YCBCR_OUTPUT_SPLIT_Y_AND_CBCR
+out vec4 Y2, Chroma2;
+#elif SECOND_YCBCR_OUTPUT_INTERLEAVED
+out vec4 YCbCr2;
#endif
#if YCBCR_ALSO_OUTPUT_RGBA
FragColor = color0;
#endif
+ // Exactly the same, just with other outputs.
+ // (GLSL does not allow arrays of outputs.)
+#if SECOND_YCBCR_OUTPUT_PLANAR
+ Y2 = color0.rrra;
+ Cb2 = color0.ggga;
+ Cr2 = color0.bbba;
+#elif SECOND_YCBCR_OUTPUT_SPLIT_Y_AND_CBCR
+ Y2 = color0.rrra;
+ Chroma2 = color0.gbba;
+#elif SECOND_YCBCR_OUTPUT_INTERLEAVED
+ YCbCr2 = color0;
+#endif
+
#if YCBCR_ALSO_OUTPUT_RGBA
RGBA = color1;
#endif
void EffectChainTester::run(float *out_data, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
{
- internal_run<float>(out_data, NULL, NULL, GL_FLOAT, format, color_space, gamma_curve, alpha_format);
+ internal_run<float>(out_data, NULL, NULL, NULL, GL_FLOAT, format, color_space, gamma_curve, alpha_format);
}
void EffectChainTester::run(float *out_data, float *out_data2, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
{
- internal_run<float>(out_data, out_data2, NULL, GL_FLOAT, format, color_space, gamma_curve, alpha_format);
+ internal_run<float>(out_data, out_data2, NULL, NULL, GL_FLOAT, format, color_space, gamma_curve, alpha_format);
}
void EffectChainTester::run(float *out_data, float *out_data2, float *out_data3, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
{
- internal_run(out_data, out_data2, out_data3, GL_FLOAT, format, color_space, gamma_curve, alpha_format);
+ internal_run<float>(out_data, out_data2, out_data3, NULL, GL_FLOAT, format, color_space, gamma_curve, alpha_format);
+}
+
+void EffectChainTester::run(float *out_data, float *out_data2, float *out_data3, float *out_data4, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
+{
+ internal_run(out_data, out_data2, out_data3, out_data4, GL_FLOAT, format, color_space, gamma_curve, alpha_format);
}
void EffectChainTester::run(unsigned char *out_data, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
{
- internal_run<unsigned char>(out_data, NULL, NULL, GL_UNSIGNED_BYTE, format, color_space, gamma_curve, alpha_format);
+ internal_run<unsigned char>(out_data, NULL, NULL, NULL, GL_UNSIGNED_BYTE, format, color_space, gamma_curve, alpha_format);
}
void EffectChainTester::run(unsigned char *out_data, unsigned char *out_data2, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
{
- internal_run<unsigned char>(out_data, out_data2, NULL, GL_UNSIGNED_BYTE, format, color_space, gamma_curve, alpha_format);
+ internal_run<unsigned char>(out_data, out_data2, NULL, NULL, GL_UNSIGNED_BYTE, format, color_space, gamma_curve, alpha_format);
}
void EffectChainTester::run(unsigned char *out_data, unsigned char *out_data2, unsigned char *out_data3, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
{
- internal_run(out_data, out_data2, out_data3, GL_UNSIGNED_BYTE, format, color_space, gamma_curve, alpha_format);
+ internal_run<unsigned char>(out_data, out_data2, out_data3, NULL, GL_UNSIGNED_BYTE, format, color_space, gamma_curve, alpha_format);
+}
+
+void EffectChainTester::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)
+{
+ internal_run(out_data, out_data2, out_data3, out_data4, GL_UNSIGNED_BYTE, 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, GL_UNSIGNED_INT_2_10_10_10_REV, format, color_space, gamma_curve, 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);
}
template<class T>
-void EffectChainTester::internal_run(T *out_data, T *out_data2, T *out_data3, GLenum internal_format, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
+void EffectChainTester::internal_run(T *out_data, T *out_data2, T *out_data3, T *out_data4, GLenum internal_format, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
{
if (!finalized) {
finalize_chain(color_space, gamma_curve, alpha_format);
}
unsigned num_outputs;
- if (out_data3 != NULL) {
+ if (out_data4 != NULL) {
+ num_outputs = 4;
+ } else if (out_data3 != NULL) {
num_outputs = 3;
} else if (out_data2 != NULL) {
num_outputs = 2;
num_outputs = 1;
}
- GLuint fbo, texnum[3];
+ GLuint fbo, texnum[4];
glGenTextures(num_outputs, texnum);
check_error();
check_error();
}
- GLenum bufs[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
+ GLenum bufs[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 };
glDrawBuffers(num_outputs, bufs);
chain.render_to_fbo(fbo, width, height);
- T *data[3] = { out_data, out_data2, out_data3 };
+ T *data[4] = { out_data, out_data2, out_data3, out_data4 };
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
check_error();
void run(float *out_data, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format = OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
void run(float *out_data, float *out_data2, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format = OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
void run(float *out_data, float *out_data2, float *out_data3, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format = OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
+ void run(float *out_data, float *out_data2, float *out_data3, float *out_data4, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format = OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
void run(unsigned char *out_data, 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, 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_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 finalize_chain(Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format);
template<class T>
- void internal_run(T *out_data, T *out_data2, T *out_data3, GLenum internal_format, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format = OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
+ void internal_run(T *out_data, T *out_data2, T *out_data3, T *out_data4, GLenum internal_format, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format = OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
EffectChain chain;
unsigned width, height;
// changes, even within git versions. There is no specific version
// documentation outside the regular changelogs, though.
-#define MOVIT_VERSION 26
+#define MOVIT_VERSION 27
#endif // !defined(_MOVIT_VERSION_H)
expect_equal(expected_rgba, out_rgba, 4 * width, height, 7, 255 * 0.002);
}
+TEST(YCbCrConversionEffectTest, MultipleOutputsAndRGBA) {
+ 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,
+ };
+ unsigned char expected_ycbcr[width * height * 4] = {
+ // The same data, just rearranged.
+ 16, 128, 128, 255,
+ 235, 128, 128, 255,
+ 81, 90, 240, 255,
+ 145, 54, 34, 255,
+ 41, 240, 110, 255
+ };
+ unsigned char expected_rgba[width * height * 4] = {
+ 0, 0, 0, 255,
+ 255, 255, 255, 255,
+ 255, 0, 0, 255,
+ 0, 255, 0, 255,
+ 0, 0, 255, 255,
+ };
+
+ unsigned char out_ycbcr[width * height * 4];
+ unsigned char out_y[width * height * 4];
+ unsigned char out_cbcr[width * height * 4];
+ unsigned char out_rgba[width * height * 4];
+
+ EffectChainTester tester(NULL, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGBA8);
+
+ 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;
+
+ tester.add_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
+ tester.add_ycbcr_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format);
+ tester.add_ycbcr_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format, YCBCR_OUTPUT_SPLIT_Y_AND_CBCR);
+
+ YCbCrInput *input = new YCbCrInput(format, 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);
+
+ // Note: We don't test that the values actually get dithered,
+ // just that the shader compiles and doesn't mess up badly.
+ tester.get_chain()->set_dither_bits(8);
+
+ tester.run(out_ycbcr, out_y, out_cbcr, out_rgba, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
+ expect_equal(expected_ycbcr, out_ycbcr, width * 4, height);
+
+ // Check that the extra Y' and CbCr outputs also are fine.
+ for (unsigned i = 0; i < width * height; ++i) {
+ out_ycbcr[i * 4] = out_y[i * 4];
+ out_ycbcr[i * 4 + 1] = out_cbcr[i * 4 + 0];
+ out_ycbcr[i * 4 + 2] = out_cbcr[i * 4 + 1];
+ }
+ expect_equal(expected_ycbcr, out_ycbcr, width * 4, height);
+
+ // Y'CbCr isn't 100% accurate (the input values are rounded),
+ // so we need some leeway.
+ expect_equal(expected_rgba, out_rgba, 4 * width, height, 7, 255 * 0.002);
+}
+
// Very similar to PlanarOutput.
TEST(YCbCrConversionEffectTest, ChangeOutputFormat) {
const int width = 1;