SDL: Use a reasonable default for unspecified YUV colorspace

From c3e4481d56a9a1e4574957368b00dee081caaa50 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 5 Feb 2024 11:20:14 -0800
Subject: [PATCH] Use a reasonable default for unspecified YUV colorspace

---
 src/render/direct3d/SDL_render_d3d.c     |   2 +-
 src/render/direct3d11/SDL_render_d3d11.c |  17 ++-
 src/render/direct3d12/SDL_render_d3d12.c |   4 +-
 src/render/metal/SDL_render_metal.m      |   8 +-
 src/render/opengl/SDL_render_gl.c        |   2 +-
 src/render/opengles2/SDL_render_gles2.c  |  10 +-
 src/video/SDL_pixels.c                   | 178 ++++++++++++++---------
 src/video/SDL_pixels_c.h                 |   2 +-
 8 files changed, 137 insertions(+), 86 deletions(-)

diff --git a/src/render/direct3d/SDL_render_d3d.c b/src/render/direct3d/SDL_render_d3d.c
index 6ff737b39c5d..d0381b9ba0a8 100644
--- a/src/render/direct3d/SDL_render_d3d.c
+++ b/src/render/direct3d/SDL_render_d3d.c
@@ -558,7 +558,7 @@ static int D3D_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_P
         }
 
         texturedata->shader = SHADER_YUV;
-        texturedata->shader_params = SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace);
+        texturedata->shader_params = SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8);
         if (texturedata->shader_params == NULL) {
             return SDL_SetError("Unsupported YUV colorspace");
         }
diff --git a/src/render/direct3d11/SDL_render_d3d11.c b/src/render/direct3d11/SDL_render_d3d11.c
index e720143e3f8c..f83ef3fe634b 100644
--- a/src/render/direct3d11/SDL_render_d3d11.c
+++ b/src/render/direct3d11/SDL_render_d3d11.c
@@ -1297,7 +1297,7 @@ static int D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL
         SDL_SetProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_D3D11_TEXTURE_V_POINTER, textureData->mainTextureV);
 
         textureData->shader = SHADER_YUV;
-        textureData->shader_params = SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace);
+        textureData->shader_params = SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8);
         if (!textureData->shader_params) {
             return SDL_SetError("Unsupported YUV colorspace");
         }
@@ -1306,6 +1306,8 @@ static int D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL
         texture->format == SDL_PIXELFORMAT_NV21 ||
         texture->format == SDL_PIXELFORMAT_P010 ||
         texture->format == SDL_PIXELFORMAT_P016) {
+        int bits_per_pixel;
+
         textureData->nv12 = SDL_TRUE;
 
         switch (texture->format) {
@@ -1328,7 +1330,18 @@ static int D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL
             /* This should never happen because of the check above */
             return SDL_SetError("Unsupported YUV colorspace");
         }
-        textureData->shader_params = SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace);
+        switch (texture->format) {
+        case SDL_PIXELFORMAT_P010:
+            bits_per_pixel = 10;
+            break;
+        case SDL_PIXELFORMAT_P016:
+            bits_per_pixel = 16;
+            break;
+        default:
+            bits_per_pixel = 8;
+            break;
+        }
+        textureData->shader_params = SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, bits_per_pixel);
         if (!textureData->shader_params) {
             return SDL_SetError("Unsupported YUV colorspace");
         }
diff --git a/src/render/direct3d12/SDL_render_d3d12.c b/src/render/direct3d12/SDL_render_d3d12.c
index afd25c9cdfae..c1ae85ba5459 100644
--- a/src/render/direct3d12/SDL_render_d3d12.c
+++ b/src/render/direct3d12/SDL_render_d3d12.c
@@ -1641,7 +1641,7 @@ static int D3D12_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL
         SDL_SetProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_D3D12_TEXTURE_V_POINTER, textureData->mainTextureV);
 
         textureData->shader = SHADER_YUV;
-        textureData->shader_params = SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace);
+        textureData->shader_params = SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8);
         if (!textureData->shader_params) {
             return SDL_SetError("Unsupported YUV colorspace");
         }
