SDL: Use YUV colorspaces instead of a global YUV conversion mode

From 50a805cdd1bffc43c0eaebbcd8eef818b943f9e9 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 3 Feb 2024 07:05:32 -0800
Subject: [PATCH] Use YUV colorspaces instead of a global YUV conversion mode

Fixes https://github.com/libsdl-org/SDL/issues/8669
---
 docs/README-migration.md                 |  3 +
 include/SDL3/SDL_pixels.h                |  9 +++
 include/SDL3/SDL_render.h                |  2 +-
 include/SDL3/SDL_surface.h               | 41 ----------
 src/dynapi/SDL_dynapi.sym                |  3 -
 src/dynapi/SDL_dynapi_overrides.h        |  3 -
 src/dynapi/SDL_dynapi_procs.h            |  3 -
 src/render/direct3d/SDL_render_d3d.c     | 24 +++---
 src/render/direct3d11/SDL_render_d3d11.c | 48 ++++++------
 src/render/direct3d12/SDL_render_d3d12.c | 48 ++++++------
 src/render/metal/SDL_render_metal.m      | 36 ++++-----
 src/render/opengl/SDL_render_gl.c        | 63 +++++++--------
 src/render/opengles2/SDL_render_gles2.c  | 93 ++++++++++++----------
 src/render/vitagxm/SDL_render_vita_gxm.c | 25 +++---
 src/video/SDL_pixels.c                   |  2 +-
 src/video/SDL_surface.c                  | 21 ++---
 src/video/SDL_yuv.c                      | 98 ++++++++++--------------
 src/video/SDL_yuv_c.h                    |  6 +-
 test/testffmpeg.c                        | 56 +++++++++-----
 test/testyuv.c                           | 53 ++++++++-----
 test/testyuv_cvt.c                       | 39 ++++++++--
 test/testyuv_cvt.h                       | 12 ++-
 22 files changed, 361 insertions(+), 327 deletions(-)

diff --git a/docs/README-migration.md b/docs/README-migration.md
index 5f135c38d2d0..4d3768bfa678 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -1382,6 +1382,9 @@ The following functions have been renamed:
 * SDL_UpperBlitScaled() => SDL_BlitSurfaceScaled()
 
 The following functions have been removed:
+* SDL_GetYUVConversionMode()
+* SDL_GetYUVConversionModeForResolution()
+* SDL_SetYUVConversionMode() - use SDL_SetSurfaceColorspace() to set the surface colorspace and SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER with SDL_CreateTextureWithProperties() to set the texture colorspace. The default colorspace for YUV pixel formats is SDL_COLORSPACE_BT601_LIMITED.
 * SDL_SoftStretchLinear() - use SDL_SoftStretch() with SDL_SCALEMODE_LINEAR
 
 ## SDL_system.h
diff --git a/include/SDL3/SDL_pixels.h b/include/SDL3/SDL_pixels.h
index f53cd7f644e7..112c6534f503 100644
--- a/include/SDL3/SDL_pixels.h
+++ b/include/SDL3/SDL_pixels.h
@@ -555,6 +555,11 @@ typedef enum
 #define SDL_COLORSPACETRANSFER(X)   (SDL_TransferCharacteristics)(((X) >> 5) & 0x1F)
 #define SDL_COLORSPACEMATRIX(X)     (SDL_MatrixCoefficients)((X) & 0x1F)
 
