diff --git a/src/amd/common/nir/ac_nir_lower_image_tex.c b/src/amd/common/nir/ac_nir_lower_image_tex.c index e66cd46bfeb..8eb4c6152b7 100644 --- a/src/amd/common/nir/ac_nir_lower_image_tex.c +++ b/src/amd/common/nir/ac_nir_lower_image_tex.c @@ -584,6 +584,7 @@ move_tex_coords(struct move_tex_coords_state *state, nir_function_impl *impl, ni case GLSL_SAMPLER_DIM_3D: case GLSL_SAMPLER_DIM_CUBE: case GLSL_SAMPLER_DIM_EXTERNAL: + case GLSL_SAMPLER_DIM_EXTERNAL_2D_Y2Y: break; case GLSL_SAMPLER_DIM_RECT: case GLSL_SAMPLER_DIM_BUF: diff --git a/src/compiler/builtin_types.py b/src/compiler/builtin_types.py index 624ca2940e7..102cd8fd3d8 100644 --- a/src/compiler/builtin_types.py +++ b/src/compiler/builtin_types.py @@ -100,6 +100,8 @@ simple_type("dmat4x3", "GL_DOUBLE_MAT4x3", "GLSL_TYPE_DOUBLE", 3, 4) simple_type("atomic_uint", "GL_UNSIGNED_INT_ATOMIC_COUNTER", "GLSL_TYPE_ATOMIC_UINT", 1, 1) +simple_type("yuvCscStandardEXT", "GL_INVALID_ENUM", "GLSL_TYPE_INT", 1, 1) + sampler_type("sampler", "GL_SAMPLER_1D", "GLSL_TYPE_SAMPLER", "GLSL_SAMPLER_DIM_1D", 0, 0, "GLSL_TYPE_VOID") sampler_type("sampler1D", "GL_SAMPLER_1D", "GLSL_TYPE_SAMPLER", "GLSL_SAMPLER_DIM_1D", 0, 0, "GLSL_TYPE_FLOAT") sampler_type("sampler2D", "GL_SAMPLER_2D", "GLSL_TYPE_SAMPLER", "GLSL_SAMPLER_DIM_2D", 0, 0, "GLSL_TYPE_FLOAT") @@ -148,6 +150,8 @@ sampler_type("sampler2DRectShadow", "GL_SAMPLER_2D_RECT_SHADOW", "GLSL sampler_type("samplerExternalOES", "GL_SAMPLER_EXTERNAL_OES", "GLSL_TYPE_SAMPLER", "GLSL_SAMPLER_DIM_EXTERNAL", 0, 0, "GLSL_TYPE_FLOAT") +sampler_type("samplerExternal2DY2YEXT", "GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT", "GLSL_TYPE_SAMPLER", "GLSL_SAMPLER_DIM_2D", 0, 0, "GLSL_TYPE_FLOAT") + sampler_type("texture1D", "GL_SAMPLER_1D", "GLSL_TYPE_TEXTURE", "GLSL_SAMPLER_DIM_1D", 0, 0, "GLSL_TYPE_FLOAT") sampler_type("texture2D", "GL_SAMPLER_2D", "GLSL_TYPE_TEXTURE", "GLSL_SAMPLER_DIM_2D", 0, 0, "GLSL_TYPE_FLOAT") sampler_type("texture3D", "GL_SAMPLER_3D", "GLSL_TYPE_TEXTURE", "GLSL_SAMPLER_DIM_3D", 0, 0, "GLSL_TYPE_FLOAT") diff --git a/src/compiler/glsl/ast.h b/src/compiler/glsl/ast.h index cded74361f3..d5f4364f8a9 100644 --- a/src/compiler/glsl/ast.h +++ b/src/compiler/glsl/ast.h @@ -204,6 +204,8 @@ enum ast_operators { ast_int64_constant, ast_uint64_constant, + ast_csc_standard, + ast_sequence, ast_aggregate @@ -266,6 +268,7 @@ public: double double_constant; uint64_t uint64_constant; int64_t int64_constant; + enum yuv_csc_standard csc_standard; } primary_expression; @@ -687,6 +690,9 @@ struct ast_type_qualifier { unsigned task_payload:1; unsigned per_primitive:1; unsigned max_primitives:1; + + /** GL_EXT_YUV_target */ + unsigned yuv:1; } /** \brief Set of flags, accessed by name. */ q; diff --git a/src/compiler/glsl/ast_function.cpp b/src/compiler/glsl/ast_function.cpp index cf286762669..e578f8866c2 100644 --- a/src/compiler/glsl/ast_function.cpp +++ b/src/compiler/glsl/ast_function.cpp @@ -2156,7 +2156,8 @@ ast_function_expression::handle_method(ir_exec_list *instructions, static inline bool is_valid_constructor(const glsl_type *type, struct _mesa_glsl_parse_state *state) { - return glsl_type_is_numeric(type) || glsl_type_is_boolean(type) || + return (glsl_type_is_numeric(type) && type != &glsl_type_builtin_yuvCscStandardEXT) || + glsl_type_is_boolean(type) || (state->has_bindless() && (glsl_type_is_sampler(type) || glsl_type_is_image(type))); } diff --git a/src/compiler/glsl/ast_to_hir.cpp b/src/compiler/glsl/ast_to_hir.cpp index 0aa4069183f..e615baa7ffd 100644 --- a/src/compiler/glsl/ast_to_hir.cpp +++ b/src/compiler/glsl/ast_to_hir.cpp @@ -363,6 +363,12 @@ apply_implicit_conversion(const glsl_type *to, ir_rvalue * &from, } } +static bool +is_arithmetic_type(const glsl_type *type) +{ + return glsl_type_is_numeric(type) && + type != &glsl_type_builtin_yuvCscStandardEXT; +} static const struct glsl_type * arithmetic_result_type(ir_rvalue * &value_a, ir_rvalue * &value_b, @@ -378,7 +384,7 @@ arithmetic_result_type(ir_rvalue * &value_a, ir_rvalue * &value_b, * multiply (*), and divide (/) operate on integer and * floating-point scalars, vectors, and matrices." */ - if (!glsl_type_is_numeric(type_a) || !glsl_type_is_numeric(type_b)) { + if (!is_arithmetic_type(type_a) || !is_arithmetic_type(type_b)) { _mesa_glsl_error(loc, state, "operands to arithmetic operators must be numeric"); return &glsl_type_builtin_error; @@ -517,7 +523,7 @@ unary_arithmetic_result_type(const struct glsl_type *type, * component-wise on their operands. These result with the same type * they operated on." */ - if (!glsl_type_is_numeric(type)) { + if (!is_arithmetic_type(type)) { _mesa_glsl_error(loc, state, "operands to arithmetic operators must be numeric"); return &glsl_type_builtin_error; @@ -1624,6 +1630,10 @@ ast_expression::do_hir(ir_exec_list *instructions, glsl_contains_opaque(op[1]->type))) { _mesa_glsl_error(&loc, state, "opaque type comparisons forbidden"); error_emitted = true; + } else if (op[0]->type == &glsl_type_builtin_yuvCscStandardEXT || + op[1]->type == &glsl_type_builtin_yuvCscStandardEXT) { + _mesa_glsl_error(&loc, state, "yuvCscStandardEXT comparisons forbidden"); + error_emitted = true; } if (error_emitted) { @@ -2197,6 +2207,14 @@ ast_expression::do_hir(ir_exec_list *instructions, result = new(linalloc) ir_constant(this->primary_expression.int64_constant); break; + case ast_csc_standard: { + ir_constant_data data = { { 0 } }; + data.i[0] = this->primary_expression.csc_standard; + result = new(linalloc) ir_constant(&glsl_type_builtin_yuvCscStandardEXT, + &data); + break; + } + case ast_sequence: { /* It should not be possible to generate a sequence in the AST without * any expressions in it. @@ -2326,6 +2344,7 @@ ast_expression::has_sequence_subexpression() const case ast_double_constant: case ast_int64_constant: case ast_uint64_constant: + case ast_csc_standard: return false; case ast_aggregate: @@ -2633,6 +2652,13 @@ get_type_name_for_precision_qualifier(const glsl_type *type) }; return names[type_idx]; } + case GLSL_SAMPLER_DIM_EXTERNAL_2D_Y2Y: { + assert(glsl_type_is_sampler(type)); + static const char *const names[4] = { + "__samplerExternal2DY2YEXT", NULL, NULL, NULL + }; + return names[type_idx]; + } default: UNREACHABLE("Unsupported sampler/image dimensionality"); } /* sampler/image float dimensionality */ @@ -4221,6 +4247,9 @@ apply_type_qualifier_to_variable(const struct ast_type_qualifier *qual, if (qual->flags.q.patch) var->data.patch = 1; + if (qual->flags.q.yuv) + var->data.yuv = 1; + if (qual->flags.q.attribute && state->stage != MESA_SHADER_VERTEX) { var->type = &glsl_type_builtin_error; _mesa_glsl_error(loc, state, @@ -9455,10 +9484,13 @@ detect_conflicting_assignments(struct _mesa_glsl_parse_state *state, { bool gl_FragColor_assigned = false; bool gl_FragData_assigned = false; + bool gl_FragDepth_assigned = false; bool gl_FragSecondaryColor_assigned = false; bool gl_FragSecondaryData_assigned = false; bool user_defined_fs_output_assigned = false; ir_variable *user_defined_fs_output = NULL; + bool yuv_layout_used = false; + int color_outputs = 0; /* It would be nice to have proper location information. */ YYLTYPE loc; @@ -9467,7 +9499,13 @@ detect_conflicting_assignments(struct _mesa_glsl_parse_state *state, ir_foreach_in_list(ir_instruction, node, instructions) { ir_variable *var = node->as_variable(); - if (!var || !var->data.assigned) + if (!var) + continue; + + if (var->data.yuv) + yuv_layout_used = true; + + if (!var->data.assigned) continue; if (strcmp(var->name, "gl_FragColor") == 0) { @@ -9485,11 +9523,14 @@ detect_conflicting_assignments(struct _mesa_glsl_parse_state *state, gl_FragSecondaryColor_assigned = true; else if (strcmp(var->name, "gl_SecondaryFragDataEXT") == 0) gl_FragSecondaryData_assigned = true; + else if (strcmp(var->name, "gl_FragDepth") == 0) + gl_FragDepth_assigned = true; else if (!is_gl_identifier(var->name)) { if (state->stage == MESA_SHADER_FRAGMENT && var->data.mode == ir_var_shader_out) { user_defined_fs_output_assigned = true; user_defined_fs_output = var; + color_outputs++; } } } @@ -9539,6 +9580,28 @@ detect_conflicting_assignments(struct _mesa_glsl_parse_state *state, _mesa_glsl_error(&loc, state, "Dual source blending requires EXT_blend_func_extended"); } + + if (yuv_layout_used) { + /** + * From the GL_EXT_YUV_target spec: + * + * "Additionally if the shader qualifies fragment shader output with + * the new yuv qualifier and write depth or multiple color output, + * it would cause compilation failure." + * + * However, since the extension requires GLSL ES 3.00, we don't need to + * consider interactions with gl_FragColor and gl_FragData. + */ + + if (gl_FragDepth_assigned) { + _mesa_glsl_error(&loc, state, "fragment shader uses yuv-layout and " + "`gl_FragDepth'"); + } else if (color_outputs > 1) { + _mesa_glsl_error(&loc, state, "fragment shader uses yuv-layout and " + "multiple color-outputs"); + } + } + } static void diff --git a/src/compiler/glsl/ast_type.cpp b/src/compiler/glsl/ast_type.cpp index 51fa331bf00..6f0ddaa5752 100644 --- a/src/compiler/glsl/ast_type.cpp +++ b/src/compiler/glsl/ast_type.cpp @@ -546,6 +546,25 @@ ast_type_qualifier::merge_qualifier(YYLTYPE *loc, } } + if ((this->flags.q.explicit_location && q.flags.q.yuv) || + (this->flags.q.yuv && q.flags.q.explicit_location)) { + /*** + * The EXT_YUV_target spec says: + * The new yuv layout qualifier can't be combined with any other + * layout qualifier, + * + * However, forbidding *all* layout qualifiers seems like a massive + * over-reaction, and limiting potentially useful stuff. So instead, + * let's just disallow the location=n layout. This is the only valid + * output layout in GLSL ES 3.00, and the extension spec explicitly + * says that it's not valid together with multiple color outputs + * anyway. + */ + _mesa_glsl_error(loc, state, "yuv layout can't be combined with any " + "other layout qualifier"); + return false; + } + return r; } diff --git a/src/compiler/glsl/builtin_functions.cpp b/src/compiler/glsl/builtin_functions.cpp index 13a9481fe93..74978be5bf6 100644 --- a/src/compiler/glsl/builtin_functions.cpp +++ b/src/compiler/glsl/builtin_functions.cpp @@ -87,6 +87,7 @@ #include #include "builtin_functions.h" #include "util/hash_table.h" +#include "util/u_ycbcr.h" #ifndef M_PIf #define M_PIf ((float) M_PI) @@ -230,6 +231,14 @@ texture_external_es3(const _mesa_glsl_parse_state *state) state->is_version(0, 300); } +static bool +texture_external_2d_y2y(const _mesa_glsl_parse_state *state) +{ + return state->EXT_YUV_target_enable && + state->es_shader && + state->is_version(0, 300); +} + static bool texture_shadow2Dext(const _mesa_glsl_parse_state *state) { @@ -1226,6 +1235,11 @@ private: unsigned num_arguments, unsigned flags); + ir_variable *get_to_ycbcr_matrix(ir_factory &body, const char *name, + const float coeffs[3], bool full_range); + ir_variable *get_to_rgb_matrix(ir_factory &body, const char *name, + const float coeffs[3], bool full_range); + /** * Create a new image built-in function for all known image types. * \p flags is a bitfield of \c image_function_flags flags. @@ -1599,6 +1613,9 @@ private: ir_function_signature *_set_mesh_outputs_intrinsic(); ir_function_signature *_set_mesh_outputs(); + ir_function_signature *_rgb_2_yuv(); + ir_function_signature *_yuv_2_rgb(); + #undef B0 #undef B1 #undef B2 @@ -2856,6 +2873,8 @@ builtin_builder::create_builtins() _textureSize(texture_multisample_array, &glsl_type_builtin_ivec3, &glsl_type_builtin_usampler2DMSArray), _textureSize(texture_external_es3, &glsl_type_builtin_ivec2, &glsl_type_builtin_samplerExternalOES), + + _textureSize(texture_external_2d_y2y, &glsl_type_builtin_ivec2, &glsl_type_builtin_samplerExternal2DY2YEXT), NULL); add_function("textureSize1D", @@ -2964,6 +2983,8 @@ builtin_builder::create_builtins() _texture(ir_tex, texture_external_es3, &glsl_type_builtin_vec4, &glsl_type_builtin_samplerExternalOES, &glsl_type_builtin_vec2), + _texture(ir_tex, texture_external_2d_y2y, &glsl_type_builtin_vec4, &glsl_type_builtin_samplerExternal2DY2YEXT, &glsl_type_builtin_vec2), + _texture(ir_txb, v130_derivatives_only, &glsl_type_builtin_vec4, &glsl_type_builtin_sampler1D, &glsl_type_builtin_float), _texture(ir_txb, v130_derivatives_only, &glsl_type_builtin_ivec4, &glsl_type_builtin_isampler1D, &glsl_type_builtin_float), _texture(ir_txb, v130_derivatives_only, &glsl_type_builtin_uvec4, &glsl_type_builtin_usampler1D, &glsl_type_builtin_float), @@ -3209,6 +3230,8 @@ builtin_builder::create_builtins() _texture(ir_tex, v130, &glsl_type_builtin_ivec4, &glsl_type_builtin_isampler2DRect, &glsl_type_builtin_vec3, TEX_PROJECT), _texture(ir_tex, texture_external_es3, &glsl_type_builtin_vec4, &glsl_type_builtin_samplerExternalOES, &glsl_type_builtin_vec3, TEX_PROJECT), _texture(ir_tex, texture_external_es3, &glsl_type_builtin_vec4, &glsl_type_builtin_samplerExternalOES, &glsl_type_builtin_vec4, TEX_PROJECT), + _texture(ir_tex, texture_external_2d_y2y, &glsl_type_builtin_vec4, &glsl_type_builtin_samplerExternal2DY2YEXT, &glsl_type_builtin_vec3, TEX_PROJECT), + _texture(ir_tex, texture_external_2d_y2y, &glsl_type_builtin_vec4, &glsl_type_builtin_samplerExternal2DY2YEXT, &glsl_type_builtin_vec4, TEX_PROJECT), _texture(ir_tex, v130, &glsl_type_builtin_uvec4, &glsl_type_builtin_usampler2DRect, &glsl_type_builtin_vec3, TEX_PROJECT), _texture(ir_tex, v130, &glsl_type_builtin_vec4, &glsl_type_builtin_sampler2DRect, &glsl_type_builtin_vec4, TEX_PROJECT), @@ -3278,6 +3301,8 @@ builtin_builder::create_builtins() _texelFetch(texture_external_es3, &glsl_type_builtin_vec4, &glsl_type_builtin_samplerExternalOES, &glsl_type_builtin_ivec2), + _texelFetch(texture_external_2d_y2y, &glsl_type_builtin_vec4, &glsl_type_builtin_samplerExternal2DY2YEXT, &glsl_type_builtin_ivec2), + NULL); add_function("texelFetch1D", @@ -6044,6 +6069,9 @@ builtin_builder::create_builtins() add_function("EmitMeshTasksEXT", _emit_mesh_tasks(), NULL); add_function("SetMeshOutputsEXT", _set_mesh_outputs(), NULL); + add_function("rgb_2_yuv", _rgb_2_yuv(), NULL); + add_function("yuv_2_rgb", _yuv_2_rgb(), NULL); + #undef F #undef FI #undef FIUDHF_VEC @@ -9623,6 +9651,158 @@ ir_function_signature *builtin_builder::_set_mesh_outputs() return sig; } +ir_variable *builtin_builder::get_to_ycbcr_matrix(ir_factory &body, + const char *name, + const float coeffs[3], + bool full_range) +{ + float data[3][4]; + util_get_rgb_to_ycbcr_matrix(data, coeffs); + + const unsigned bpc[3] = { 8, 8, 8 }; + float range[3][2]; + if (full_range) + util_get_full_range_coeffs(range, bpc); + else + util_get_narrow_range_coeffs(range, bpc); + + util_ycbcr_adjust_to_range(data, range); + + ir_variable *var = body.make_temp(&glsl_type_builtin_mat4x3, name); + + for (int i = 0; i < 4; ++i) { + ir_constant_data col_data; + col_data.f[0] = data[0][i]; + col_data.f[1] = data[1][i]; + col_data.f[2] = data[2][i]; + ir_constant *col = imm(&glsl_type_builtin_vec3, col_data); + body.emit(assign(array_ref(var, i), col)); + } + + return var; +} + +ir_variable *builtin_builder::get_to_rgb_matrix(ir_factory &body, + const char *name, + const float coeffs[3], + bool full_range) +{ + float data[3][4]; + util_get_ycbcr_to_rgb_matrix(data, coeffs); + + const unsigned bpc[3] = { 8, 8, 8 }; + float range[3][2]; + if (full_range) + util_get_full_range_coeffs(range, bpc); + else + util_get_narrow_range_coeffs(range, bpc); + + util_ycbcr_adjust_from_range(data, range); + + ir_variable *var = body.make_temp(&glsl_type_builtin_mat4x3, name); + + for (int i = 0; i < 4; ++i) { + ir_constant_data col_data; + col_data.f[0] = data[0][i]; + col_data.f[1] = data[1][i]; + col_data.f[2] = data[2][i]; + ir_constant *col = imm(&glsl_type_builtin_vec3, col_data); + body.emit(assign(array_ref(var, i), col)); + } + + return var; +} + +ir_function_signature * +builtin_builder::_rgb_2_yuv() +{ + ir_variable *color = in_var(&glsl_type_builtin_vec3, "color"); + ir_variable *conv_standard = in_var(&glsl_type_builtin_yuvCscStandardEXT, "conv_standard"); + MAKE_SIG(&glsl_type_builtin_vec3, texture_external_2d_y2y, 2, color, conv_standard); + + ir_swizzle *r = swizzle(color, SWIZZLE_XXXX, 3); + ir_swizzle *g = swizzle(color, SWIZZLE_YYYY, 3); + ir_swizzle *b = swizzle(color, SWIZZLE_ZZZZ, 3); + + ir_constant_data data; + memset(&data, 0, sizeof(data)); + data.i[0] = YUV_CSC_STANDARD_601; + ir_constant *itu_601 = imm(&glsl_type_builtin_yuvCscStandardEXT, data); + data.i[0] = YUV_CSC_STANDARD_601_FULL_RANGE; + ir_constant *itu_601_full_range = imm(&glsl_type_builtin_yuvCscStandardEXT, data); + data.i[0] = YUV_CSC_STANDARD_709; + ir_constant *itu_709 = imm(&glsl_type_builtin_yuvCscStandardEXT, data); + + ir_variable *m = body.make_temp(&glsl_type_builtin_mat4x3, "m"); + body.emit( + if_tree( + equal(conv_standard, itu_601), + assign(m, get_to_ycbcr_matrix(body, "m_601", util_ycbcr_bt601_coeffs, false)), + if_tree( + equal(conv_standard, itu_601_full_range), + assign(m, get_to_ycbcr_matrix(body, "m_601f", util_ycbcr_bt601_coeffs, true)), + if_tree( + equal(conv_standard, itu_709), + assign(m, get_to_ycbcr_matrix(body, "m_709", util_ycbcr_bt709_coeffs, false)) + ) + ) + ) + ); + + body.emit(ret( + ir_builder::fma(r, array_ref(m, 0), + ir_builder::fma(g, array_ref(m, 1), + ir_builder::fma(b, array_ref(m, 2), + array_ref(m, 3)))))); + + return sig; +} + +ir_function_signature * +builtin_builder::_yuv_2_rgb() +{ + ir_variable *color = in_var(&glsl_type_builtin_vec3, "color"); + ir_variable *conv_standard = in_var(&glsl_type_builtin_yuvCscStandardEXT, "conv_standard"); + MAKE_SIG(&glsl_type_builtin_vec3, texture_external_2d_y2y, 2, color, conv_standard); + + ir_swizzle *y = swizzle(color, SWIZZLE_XXXX, 3); + ir_swizzle *u = swizzle(color, SWIZZLE_YYYY, 3); + ir_swizzle *v = swizzle(color, SWIZZLE_ZZZZ, 3); + + ir_constant_data data; + memset(&data, 0, sizeof(data)); + data.i[0] = YUV_CSC_STANDARD_601; + ir_constant *itu_601 = imm(&glsl_type_builtin_yuvCscStandardEXT, data); + data.i[0] = YUV_CSC_STANDARD_601_FULL_RANGE; + ir_constant *itu_601_full_range = imm(&glsl_type_builtin_yuvCscStandardEXT, data); + data.i[0] = YUV_CSC_STANDARD_709; + ir_constant *itu_709 = imm(&glsl_type_builtin_yuvCscStandardEXT, data); + + ir_variable *m = body.make_temp(&glsl_type_builtin_mat4x3, "m"); + body.emit( + if_tree( + equal(conv_standard, itu_601), + assign(m, get_to_rgb_matrix(body, "m_601", util_ycbcr_bt601_coeffs, false)), + if_tree( + equal(conv_standard, itu_601_full_range), + assign(m, get_to_rgb_matrix(body, "m_601f", util_ycbcr_bt601_coeffs, true)), + if_tree( + equal(conv_standard, itu_709), + assign(m, get_to_rgb_matrix(body, "m_709", util_ycbcr_bt709_coeffs, false)) + ) + ) + ) + ); + + body.emit(ret( + ir_builder::fma(y, array_ref(m, 0), + ir_builder::fma(u, array_ref(m, 1), + ir_builder::fma(v, array_ref(m, 2), + array_ref(m, 3)))))); + + return sig; +} + /** @} */ /******************************************************************************/ diff --git a/src/compiler/glsl/glsl_lexer.ll b/src/compiler/glsl/glsl_lexer.ll index 4bc34f98a7b..03090680c0b 100644 --- a/src/compiler/glsl/glsl_lexer.ll +++ b/src/compiler/glsl/glsl_lexer.ll @@ -438,6 +438,32 @@ mat4x2 TYPE(120, 300, 120, 300, &glsl_type_builtin_mat4x2); mat4x3 TYPE(120, 300, 120, 300, &glsl_type_builtin_mat4x3); mat4x4 TYPE(120, 300, 120, 300, &glsl_type_builtin_mat4); +yuvCscStandardEXT TYPE_WITH_ALT(0, 0, 0, 0, yyextra->EXT_YUV_target_enable, &glsl_type_builtin_yuvCscStandardEXT); + +itu_601 { + if (!yyextra->EXT_YUV_target_enable) + return classify_identifier(yyextra, yytext, yyleng, yylval); + + yylval->csc_standard = YUV_CSC_STANDARD_601; + return CSCSTANDARD; + } + +itu_601_full_range { + if (!yyextra->EXT_YUV_target_enable) + return classify_identifier(yyextra, yytext, yyleng, yylval); + + yylval->csc_standard = YUV_CSC_STANDARD_601_FULL_RANGE; + return CSCSTANDARD; + } + +itu_709 { + if (!yyextra->EXT_YUV_target_enable) + return classify_identifier(yyextra, yytext, yyleng, yylval); + + yylval->csc_standard = YUV_CSC_STANDARD_709; + return CSCSTANDARD; + } + in return IN_TOK; out return OUT_TOK; inout return INOUT_TOK; @@ -502,6 +528,14 @@ samplerExternalOES { return IDENTIFIER; } +__samplerExternal2DY2YEXT { + if (yyextra->EXT_YUV_target_enable) { + yylval->type = &glsl_type_builtin_samplerExternal2DY2YEXT; + return BASIC_TYPE_TOK; + } else + return IDENTIFIER; + } + /* keywords available with ARB_gpu_shader5 */ precise KEYWORD_WITH_ALT(400, 310, 400, 320, yyextra->ARB_gpu_shader5_enable || yyextra->EXT_gpu_shader5_enable || yyextra->OES_gpu_shader5_enable, PRECISE); diff --git a/src/compiler/glsl/glsl_parser.yy b/src/compiler/glsl/glsl_parser.yy index 996aec41ef3..092228d3194 100644 --- a/src/compiler/glsl/glsl_parser.yy +++ b/src/compiler/glsl/glsl_parser.yy @@ -105,6 +105,7 @@ static bool match_layout_qualifier(const char *s1, const char *s2, float real; double dreal; const char *identifier; + enum yuv_csc_standard csc_standard; struct ast_type_qualifier type_qualifier; @@ -159,6 +160,7 @@ static bool match_layout_qualifier(const char *s1, const char *s2, %token INTCONSTANT UINTCONSTANT BOOLCONSTANT %token INT64CONSTANT UINT64CONSTANT %token FIELD_SELECTION +%token CSCSTANDARD %token LEFT_OP RIGHT_OP %token INC_OP DEC_OP LE_OP GE_OP EQ_OP NE_OP %token AND_OP OR_OP XOR_OP MUL_ASSIGN DIV_ASSIGN ADD_ASSIGN @@ -311,6 +313,7 @@ translation_unit: } state->symbols->add_default_precision_qualifier("sampler2D", ast_precision_low); state->symbols->add_default_precision_qualifier("samplerExternalOES", ast_precision_low); + state->symbols->add_default_precision_qualifier("__samplerExternal2DY2YEXT", ast_precision_low); state->symbols->add_default_precision_qualifier("samplerCube", ast_precision_low); state->symbols->add_default_precision_qualifier("atomic_uint", ast_precision_high); } @@ -493,6 +496,13 @@ primary_expression: $$->set_location(@1); $$->primary_expression.bool_constant = $1; } + | CSCSTANDARD + { + linear_ctx *ctx = state->linalloc; + $$ = new(ctx) ast_expression(ast_csc_standard, NULL, NULL, NULL); + $$->set_location(@1); + $$->primary_expression.csc_standard = $1; + } | '(' expression ')' { $$ = $2; @@ -1743,6 +1753,24 @@ layout_qualifier_id: } } + /* Layout qualifier for EXT_YUV_target. */ + if (match_layout_qualifier($1, "yuv", state) == 0) { + if (state->stage != MESA_SHADER_FRAGMENT) { + _mesa_glsl_error(& @1, state, + "yuv layout qualifier only valid in fragment " + "shaders"); + } + + if (state->EXT_YUV_target_enable) { + $$.flags.q.yuv = 1; + } else { + _mesa_glsl_error(& @1, state, + "yuv layout qualifier present, but the " + "EXT_YUV_target_enable extension is not " + "enabled."); + } + } + if (!$$.flags.i) { _mesa_glsl_error(& @1, state, "unrecognized layout identifier " "`%s'", $1); diff --git a/src/compiler/glsl/glsl_parser_extras.cpp b/src/compiler/glsl/glsl_parser_extras.cpp index 52c2c51b69f..51538b08f7e 100644 --- a/src/compiler/glsl/glsl_parser_extras.cpp +++ b/src/compiler/glsl/glsl_parser_extras.cpp @@ -853,6 +853,7 @@ static const _mesa_glsl_extension _mesa_glsl_supported_extensions[] = { EXT_AEP(EXT_texture_cube_map_array), EXT(EXT_texture_query_lod), EXT(EXT_texture_shadow_lod), + EXT(EXT_YUV_target), EXT(INTEL_conservative_rasterization), EXT(INTEL_shader_atomic_float_minmax), EXT(INTEL_shader_integer_functions2), @@ -1355,6 +1356,8 @@ _mesa_ast_type_qualifier_print(const struct ast_type_qualifier *q) printf("noperspective "); if (q->flags.q.per_primitive) printf("per_primitive "); + if (q->flags.q.yuv) + printf("yuv "); } @@ -1537,6 +1540,17 @@ ast_expression::print(void) const break; } + case ast_csc_standard: + switch (primary_expression.csc_standard) { + case YUV_CSC_STANDARD_601: + printf("itu_601 "); + case YUV_CSC_STANDARD_601_FULL_RANGE: + printf("itu_601_full_range "); + case YUV_CSC_STANDARD_709: + printf("itu_709 "); + } + break; + default: assert(0); break; diff --git a/src/compiler/glsl/glsl_parser_extras.h b/src/compiler/glsl/glsl_parser_extras.h index b57bd7b9e73..05100e4b556 100644 --- a/src/compiler/glsl/glsl_parser_extras.h +++ b/src/compiler/glsl/glsl_parser_extras.h @@ -937,6 +937,8 @@ struct _mesa_glsl_parse_state { bool EXT_texture_query_lod_warn; bool EXT_texture_shadow_lod_enable; bool EXT_texture_shadow_lod_warn; + bool EXT_YUV_target_enable; + bool EXT_YUV_target_warn; bool INTEL_conservative_rasterization_enable; bool INTEL_conservative_rasterization_warn; bool INTEL_shader_atomic_float_minmax_enable; diff --git a/src/compiler/glsl/glsl_to_nir.cpp b/src/compiler/glsl/glsl_to_nir.cpp index 306fd66126d..12b3e2897af 100644 --- a/src/compiler/glsl/glsl_to_nir.cpp +++ b/src/compiler/glsl/glsl_to_nir.cpp @@ -475,6 +475,7 @@ nir_visitor::visit(ir_variable *ir) var->data.implicit_sized_array = ir->data.implicit_sized_array; var->data.from_ssbo_unsized_array = ir->data.from_ssbo_unsized_array; var->data.per_primitive = ir->data.per_primitive; + var->data.yuv = ir->data.yuv; switch(ir->data.mode) { case ir_var_auto: diff --git a/src/compiler/glsl/ir.h b/src/compiler/glsl/ir.h index eab911f9925..a3738bb15fa 100644 --- a/src/compiler/glsl/ir.h +++ b/src/compiler/glsl/ir.h @@ -870,6 +870,11 @@ public: */ unsigned pixel_local_storage:2; + /** + * Non-zero if the fragment shader output produce YUV color output + */ + unsigned yuv:1; + /** * Emit a warning if this variable is accessed. */ diff --git a/src/compiler/glsl_types.c b/src/compiler/glsl_types.c index d57af7c16b4..48ac3fddbf4 100644 --- a/src/compiler/glsl_types.c +++ b/src/compiler/glsl_types.c @@ -876,6 +876,11 @@ glsl_sampler_type(enum glsl_sampler_dim dim, bool shadow, return &glsl_type_builtin_error; else return &glsl_type_builtin_samplerExternalOES; + case GLSL_SAMPLER_DIM_EXTERNAL_2D_Y2Y: + if (shadow || array) + return &glsl_type_builtin_error; + else + return &glsl_type_builtin_samplerExternal2DY2YEXT; case GLSL_SAMPLER_DIM_SUBPASS: case GLSL_SAMPLER_DIM_SUBPASS_MS: return &glsl_type_builtin_error; @@ -906,6 +911,7 @@ glsl_sampler_type(enum glsl_sampler_dim dim, bool shadow, case GLSL_SAMPLER_DIM_MS: return (array ? &glsl_type_builtin_isampler2DMSArray : &glsl_type_builtin_isampler2DMS); case GLSL_SAMPLER_DIM_EXTERNAL: + case GLSL_SAMPLER_DIM_EXTERNAL_2D_Y2Y: return &glsl_type_builtin_error; case GLSL_SAMPLER_DIM_SUBPASS: case GLSL_SAMPLER_DIM_SUBPASS_MS: @@ -937,6 +943,7 @@ glsl_sampler_type(enum glsl_sampler_dim dim, bool shadow, case GLSL_SAMPLER_DIM_MS: return (array ? &glsl_type_builtin_usampler2DMSArray : &glsl_type_builtin_usampler2DMS); case GLSL_SAMPLER_DIM_EXTERNAL: + case GLSL_SAMPLER_DIM_EXTERNAL_2D_Y2Y: return &glsl_type_builtin_error; case GLSL_SAMPLER_DIM_SUBPASS: case GLSL_SAMPLER_DIM_SUBPASS_MS: @@ -999,6 +1006,8 @@ glsl_texture_type(enum glsl_sampler_dim dim, bool array, enum glsl_base_type typ return &glsl_type_builtin_error; else return &glsl_type_builtin_textureExternalOES; + case GLSL_SAMPLER_DIM_EXTERNAL_2D_Y2Y: + return &glsl_type_builtin_error; } break; case GLSL_TYPE_INT: @@ -1028,6 +1037,7 @@ glsl_texture_type(enum glsl_sampler_dim dim, bool array, enum glsl_base_type typ case GLSL_SAMPLER_DIM_SUBPASS_MS: return &glsl_type_builtin_itextureSubpassInputMS; case GLSL_SAMPLER_DIM_EXTERNAL: + case GLSL_SAMPLER_DIM_EXTERNAL_2D_Y2Y: return &glsl_type_builtin_error; } break; @@ -1058,6 +1068,7 @@ glsl_texture_type(enum glsl_sampler_dim dim, bool array, enum glsl_base_type typ case GLSL_SAMPLER_DIM_SUBPASS_MS: return &glsl_type_builtin_utextureSubpassInputMS; case GLSL_SAMPLER_DIM_EXTERNAL: + case GLSL_SAMPLER_DIM_EXTERNAL_2D_Y2Y: return &glsl_type_builtin_error; } break; @@ -1114,6 +1125,7 @@ glsl_image_type(enum glsl_sampler_dim dim, bool array, enum glsl_base_type type) case GLSL_SAMPLER_DIM_SUBPASS_MS: return &glsl_type_builtin_subpassInputMS; case GLSL_SAMPLER_DIM_EXTERNAL: + case GLSL_SAMPLER_DIM_EXTERNAL_2D_Y2Y: return &glsl_type_builtin_error; } break; @@ -1144,6 +1156,7 @@ glsl_image_type(enum glsl_sampler_dim dim, bool array, enum glsl_base_type type) case GLSL_SAMPLER_DIM_SUBPASS_MS: return &glsl_type_builtin_isubpassInputMS; case GLSL_SAMPLER_DIM_EXTERNAL: + case GLSL_SAMPLER_DIM_EXTERNAL_2D_Y2Y: return &glsl_type_builtin_error; } break; @@ -1174,6 +1187,7 @@ glsl_image_type(enum glsl_sampler_dim dim, bool array, enum glsl_base_type type) case GLSL_SAMPLER_DIM_SUBPASS_MS: return &glsl_type_builtin_usubpassInputMS; case GLSL_SAMPLER_DIM_EXTERNAL: + case GLSL_SAMPLER_DIM_EXTERNAL_2D_Y2Y: return &glsl_type_builtin_error; } break; @@ -1202,6 +1216,7 @@ glsl_image_type(enum glsl_sampler_dim dim, bool array, enum glsl_base_type type) case GLSL_SAMPLER_DIM_SUBPASS: case GLSL_SAMPLER_DIM_SUBPASS_MS: case GLSL_SAMPLER_DIM_EXTERNAL: + case GLSL_SAMPLER_DIM_EXTERNAL_2D_Y2Y: return &glsl_type_builtin_error; } break; @@ -1230,6 +1245,7 @@ glsl_image_type(enum glsl_sampler_dim dim, bool array, enum glsl_base_type type) case GLSL_SAMPLER_DIM_SUBPASS: case GLSL_SAMPLER_DIM_SUBPASS_MS: case GLSL_SAMPLER_DIM_EXTERNAL: + case GLSL_SAMPLER_DIM_EXTERNAL_2D_Y2Y: return &glsl_type_builtin_error; } break; diff --git a/src/compiler/nir/nir.h b/src/compiler/nir/nir.h index 7c552657292..d2ba790733b 100644 --- a/src/compiler/nir/nir.h +++ b/src/compiler/nir/nir.h @@ -696,6 +696,11 @@ typedef struct nir_variable { */ unsigned depth_layout : 3; + /** + * Whether the variable is a YUV color-output. + */ + unsigned yuv : 1; + /** * Vertex stream output identifier. * diff --git a/src/compiler/shader_enums.h b/src/compiler/shader_enums.h index 66a77a72466..d4aebe6ab84 100644 --- a/src/compiler/shader_enums.h +++ b/src/compiler/shader_enums.h @@ -1641,11 +1641,18 @@ enum glsl_sampler_dim { GLSL_SAMPLER_DIM_RECT, GLSL_SAMPLER_DIM_BUF, GLSL_SAMPLER_DIM_EXTERNAL, + GLSL_SAMPLER_DIM_EXTERNAL_2D_Y2Y, GLSL_SAMPLER_DIM_MS, GLSL_SAMPLER_DIM_SUBPASS, /* for vulkan input attachments */ GLSL_SAMPLER_DIM_SUBPASS_MS, /* for multisampled vulkan input attachments */ }; +enum yuv_csc_standard { + YUV_CSC_STANDARD_601, + YUV_CSC_STANDARD_601_FULL_RANGE, + YUV_CSC_STANDARD_709, +}; + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/src/mesa/main/consts_exts.h b/src/mesa/main/consts_exts.h index de58a3100cb..2f47a4e636d 100644 --- a/src/mesa/main/consts_exts.h +++ b/src/mesa/main/consts_exts.h @@ -219,6 +219,7 @@ struct gl_extensions GLboolean EXT_timer_query; GLboolean EXT_vertex_array_bgra; GLboolean EXT_window_rectangles; + GLboolean EXT_YUV_target; GLboolean OES_copy_image; GLboolean OES_primitive_bounding_box; GLboolean OES_sample_variables; diff --git a/src/mesa/main/extensions.c b/src/mesa/main/extensions.c index ad9617d8378..ce4c3ed9151 100644 --- a/src/mesa/main/extensions.c +++ b/src/mesa/main/extensions.c @@ -287,6 +287,7 @@ _mesa_init_extensions(struct gl_extensions *extensions) extensions->EXT_shadow_samplers = GL_TRUE; extensions->EXT_stencil_two_side = GL_TRUE; extensions->EXT_texture_env_dot3 = GL_TRUE; + extensions->EXT_YUV_target = GL_TRUE; extensions->ATI_fragment_shader = GL_TRUE; extensions->ATI_texture_env_combine3 = GL_TRUE; diff --git a/src/mesa/main/extensions_table.h b/src/mesa/main/extensions_table.h index ecfc6cde90c..5568e0d801b 100644 --- a/src/mesa/main/extensions_table.h +++ b/src/mesa/main/extensions_table.h @@ -365,6 +365,7 @@ EXT(EXT_vertex_array , dummy_true EXT(EXT_vertex_array_bgra , EXT_vertex_array_bgra , GLL, GLC, x , x , 2008) EXT(EXT_vertex_attrib_64bit , ARB_vertex_attrib_64bit , 32, GLC, x , x , 2010) EXT(EXT_window_rectangles , EXT_window_rectangles , GLL, GLC, x , 30, 2016) +EXT(EXT_YUV_target , EXT_YUV_target , x , x , x , 30, 2013) EXT(GREMEDY_string_marker , GREMEDY_string_marker , GLL, GLC, x , x , 2007) diff --git a/src/util/meson.build b/src/util/meson.build index 62fc844f07f..4f3b71dd730 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -466,6 +466,7 @@ if with_tests 'tests/u_memstream_test.cpp', 'tests/u_printf_test.cpp', 'tests/u_qsort_test.cpp', + 'tests/u_ycbcr_test.cpp', 'tests/vector_test.cpp', ) diff --git a/src/util/tests/u_ycbcr_test.cpp b/src/util/tests/u_ycbcr_test.cpp new file mode 100644 index 00000000000..344e8cb917f --- /dev/null +++ b/src/util/tests/u_ycbcr_test.cpp @@ -0,0 +1,520 @@ +/** + * Copyright (c) 2026 Collabora Ltd. + * + * SPDX-License-Identifier: MIT + */ + +#include + +#include + +#include "util/u_ycbcr.h" + +#include "macros.h" + +static void +test_to_rgb_coeffs(const float coeffs[3]) +{ + float mat[3][4]; + util_get_ycbcr_to_rgb_matrix(mat, coeffs); + + float a = coeffs[0]; + float b = coeffs[1]; + float c = coeffs[2]; + float d = 2 - 2 * c; + float e = 2 - 2 * a; + + const struct { + float input[3]; + float expected[3]; + } test_data[] = { { + { 1.0f, 0.0f, 0.0f}, + { 1.0f, 1.0f, 1.0f }, + }, { + { 0.0f, 0.0f, 0.0f}, + { 0.0f, 0.0f, 0.0f }, + }, { + { 0.5f, 0.0f, 0.0f }, + { 0.5f, 0.5f, 0.5f }, + }, { + { a, -a / d, 0.5f }, + { 1.0f, 0.0f, 0.0f }, + }, { + { b, -b / d, -b / e }, + { 0.0f, 1.0f, 0.0f }, + }, { + { c, 0.5f, -c / e }, + { 0.0f, 0.0f, 1.0f }, + }, { + { 1 - c, -0.5f, c / e }, + { 1.0f, 1.0f, 0.0f }, + }, { + { 1 - a, a / d, (a - 1) / e }, + { 0.0f, 1.0f, 1.0f }, + }, { + { 1 - b, b / d, b / e }, + { 1.0f, 0.0f, 1.0f }, + } + }; + + for (size_t i = 0; i < ARRAY_SIZE(test_data); ++i) { + float result[3]; + for (int c = 0; c < 3; ++c) { + result[c] = test_data[i].input[0] * mat[c][0] + + test_data[i].input[1] * mat[c][1] + + test_data[i].input[2] * mat[c][2]; + + } + + EXPECT_NEAR(test_data[i].expected[0], result[0], 1e-7); + EXPECT_NEAR(test_data[i].expected[1], result[1], 1e-7); + EXPECT_NEAR(test_data[i].expected[2], result[2], 1e-7); + + /* verify with reference equation */ + float Y = test_data[i].input[0]; + float Cb = test_data[i].input[1]; + float Cr = test_data[i].input[2]; + + result[0] = Y + e * Cr; + result[1] = Y - (a * e / b) * Cr - (c * d / b) * Cb; + result[2] = Y + d * Cb; + + EXPECT_NEAR(test_data[i].expected[0], result[0], 1e-6); + EXPECT_NEAR(test_data[i].expected[1], result[1], 1e-6); + EXPECT_NEAR(test_data[i].expected[2], result[2], 1e-6); + } +} + +TEST(u_ycbcr_test, to_rgb) +{ + test_to_rgb_coeffs(util_ycbcr_bt601_coeffs); + test_to_rgb_coeffs(util_ycbcr_bt709_coeffs); + test_to_rgb_coeffs(util_ycbcr_bt2020_coeffs); +} + +static void +test_to_ycbcr_coeffs(const float coeffs[3]) +{ + float mat[3][4]; + util_get_rgb_to_ycbcr_matrix(mat, coeffs); + + float a = coeffs[0]; + float b = coeffs[1]; + float c = coeffs[2]; + float d = 2 - 2 * c; + float e = 2 - 2 * a; + + const struct { + float input[3]; + float expected[3]; + } test_data[] = { { + { 1.0f, 1.0f, 1.0f }, + { 1.0f, 0.0f, 0.0f}, + }, { + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f}, + }, { + { 0.5f, 0.5f, 0.5f }, + { 0.5f, 0.0f, 0.0f }, + }, { + { 1.0f, 0.0f, 0.0f }, + { a, -a / d, 0.5f }, + }, { + { 0.0f, 1.0f, 0.0f }, + { b, -b / d, -b / e }, + }, { + { 0.0f, 0.0f, 1.0f }, + { c, 0.5f, -c / e }, + }, { + { 1.0f, 1.0f, 0.0f }, + { 1 - c, -0.5f, c / e }, + }, { + { 0.0f, 1.0f, 1.0f }, + { 1 - a, a / d, (a - 1) / e }, + }, { + { 1.0f, 0.0f, 1.0f }, + { 1 - b, b / d, b / e }, + } + }; + + for (size_t i = 0; i < ARRAY_SIZE(test_data); ++i) { + float result[3]; + for (int c = 0; c < 3; ++c) { + result[c] = test_data[i].input[0] * mat[c][0] + + test_data[i].input[1] * mat[c][1] + + test_data[i].input[2] * mat[c][2]; + + } + + EXPECT_NEAR(test_data[i].expected[0], result[0], 1e-7); + EXPECT_NEAR(test_data[i].expected[1], result[1], 1e-7); + EXPECT_NEAR(test_data[i].expected[2], result[2], 1e-7); + + /* verify with reference equation */ + float R = test_data[i].input[0]; + float G = test_data[i].input[1]; + float B = test_data[i].input[2]; + + float Y = a * R + b * G + c * B;; + result[0] = Y; + result[1] = (B - Y) / d; + result[2] = (R - Y) / e; + + EXPECT_NEAR(test_data[i].expected[0], result[0], 1e-7); + EXPECT_NEAR(test_data[i].expected[1], result[1], 1e-7); + EXPECT_NEAR(test_data[i].expected[2], result[2], 1e-7); + } +} + +TEST(u_ycbcr_test, to_ycbcr) +{ + test_to_ycbcr_coeffs(util_ycbcr_bt601_coeffs); + test_to_ycbcr_coeffs(util_ycbcr_bt709_coeffs); + test_to_ycbcr_coeffs(util_ycbcr_bt2020_coeffs); +} + +static void +test_to_ycbcr_and_back_coeffs(const float coeffs[3]) +{ + float to_ycbcr[3][4], to_rgb[3][4]; + util_get_rgb_to_ycbcr_matrix(to_ycbcr, coeffs); + util_get_ycbcr_to_rgb_matrix(to_rgb, coeffs); + + float inputs[][3] = { + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.5f, 0.5f, 0.5f }, + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f }, + { 1.0f, 1.0f, 0.0f }, + { 0.0f, 1.0f, 1.0f }, + { 1.0f, 0.0f, 1.0f }, + }; + + for (size_t i = 0; i < ARRAY_SIZE(inputs); ++i) { + float ycbcr[3]; + for (int c = 0; c < 3; ++c) { + ycbcr[c] = inputs[i][0] * to_ycbcr[c][0] + + inputs[i][1] * to_ycbcr[c][1] + + inputs[i][2] * to_ycbcr[c][2]; + + } + + float result[3]; + for (int c = 0; c < 3; ++c) { + result[c] = ycbcr[0] * to_rgb[c][0] + + ycbcr[1] * to_rgb[c][1] + + ycbcr[2] * to_rgb[c][2]; + + } + + EXPECT_NEAR(inputs[i][0], result[0], 1e-7); + EXPECT_NEAR(inputs[i][1], result[1], 1e-7); + EXPECT_NEAR(inputs[i][2], result[2], 1e-7); + } +} + +TEST(u_ycbcr_test, to_ycbcr_and_back) +{ + test_to_ycbcr_and_back_coeffs(util_ycbcr_bt601_coeffs); + test_to_ycbcr_and_back_coeffs(util_ycbcr_bt709_coeffs); + test_to_ycbcr_and_back_coeffs(util_ycbcr_bt2020_coeffs); +} + +TEST(u_ycbcr_test, full_range) +{ + float range[3][2]; + unsigned bpc[3] = {8, 8, 8}; + util_get_full_range_coeffs(range, bpc); + + const struct { + uint8_t input[3]; + float expected[3]; + } test_data[] = { { + { 0, 128, 128 }, + { 0.0f, 0.0f, 0.0f }, + }, { + { 255, 128, 128 }, + { 1.0f, 0.0f, 0.0f }, + }, { + { 0, 0, 255 }, + { 0.0f, -128.0f / 255, 127.0f / 255 }, + }, { + { 255, 255, 0 }, + { 1.0f, 127.0f / 255, -128.0f / 255 }, + } + }; + + for (size_t i = 0; i < ARRAY_SIZE(test_data); ++i) { + float input[3] = { + test_data[i].input[0] / 255.0f, + test_data[i].input[1] / 255.0f, + test_data[i].input[2] / 255.0f, + }; + + float result[3]; + for (int c = 0; c < 3; ++c) + result[c] = input[c] * range[c][0] + range[c][1]; + + EXPECT_NEAR(test_data[i].expected[0], result[0], 1e-7); + EXPECT_NEAR(test_data[i].expected[1], result[1], 1e-7); + EXPECT_NEAR(test_data[i].expected[2], result[2], 1e-7); + } +} + +TEST(u_ycbcr_test, narrow_range) +{ + float range[3][2]; + unsigned bpc[3] = {8, 8, 8}; + util_get_narrow_range_coeffs(range, bpc); + + const struct { + uint8_t input[3]; + float expected[3]; + } test_data[] = { { + { 16, 128, 128 }, + { 0.0f, 0.0f, 0.0f }, + }, { + { 235, 128, 128 }, + { 1.0f, 0.0f, 0.0f }, + }, { + { 16, 16, 240 }, + { 0.0f, -0.5f, 0.5f }, + }, { + { 235, 240, 16 }, + { 1.0f, 0.5f, -0.5f }, + } + }; + + for (size_t i = 0; i < ARRAY_SIZE(test_data); ++i) { + float input[3] = { + test_data[i].input[0] / 255.0f, + test_data[i].input[1] / 255.0f, + test_data[i].input[2] / 255.0f, + }; + + float result[3]; + for (int c = 0; c < 3; ++c) + result[c] = input[c] * range[c][0] + range[c][1]; + + EXPECT_NEAR(test_data[i].expected[0], result[0], 1e-7); + EXPECT_NEAR(test_data[i].expected[1], result[1], 1e-7); + EXPECT_NEAR(test_data[i].expected[2], result[2], 1e-7); + } +} + +static void +test_to_rgb_narrow_range_coeffs(const float coeffs[3]) +{ + const unsigned bpc[3] = {8, 8, 10}; + float range[3][2]; + util_get_narrow_range_coeffs(range, bpc); + + float mat[3][4]; + util_get_ycbcr_to_rgb_matrix(mat, coeffs); + util_ycbcr_adjust_from_range(mat, range); + + float a = coeffs[0]; + float b = coeffs[1]; + float c = coeffs[2]; + float d = 2 - 2 * c; + float e = 2 - 2 * a; + + const struct { + float input[3]; + float expected[3]; + } test_data[] = { { + { 1.0f, 0.0f, 0.0f}, + { 1.0f, 1.0f, 1.0f }, + }, { + { 0.0f, 0.0f, 0.0f}, + { 0.0f, 0.0f, 0.0f }, + }, { + { 0.5f, 0.0f, 0.0f }, + { 0.5f, 0.5f, 0.5f }, + }, { + { a, -a / d, 0.5f }, + { 1.0f, 0.0f, 0.0f }, + }, { + { b, -b / d, -b / e }, + { 0.0f, 1.0f, 0.0f }, + }, { + { c, 0.5f, -c / e }, + { 0.0f, 0.0f, 1.0f }, + }, { + { 1 - c, -0.5f, c / e }, + { 1.0f, 1.0f, 0.0f }, + }, { + { 1 - a, a / d, (a - 1) / e }, + { 0.0f, 1.0f, 1.0f }, + }, { + { 1 - b, b / d, b / e }, + { 1.0f, 0.0f, 1.0f }, + } + }; + for (size_t i = 0; i < ARRAY_SIZE(test_data); ++i) { + float input[3] = { + (16 + test_data[i].input[0] * (235 - 16)) / 255.0f, + (16 + (test_data[i].input[1] + 0.5f) * (240 - 16)) / 255.0f, + (16 + (test_data[i].input[2] + 0.5f) * (240 - 16)) / 255.75f, + }; + + float result[3]; + for (int c = 0; c < 3; ++c) { + result[c] = input[0] * mat[c][0] + + input[1] * mat[c][1] + + input[2] * mat[c][2] + + mat[c][3]; + + } + + EXPECT_NEAR(test_data[i].expected[0], result[0], 1e-6); + EXPECT_NEAR(test_data[i].expected[1], result[1], 1e-6); + EXPECT_NEAR(test_data[i].expected[2], result[2], 1e-6); + } +} + +TEST(u_ycbcr_test, bt601_to_rgb_narrow_range) +{ + test_to_rgb_narrow_range_coeffs(util_ycbcr_bt601_coeffs); + test_to_rgb_narrow_range_coeffs(util_ycbcr_bt709_coeffs); + test_to_rgb_narrow_range_coeffs(util_ycbcr_bt2020_coeffs); +} + + +static void +test_to_ycbcr_narrow_range_coeffs(const float coeffs[3]) +{ + const unsigned bpc[3] = {8, 8, 10}; + + float range[3][2]; + util_get_narrow_range_coeffs(range, bpc); + + float mat[3][4]; + util_get_rgb_to_ycbcr_matrix(mat, coeffs); + util_ycbcr_adjust_to_range(mat, range); + + float a = coeffs[0]; + float b = coeffs[1]; + float c = coeffs[2]; + float d = 2 - 2 * c; + float e = 2 - 2 * a; + + const struct { + float input[3]; + float expected[3]; + } test_data[] = { { + { 1.0f, 1.0f, 1.0f }, + { 1.0f, 0.0f, 0.0f}, + }, { + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f}, + }, { + { 0.5f, 0.5f, 0.5f }, + { 0.5f, 0.0f, 0.0f }, + }, { + { 1.0f, 0.0f, 0.0f }, + { a, -a / d, 0.5f }, + }, { + { 0.0f, 1.0f, 0.0f }, + { b, -b / d, -b / e }, + }, { + { 0.0f, 0.0f, 1.0f }, + { c, 0.5f, -c / e }, + }, { + { 1.0f, 1.0f, 0.0f }, + { 1 - c, -0.5f, c / e }, + }, { + { 0.0f, 1.0f, 1.0f }, + { 1 - a, a / d, (a - 1) / e }, + }, { + { 1.0f, 0.0f, 1.0f }, + { 1 - b, b / d, b / e }, + } + }; + for (size_t i = 0; i < ARRAY_SIZE(test_data); ++i) { + float result[3]; + for (int c = 0; c < 3; ++c) { + result[c] = test_data[i].input[0] * mat[c][0] + + test_data[i].input[1] * mat[c][1] + + test_data[i].input[2] * mat[c][2] + + mat[c][3]; + } + + float expected[3] = { + (16 + test_data[i].expected[0] * (235 - 16)) / 255.0f, + (16 + (test_data[i].expected[1] + 0.5f) * (240 - 16)) / 255.0f, + (16 + (test_data[i].expected[2] + 0.5f) * (240 - 16)) / 255.75f, + }; + + EXPECT_NEAR(expected[0], result[0], 1e-6); + EXPECT_NEAR(expected[1], result[1], 1e-6); + EXPECT_NEAR(expected[2], result[2], 1e-6); + } +} + +TEST(u_ycbcr_test, bt601_to_ycbcr_narrow_range) +{ + test_to_ycbcr_narrow_range_coeffs(util_ycbcr_bt601_coeffs); + test_to_ycbcr_narrow_range_coeffs(util_ycbcr_bt709_coeffs); + test_to_ycbcr_narrow_range_coeffs(util_ycbcr_bt2020_coeffs); +} + +static void +test_to_ycbcr_range_and_back_coeffs(const float coeffs[3]) +{ + const unsigned bpc[3] = {8, 8, 10}; + + float range[3][2]; + util_get_narrow_range_coeffs(range, bpc); + + float to_ycbcr[3][4]; + util_get_rgb_to_ycbcr_matrix(to_ycbcr, coeffs); + util_ycbcr_adjust_to_range(to_ycbcr, range); + + float to_rgb[3][4]; + util_get_ycbcr_to_rgb_matrix(to_rgb, coeffs); + util_ycbcr_adjust_from_range(to_rgb, range); + + float inputs[][3] = { + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.5f, 0.5f, 0.5f }, + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f }, + { 1.0f, 1.0f, 0.0f }, + { 0.0f, 1.0f, 1.0f }, + { 1.0f, 0.0f, 1.0f }, + }; + + for (size_t i = 0; i < ARRAY_SIZE(inputs); ++i) { + float ycbcr[3]; + for (int c = 0; c < 3; ++c) { + ycbcr[c] = inputs[i][0] * to_ycbcr[c][0] + + inputs[i][1] * to_ycbcr[c][1] + + inputs[i][2] * to_ycbcr[c][2]; + + } + + float result[3]; + for (int c = 0; c < 3; ++c) { + result[c] = ycbcr[0] * to_rgb[c][0] + + ycbcr[1] * to_rgb[c][1] + + ycbcr[2] * to_rgb[c][2]; + + } + + EXPECT_NEAR(inputs[i][0], result[0], 1e-6); + EXPECT_NEAR(inputs[i][1], result[1], 1e-6); + EXPECT_NEAR(inputs[i][2], result[2], 1e-6); + } +} + +TEST(u_ycbcr_test, to_ycbcr_range_and_back) +{ + test_to_ycbcr_range_and_back_coeffs(util_ycbcr_bt601_coeffs); + test_to_ycbcr_range_and_back_coeffs(util_ycbcr_bt709_coeffs); + test_to_ycbcr_range_and_back_coeffs(util_ycbcr_bt2020_coeffs); +} diff --git a/src/util/u_ycbcr.h b/src/util/u_ycbcr.h new file mode 100644 index 00000000000..448da7f12d0 --- /dev/null +++ b/src/util/u_ycbcr.h @@ -0,0 +1,158 @@ +/** + * Copyright (c) 2026 Collabora Ltd. + * + * SPDX-License-Identifier: MIT + */ + +#ifndef U_YCBCR_H +#define U_YCBCR_H + +/* BT.601 coefficients */ +static const float util_ycbcr_bt601_coeffs[3] = { + 0.299f, 0.587f, 0.114f +}; + +/* BT.701 coefficients */ +static const float util_ycbcr_bt709_coeffs[3] = { + 0.2126f, 0.7152f, 0.0722f +}; + +/* BT.2020 coefficients */ +static const float util_ycbcr_bt2020_coeffs[3] = { + 0.2627f, 0.6780f, 0.0593f +}; + +/* SMPTE 240M coefficients */ +static const float util_ycbcr_smpte240m_coeffs[3] = { + 0.2122f, 0.7013f, 0.0865f +}; + +static inline void +util_get_ycbcr_to_rgb_matrix(float m[3][4], const float coeffs[3]) +{ + /** + * Sets up a 3x4 matrix that computes: + * + * R = Y + e * Cr + * G = Y - (a * e / b) * Cr - (c * d / b) * Cb + * B = Y + d * Cb + */ + + float a = coeffs[0]; + float b = coeffs[1]; + float c = coeffs[2]; + float d = 2 - 2 * c; + float e = 2 - 2 * a; + float f = 1.0f / b; + + m[0][0] = 1; m[0][1] = 0; m[0][2] = e; m[0][3] = 0; + m[1][0] = 1; m[1][1] = -c * d * f; m[1][2] = -a * e * f; m[1][3] = 0; + m[2][0] = 1; m[2][1] = d; m[2][2] = 0; m[2][3] = 0; +} + +static inline void +util_get_rgb_to_ycbcr_matrix(float m[3][4], const float coeffs[3]) +{ + /** + * Sets up a 3x4 matrix that computes: + * + * Y = a * R + b * G + c * B + * Cb = (B - Y) / d + * Cr = (R - Y) / e + */ + + float a = coeffs[0]; + float b = coeffs[1]; + float c = coeffs[2]; + float d = 0.5f / (c - 1); + float e = 0.5f / (a - 1); + + m[0][0] = a; m[0][1] = b; m[0][2] = c; m[0][3] = 0; + m[1][0] = d * a; m[1][1] = d * b; m[1][2] = 0.5f; m[1][3] = 0; + m[2][0] = 0.5f; m[2][1] = e * b; m[2][2] = e * c; m[2][3] = 0; +} + +static inline float +util_get_full_range_chroma_bias(unsigned bpc) +{ + return -(1 << (bpc - 1)) / ((1 << bpc) - 1.0f); +} + +static inline void +util_get_full_range_coeffs(float out[3][2], const unsigned bpc[3]) +{ + out[0][0] = 1; out[0][1] = 0; + out[1][0] = 1; out[1][1] = util_get_full_range_chroma_bias(bpc[1]); + out[2][0] = 1; out[2][1] = util_get_full_range_chroma_bias(bpc[2]); +} + +static inline float +util_get_narrow_range(unsigned bpc) +{ + return 1 - 1.0f / (1 << bpc); +} + +static inline float +util_get_narrow_range_luma_factor(unsigned bpc) +{ + return util_get_narrow_range(bpc) * (256.0f / 219); +} + +static inline float +util_get_narrow_range_chroma_factor(unsigned bpc) +{ + return util_get_narrow_range(bpc) * (256.0f / 224); +} + +static inline void +util_get_narrow_range_coeffs(float out[3][2], const unsigned bpc[3]) +{ + float y_factor = util_get_narrow_range_luma_factor(bpc[0]); + float cb_factor = util_get_narrow_range_chroma_factor(bpc[1]); + float cr_factor = util_get_narrow_range_chroma_factor(bpc[2]); + + float y_bias = -16.0f / 219; + float c_bias = -128.0f / 224; + + out[0][0] = y_factor; out[0][1] = y_bias; + out[1][0] = cb_factor; out[1][1] = c_bias; + out[2][0] = cr_factor; out[2][1] = c_bias; +} + +static inline void +util_get_identity_range_coeffs(float out[3][2]) +{ + out[0][0] = out[1][0] = out[2][0] = 1.0f; + out[0][1] = out[1][1] = out[2][1] = 0.0f; +} + +static inline void +util_ycbcr_adjust_from_range(float mat[3][4], + const float range[3][2]) +{ + for (int i = 0; i < 3; ++i) { + mat[i][3] = range[0][1] * mat[i][0] + + range[1][1] * mat[i][1] + + range[2][1] * mat[i][2] + + mat[i][3]; + + mat[i][0] = mat[i][0] * range[0][0]; + mat[i][1] = mat[i][1] * range[1][0]; + mat[i][2] = mat[i][2] * range[2][0]; + } +} + +static inline void +util_ycbcr_adjust_to_range(float mat[3][4], + const float range[3][2]) +{ + for (int i = 0; i < 3; ++i) { + float tmp = 1.0f / range[i][0]; + mat[i][0] = mat[i][0] * tmp; + mat[i][1] = mat[i][1] * tmp; + mat[i][2] = mat[i][2] * tmp; + mat[i][3] -= range[i][1] * tmp; + } +} + +#endif /* U_YCBCR_H */