@@ -1652,7 +1652,7 @@ static int D3D12_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL
         textureData->nv12 = SDL_TRUE;
 
         textureData->shader = (texture->format == SDL_PIXELFORMAT_NV12 ? SHADER_NV12 : SHADER_NV21);
-        textureData->shader_params = SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace);
+        textureData->shader_params = SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8);
         if (!textureData->shader_params) {
             return SDL_SetError("Unsupported YUV colorspace");
         }
diff --git a/src/render/metal/SDL_render_metal.m b/src/render/metal/SDL_render_metal.m
index d3bb3f150662..4effe602db39 100644
--- a/src/render/metal/SDL_render_metal.m
+++ b/src/render/metal/SDL_render_metal.m
@@ -1822,10 +1822,10 @@ in case we want to use it later (recreating the renderer)
         constantdata = [mtlbufconstantstaging contents];
         SDL_memcpy(constantdata + CONSTANTS_OFFSET_IDENTITY, identitytransform, sizeof(identitytransform));
         SDL_memcpy(constantdata + CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, halfpixeltransform, sizeof(halfpixeltransform));
-        SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601_LIMITED, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT601_LIMITED), YCbCr_shader_matrix_size);
-        SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601_FULL, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT601_FULL), YCbCr_shader_matrix_size);
-        SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709_LIMITED, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT709_LIMITED), YCbCr_shader_matrix_size);
-        SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709_FULL, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT709_FULL), YCbCr_shader_matrix_size);
+        SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601_LIMITED, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT601_LIMITED, 0, 0, 8), YCbCr_shader_matrix_size);
+        SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601_FULL, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT601_FULL, 0, 0, 8), YCbCr_shader_matrix_size);
+        SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709_LIMITED, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT709_LIMITED, 0, 0, 8), YCbCr_shader_matrix_size);
+        SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709_FULL, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT709_FULL, 0, 0, 8), YCbCr_shader_matrix_size);
 
         mtlbufquadindicesstaging = [data.mtldevice newBufferWithLength:indicessize options:MTLResourceStorageModeShared];
 
diff --git a/src/render/opengl/SDL_render_gl.c b/src/render/opengl/SDL_render_gl.c
index bfda77082f85..72dfb25619c1 100644
--- a/src/render/opengl/SDL_render_gl.c
+++ b/src/render/opengl/SDL_render_gl.c
@@ -680,7 +680,7 @@ static int GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_Pr
                 data->shader = SHADER_NV21_RA;
             }
         }
-        data->shader_params = SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace);
+        data->shader_params = SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8);
         if (!data->shader_params) {
             return SDL_SetError("Unsupported YUV colorspace");
         }
diff --git a/src/render/opengles2/SDL_render_gles2.c b/src/render/opengles2/SDL_render_gles2.c
index 7d07d02dd367..f528b5439ab4 100644
--- a/src/render/opengles2/SDL_render_gles2.c
+++ b/src/render/opengles2/SDL_render_gles2.c
@@ -629,7 +629,7 @@ static int GLES2_SelectProgram(GLES2_RenderData *data, GLES2_ImageSource source,
 #if SDL_HAVE_YUV
     case GLES2_IMAGESOURCE_TEXTURE_YUV:
         ftype = GLES2_SHADER_FRAGMENT_TEXTURE_YUV;
-        shader_params = SDL_GetYCbCRtoRGBConversionMatrix(colorspace);
+        shader_params = SDL_GetYCbCRtoRGBConversionMatrix(colorspace, 0, 0, 8);
         if (!shader_params) {
             SDL_SetError("Unsupported YUV colorspace");
             goto fault;
@@ -641,7 +641,7 @@ static int GLES2_SelectProgram(GLES2_RenderData *data, GLES2_ImageSource source,
         } else {
             ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV12_RA;
         }
-        shader_params = SDL_GetYCbCRtoRGBConversionMatrix(colorspace);
+        shader_params = SDL_GetYCbCRtoRGBConversionMatrix(colorspace, 0, 0, 8);
         if (!shader_params) {
             SDL_SetError("Unsupported YUV colorspace");
             goto fault;
@@ -653,7 +653,7 @@ static int GLES2_SelectProgram(GLES2_RenderData *data, GLES2_ImageSource source,
         } else {
             ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV21_RA;
         }
-        shader_params = SDL_GetYCbCRtoRGBConversionMatrix(colorspace);
+        shader_params = SDL_GetYCbCRtoRGBConversionMatrix(colorspace, 0, 0, 8);
         if (!shader_params) {
             SDL_SetError("Unsupported YUV colorspace");
             goto fault;
@@ -1548,7 +1548,7 @@ static int GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL
         }
         SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_U_NUMBER, data->texture_u);
 
-        if (!SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace)) {
+        if (!SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8)) {
             return SDL_SetError("Unsupported YUV colorspace");
         }
     } else if (data->nv12) {
@@ -1573,7 +1573,7 @@ static int GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL
         }
         SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_UV_NUMBER, data->texture_u);
 