+#define SDL_ISCOLORSPACE_YUV_BT601(X)       (SDL_COLORSPACEMATRIX(X) == SDL_MATRIX_COEFFICIENTS_BT601 || SDL_COLORSPACEMATRIX(X) == SDL_MATRIX_COEFFICIENTS_BT470BG)
+#define SDL_ISCOLORSPACE_YUV_BT709(X)       (SDL_COLORSPACEMATRIX(X) == SDL_MATRIX_COEFFICIENTS_BT709)
+#define SDL_ISCOLORSPACE_LIMITED_RANGE(X)   (SDL_COLORSPACERANGE(X) == SDL_COLOR_RANGE_LIMITED)
+#define SDL_ISCOLORSPACE_FULL_RANGE(X)      (SDL_COLORSPACERANGE(X) == SDL_COLOR_RANGE_LIMITED)
+
 typedef enum
 {
     SDL_COLORSPACE_UNKNOWN,
@@ -620,6 +625,10 @@ typedef enum
 
     /* The default colorspace for RGB surfaces if no colorspace is specified */
     SDL_COLORSPACE_RGB_DEFAULT = SDL_COLORSPACE_SRGB,
+
+    /* The default colorspace for YUV surfaces if no colorspace is specified */
+    SDL_COLORSPACE_YUV_DEFAULT = SDL_COLORSPACE_BT601_LIMITED,
+
 } SDL_Colorspace;
 
 /**
diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h
index b1aba7adcefb..1764e02a623a 100644
--- a/include/SDL3/SDL_render.h
+++ b/include/SDL3/SDL_render.h
@@ -458,7 +458,7 @@ extern DECLSPEC SDL_Texture *SDLCALL SDL_CreateTextureFromSurface(SDL_Renderer *
  * - `SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER`: an SDL_ColorSpace value
  *   describing the texture colorspace, defaults to SDL_COLORSPACE_SCRGB for
  *   floating point textures, SDL_COLORSPACE_HDR10 for 10-bit textures,
- *   SDL_COLORSPACE_SRGB for other RGB textures and SDL_COLORSPACE_BT709_FULL
+ *   SDL_COLORSPACE_SRGB for other RGB textures and SDL_COLORSPACE_BT601_LIMITED
  *   for YUV textures.
  * - `SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER`: one of the enumerated values in
  *   SDL_PixelFormatEnum, defaults to the best RGBA format for the renderer
diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h
index f4c647395d60..8e064109a9b1 100644
--- a/include/SDL3/SDL_surface.h
+++ b/include/SDL3/SDL_surface.h
@@ -138,17 +138,6 @@ typedef int (SDLCALL *SDL_blit) (struct SDL_Surface *src, const SDL_Rect *srcrec
                                  struct SDL_Surface *dst, const SDL_Rect *dstrect);
 
 
-/**
- * The formula used for converting between YUV and RGB
- */
-typedef enum
-{
-    SDL_YUV_CONVERSION_JPEG,        /**< Full range JPEG */
-    SDL_YUV_CONVERSION_BT601,       /**< BT.601 (the default) */
-    SDL_YUV_CONVERSION_BT709,       /**< BT.709 */
-    SDL_YUV_CONVERSION_AUTOMATIC    /**< BT.601 for SD content, BT.709 for HD content */
-} SDL_YUV_CONVERSION_MODE;
-
 /**
  * Allocate a new RGB surface with a specific pixel format.
  *
@@ -1031,36 +1020,6 @@ extern DECLSPEC int SDLCALL SDL_BlitSurfaceUncheckedScaled(SDL_Surface *src, con
  */
 extern DECLSPEC int SDLCALL SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a);
 
