From dd9f04a57a8637f4cd6e3b4e04da3db63c6eeebb Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Mon, 16 Feb 2026 13:56:49 +0100 Subject: [PATCH 01/11] util: add common ycbcr coefficient math code This adds some reusable math to set up YCbCr to RGB color transforms. It covers ITU BT.601, ITU BT.709 and ITU BT.2020 YUV <-> RGB conversion, as well as "narrow"" and "full" range. This code is intended to replace three different implementations of YUV-transforms already present in Mesa, all of them with different parameterizations and differences in data-formats. These implementations are: nir_lower_tex.c, vk_nir_convert_ycbcr.c and vl_csc.c. None of the exising implementations seems to fully cover all of the needs of the others. The one that comes the closest is the one in vl_csc.c, but it has a few issues: 1. It doesn't differentiate between per-channel bit-sizes, which the Vulkan code needs. 2. It uses enums from p_video_enums.h in Gallium to paremeterize the behavior. 3. It's written in a monolithic way, handling up to two range-remappings, which the other implementations doesn't need. While it could be possible to entangle all of that, that would likely end up being a more or less a new implementation anyway. So let's instead try to pick the best of all three implementations into one new one, that's broken into smaller pieces that can be assembled into either of the three. In addition, this implementation has a bunch of unit-tests, to make sure we don't introduce subtle breakages down the line. --- src/util/meson.build | 1 + src/util/tests/u_ycbcr_test.cpp | 520 ++++++++++++++++++++++++++++++++ src/util/u_ycbcr.h | 158 ++++++++++ 3 files changed, 679 insertions(+) create mode 100644 src/util/tests/u_ycbcr_test.cpp create mode 100644 src/util/u_ycbcr.h 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 */ From 9ce37f4bf71d9e7f2d70d83cbfff46f46bc425e6 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Mon, 16 Jun 2025 14:17:53 +0200 Subject: [PATCH 02/11] main,glsl: add initial GL_EXT_YUV_target bits These are just the enable-bits. They're comming first, because several of the following patches depends on them, even if they're not set until the end of the MR. --- src/compiler/glsl/glsl_parser_extras.cpp | 1 + src/compiler/glsl/glsl_parser_extras.h | 2 ++ src/mesa/main/consts_exts.h | 1 + src/mesa/main/extensions_table.h | 1 + 4 files changed, 5 insertions(+) diff --git a/src/compiler/glsl/glsl_parser_extras.cpp b/src/compiler/glsl/glsl_parser_extras.cpp index 3bcf7872764..ed412c0ac68 100644 --- a/src/compiler/glsl/glsl_parser_extras.cpp +++ b/src/compiler/glsl/glsl_parser_extras.cpp @@ -851,6 +851,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), diff --git a/src/compiler/glsl/glsl_parser_extras.h b/src/compiler/glsl/glsl_parser_extras.h index 8a1ccc993b7..3087e154e65 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/mesa/main/consts_exts.h b/src/mesa/main/consts_exts.h index 65f20369485..e548888eeef 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_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) From ed112a37dc78c34f1dbcf748805d20bbc99d3273 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Tue, 10 Feb 2026 15:59:13 +0100 Subject: [PATCH 03/11] glsl: add samplerExternal2DY2YEXT --- src/amd/common/nir/ac_nir_lower_image_tex.c | 1 + src/compiler/builtin_types.py | 2 ++ src/compiler/glsl/ast_to_hir.cpp | 7 +++++++ src/compiler/glsl/builtin_functions.cpp | 16 ++++++++++++++++ src/compiler/glsl/glsl_lexer.ll | 8 ++++++++ src/compiler/glsl/glsl_parser.yy | 1 + src/compiler/glsl_types.c | 16 ++++++++++++++++ src/compiler/shader_enums.h | 1 + 8 files changed, 52 insertions(+) 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..cf23e5af832 100644 --- a/src/compiler/builtin_types.py +++ b/src/compiler/builtin_types.py @@ -148,6 +148,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_to_hir.cpp b/src/compiler/glsl/ast_to_hir.cpp index b62034af5c0..4c27acbc58e 100644 --- a/src/compiler/glsl/ast_to_hir.cpp +++ b/src/compiler/glsl/ast_to_hir.cpp @@ -2633,6 +2633,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 */ diff --git a/src/compiler/glsl/builtin_functions.cpp b/src/compiler/glsl/builtin_functions.cpp index 13a9481fe93..d97a96345b8 100644 --- a/src/compiler/glsl/builtin_functions.cpp +++ b/src/compiler/glsl/builtin_functions.cpp @@ -230,6 +230,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) { @@ -2856,6 +2864,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 +2974,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 +3221,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 +3292,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", diff --git a/src/compiler/glsl/glsl_lexer.ll b/src/compiler/glsl/glsl_lexer.ll index 4bc34f98a7b..9c6f2696dce 100644 --- a/src/compiler/glsl/glsl_lexer.ll +++ b/src/compiler/glsl/glsl_lexer.ll @@ -502,6 +502,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..9d7e2b01806 100644 --- a/src/compiler/glsl/glsl_parser.yy +++ b/src/compiler/glsl/glsl_parser.yy @@ -311,6 +311,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); } 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/shader_enums.h b/src/compiler/shader_enums.h index 66a77a72466..acb10b4d078 100644 --- a/src/compiler/shader_enums.h +++ b/src/compiler/shader_enums.h @@ -1641,6 +1641,7 @@ 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 */ From 72ed9bc54c8ae4022a6dc968d76b7124cdba558a Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Tue, 10 Feb 2026 16:12:04 +0100 Subject: [PATCH 04/11] glsl: add yuvCscStandardEXT type --- src/compiler/builtin_types.py | 2 ++ src/compiler/glsl/ast.h | 3 +++ src/compiler/glsl/ast_function.cpp | 3 ++- src/compiler/glsl/ast_to_hir.cpp | 23 +++++++++++++++++++++-- src/compiler/glsl/glsl_lexer.ll | 2 ++ src/compiler/glsl/glsl_parser.yy | 9 +++++++++ src/compiler/glsl/glsl_parser_extras.cpp | 11 +++++++++++ src/compiler/shader_enums.h | 6 ++++++ 8 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/compiler/builtin_types.py b/src/compiler/builtin_types.py index cf23e5af832..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") diff --git a/src/compiler/glsl/ast.h b/src/compiler/glsl/ast.h index cded74361f3..72e30fc4d63 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; 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 4c27acbc58e..8eeff9f4fb1 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: diff --git a/src/compiler/glsl/glsl_lexer.ll b/src/compiler/glsl/glsl_lexer.ll index 9c6f2696dce..63be9ed5962 100644 --- a/src/compiler/glsl/glsl_lexer.ll +++ b/src/compiler/glsl/glsl_lexer.ll @@ -438,6 +438,8 @@ 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); + in return IN_TOK; out return OUT_TOK; inout return INOUT_TOK; diff --git a/src/compiler/glsl/glsl_parser.yy b/src/compiler/glsl/glsl_parser.yy index 9d7e2b01806..6408c6c4169 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 @@ -494,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; diff --git a/src/compiler/glsl/glsl_parser_extras.cpp b/src/compiler/glsl/glsl_parser_extras.cpp index ed412c0ac68..4a32e522e6a 100644 --- a/src/compiler/glsl/glsl_parser_extras.cpp +++ b/src/compiler/glsl/glsl_parser_extras.cpp @@ -1536,6 +1536,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/shader_enums.h b/src/compiler/shader_enums.h index acb10b4d078..d4aebe6ab84 100644 --- a/src/compiler/shader_enums.h +++ b/src/compiler/shader_enums.h @@ -1647,6 +1647,12 @@ enum glsl_sampler_dim { 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 From b16ee4d25c6f0e6b76c4c964189ee6e6aa76f78f Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Tue, 10 Feb 2026 16:13:07 +0100 Subject: [PATCH 05/11] glsl: add color-space keywords --- src/compiler/glsl/glsl_lexer.ll | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/compiler/glsl/glsl_lexer.ll b/src/compiler/glsl/glsl_lexer.ll index 63be9ed5962..03090680c0b 100644 --- a/src/compiler/glsl/glsl_lexer.ll +++ b/src/compiler/glsl/glsl_lexer.ll @@ -440,6 +440,30 @@ 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; From cafaf30946426412155567e67c8e1abbef5b5934 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Tue, 10 Feb 2026 15:12:12 +0100 Subject: [PATCH 06/11] glsl: add layout for EXT_YUV_target --- src/compiler/glsl/ast.h | 3 +++ src/compiler/glsl/ast_to_hir.cpp | 3 +++ src/compiler/glsl/glsl_parser.yy | 18 ++++++++++++++++++ src/compiler/glsl/glsl_parser_extras.cpp | 2 ++ src/compiler/glsl/glsl_to_nir.cpp | 1 + src/compiler/glsl/ir.h | 5 +++++ src/compiler/nir/nir.h | 5 +++++ 7 files changed, 37 insertions(+) diff --git a/src/compiler/glsl/ast.h b/src/compiler/glsl/ast.h index 72e30fc4d63..d5f4364f8a9 100644 --- a/src/compiler/glsl/ast.h +++ b/src/compiler/glsl/ast.h @@ -690,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_to_hir.cpp b/src/compiler/glsl/ast_to_hir.cpp index 8eeff9f4fb1..c000b363b39 100644 --- a/src/compiler/glsl/ast_to_hir.cpp +++ b/src/compiler/glsl/ast_to_hir.cpp @@ -4247,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, diff --git a/src/compiler/glsl/glsl_parser.yy b/src/compiler/glsl/glsl_parser.yy index 6408c6c4169..092228d3194 100644 --- a/src/compiler/glsl/glsl_parser.yy +++ b/src/compiler/glsl/glsl_parser.yy @@ -1753,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 4a32e522e6a..3c9d1480a50 100644 --- a/src/compiler/glsl/glsl_parser_extras.cpp +++ b/src/compiler/glsl/glsl_parser_extras.cpp @@ -1354,6 +1354,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 "); } 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/nir/nir.h b/src/compiler/nir/nir.h index ff6a139227e..7e8236919c4 100644 --- a/src/compiler/nir/nir.h +++ b/src/compiler/nir/nir.h @@ -695,6 +695,11 @@ typedef struct nir_variable { */ unsigned depth_layout : 3; + /** + * Whether the variable is a YUV color-output. + */ + unsigned yuv : 1; + /** * Vertex stream output identifier. * From ce3d714a4240ee7f66b9005f3aa2de2117769d75 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Wed, 11 Feb 2026 16:31:54 +0100 Subject: [PATCH 07/11] glsl: emit error when combining layouts --- src/compiler/glsl/ast_type.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/compiler/glsl/ast_type.cpp b/src/compiler/glsl/ast_type.cpp index 51fa331bf00..f2184335bc3 100644 --- a/src/compiler/glsl/ast_type.cpp +++ b/src/compiler/glsl/ast_type.cpp @@ -546,6 +546,21 @@ ast_type_qualifier::merge_qualifier(YYLTYPE *loc, } } + // strip the "out" flag + ast_type_qualifier tmpa = *this; + ast_type_qualifier tmpb = q; + tmpa.flags.q.out = 0; + tmpb.flags.q.out = 0; + if ((tmpa.flags.i && tmpb.flags.q.yuv) || + (tmpa.flags.q.yuv && tmpb.flags.i)) { + // The EXT_YUV_target spec says: + // The new yuv layout qualifier can't be combined with any other + // layout qualifier, + _mesa_glsl_error(loc, state, "yuv layout can't be combined with any " + "other layout qualifier"); + return false; + } + return r; } From f6b6bf8b67063c3e11215bf694249d99f08699fc Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Wed, 11 Feb 2026 16:36:57 +0100 Subject: [PATCH 08/11] glsl: losen EXT_YUV_target restriction 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. --- src/compiler/glsl/ast_type.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/compiler/glsl/ast_type.cpp b/src/compiler/glsl/ast_type.cpp index f2184335bc3..6f0ddaa5752 100644 --- a/src/compiler/glsl/ast_type.cpp +++ b/src/compiler/glsl/ast_type.cpp @@ -546,16 +546,20 @@ ast_type_qualifier::merge_qualifier(YYLTYPE *loc, } } - // strip the "out" flag - ast_type_qualifier tmpa = *this; - ast_type_qualifier tmpb = q; - tmpa.flags.q.out = 0; - tmpb.flags.q.out = 0; - if ((tmpa.flags.i && tmpb.flags.q.yuv) || - (tmpa.flags.q.yuv && tmpb.flags.i)) { - // The EXT_YUV_target spec says: - // The new yuv layout qualifier can't be combined with any other - // layout qualifier, + 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; From 1ac35364d48e10162c9b212c5afd71ff627772af Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Thu, 12 Feb 2026 12:20:25 +0100 Subject: [PATCH 09/11] glsl: emit yuv-layout related errors GL_EXT_YUV_target does not allow writing fragment-depth or multiple render-targets while using the yuv-layout. So we need to derect this and error out appropriately. However, since the extension requires GLSL ES 3.00, we don't need to consider interactions with gl_FragColor and gl_FragData. --- src/compiler/glsl/ast_to_hir.cpp | 36 +++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/compiler/glsl/ast_to_hir.cpp b/src/compiler/glsl/ast_to_hir.cpp index c000b363b39..eccde58358b 100644 --- a/src/compiler/glsl/ast_to_hir.cpp +++ b/src/compiler/glsl/ast_to_hir.cpp @@ -9483,10 +9483,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; @@ -9495,7 +9498,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) { @@ -9513,11 +9522,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++; } } } @@ -9567,6 +9579,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 From 34ace0776952e780af4ea3638e480344bc140d1f Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Tue, 10 Feb 2026 16:02:14 +0100 Subject: [PATCH 10/11] glsl: add rgb_2_yuv and yuv_2_rgb This implements the rgb_2_yuv and yuv_2_rgb functions from GL_EXT_YUV_target. They are fully implemented in the compiler-front-end, which has benefits, but also disadvantages. The primary benefit is that the implementation touches few parts of the compiler; this will just do the right thing regardless of driver support. The major disadvantage is that GPUs with hardware support for RGB <-> YUV conversion (like Mali GPUs) doesn't get to use that support, at least not witout bending over backwards, and trying to recognize the different matrix coefficients. That would be a very fragile optimization. --- src/compiler/glsl/builtin_functions.cpp | 164 ++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/src/compiler/glsl/builtin_functions.cpp b/src/compiler/glsl/builtin_functions.cpp index d97a96345b8..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) @@ -1234,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. @@ -1607,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 @@ -6060,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 @@ -9639,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; +} + /** @} */ /******************************************************************************/ From a0df9e7788b680067b24d078e9781a3e27bf6745 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Tue, 10 Feb 2026 16:51:29 +0100 Subject: [PATCH 11/11] HACK: main: force-enable GL_EXT_YUV_target This is just temporary to make it possible to tese the general mechanisms here. This should obviously not be merged, and should be replaced by a commit to enable this on some actual driver. --- src/mesa/main/extensions.c | 1 + 1 file changed, 1 insertion(+) 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;