-        if (!SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace)) {
+        if (!SDL_GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8)) {
             return SDL_SetError("Unsupported YUV colorspace");
         }
     }
diff --git a/src/video/SDL_pixels.c b/src/video/SDL_pixels.c
index 5202949ab6e2..b1b94619527d 100644
--- a/src/video/SDL_pixels.c
+++ b/src/video/SDL_pixels.c
@@ -764,88 +764,126 @@ float SDL_PQfromNits(float v)
     return SDL_powf(num / den, m2);
 }
 
-const float *SDL_GetYCbCRtoRGBConversionMatrix(SDL_Colorspace colorspace)
-{
-    /* This is a helpful tool for deriving these:
-     * https://kdashg.github.io/misc/colors/from-coeffs.html
-     */
-    static const float mat_BT601_Limited_8bit[] = {
-        -0.0627451017f, -0.501960814f, -0.501960814f, 0.0f, /* offset */
-        1.1644f,  0.0000f,  1.5960f, 0.0f,                  /* Rcoeff */
-        1.1644f, -0.3918f, -0.8130f, 0.0f,                  /* Gcoeff */
-        1.1644f,  2.0172f,  0.0000f, 0.0f,                  /* Bcoeff */
-    };
+/* This is a helpful tool for deriving these:
+ * https://kdashg.github.io/misc/colors/from-coeffs.html
+ */
+static const float mat_BT601_Limited_8bit[] = {
+    -0.0627451017f, -0.501960814f, -0.501960814f, 0.0f, /* offset */
+    1.1644f, 0.0000f, 1.5960f, 0.0f,                    /* Rcoeff */
+    1.1644f, -0.3918f, -0.8130f, 0.0f,                  /* Gcoeff */
+    1.1644f, 2.0172f, 0.0000f, 0.0f,                    /* Bcoeff */
+};
 
-    static const float mat_BT601_Full_8bit[] = {
-        0.0f, -0.501960814f, -0.501960814f, 0.0f,           /* offset */
-        1.0000f,  0.0000f, 1.4020f, 0.0f,                   /* Rcoeff */
-        1.0000f, -0.3441f, -0.7141f, 0.0f,                  /* Gcoeff */
-        1.0000f,  1.7720f, 0.0000f, 0.0f,                   /* Bcoeff */
-    };
+static const float mat_BT601_Full_8bit[] = {
+    0.0f, -0.501960814f, -0.501960814f, 0.0f,           /* offset */
+    1.0000f, 0.0000f, 1.4020f, 0.0f,                    /* Rcoeff */
+    1.0000f, -0.3441f, -0.7141f, 0.0f,                  /* Gcoeff */
+    1.0000f, 1.7720f, 0.0000f, 0.0f,                    /* Bcoeff */
+};
 
-    static const float mat_BT709_Limited_8bit[] = {
-        -0.0627451017f, -0.501960814f, -0.501960814f, 0.0f, /* offset */
-        1.1644f,  0.0000f,  1.7927f, 0.0f,                  /* Rcoeff */
-        1.1644f, -0.2132f, -0.5329f, 0.0f,                  /* Gcoeff */
-        1.1644f,  2.1124f,  0.0000f, 0.0f,                  /* Bcoeff */
-    };
+static const float mat_BT709_Limited_8bit[] = {
+    -0.0627451017f, -0.501960814f, -0.501960814f, 0.0f, /* offset */
+    1.1644f, 0.0000f, 1.7927f, 0.0f,                    /* Rcoeff */
+    1.1644f, -0.2132f, -0.5329f, 0.0f,                  /* Gcoeff */
+    1.1644f, 2.1124f, 0.0000f, 0.0f,                    /* Bcoeff */
+};
 
-    static const float mat_BT709_Full_8bit[] = {
-        0.0f, -0.501960814f, -0.501960814f, 0.0f,           /* offset */
-        1.0000f,  0.0000f,  1.5748f, 0.0f,                  /* Rcoeff */
-        1.0000f, -0.1873f, -0.4681f, 0.0f,                  /* Gcoeff */
-        1.0000f,  1.8556f,  0.0000f, 0.0f,                  /* Bcoeff */
-    };
+static const float mat_BT709_Full_8bit[] = {
+    0.0f, -0.501960814f, -0.501960814f, 0.0f,           /* offset */
+    1.0000f, 0.0000f, 1.5748f, 0.0f,                    /* Rcoeff */
+    1.0000f, -0.1873f, -0.4681f, 0.0f,                  /* Gcoeff */
+    1.0000f, 1.8556f, 0.0000f, 0.0f,                    /* Bcoeff */
+};
 
-    static const float mat_BT2020_Limited_10bit[] = {
-        -0.062561095f, -0.500488759f, -0.500488759f, 0.0f,  /* offset */
-        1.1678f,  0.0000f,  1.6836f, 0.0f,                  /* Rcoeff */
-        1.1678f, -0.1879f, -0.6523f, 0.0f,                  /* Gcoeff */
-        1.1678f,  2.1481f,  0.0000f, 0.0f,                  /* Bcoeff */
-    };
+static const float mat_BT2020_Limited_10bit[] = {
+    -0.062561095f, -0.500488759f, -0.500488759f, 0.0f,  /* offset */
+    1.1678f, 0.0000f, 1.6836f, 0.0f,                    /* Rcoeff */
+    1.1678f, -0.1879f, -0.6523f, 0.0f,                  /* Gcoeff */
+    1.1678f, 2.1481f, 0.0000f, 0.0f,                    /* Bcoeff */
+};
 
-    static const float mat_BT2020_Full_10bit[] = {
-        0.0f, -0.500488759f, -0.500488759f, 0.0f,           /* offset */
-        1.0000f,  0.0000f,  1.4760f, 0.0f,                  /* Rcoeff */
-        1.0000f, -0.1647f, -0.5719f, 0.0f,                  /* Gcoeff */
-        1.0000f,  1.8832f,  0.0000f, 0.0f,                  /* Bcoeff */
-    };
+static const float mat_BT2020_Full_10bit[] = {
+    0.0f, -0.500488759f, -0.500488759f, 0.0f,           /* offset */
+    1.0000f, 0.0000f, 1.4760f, 0.0f,                    /* Rcoeff */
+    1.0000f, -0.1647f, -0.5719f, 0.0f,                  /* Gcoeff */
+    1.0000f, 1.8832f, 0.0000f, 0.0f,                    /* Bcoeff */
+};
+
+static const float *SDL_GetBT601ConversionMatrix( SDL_Colorspace colorspace )
+{
+    switch (SDL_COLORSPACERANGE(colorspace)) {
+    case SDL_COLOR_RANGE_LIMITED:
+    case SDL_COLOR_RANGE_UNKNOWN:
+        return mat_BT601_Limited_8bit;
+        break;
+    case SDL_COLOR_RANGE_FULL:
+        return mat_BT601_Full_8bit;
+        break;
+    default:
+        break;
+    }
+    return NULL;
+}
+
+static const float *SDL_GetBT709ConversionMatrix(SDL_Colorspace colorspace)
+{
+    switch (SDL_COLORSPACERANGE(colorspace)) {
+    case SDL_COLOR_RANGE_LIMITED:
+    case SDL_COLOR_RANGE_UNKNOWN:
+        return mat_BT709_Limited_8bit;
+        break;
+    case SDL_COLOR_RANGE_FULL:
+        return mat_BT709_Full_8bit;
+        break;
+    default:
+        break;
+    }
+    return NULL;
+}
+
+static const float *SDL_GetBT2020ConversionMatrix(SDL_Colorspace colorspace)
+{
+    switch (SDL_COLORSPACERANGE(colorspace)) {
+    case SDL_COLOR_RANGE_LIMITED:
+    case SDL_COLOR_RANGE_UNKNOWN:
+        return mat_BT2020_Limited_10bit;
+        break;
+    case SDL_COLOR_RANGE_FULL:
+        return mat_BT2020_Full_10bit;
+        break;
+    default:
+        break;
+    }
+    return NULL;
+}
+
+const float *SDL_GetYCbCRtoRGBConversionMatrix(SDL_Colorspace colorspace, int w, int h, int bits_per_pixel)
+{
+    const int YUV_SD_THRESHOLD = 576;
 
     switch (SDL_COLORSPACEMATRIX(colorspace)) {
     case SDL_MATRIX_COEFFICIENTS_BT601:
-        switch (SDL_COLORSPACERANGE(colorspace)) {
-        case SDL_COLOR_RANGE_LIMITED:
-            return mat_BT601_Limited_8bit;
-            break;
-        case SDL_COLOR_RANGE_FULL:
-            return mat_BT601_Full_8bit;
-            break;
-        default:
-            break;
-        }
-        break;
+        return SDL_GetBT601ConversionMatrix(colorspace);
+
     case SDL_MATRIX_COEFFICIENTS_BT709:
-        switch (SDL_COLORSPACERANGE(colorspace)) {
-        case SDL_COLOR_RANGE_LIMITED:
-            return mat_BT709_Limited_8bit;
-            break;
-        case SDL_COLOR_RANGE_FULL:
-            return mat_BT709_Full_8bit;
-            break;
-        default:
-            break;
-        }
-        break;
+        return SDL_GetBT709ConversionMatrix(colorspace);
+
     /* FIXME: Are these the same? */
     case SDL_MATRIX_COEFFICIENTS_BT2020_NCL:
     case SDL_MATRIX_COEFFICIENTS_BT2020_CL:
-        switch (SDL_COLORSPACERANGE(colorspace)) {
-        case SDL_COLOR_RANGE_LIMITED:
-            return mat_BT2020_Limited_10bit;
-            break;
-        case SDL_COLOR_RANGE_FULL:
-            return mat_BT2020_Full_10bit;
-            break;
+        return SDL_GetBT2020ConversionMatrix(colorspace);
+
+    case SDL_MATRIX_COEFFICIENTS_UNSPECIFIED:
+        switch (bits_per_pixel) {
+        case 8:
+            if (h <= YUV_SD_THRESHOLD) {
+                return SDL_GetBT601ConversionMatrix(colorspace);
+            } else {
+                return SDL_GetBT709ConversionMatrix(colorspace);
+            }
+        case 10:
+        case 16:
+            return SDL_GetBT2020ConversionMatrix(colorspace);
         default:
             break;
         }
diff --git a/src/video/SDL_pixels_c.h b/src/video/SDL_pixels_c.h
index 8406168aa494..3f98bbaffaab 100644
--- a/src/video/SDL_pixels_c.h
+++ b/src/video/SDL_pixels_c.h
@@ -40,7 +40,7 @@ extern float SDL_sRGBtoNits(float v);
 extern float SDL_sRGBfromNits(float v);
 extern float SDL_PQtoNits(float v);
 extern float SDL_PQfromNits(float v);
-extern const float *SDL_GetYCbCRtoRGBConversionMatrix(SDL_Colorspace colorspace);
+extern const float *SDL_GetYCbCRtoRGBConversionMatrix(SDL_Colorspace colorspace, int w, int h, int bits_per_pixel);
 extern const float *SDL_GetColorPrimariesConversionMatrix(SDL_ColorPrimaries src, SDL_ColorPrimaries dst);
 extern void SDL_ConvertColorPrimaries(float *fR, float *fG, float *fB, const float *matrix);