-/**
- * Set the YUV conversion mode
- *
- * \param mode YUV conversion mode
- *
- * \since This function is available since SDL 3.0.0.
- */
-extern DECLSPEC void SDLCALL SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_MODE mode);
-
-/**
- * Get the YUV conversion mode
- *
- * \returns YUV conversion mode
- *
- * \since This function is available since SDL 3.0.0.
- */
-extern DECLSPEC SDL_YUV_CONVERSION_MODE SDLCALL SDL_GetYUVConversionMode(void);
-
-/**
- * Get the YUV conversion mode, returning the correct mode for the resolution
- * when the current conversion mode is SDL_YUV_CONVERSION_AUTOMATIC
- *
- * \param width width
- * \param height height
- * \returns YUV conversion mode
- *
- * \since This function is available since SDL 3.0.0.
- */
-extern DECLSPEC SDL_YUV_CONVERSION_MODE SDLCALL SDL_GetYUVConversionModeForResolution(int width, int height);
-
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 631fe95d66e6..07f36e853532 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -347,8 +347,6 @@ SDL3_0.0.0 {
     SDL_GetWindowSizeInPixels;
     SDL_GetWindowSurface;
     SDL_GetWindowTitle;
-    SDL_GetYUVConversionMode;
-    SDL_GetYUVConversionModeForResolution;
     SDL_CloseHaptic;
     SDL_DestroyHapticEffect;
     SDL_HapticEffectSupported;
@@ -594,7 +592,6 @@ SDL3_0.0.0 {
     SDL_SetWindowSize;
     SDL_SetWindowTitle;
     SDL_SetWindowsMessageHook;
-    SDL_SetYUVConversionMode;
     SDL_ShowCursor;
     SDL_ShowMessageBox;
     SDL_ShowSimpleMessageBox;
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 4f479e8c15c7..208eb12382a3 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -371,8 +371,6 @@
 #define SDL_GetWindowSizeInPixels SDL_GetWindowSizeInPixels_REAL
 #define SDL_GetWindowSurface SDL_GetWindowSurface_REAL
 #define SDL_GetWindowTitle SDL_GetWindowTitle_REAL
-#define SDL_GetYUVConversionMode SDL_GetYUVConversionMode_REAL
-#define SDL_GetYUVConversionModeForResolution SDL_GetYUVConversionModeForResolution_REAL
 #define SDL_CloseHaptic SDL_CloseHaptic_REAL
 #define SDL_DestroyHapticEffect SDL_DestroyHapticEffect_REAL
 #define SDL_HapticEffectSupported SDL_HapticEffectSupported_REAL
@@ -617,7 +615,6 @@
 #define SDL_SetWindowSize SDL_SetWindowSize_REAL
 #define SDL_SetWindowTitle SDL_SetWindowTitle_REAL
 #define SDL_SetWindowsMessageHook   SDL_SetWindowsMessageHook_REAL
-#define SDL_SetYUVConversionMode SDL_SetYUVConversionMode_REAL
 #define SDL_ShowCursor SDL_ShowCursor_REAL
 #define SDL_ShowMessageBox SDL_ShowMessageBox_REAL
 #define SDL_ShowSimpleMessageBox SDL_ShowSimpleMessageBox_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 2fac10f7fa52..62b4e6c4fe82 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -430,8 +430,6 @@ SDL_DYNAPI_PROC(int,SDL_GetWindowSize,(SDL_Window *a, int *b, int *c),(a,b,c),re
 SDL_DYNAPI_PROC(int,SDL_GetWindowSizeInPixels,(SDL_Window *a, int *b, int *c),(a,b,c),return)
 SDL_DYNAPI_PROC(SDL_Surface*,SDL_GetWindowSurface,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetWindowTitle,(SDL_Window *a),(a),return)
-SDL_DYNAPI_PROC(SDL_YUV_CONVERSION_MODE,SDL_GetYUVConversionMode,(void),(),return)
-SDL_DYNAPI_PROC(SDL_YUV_CONVERSION_MODE,SDL_GetYUVConversionModeForResolution,(int a, int b),(a,b),return)
 SDL_DYNAPI_PROC(void,SDL_CloseHaptic,(SDL_Haptic *a),(a),)
 SDL_DYNAPI_PROC(void,SDL_DestroyHapticEffect,(SDL_Haptic *a, int b),(a,b),)
 SDL_DYNAPI_PROC(SDL_bool,SDL_HapticEffectSupported,(SDL_Haptic *a, const SDL_HapticEffect *b),(a,b),return)
@@ -659,7 +657,6 @@ SDL_DYNAPI_PROC(int,SDL_SetWindowPosition,(SDL_Window *a, int b, int c),(a,b,c),
 SDL_DYNAPI_PROC(int,SDL_SetWindowResizable,(SDL_Window *a, SDL_bool b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetWindowSize,(SDL_Window *a, int b, int c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_SetWindowTitle,(SDL_Window *a, const char *b),(a,b),return)
-SDL_DYNAPI_PROC(void,SDL_SetYUVConversionMode,(SDL_YUV_CONVERSION_MODE a),(a),)
 SDL_DYNAPI_PROC(int,SDL_ShowCursor,(void),(),return)
 SDL_DYNAPI_PROC(int,SDL_ShowMessageBox,(const SDL_MessageBoxData *a, int *b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_ShowSimpleMessageBox,(Uint32 a, const char *b, const char *c, SDL_Window *d),(a,b,c,d),return)
diff --git a/src/render/direct3d/SDL_render_d3d.c b/src/render/direct3d/SDL_render_d3d.c
index 4ba113a93b4f..c8d2e7b46b4f 100644
--- a/src/render/direct3d/SDL_render_d3d.c
+++ b/src/render/direct3d/SDL_render_d3d.c
@@ -944,17 +944,19 @@ static int SetupTextureState(D3D_RenderData *data, SDL_Texture *texture, LPDIREC
     }
 #if SDL_HAVE_YUV
     if (texturedata->yuv) {
-        switch (SDL_GetYUVConversionModeForResolution(texture->w, texture->h)) {
-        case SDL_YUV_CONVERSION_JPEG:
-            *shader = data->shaders[SHADER_YUV_JPEG];
-            break;
-        case SDL_YUV_CONVERSION_BT601:
-            *shader = data->shaders[SHADER_YUV_BT601];
-            break;
-        case SDL_YUV_CONVERSION_BT709:
-            *shader = data->shaders[SHADER_YUV_BT709];
-            break;
-        default:
+        if (SDL_ISCOLORSPACE_YUV_BT601(texture->colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(texture->colorspace)) {
+                *shader = data->shaders[SHADER_YUV_BT601];
+            } else {
+                *shader = data->shaders[SHADER_YUV_JPEG];
+            }
+        } else if (SDL_ISCOLORSPACE_YUV_BT709(texture->colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(texture->colorspace)) {
+                *shader = data->shaders[SHADER_YUV_BT709];
+            } else {
+                return SDL_SetError("Unsupported YUV conversion mode");
+            }
+        } else {
             return SDL_SetError("Unsupported YUV conversion mode");
         }
 
diff --git a/src/render/direct3d11/SDL_render_d3d11.c b/src/render/direct3d11/SDL_render_d3d11.c
index 3c61a192e3cf..f1bbcea57f86 100644
--- a/src/render/direct3d11/SDL_render_d3d11.c
+++ b/src/render/direct3d11/SDL_render_d3d11.c
@@ -2151,17 +2151,19 @@ static int D3D11_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *c
         };
         D3D11_Shader shader;
 
-        switch (SDL_GetYUVConversionModeForResolution(texture->w, texture->h)) {
-        case SDL_YUV_CONVERSION_JPEG:
-            shader = SHADER_YUV_JPEG;
-            break;
-        case SDL_YUV_CONVERSION_BT601:
-            shader = SHADER_YUV_BT601;
-            break;
-        case SDL_YUV_CONVERSION_BT709:
-            shader = SHADER_YUV_BT709;
-            break;
-        default:
+        if (SDL_ISCOLORSPACE_YUV_BT601(texture->colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(texture->colorspace)) {
+                shader = SHADER_YUV_BT601;
+            } else {
+                shader = SHADER_YUV_JPEG;
+            }
+        } else if (SDL_ISCOLORSPACE_YUV_BT709(texture->colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(texture->colorspace)) {
+                shader = SHADER_YUV_BT709;
+            } else {
+                return SDL_SetError("Unsupported YUV conversion mode");
+            }
+        } else {
             return SDL_SetError("Unsupported YUV conversion mode");
         }
 
@@ -2175,17 +2177,19 @@ static int D3D11_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *c
         };
         D3D11_Shader shader;
 
-        switch (SDL_GetYUVConversionModeForResolution(texture->w, texture->h)) {
-        case SDL_YUV_CONVERSION_JPEG:
-            shader = texture->format == SDL_PIXELFORMAT_NV12 ? SHADER_NV12_JPEG : SHADER_NV21_JPEG;
-            break;
-        case SDL_YUV_CONVERSION_BT601:
-            shader = texture->format == SDL_PIXELFORMAT_NV12 ? SHADER_NV12_BT601 : SHADER_NV21_BT601;
-            break;
-        case SDL_YUV_CONVERSION_BT709:
-            shader = texture->format == SDL_PIXELFORMAT_NV12 ? SHADER_NV12_BT709 : SHADER_NV21_BT709;
-            break;
-        default:
+        if (SDL_ISCOLORSPACE_YUV_BT601(texture->colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(texture->colorspace)) {
+                shader = texture->format == SDL_PIXELFORMAT_NV12 ? SHADER_NV12_BT601 : SHADER_NV21_BT601;
+            } else {
+                shader = texture->format == SDL_PIXELFORMAT_NV12 ? SHADER_NV12_JPEG : SHADER_NV21_JPEG;
+            }
+        } else if (SDL_ISCOLORSPACE_YUV_BT709(texture->colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(texture->colorspace)) {
+                shader = texture->format == SDL_PIXELFORMAT_NV12 ? SHADER_NV12_BT709 : SHADER_NV21_BT709;
+            } else {
+                return SDL_SetError("Unsupported YUV conversion mode");
+            }
+        } else {
             return SDL_SetError("Unsupported YUV conversion mode");
         }
 
diff --git a/src/render/direct3d12/SDL_render_d3d12.c b/src/render/direct3d12/SDL_render_d3d12.c
index 0d6c40590166..484d0e27270f 100644
--- a/src/render/direct3d12/SDL_render_d3d12.c
+++ b/src/render/direct3d12/SDL_render_d3d12.c
@@ -2589,17 +2589,19 @@ static int D3D12_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *c
         };
         D3D12_Shader shader;
 
-        switch (SDL_GetYUVConversionModeForResolution(texture->w, texture->h)) {
-        case SDL_YUV_CONVERSION_JPEG:
-            shader = SHADER_YUV_JPEG;
-            break;
-        case SDL_YUV_CONVERSION_BT601:
-            shader = SHADER_YUV_BT601;
-            break;
-        case SDL_YUV_CONVERSION_BT709:
-            shader = SHADER_YUV_BT709;
-            break;
-        default:
+        if (SDL_ISCOLORSPACE_YUV_BT601(texture->colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(texture->colorspace)) {
+                shader = SHADER_YUV_BT601;
+            } else {
+                shader = SHADER_YUV_JPEG;
+            }
+        } else if (SDL_ISCOLORSPACE_YUV_BT709(texture->colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(texture->colorspace)) {
+                shader = SHADER_YUV_BT709;
+            } else {
+                return SDL_SetError("Unsupported YUV conversion mode");
+            }
+        } else {
             return SDL_SetError("Unsupported YUV conversion mode");
         }
 
@@ -2620,17 +2622,19 @@ static int D3D12_SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *c
         };
         D3D12_Shader shader;
 
-        switch (SDL_GetYUVConversionModeForResolution(texture->w, texture->h)) {
-        case SDL_YUV_CONVERSION_JPEG:
-            shader = texture->format == SDL_PIXELFORMAT_NV12 ? SHADER_NV12_JPEG : SHADER_NV21_JPEG;
-            break;
-        case SDL_YUV_CONVERSION_BT601:
-            shader = texture->format == SDL_PIXELFORMAT_NV12 ? SHADER_NV12_BT601 : SHADER_NV21_BT601;
-            break;
-        case SDL_YUV_CONVERSION_BT709:
-            shader = texture->format == SDL_PIXELFORMAT_NV12 ? SHADER_NV12_BT709 : SHADER_NV21_BT709;
-            break;
-        default:
+        if (SDL_ISCOLORSPACE_YUV_BT601(texture->colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(texture->colorspace)) {
+                shader = texture->format == SDL_PIXELFORMAT_NV12 ? SHADER_NV12_BT601 : SHADER_NV21_BT601;
+            } else {
+                shader = texture->format == SDL_PIXELFORMAT_NV12 ? SHADER_NV12_JPEG : SHADER_NV21_JPEG;
+            }
+        } else if (SDL_ISCOLORSPACE_YUV_BT709(texture->colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(texture->colorspace)) {
+                shader = texture->format == SDL_PIXELFORMAT_NV12 ? SHADER_NV12_BT709 : SHADER_NV21_BT709;
+            } else {
+                return SDL_SetError("Unsupported YUV conversion mode");
+            }
+        } else {
             return SDL_SetError("Unsupported YUV conversion mode");
         }
 
diff --git a/src/render/metal/SDL_render_metal.m b/src/render/metal/SDL_render_metal.m
index 09f39fbf20b1..fb545336577e 100644
--- a/src/render/metal/SDL_render_metal.m
+++ b/src/render/metal/SDL_render_metal.m
@@ -638,20 +638,20 @@ static int METAL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL
 #if SDL_HAVE_YUV
         if (yuv || nv12) {
             size_t offset = 0;
-            SDL_YUV_CONVERSION_MODE mode = SDL_GetYUVConversionModeForResolution(texture->w, texture->h);
-            switch (mode) {
-            case SDL_YUV_CONVERSION_JPEG:
-                offset = CONSTANTS_OFFSET_DECODE_JPEG;
-                break;
-            case SDL_YUV_CONVERSION_BT601:
-                offset = CONSTANTS_OFFSET_DECODE_BT601;
-                break;
-            case SDL_YUV_CONVERSION_BT709:
-                offset = CONSTANTS_OFFSET_DECODE_BT709;
-                break;
-            default:
-                offset = 0;
-                break;
+            if (SDL_ISCOLORSPACE_YUV_BT601(texture->colorspace)) {
+                if (SDL_ISCOLORSPACE_LIMITED_RANGE(texture->colorspace)) {
+                    offset = CONSTANTS_OFFSET_DECODE_BT601;
+                } else {
+                    offset = CONSTANTS_OFFSET_DECODE_JPEG;
+                }
+            } else if (SDL_ISCOLORSPACE_YUV_BT709(texture->colorspace)) {
+                if (SDL_ISCOLORSPACE_LIMITED_RANGE(texture->colorspace)) {
+                    offset = CONSTANTS_OFFSET_DECODE_BT709;
+                } else {
+                    return SDL_SetError("Unsupported YUV conversion mode");
+                }
+            } else {
+                return SDL_SetError("Unsupported YUV conversion mode");
             }
             texturedata.conversionBufferOffset = offset;
         }
@@ -1723,10 +1723,10 @@ static SDL_MetalView GetWindowView(SDL_Window *window)
         };
 
         float decodetransformBT709[4 * 4] = {
-            0.0, -0.501960814, -0.501960814, 0.0, /* offset */
-            1.0000, 0.0000, 1.4020, 0.0,          /* Rcoeff */
-            1.0000, -0.3441, -0.7141, 0.0,        /* Gcoeff */
-            1.0000, 1.7720, 0.0000, 0.0,          /* Bcoeff */
+            -0.0627451017, -0.501960814, -0.501960814, 0.0, /* offset */
+            1.1644,  0.0000,  1.7927, 0.0,                  /* Rcoeff */
+            1.1644, -0.2132, -0.5329, 0.0,                  /* Gcoeff */
+            1.1644,  2.1124,  0.0000, 0.0,                  /* Bcoeff */
         };
 
         if (!IsMetalAvailable()) {
diff --git a/src/render/opengl/SDL_render_gl.c b/src/render/opengl/SDL_render_gl.c
index e758e0de045a..5749e496affc 100644
--- a/src/render/opengl/SDL_render_gl.c
+++ b/src/render/opengl/SDL_render_gl.c
@@ -662,45 +662,46 @@ static int GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_Pr
 
 #if SDL_HAVE_YUV
     if (data->yuv || data->nv12) {
-        switch (SDL_GetYUVConversionModeForResolution(texture->w, texture->h)) {
-        case SDL_YUV_CONVERSION_JPEG:
-            if (data->yuv) {
-                data->shader = SHADER_YUV_JPEG;
-            } else if (texture->format == SDL_PIXELFORMAT_NV12) {
-                data->shader = SHADER_NV12_JPEG;
-            } else {
-                data->shader = SHADER_NV21_JPEG;
-            }
-            break;
-        case SDL_YUV_CONVERSION_BT601:
-            if (data->yuv) {
-                data->shader = SHADER_YUV_BT601;
-            } else if (texture->format == SDL_PIXELFORMAT_NV12) {
-                if (SDL_GetHintBoolean("SDL_RENDER_OPENGL_NV12_RG_SHADER", SDL_FALSE)) {
-                    data->shader = SHADER_NV12_RG_BT601;
+        if (SDL_ISCOLORSPACE_YUV_BT601(texture->colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(texture->colorspace)) {
+                if (data->yuv) {
+                    data->shader = SHADER_YUV_BT601;
+                } else if (texture->format == SDL_PIXELFORMAT_NV12) {
+                    if (SDL_GetHintBoolean("SDL_RENDER_OPENGL_NV12_RG_SHADER", SDL_FALSE)) {
+                        data->shader = SHADER_NV12_RG_BT601;
+                    } else {
+                        data->shader = SHADER_NV12_RA_BT601;
+                    }
                 } else {
-                    data->shader = SHADER_NV12_RA_BT601;
+                    data->shader = SHADER_NV21_BT601;
                 }
             } else {
-                data->shader = SHADER_NV21_BT601;
+                if (data->yuv) {
+                    data->shader = SHADER_YUV_JPEG;
+                } else if (texture->format == SDL_PIXELFORMAT_NV12) {
+                    data->shader = SHADER_NV12_JPEG;
+                } else {
+                    data->shader = SHADER_NV21_JPEG;
+                }
             }
-            break;
-        case SDL_YUV_CONVERSION_BT709:
-            if (data->yuv) {
-                data->shader = SHADER_YUV_BT709;
-            } else if (texture->format == SDL_PIXELFORMAT_NV12) {
-                if (SDL_GetHintBoolean("SDL_RENDER_OPENGL_NV12_RG_SHADER", SDL_FALSE)) {
-                    data->shader = SHADER_NV12_RG_BT709;
+        } else if (SDL_ISCOLORSPACE_YUV_BT709(texture->colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(texture->colorspace)) {
+                if (data->yuv) {
+                    data->shader = SHADER_YUV_BT709;
+                } else if (texture->format == SDL_PIXELFORMAT_NV12) {
+                    if (SDL_GetHintBoolean("SDL_RENDER_OPENGL_NV12_RG_SHADER", SDL_FALSE)) {
+                        data->shader = SHADER_NV12_RG_BT709;
+                    } else {
+                        data->shader = SHADER_NV12_RA_BT709;
+                    }
                 } else {
-                    data->shader = SHADER_NV12_RA_BT709;
+                    data->shader = SHADER_NV21_BT709;
                 }
             } else {
-                data->shader = SHADER_NV21_BT709;
+                return SDL_SetError("Unsupported YUV conversion mode");
             }
-            break;
-        default:
-            SDL_assert(!"unsupported YUV conversion mode");
-            break;
+        } else {
+            return SDL_SetError("Unsupported YUV conversion mode");
         }
     }
 #endif /* SDL_HAVE_YUV */
diff --git a/src/render/opengles2/SDL_render_gles2.c b/src/render/opengles2/SDL_render_gles2.c
index a3ea1eee046e..2ac81959902b 100644
--- a/src/render/opengles2/SDL_render_gles2.c
+++ b/src/render/opengles2/SDL_render_gles2.c
@@ -588,7 +588,7 @@ static int GLES2_CacheShaders(GLES2_RenderData *data)
     return 0;
 }
 
-static int GLES2_SelectProgram(GLES2_RenderData *data, GLES2_ImageSource source, int w, int h)
+static int GLES2_SelectProgram(GLES2_RenderData *data, GLES2_ImageSource source, SDL_Colorspace colorspace)
 {
     GLuint vertex;
     GLuint fragment;
@@ -615,58 +615,67 @@ static int GLES2_SelectProgram(GLES2_RenderData *data, GLES2_ImageSource source,
         break;
 #if SDL_HAVE_YUV
     case GLES2_IMAGESOURCE_TEXTURE_YUV:
-        switch (SDL_GetYUVConversionModeForResolution(w, h)) {
-        case SDL_YUV_CONVERSION_JPEG:
-            ftype = GLES2_SHADER_FRAGMENT_TEXTURE_YUV_JPEG;
-            break;
-        case SDL_YUV_CONVERSION_BT601:
-            ftype = GLES2_SHADER_FRAGMENT_TEXTURE_YUV_BT601;
-            break;
-        case SDL_YUV_CONVERSION_BT709:
-            ftype = GLES2_SHADER_FRAGMENT_TEXTURE_YUV_BT709;
-            break;
-        default:
-            SDL_SetError("Unsupported YUV conversion mode: %d\n", SDL_GetYUVConversionModeForResolution(w, h));
+        if (SDL_ISCOLORSPACE_YUV_BT601(colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(colorspace)) {
+                ftype = GLES2_SHADER_FRAGMENT_TEXTURE_YUV_BT601;
+            } else {
+                ftype = GLES2_SHADER_FRAGMENT_TEXTURE_YUV_JPEG;
+            }
+        } else if (SDL_ISCOLORSPACE_YUV_BT709(colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(colorspace)) {
+                ftype = GLES2_SHADER_FRAGMENT_TEXTURE_YUV_BT709;
+            } else {
+                SDL_SetError("Unsupported YUV conversion mode");
+                goto fault;
+            }
+        } else {
+            SDL_SetError("Unsupported YUV conversion mode");
             goto fault;
         }
         break;
     case GLES2_IMAGESOURCE_TEXTURE_NV12:
-        switch (SDL_GetYUVConversionModeForResolution(w, h)) {
-        case SDL_YUV_CONVERSION_JPEG:
-            ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV12_JPEG;
-            break;
-        case SDL_YUV_CONVERSION_BT601:
-            if (SDL_GetHintBoolean("SDL_RENDER_OPENGL_NV12_RG_SHADER", SDL_FALSE)) {
-                ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV12_RG_BT601;
+        if (SDL_ISCOLORSPACE_YUV_BT601(colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(colorspace)) {
+                if (SDL_GetHintBoolean("SDL_RENDER_OPENGL_NV12_RG_SHADER", SDL_FALSE)) {
+                    ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV12_RG_BT601;
+                } else {
+                    ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV12_RA_BT601;
+                }
             } else {
-                ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV12_RA_BT601;
+                ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV12_JPEG;
             }
-            break;
-        case SDL_YUV_CONVERSION_BT709:
-            if (SDL_GetHintBoolean("SDL_RENDER_OPENGL_NV12_RG_SHADER", SDL_FALSE)) {
-                ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV12_RG_BT709;
+        } else if (SDL_ISCOLORSPACE_YUV_BT709(colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(colorspace)) {
+                if (SDL_GetHintBoolean("SDL_RENDER_OPENGL_NV12_RG_SHADER", SDL_FALSE)) {
+                    ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV12_RG_BT709;
+                } else {
+                    ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV12_RA_BT709;
+                }
             } else {
-                ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV12_RA_BT709;
+                SDL_SetError("Unsupported YUV conversion mode");
+                goto fault;
             }
-            break;
-        default:
-            SDL_SetError("Unsupported YUV conversion mode: %d\n", SDL_GetYUVConversionModeForResolution(w, h));
+        } else {
+            SDL_SetError("Unsupported YUV conversion mode");
             goto fault;
         }
         break;
     case GLES2_IMAGESOURCE_TEXTURE_NV21:
-        switch (SDL_GetYUVConversionModeForResolution(w, h)) {
-        case SDL_YUV_CONVERSION_JPEG:
-            ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV21_JPEG;
-            break;
-        case SDL_YUV_CONVERSION_BT601:
-            ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV21_BT601;
-            break;
-        case SDL_YUV_CONVERSION_BT709:
-            ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV21_BT709;
-            break;
-        default:
-            SDL_SetError("Unsupported YUV conversion mode: %d\n", SDL_GetYUVConversionModeForResolution(w, h));
+        if (SDL_ISCOLORSPACE_YUV_BT601(colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(colorspace)) {
+                ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV21_BT601;
+            } else {
+                ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV21_JPEG;
+            }
+        } else if (SDL_ISCOLORSPACE_YUV_BT709(colorspace)) {
+            if (SDL_ISCOLORSPACE_LIMITED_RANGE(colorspace)) {
+                ftype = GLES2_SHADER_FRAGMENT_TEXTURE_NV21_BT709;
+            } else {
+                SDL_SetError("Unsupported YUV conversion mode");
+                goto fault;
+            }
+        } else {
+            SDL_SetError("Unsupported YUV conversion mode");
             goto fault;
         }
         break;
@@ -961,7 +970,7 @@ static int SetDrawState(GLES2_RenderData *data, const SDL_RenderCommand *cmd, co
         data->glVertexAttribPointer(GLES2_ATTRIBUTE_TEXCOORD, 2, GL_FLOAT, GL_FALSE, stride, (const GLvoid *)&verts->tex_coord);
     }
 
-    if (GLES2_SelectProgram(data, imgsrc, texture ? texture->w : 0, texture ? texture->h : 0) < 0) {
+    if (GLES2_SelectProgram(data, imgsrc, texture ? texture->colorspace : SDL_COLORSPACE_SRGB) < 0) {
         return -1;
     }
 
diff --git a/src/render/vitagxm/SDL_render_vita_gxm.c b/src/render/vitagxm/SDL_render_vita_gxm.c
index 5bd09e4a18d2..c82219c42a9b 100644
--- a/src/render/vitagxm/SDL_render_vita_gxm.c
+++ b/src/render/vitagxm/SDL_render_vita_gxm.c
@@ -341,17 +341,20 @@ static void VITA_GXM_SetYUVProfile(SDL_Renderer *renderer, SDL_Texture *texture)
 {
     VITA_GXM_RenderData *data = (VITA_GXM_RenderData *)renderer->driverdata;
     int ret = 0;
-    switch (SDL_GetYUVConversionModeForResolution(texture->w, texture->h)) {
-    case SDL_YUV_CONVERSION_BT601:
-        ret = sceGxmSetYuvProfile(data->gxm_context, 0, SCE_GXM_YUV_PROFILE_BT601_STANDARD);
-        break;
-    case SDL_YUV_CONVERSION_BT709:
-        ret = sceGxmSetYuvProfile(data->gxm_context, 0, SCE_GXM_YUV_PROFILE_BT709_STANDARD);
-        break;
-    case SDL_YUV_CONVERSION_JPEG:
-    default:
-        SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Unsupported YUV profile: %d\n", SDL_GetYUVConversionModeForResolution(texture->w, texture->h));
-        break;
+    if (SDL_ISCOLORSPACE_YUV_BT601(texture->colorspace)) {
+        if (SDL_ISCOLORSPACE_LIMITED_RANGE(texture->colorspace)) {
+            ret = sceGxmSetYuvProfile(data->gxm_context, 0, SCE_GXM_YUV_PROFILE_BT601_STANDARD);
+        } else {
+            ret = sceGxmSetYuvProfile(data->gxm_context, 0, SCE_GXM_YUV_PROFILE_BT601_FULL_RANGE);
+        }
+    } else if (SDL_ISCOLORSPACE_YUV_BT709(texture->colorspace)) {
+        if (SDL_ISCOLORSPACE_LIMITED_RANGE(texture->colorspace)) {
+            ret = sceGxmSetYuvProfile(data->gxm_context, 0, SCE_GXM_YUV_PROFILE_BT709_STANDARD);
+        } else {
+            ret = sceGxmSetYuvProfile(data->gxm_context, 0, SCE_GXM_YUV_PROFILE_BT709_FULL_RANGE);
+        }
+    } else {
+        SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Unsupported YUV conversion mode\n");
     }
 
     if (ret < 0) {
diff --git a/src/video/SDL_pixels.c b/src/video/SDL_pixels.c
index 2b6ece7fa71d..c76805151969 100644
--- a/src/video/SDL_pixels.c
+++ b/src/video/SDL_pixels.c
@@ -695,7 +695

(Patch may be truncated, please check the link at the top of this post.)