Browse Source

Added support for the scRGB colorspace on D3D11 and D3D12

Sam Lantinga 1 year ago
parent
commit
6f443e2aca

+ 93 - 70
include/SDL3/SDL_surface.h

@@ -216,8 +216,7 @@ extern DECLSPEC void SDLCALL SDL_DestroySurface(SDL_Surface *surface);
  *
  * The following properties are understood by SDL:
  *
- * - `SDL_PROP_SURFACE_COLORSPACE_NUMBER`: an SDL_ColorSpace value describing
- *   the surface colorspace
+ * - `SDL_PROP_SURFACE_COLORSPACE_NUMBER`: an SDL_ColorSpace value describing the surface colorspace, defaults to SDL_COLORSPACE_SCRGB for floating point formats, SDL_COLORSPACE_HDR10 for 10-bit formats, SDL_COLORSPACE_SRGB for other RGB surfaces and SDL_COLORSPACE_BT709_FULL for YUV textures.
  * - `SDL_PROP_SURFACE_MAXCLL_NUMBER`: MaxCLL (Maximum Content Light Level)
  *   indicates the maximum light level of any single pixel (in cd/m2 or nits)
  *   of the entire playback sequence. MaxCLL is usually measured off the final
@@ -245,6 +244,34 @@ extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetSurfaceProperties(SDL_Surface *s
 #define SDL_PROP_SURFACE_MAXCLL_NUMBER                      "SDL.surface.maxCLL"
 #define SDL_PROP_SURFACE_MAXFALL_NUMBER                     "SDL.surface.maxFALL"
 
+/**
+ * Set the colorspace used by a surface.
+ *
+ * Setting the colorspace doesn't change the pixels, only how they are interpreted in color operations.
+ *
+ * \param surface the SDL_Surface structure to update
+ * \param colorspace an SDL_ColorSpace value describing the surface colorspace
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_SetSurfaceColorspace(SDL_Surface *surface, SDL_Colorspace colorspace);
+
+/**
+ * Get the colorspace used by a surface.
+ *
+ * The colorspace defaults to SDL_COLORSPACE_SCRGB for floating point formats, SDL_COLORSPACE_HDR10 for 10-bit formats, SDL_COLORSPACE_SRGB for other RGB surfaces and SDL_COLORSPACE_BT709_FULL for YUV textures.
+ *
+ * \param surface the SDL_Surface structure to query
+ * \param colorspace a pointer filled in with an SDL_ColorSpace value describing the surface colorspace
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_GetSurfaceColorspace(SDL_Surface *surface, SDL_Colorspace *colorspace);
+
 /**
  * Set the palette used by a surface.
  *
@@ -257,8 +284,7 @@ extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetSurfaceProperties(SDL_Surface *s
  *
  * \since This function is available since SDL 3.0.0.
  */
-extern DECLSPEC int SDLCALL SDL_SetSurfacePalette(SDL_Surface *surface,
-                                                  SDL_Palette *palette);
+extern DECLSPEC int SDLCALL SDL_SetSurfacePalette(SDL_Surface *surface, SDL_Palette *palette);
 
 /**
  * Set up a surface for directly accessing the pixels.
@@ -393,8 +419,7 @@ extern DECLSPEC int SDLCALL SDL_SaveBMP(SDL_Surface *surface, const char *file);
  * \sa SDL_LockSurface
  * \sa SDL_UnlockSurface
  */
-extern DECLSPEC int SDLCALL SDL_SetSurfaceRLE(SDL_Surface *surface,
-                                              int flag);
+extern DECLSPEC int SDLCALL SDL_SetSurfaceRLE(SDL_Surface *surface, int flag);
 
 /**
  * Returns whether the surface is RLE enabled
@@ -434,8 +459,7 @@ extern DECLSPEC SDL_bool SDLCALL SDL_SurfaceHasRLE(SDL_Surface *surface);
  * \sa SDL_BlitSurface
  * \sa SDL_GetSurfaceColorKey
  */
-extern DECLSPEC int SDLCALL SDL_SetSurfaceColorKey(SDL_Surface *surface,
-                                            int flag, Uint32 key);
+extern DECLSPEC int SDLCALL SDL_SetSurfaceColorKey(SDL_Surface *surface, int flag, Uint32 key);
 
 /**
  * Returns whether the surface has a color key
@@ -470,8 +494,7 @@ extern DECLSPEC SDL_bool SDLCALL SDL_SurfaceHasColorKey(SDL_Surface *surface);
  * \sa SDL_BlitSurface
  * \sa SDL_SetSurfaceColorKey
  */
-extern DECLSPEC int SDLCALL SDL_GetSurfaceColorKey(SDL_Surface *surface,
-                                            Uint32 *key);
+extern DECLSPEC int SDLCALL SDL_GetSurfaceColorKey(SDL_Surface *surface, Uint32 *key);
 
 /**
  * Set an additional color value multiplied into blit operations.
@@ -494,8 +517,7 @@ extern DECLSPEC int SDLCALL SDL_GetSurfaceColorKey(SDL_Surface *surface,
  * \sa SDL_GetSurfaceColorMod
  * \sa SDL_SetSurfaceAlphaMod
  */
-extern DECLSPEC int SDLCALL SDL_SetSurfaceColorMod(SDL_Surface *surface,
-                                                   Uint8 r, Uint8 g, Uint8 b);
+extern DECLSPEC int SDLCALL SDL_SetSurfaceColorMod(SDL_Surface *surface, Uint8 r, Uint8 g, Uint8 b);
 
 
 /**
@@ -513,9 +535,7 @@ extern DECLSPEC int SDLCALL SDL_SetSurfaceColorMod(SDL_Surface *surface,
  * \sa SDL_GetSurfaceAlphaMod
  * \sa SDL_SetSurfaceColorMod
  */
-extern DECLSPEC int SDLCALL SDL_GetSurfaceColorMod(SDL_Surface *surface,
-                                                   Uint8 *r, Uint8 *g,
-                                                   Uint8 *b);
+extern DECLSPEC int SDLCALL SDL_GetSurfaceColorMod(SDL_Surface *surface, Uint8 *r, Uint8 *g, Uint8 *b);
 
 /**
  * Set an additional alpha value used in blit operations.
@@ -535,8 +555,7 @@ extern DECLSPEC int SDLCALL SDL_GetSurfaceColorMod(SDL_Surface *surface,
  * \sa SDL_GetSurfaceAlphaMod
  * \sa SDL_SetSurfaceColorMod
  */
-extern DECLSPEC int SDLCALL SDL_SetSurfaceAlphaMod(SDL_Surface *surface,
-                                                   Uint8 alpha);
+extern DECLSPEC int SDLCALL SDL_SetSurfaceAlphaMod(SDL_Surface *surface, Uint8 alpha);
 
 /**
  * Get the additional alpha value used in blit operations.
@@ -551,8 +570,7 @@ extern DECLSPEC int SDLCALL SDL_SetSurfaceAlphaMod(SDL_Surface *surface,
  * \sa SDL_GetSurfaceColorMod
  * \sa SDL_SetSurfaceAlphaMod
  */
-extern DECLSPEC int SDLCALL SDL_GetSurfaceAlphaMod(SDL_Surface *surface,
-                                                   Uint8 *alpha);
+extern DECLSPEC int SDLCALL SDL_GetSurfaceAlphaMod(SDL_Surface *surface, Uint8 *alpha);
 
 /**
  * Set the blend mode used for blit operations.
@@ -570,8 +588,7 @@ extern DECLSPEC int SDLCALL SDL_GetSurfaceAlphaMod(SDL_Surface *surface,
  *
  * \sa SDL_GetSurfaceBlendMode
  */
-extern DECLSPEC int SDLCALL SDL_SetSurfaceBlendMode(SDL_Surface *surface,
-                                                    SDL_BlendMode blendMode);
+extern DECLSPEC int SDLCALL SDL_SetSurfaceBlendMode(SDL_Surface *surface, SDL_BlendMode blendMode);
 
 /**
  * Get the blend mode used for blit operations.
@@ -585,8 +602,7 @@ extern DECLSPEC int SDLCALL SDL_SetSurfaceBlendMode(SDL_Surface *surface,
  *
  * \sa SDL_SetSurfaceBlendMode
  */
-extern DECLSPEC int SDLCALL SDL_GetSurfaceBlendMode(SDL_Surface *surface,
-                                                    SDL_BlendMode *blendMode);
+extern DECLSPEC int SDLCALL SDL_GetSurfaceBlendMode(SDL_Surface *surface, SDL_BlendMode *blendMode);
 
 /**
  * Set the clipping rectangle for a surface.
@@ -608,8 +624,7 @@ extern DECLSPEC int SDLCALL SDL_GetSurfaceBlendMode(SDL_Surface *surface,
  * \sa SDL_BlitSurface
  * \sa SDL_GetSurfaceClipRect
  */
-extern DECLSPEC SDL_bool SDLCALL SDL_SetSurfaceClipRect(SDL_Surface *surface,
-                                                 const SDL_Rect *rect);
+extern DECLSPEC SDL_bool SDLCALL SDL_SetSurfaceClipRect(SDL_Surface *surface, const SDL_Rect *rect);
 
 /**
  * Get the clipping rectangle for a surface.
@@ -629,8 +644,7 @@ extern DECLSPEC SDL_bool SDLCALL SDL_SetSurfaceClipRect(SDL_Surface *surface,
  * \sa SDL_BlitSurface
  * \sa SDL_SetSurfaceClipRect
  */
-extern DECLSPEC int SDLCALL SDL_GetSurfaceClipRect(SDL_Surface *surface,
-                                             SDL_Rect *rect);
+extern DECLSPEC int SDLCALL SDL_GetSurfaceClipRect(SDL_Surface *surface, SDL_Rect *rect);
 
 /*
  * Flip a surface vertically or horizontally.
@@ -677,11 +691,10 @@ extern DECLSPEC SDL_Surface *SDLCALL SDL_DuplicateSurface(SDL_Surface *surface);
  * \sa SDL_ConvertSurfaceFormat
  * \sa SDL_CreateSurface
  */
-extern DECLSPEC SDL_Surface *SDLCALL SDL_ConvertSurface(SDL_Surface *surface,
-                                                        const SDL_PixelFormat *format);
+extern DECLSPEC SDL_Surface *SDLCALL SDL_ConvertSurface(SDL_Surface *surface, const SDL_PixelFormat *format);
 
 /**
- * Copy an existing surface to a new surface of the specified format enum.
+ * Copy an existing surface to a new surface of the specified format.
  *
  * This function operates just like SDL_ConvertSurface(), but accepts an
  * SDL_PixelFormatEnum value instead of an SDL_PixelFormat structure. As such,
@@ -689,8 +702,26 @@ extern DECLSPEC SDL_Surface *SDLCALL SDL_ConvertSurface(SDL_Surface *surface,
  * information for the destination surface, in case that would be important.
  *
  * \param surface the existing SDL_Surface structure to convert
- * \param pixel_format the SDL_PixelFormatEnum that the new surface is
- *                     optimized for
+ * \param pixel_format the new pixel format
+ * \returns the new SDL_Surface structure that is created or NULL if it fails;
+ *          call SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_CreatePixelFormat
+ * \sa SDL_ConvertSurface
+ * \sa SDL_CreateSurface
+ */
+extern DECLSPEC SDL_Surface *SDLCALL SDL_ConvertSurfaceFormat(SDL_Surface *surface, Uint32 pixel_format);
+
+/**
+ * Copy an existing surface to a new surface of the specified format and colorspace.
+ *
+ * This function converts an existing surface to a new format and colorspace and returns the new surface. This will perform any pixel format and colorspace conversion needed.
+ *
+ * \param surface the existing SDL_Surface structure to convert
+ * \param pixel_format the new pixel format
+ * \param colorspace the new colorspace
  * \returns the new SDL_Surface structure that is created or NULL if it fails;
  *          call SDL_GetError() for more information.
  *
@@ -700,8 +731,7 @@ extern DECLSPEC SDL_Surface *SDLCALL SDL_ConvertSurface(SDL_Surface *surface,
  * \sa SDL_ConvertSurface
  * \sa SDL_CreateSurface
  */
-extern DECLSPEC SDL_Surface *SDLCALL SDL_ConvertSurfaceFormat(SDL_Surface *surface,
-                                                              Uint32 pixel_format);
+extern DECLSPEC SDL_Surface *SDLCALL SDL_ConvertSurfaceFormatAndColorspace(SDL_Surface *surface, Uint32 pixel_format, SDL_Colorspace colorspace);
 
 /**
  * Copy a block of pixels of one format to another format.
@@ -719,11 +749,27 @@ extern DECLSPEC SDL_Surface *SDLCALL SDL_ConvertSurfaceFormat(SDL_Surface *surfa
  *
  * \since This function is available since SDL 3.0.0.
  */
-extern DECLSPEC int SDLCALL SDL_ConvertPixels(int width, int height,
-                                              Uint32 src_format,
-                                              const void *src, int src_pitch,
-                                              Uint32 dst_format,
-                                              void *dst, int dst_pitch);
+extern DECLSPEC int SDLCALL SDL_ConvertPixels(int width, int height, Uint32 src_format, const void *src, int src_pitch, Uint32 dst_format, void *dst, int dst_pitch);
+
+/**
+ * Copy a block of pixels of one format and colorspace to another format and colorspace.
+ *
+ * \param width the width of the block to copy, in pixels
+ * \param height the height of the block to copy, in pixels
+ * \param src_format an SDL_PixelFormatEnum value of the `src` pixels format
+ * \param src_colorspace an SDL_ColorSpace value describing the colorspace of the `src` pixels
+ * \param src a pointer to the source pixels
+ * \param src_pitch the pitch of the source pixels, in bytes
+ * \param dst_format an SDL_PixelFormatEnum value of the `dst` pixels format
+ * \param dst_colorspace an SDL_ColorSpace value describing the colorspace of the `dst` pixels
+ * \param dst a pointer to be filled in with new pixel data
+ * \param dst_pitch the pitch of the destination pixels, in bytes
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_ConvertPixelsAndColorspace(int width, int height, Uint32 src_format, SDL_Colorspace src_colorspace, const void *src, int src_pitch, Uint32 dst_format, SDL_Colorspace dst_colorspace, void *dst, int dst_pitch);
 
 /**
  * Premultiply the alpha on a block of pixels.
@@ -745,11 +791,7 @@ extern DECLSPEC int SDLCALL SDL_ConvertPixels(int width, int height,
  *
  * \since This function is available since SDL 3.0.0.
  */
-extern DECLSPEC int SDLCALL SDL_PremultiplyAlpha(int width, int height,
-                                                 Uint32 src_format,
-                                                 const void *src, int src_pitch,
-                                                 Uint32 dst_format,
-                                                 void *dst, int dst_pitch);
+extern DECLSPEC int SDLCALL SDL_PremultiplyAlpha(int width, int height, Uint32 src_format, const void *src, int src_pitch, Uint32 dst_format, void *dst, int dst_pitch);
 
 /**
  * Perform a fast fill of a rectangle with a specific color.
@@ -774,8 +816,7 @@ extern DECLSPEC int SDLCALL SDL_PremultiplyAlpha(int width, int height,
  *
  * \sa SDL_FillSurfaceRects
  */
-extern DECLSPEC int SDLCALL SDL_FillSurfaceRect
-    (SDL_Surface *dst, const SDL_Rect *rect, Uint32 color);
+extern DECLSPEC int SDLCALL SDL_FillSurfaceRect(SDL_Surface *dst, const SDL_Rect *rect, Uint32 color);
 
 /**
  * Perform a fast fill of a set of rectangles with a specific color.
@@ -800,8 +841,7 @@ extern DECLSPEC int SDLCALL SDL_FillSurfaceRect
  *
  * \sa SDL_FillSurfaceRect
  */
-extern DECLSPEC int SDLCALL SDL_FillSurfaceRects
-    (SDL_Surface *dst, const SDL_Rect *rects, int count, Uint32 color);
+extern DECLSPEC int SDLCALL SDL_FillSurfaceRects(SDL_Surface *dst, const SDL_Rect *rects, int count, Uint32 color);
 
 /**
  * Performs a fast blit from the source surface to the destination surface.
@@ -871,9 +911,7 @@ extern DECLSPEC int SDLCALL SDL_FillSurfaceRects
  *
  * \sa SDL_BlitSurfaceScaled
  */
-extern DECLSPEC int SDLCALL SDL_BlitSurface
-    (SDL_Surface *src, const SDL_Rect *srcrect,
-     SDL_Surface *dst, SDL_Rect *dstrect);
+extern DECLSPEC int SDLCALL SDL_BlitSurface(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect);
 
 /**
  * Perform low-level surface blitting only.
@@ -894,9 +932,7 @@ extern DECLSPEC int SDLCALL SDL_BlitSurface
  *
  * \sa SDL_BlitSurface
  */
-extern DECLSPEC int SDLCALL SDL_BlitSurfaceUnchecked
-    (SDL_Surface *src, const SDL_Rect *srcrect,
-     SDL_Surface *dst, const SDL_Rect *dstrect);
+extern DECLSPEC int SDLCALL SDL_BlitSurfaceUnchecked(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect);
 
 /**
  * Perform stretch blit between two surfaces of the same format.
@@ -918,11 +954,7 @@ extern DECLSPEC int SDLCALL SDL_BlitSurfaceUnchecked
  *
  * \sa SDL_BlitSurfaceScaled
  */
-extern DECLSPEC int SDLCALL SDL_SoftStretch(SDL_Surface *src,
-                                            const SDL_Rect *srcrect,
-                                            SDL_Surface *dst,
-                                            const SDL_Rect *dstrect,
-                                            SDL_ScaleMode scaleMode);
+extern DECLSPEC int SDLCALL SDL_SoftStretch(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode);
 
 /**
  * Perform a scaled surface copy to a destination surface.
@@ -942,11 +974,7 @@ extern DECLSPEC int SDLCALL SDL_SoftStretch(SDL_Surface *src,
  *
  * \sa SDL_BlitSurface
  */
-extern DECLSPEC int SDLCALL SDL_BlitSurfaceScaled(SDL_Surface *src,
-                                                  const SDL_Rect *srcrect,
-                                                  SDL_Surface *dst,
-                                                  SDL_Rect *dstrect,
-                                                  SDL_ScaleMode scaleMode);
+extern DECLSPEC int SDLCALL SDL_BlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect, SDL_ScaleMode scaleMode);
 
 /**
  * Perform low-level surface scaled blitting only.
@@ -968,11 +996,7 @@ extern DECLSPEC int SDLCALL SDL_BlitSurfaceScaled(SDL_Surface *src,
  *
  * \sa SDL_BlitSurfaceScaled
  */
-extern DECLSPEC int SDLCALL SDL_BlitSurfaceUncheckedScaled(SDL_Surface *src,
-                                                           const SDL_Rect *srcrect,
-                                                           SDL_Surface *dst,
-                                                           const SDL_Rect *dstrect,
-                                                           SDL_ScaleMode scaleMode);
+extern DECLSPEC int SDLCALL SDL_BlitSurfaceUncheckedScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode);
 
 /**
  * Retrieves a single pixel from a surface.
@@ -1001,7 +1025,6 @@ extern DECLSPEC int SDLCALL SDL_BlitSurfaceUncheckedScaled(SDL_Surface *src,
  */
 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
  *

+ 4 - 0
src/dynapi/SDL_dynapi.sym

@@ -965,6 +965,10 @@ SDL3_0.0.0 {
     SDL_GetTextureAlphaModFloat;
     SDL_SetRenderDrawColorFloat;
     SDL_GetRenderDrawColorFloat;
+    SDL_ConvertPixelsAndColorspace;
+    SDL_SetSurfaceColorspace;
+    SDL_GetSurfaceColorspace;
+    SDL_ConvertSurfaceFormatAndColorspace;
     # extra symbols go here (don't modify this line)
   local: *;
 };

+ 4 - 0
src/dynapi/SDL_dynapi_overrides.h

@@ -990,3 +990,7 @@
 #define SDL_GetTextureAlphaModFloat SDL_GetTextureAlphaModFloat_REAL
 #define SDL_SetRenderDrawColorFloat SDL_SetRenderDrawColorFloat_REAL
 #define SDL_GetRenderDrawColorFloat SDL_GetRenderDrawColorFloat_REAL
+#define SDL_ConvertPixelsAndColorspace SDL_ConvertPixelsAndColorspace_REAL
+#define SDL_SetSurfaceColorspace SDL_SetSurfaceColorspace_REAL
+#define SDL_GetSurfaceColorspace SDL_GetSurfaceColorspace_REAL
+#define SDL_ConvertSurfaceFormatAndColorspace SDL_ConvertSurfaceFormatAndColorspace_REAL

+ 4 - 0
src/dynapi/SDL_dynapi_procs.h

@@ -1015,3 +1015,7 @@ SDL_DYNAPI_PROC(int,SDL_SetTextureAlphaModFloat,(SDL_Texture *a, float b),(a,b),
 SDL_DYNAPI_PROC(int,SDL_GetTextureAlphaModFloat,(SDL_Texture *a, float *b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetRenderDrawColorFloat,(SDL_Renderer *a, float b, float c, float d, float e),(a,b,c,d,e),return)
 SDL_DYNAPI_PROC(int,SDL_GetRenderDrawColorFloat,(SDL_Renderer *a, float *b, float *c, float *d, float *e),(a,b,c,d,e),return)
+SDL_DYNAPI_PROC(int,SDL_ConvertPixelsAndColorspace,(int a, int b, Uint32 c, SDL_Colorspace d, const void *e, int f, Uint32 g, SDL_Colorspace h, void *i, int j),(a,b,c,d,e,f,g,h,i,j),return)
+SDL_DYNAPI_PROC(int,SDL_SetSurfaceColorspace,(SDL_Surface *a, SDL_Colorspace b),(a,b),return)
+SDL_DYNAPI_PROC(int,SDL_GetSurfaceColorspace,(SDL_Surface *a, SDL_Colorspace *b),(a,b),return)
+SDL_DYNAPI_PROC(SDL_Surface*,SDL_ConvertSurfaceFormatAndColorspace,(SDL_Surface *a, Uint32 b, SDL_Colorspace c),(a,b,c),return)

+ 12 - 25
src/render/SDL_render.c

@@ -865,7 +865,7 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
     SDL_Renderer *renderer = NULL;
     const int n = SDL_GetNumRenderDrivers();
     const char *hint;
-    int i;
+    int i, attempted = 0;
 
     if (!window && surface) {
         return SDL_CreateSoftwareRenderer(surface);
@@ -904,6 +904,7 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
             const SDL_RenderDriver *driver = render_drivers[i];
             if (SDL_strcasecmp(name, driver->info.name) == 0) {
                 /* Create a new renderer instance */
+                ++attempted;
                 renderer = driver->CreateRenderer(window, props);
                 break;
             }
@@ -912,6 +913,7 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
         for (i = 0; i < n; i++) {
             const SDL_RenderDriver *driver = render_drivers[i];
             /* Create a new renderer instance */
+            ++attempted;
             renderer = driver->CreateRenderer(window, props);
             if (renderer) {
                 /* Yay, we got one! */
@@ -921,7 +923,9 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
     }
 
     if (!renderer) {
-        SDL_SetError("Couldn't find matching render driver");
+        if (!name || !attempted) {
+            SDL_SetError("Couldn't find matching render driver");
+        }
         goto error;
     }
 
@@ -1175,19 +1179,6 @@ static SDL_ScaleMode SDL_GetScaleMode(void)
     }
 }
 
-static SDL_Colorspace SDL_GetDefaultTextureColorspace(Uint32 format)
-{
-    if (SDL_ISPIXELFORMAT_FOURCC(format)) {
-        return SDL_COLORSPACE_BT709_FULL;
-    } else if (SDL_ISPIXELFORMAT_FLOAT(format)) {
-        return SDL_COLORSPACE_SCRGB;
-    } else if (SDL_ISPIXELFORMAT_10BIT(format)) {
-        return SDL_COLORSPACE_HDR10;
-    } else {
-        return SDL_COLORSPACE_SRGB;
-    }
-}
-
 SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_PropertiesID props)
 {
     SDL_Texture *texture;
@@ -1195,7 +1186,7 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert
     int access = (int)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STATIC);
     int w = (int)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, 0);
     int h = (int)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, 0);
-    Uint32 default_colorspace;
+    SDL_Colorspace default_colorspace;
     SDL_bool texture_is_fourcc_and_target;
 
     CHECK_RENDERER_MAGIC(renderer, NULL);
@@ -1223,14 +1214,14 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert
         return NULL;
     }
 
-    default_colorspace = SDL_GetDefaultTextureColorspace(format);
+    default_colorspace = SDL_GetDefaultColorspaceForFormat(format);
 
     texture = (SDL_Texture *)SDL_calloc(1, sizeof(*texture));
     if (!texture) {
         return NULL;
     }
     texture->magic = &SDL_texture_magic;
-    texture->colorspace = (Uint32)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, default_colorspace);
+    texture->colorspace = (SDL_Colorspace)SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, default_colorspace);
     texture->format = format;
     texture->access = access;
     texture->w = w;
@@ -1334,7 +1325,7 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s
     Uint32 format = SDL_PIXELFORMAT_UNKNOWN;
     SDL_Texture *texture;
     SDL_PropertiesID props;
-    SDL_Colorspace default_colorspace, colorspace;
+    SDL_Colorspace colorspace = SDL_COLORSPACE_UNKNOWN;
 
     CHECK_RENDERER_MAGIC(renderer, NULL);
 
@@ -1414,12 +1405,8 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s
         direct_update = SDL_FALSE;
     }
 
-    if (direct_update) {
-        default_colorspace = SDL_GetDefaultTextureColorspace(format);
-        colorspace = (SDL_Colorspace)SDL_GetNumberProperty(SDL_GetSurfaceProperties(surface), SDL_PROP_SURFACE_COLORSPACE_NUMBER, default_colorspace);
-    } else {
-        /* We're updating through an intermediate surface, so we lose colorspace information */
-        colorspace = SDL_COLORSPACE_RGB_DEFAULT;
+    if (SDL_GetSurfaceColorspace(surface, &colorspace) < 0) {
+        return NULL;
     }
 
     props = SDL_CreateProperties();

+ 9 - 1
src/render/direct3d/SDL_render_d3d.c

@@ -1566,6 +1566,14 @@ SDL_Renderer *D3D_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_pro
     }
     renderer->magic = &SDL_renderer_magic;
 
+    SDL_SetupRendererColorspace(renderer, create_props);
+
+    if (renderer->output_colorspace != SDL_COLORSPACE_SRGB) {
+        SDL_SetError("Unsupported output colorspace");
+        SDL_free(renderer);
+        return NULL;
+    }
+
     data = (D3D_RenderData *)SDL_calloc(1, sizeof(*data));
     if (!data) {
         SDL_free(renderer);
@@ -1573,9 +1581,9 @@ SDL_Renderer *D3D_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_pro
     }
 
     if (!D3D_LoadDLL(&data->d3dDLL, &data->d3d)) {
+        SDL_SetError("Unable to create Direct3D interface");
         SDL_free(renderer);
         SDL_free(data);
-        SDL_SetError("Unable to create Direct3D interface");
         return NULL;
     }
 

+ 93 - 29
src/render/direct3d11/SDL_render_d3d11.c

@@ -31,13 +31,14 @@
 #include "../SDL_d3dmath.h"
 
 #include <d3d11_1.h>
+#include <dxgi1_4.h>
 
 #include "SDL_shaders_d3d11.h"
 
 #ifdef SDL_PLATFORM_WINRT
 
 #if NTDDI_VERSION > NTDDI_WIN8
-#include <DXGI1_3.h>
+#include <dxgi1_3.h>
 #endif
 
 #include "SDL_render_winrt.h"
@@ -189,6 +190,7 @@ static const GUID SDL_IID_ID3D11Texture2D = { 0x6f15aaf2, 0xd208, 0x4e89, { 0x9a
 static const GUID SDL_IID_ID3D11Device1 = { 0xa04bfb29, 0x08ef, 0x43d6, { 0xa4, 0x9c, 0xa9, 0xbd, 0xbd, 0xcb, 0xe6, 0x86 } };
 static const GUID SDL_IID_ID3D11DeviceContext1 = { 0xbb2c6faa, 0xb5fb, 0x4082, { 0x8e, 0x6b, 0x38, 0x8b, 0x8c, 0xfa, 0x90, 0xe1 } };
 /*static const GUID SDL_IID_ID3D11Debug = { 0x79cf2233, 0x7536, 0x4948, { 0x9d, 0x36, 0x1e, 0x46, 0x92, 0xdc, 0x57, 0x60 } };*/
+static const GUID SDL_IID_IDXGISwapChain2 = { 0x94d99bdb, 0xf1f8, 0x4ab0, { 0xb2, 0x36, 0x7d, 0xa0, 0x17, 0x0e, 0xda, 0xb1 } };
 
 #ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
 #pragma GCC diagnostic pop
@@ -203,6 +205,10 @@ Uint32 D3D11_DXGIFormatToSDLPixelFormat(DXGI_FORMAT dxgiFormat)
     case DXGI_FORMAT_B8G8R8X8_UNORM:
     case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
         return SDL_PIXELFORMAT_XRGB8888;
+    case DXGI_FORMAT_R10G10B10A2_UNORM:
+        return SDL_PIXELFORMAT_XBGR2101010;
+    case DXGI_FORMAT_R16G16B16A16_FLOAT:
+        return SDL_PIXELFORMAT_RGBA64_FLOAT;
     default:
         return SDL_PIXELFORMAT_UNKNOWN;
     }
@@ -271,21 +277,12 @@ static void D3D11_ReleaseAll(SDL_Renderer *renderer)
     if (data) {
         int i;
 
-        SAFE_RELEASE(data->dxgiFactory);
-        SAFE_RELEASE(data->dxgiAdapter);
-        SAFE_RELEASE(data->d3dDevice);
-        SAFE_RELEASE(data->d3dContext);
-        SAFE_RELEASE(data->swapChain);
-        SAFE_RELEASE(data->mainRenderTargetView);
-        SAFE_RELEASE(data->currentOffscreenRenderTargetView);
-        SAFE_RELEASE(data->inputLayout);
-        for (i = 0; i < SDL_arraysize(data->vertexBuffers); ++i) {
-            SAFE_RELEASE(data->vertexBuffers[i]);
-        }
-        SAFE_RELEASE(data->vertexShader);
-        for (i = 0; i < SDL_arraysize(data->pixelShaders); ++i) {
-            SAFE_RELEASE(data->pixelShaders[i]);
-        }
+        SAFE_RELEASE(data->vertexShaderConstants);
+        SAFE_RELEASE(data->clippedRasterizer);
+        SAFE_RELEASE(data->mainRasterizer);
+        SAFE_RELEASE(data->linearSampler);
+        SAFE_RELEASE(data->nearestPixelSampler);
+
         if (data->blendModesCount > 0) {
             for (i = 0; i < data->blendModesCount; ++i) {
                 SAFE_RELEASE(data->blendModes[i].blendState);
@@ -294,11 +291,26 @@ static void D3D11_ReleaseAll(SDL_Renderer *renderer)
 
             data->blendModesCount = 0;
         }
-        SAFE_RELEASE(data->nearestPixelSampler);
-        SAFE_RELEASE(data->linearSampler);
-        SAFE_RELEASE(data->mainRasterizer);
-        SAFE_RELEASE(data->clippedRasterizer);
-        SAFE_RELEASE(data->vertexShaderConstants);
+        for (i = 0; i < SDL_arraysize(data->pixelShaders); ++i) {
+            SAFE_RELEASE(data->pixelShaders[i]);
+        }
+        SAFE_RELEASE(data->vertexShader);
+        for (i = 0; i < SDL_arraysize(data->vertexBuffers); ++i) {
+            SAFE_RELEASE(data->vertexBuffers[i]);
+        }
+        SAFE_RELEASE(data->inputLayout);
+        SAFE_RELEASE(data->currentOffscreenRenderTargetView);
+        SAFE_RELEASE(data->mainRenderTargetView);
+        SAFE_RELEASE(data->swapChain);
+
+        /* Make sure the swap chain is fully released */
+        ID3D11DeviceContext_ClearState(data->d3dContext);
+        ID3D11DeviceContext_Flush(data->d3dContext);
+
+        SAFE_RELEASE(data->d3dContext);
+        SAFE_RELEASE(data->d3dDevice);
+        SAFE_RELEASE(data->dxgiAdapter);
+        SAFE_RELEASE(data->dxgiFactory);
 
         data->swapEffect = (DXGI_SWAP_EFFECT)0;
         data->rotation = DXGI_MODE_ROTATION_UNSPECIFIED;
@@ -779,7 +791,17 @@ static HRESULT D3D11_CreateSwapChain(SDL_Renderer *renderer, int w, int h)
     SDL_zero(swapChainDesc);
     swapChainDesc.Width = w;
     swapChainDesc.Height = h;
-    swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; /* This is the most common swap chain format. */
+    switch (renderer->output_colorspace) {
+    case SDL_COLORSPACE_SCRGB:
+        swapChainDesc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT;
+        break;
+    case SDL_COLORSPACE_HDR10:
+        swapChainDesc.Format = DXGI_FORMAT_R10G10B10A2_UNORM;
+        break;
+    default:
+        swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; /* This is the most common swap chain format. */
+        break;
+    }
     swapChainDesc.Stereo = FALSE;
     swapChainDesc.SampleDesc.Count = 1; /* Don't use multi-sampling. */
     swapChainDesc.SampleDesc.Quality = 0;
@@ -865,6 +887,28 @@ static HRESULT D3D11_CreateSwapChain(SDL_Renderer *renderer, int w, int h)
     }
     data->swapEffect = swapChainDesc.SwapEffect;
 
+    IDXGISwapChain3 *swapChain3 = NULL;
+    if (SUCCEEDED(IDXGISwapChain1_QueryInterface(data->swapChain, &SDL_IID_IDXGISwapChain2, (void **)&swapChain3))) {
+        DXGI_COLOR_SPACE_TYPE ColorSpace;
+        switch (renderer->output_colorspace) {
+        case SDL_COLORSPACE_SCRGB:
+            ColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709;
+            break;
+        case SDL_COLORSPACE_HDR10:
+            ColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
+            break;
+        default:
+            /* sRGB */
+            ColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
+            break;
+        }
+        result = IDXGISwapChain3_SetColorSpace1(swapChain3, ColorSpace);
+        if (FAILED(result)) {
+            WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGISwapChain3::SetColorSpace1"), result);
+            goto done;
+        }
+    }
+
 done:
     SAFE_RELEASE(coreWindow);
     return result;
@@ -1004,10 +1048,20 @@ static HRESULT D3D11_CreateWindowSizeDependentResources(SDL_Renderer *renderer)
     /* Create a render target view of the swap chain back buffer. */
     D3D11_RENDER_TARGET_VIEW_DESC desc;
     SDL_zero(desc);
-    if (renderer->colorspace_conversion && renderer->output_colorspace == SDL_COLORSPACE_SRGB) {
-        desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;
-    } else {
-        desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+    switch (renderer->output_colorspace) {
+    case SDL_COLORSPACE_SCRGB:
+        desc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT;
+        break;
+    case SDL_COLORSPACE_HDR10:
+        desc.Format = DXGI_FORMAT_R10G10B10A2_UNORM;
+        break;
+    default:
+        if (renderer->colorspace_conversion && renderer->output_colorspace == SDL_COLORSPACE_SRGB) {
+            desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;
+        } else {
+            desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+        }
+        break;
     }
     desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
     result = ID3D11Device_CreateRenderTargetView(data->d3dDevice,
@@ -2354,12 +2408,14 @@ static int D3D11_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect,
     /* Copy the data into the desired buffer, converting pixels to the
      * desired format at the same time:
      */
-    status = SDL_ConvertPixels(
+    status = SDL_ConvertPixelsAndColorspace(
         rect->w, rect->h,
         D3D11_DXGIFormatToSDLPixelFormat(stagingTextureDesc.Format),
+        renderer->target ? renderer->target->colorspace : renderer->output_colorspace,
         textureMemory.pData,
         textureMemory.RowPitch,
         format,
+        renderer->input_colorspace,
         pixels,
         pitch);
 
@@ -2457,14 +2513,22 @@ SDL_Renderer *D3D11_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_p
     }
     renderer->magic = &SDL_renderer_magic;
 
+    SDL_SetupRendererColorspace(renderer, create_props);
+
+    if (renderer->output_colorspace != SDL_COLORSPACE_SRGB &&
+        renderer->output_colorspace != SDL_COLORSPACE_SCRGB &&
+        renderer->output_colorspace != SDL_COLORSPACE_HDR10) {
+        SDL_SetError("Unsupported output colorspace");
+        SDL_free(renderer);
+        return NULL;
+    }
+
     data = (D3D11_RenderData *)SDL_calloc(1, sizeof(*data));
     if (!data) {
         SDL_free(renderer);
         return NULL;
     }
 
-    SDL_SetupRendererColorspace(renderer, create_props);
-
     data->identity = MatrixIdentity();
 
     renderer->WindowEvent = D3D11_WindowEvent;

+ 59 - 10
src/render/direct3d12/SDL_render_d3d12.c

@@ -174,6 +174,7 @@ typedef struct
     ID3D12GraphicsCommandList2 *commandList;
     DXGI_SWAP_EFFECT swapEffect;
     UINT swapFlags;
+    DXGI_FORMAT renderTargetFormat;
     SDL_bool pixelSizeChanged;
 
     /* Descriptor heaps */
@@ -278,6 +279,10 @@ Uint32 D3D12_DXGIFormatToSDLPixelFormat(DXGI_FORMAT dxgiFormat)
     case DXGI_FORMAT_B8G8R8X8_UNORM:
     case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
         return SDL_PIXELFORMAT_XRGB8888;
+    case DXGI_FORMAT_R10G10B10A2_UNORM:
+        return SDL_PIXELFORMAT_XBGR2101010;
+    case DXGI_FORMAT_R16G16B16A16_FLOAT:
+        return SDL_PIXELFORMAT_RGBA64_FLOAT;
     default:
         return SDL_PIXELFORMAT_UNKNOWN;
     }
@@ -751,6 +756,8 @@ static HRESULT D3D12_CreateDeviceResources(SDL_Renderer *renderer)
         SDL_BLENDMODE_MUL
     };
     const DXGI_FORMAT defaultRTVFormats[] = {
+        DXGI_FORMAT_R16G16B16A16_FLOAT,
+        DXGI_FORMAT_R10G10B10A2_UNORM,
         DXGI_FORMAT_B8G8R8A8_UNORM,
         DXGI_FORMAT_B8G8R8X8_UNORM,
         DXGI_FORMAT_R8_UNORM
@@ -1178,7 +1185,24 @@ static HRESULT D3D12_CreateSwapChain(SDL_Renderer *renderer, int w, int h)
     SDL_zero(swapChainDesc);
     swapChainDesc.Width = w;
     swapChainDesc.Height = h;
-    swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; /* This is the most common swap chain format. */
+    switch (renderer->output_colorspace) {
+    case SDL_COLORSPACE_SCRGB:
+        swapChainDesc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT;
+        data->renderTargetFormat = DXGI_FORMAT_R16G16B16A16_FLOAT;
+        break;
+    case SDL_COLORSPACE_HDR10:
+        swapChainDesc.Format = DXGI_FORMAT_R10G10B10A2_UNORM;
+        data->renderTargetFormat = DXGI_FORMAT_R10G10B10A2_UNORM;
+        break;
+    default:
+        swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; /* This is the most common swap chain format. */
+        if (renderer->colorspace_conversion && renderer->output_colorspace == SDL_COLORSPACE_SRGB) {
+            data->renderTargetFormat = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;
+        } else {
+            data->renderTargetFormat = DXGI_FORMAT_B8G8R8A8_UNORM;
+        }
+        break;
+    }
     swapChainDesc.Stereo = FALSE;
     swapChainDesc.SampleDesc.Count = 1; /* Don't use multi-sampling. */
     swapChainDesc.SampleDesc.Quality = 0;
@@ -1227,6 +1251,25 @@ static HRESULT D3D12_CreateSwapChain(SDL_Renderer *renderer, int w, int h)
     data->swapEffect = swapChainDesc.SwapEffect;
     data->swapFlags = swapChainDesc.Flags;
 
+    DXGI_COLOR_SPACE_TYPE ColorSpace;
+    switch (renderer->output_colorspace) {
+    case SDL_COLORSPACE_SCRGB:
+        ColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709;
+        break;
+    case SDL_COLORSPACE_HDR10:
+        ColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
+        break;
+    default:
+        /* sRGB */
+        ColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
+        break;
+    }
+    result = D3D_CALL(data->swapChain, SetColorSpace1, ColorSpace);
+    if (FAILED(result)) {
+        WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGISwapChain3::SetColorSpace1"), result);
+        goto done;
+    }
+
 done:
     SAFE_RELEASE(swapChain);
     return result;
@@ -1354,11 +1397,7 @@ static HRESULT D3D12_CreateWindowSizeDependentResources(SDL_Renderer *renderer)
 #endif
 
         SDL_zero(rtvDesc);
-        if (renderer->colorspace_conversion && renderer->output_colorspace == SDL_COLORSPACE_SRGB) {
-            rtvDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;
-        } else {
-            rtvDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
-        }
+        rtvDesc.Format = data->renderTargetFormat;
         rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
 
         SDL_zero(rtvDescriptor);
@@ -2379,7 +2418,7 @@ static int D3D12_SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *c
     SDL_bool updateSubresource = SDL_FALSE;
     int i;
     D3D12_CPU_DESCRIPTOR_HANDLE firstShaderResource;
-    DXGI_FORMAT rtvFormat = DXGI_FORMAT_B8G8R8A8_UNORM;
+    DXGI_FORMAT rtvFormat = rendererData->renderTargetFormat;
 
     if (rendererData->textureRenderTarget) {
         rtvFormat = rendererData->textureRenderTarget->mainTextureFormat;
@@ -2877,12 +2916,14 @@ static int D3D12_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect,
     /* Copy the data into the desired buffer, converting pixels to the
      * desired format at the same time:
      */
-    status = SDL_ConvertPixels(
+    status = SDL_ConvertPixelsAndColorspace(
         rect->w, rect->h,
         D3D12_DXGIFormatToSDLPixelFormat(textureDesc.Format),
+        renderer->target ? renderer->target->colorspace : renderer->output_colorspace,
         textureMemory,
         pitchedDesc.RowPitch,
         format,
+        renderer->input_colorspace,
         pixels,
         pitch);
 
@@ -2997,14 +3038,22 @@ SDL_Renderer *D3D12_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_p
     }
     renderer->magic = &SDL_renderer_magic;
 
+    SDL_SetupRendererColorspace(renderer, create_props);
+
+    if (renderer->output_colorspace != SDL_COLORSPACE_SRGB &&
+        renderer->output_colorspace != SDL_COLORSPACE_SCRGB &&
+        renderer->output_colorspace != SDL_COLORSPACE_HDR10) {
+        SDL_SetError("Unsupported output colorspace");
+        SDL_free(renderer);
+        return NULL;
+    }
+
     data = (D3D12_RenderData *)SDL_calloc(1, sizeof(*data));
     if (!data) {
         SDL_free(renderer);
         return NULL;
     }
 
-    SDL_SetupRendererColorspace(renderer, create_props);
-
     data->identity = MatrixIdentity();
 
     renderer->WindowEvent = D3D12_WindowEvent;

+ 9 - 0
src/render/metal/SDL_render_metal.m

@@ -1737,6 +1737,15 @@ static SDL_Renderer *METAL_CreateRenderer(SDL_Window *window, SDL_PropertiesID c
         if (!renderer) {
             return NULL;
         }
+        renderer->magic = &SDL_renderer_magic;
+
+        SDL_SetupRendererColorspace(renderer, create_props);
+
+        if (renderer->output_colorspace != SDL_COLORSPACE_SRGB) {
+            SDL_SetError("Unsupported output colorspace");
+            SDL_free(renderer);
+            return NULL;
+        }
 
 #ifdef SDL_PLATFORM_MACOS
         if (SDL_GetHintBoolean(SDL_HINT_RENDER_METAL_PREFER_LOW_POWER_DEVICE, SDL_TRUE)) {

+ 11 - 2
src/render/opengl/SDL_render_gl.c

@@ -1710,14 +1710,20 @@ static SDL_Renderer *GL_CreateRenderer(SDL_Window *window, SDL_PropertiesID crea
         goto error;
     }
 
+    SDL_SetupRendererColorspace(renderer, create_props);
+
+    if (renderer->output_colorspace != SDL_COLORSPACE_SRGB) {
+        SDL_SetError("Unsupported output colorspace");
+        SDL_free(renderer);
+        goto error;
+    }
+
     data = (GL_RenderData *)SDL_calloc(1, sizeof(*data));
     if (!data) {
         SDL_free(renderer);
         goto error;
     }
 
-    SDL_SetupRendererColorspace(renderer, create_props);
-
     renderer->WindowEvent = GL_WindowEvent;
     renderer->SupportsBlendMode = GL_SupportsBlendMode;
     renderer->CreateTexture = GL_CreateTexture;
@@ -1939,10 +1945,13 @@ static SDL_Renderer *GL_CreateRenderer(SDL_Window *window, SDL_PropertiesID crea
 error:
     if (changed_window) {
         /* Uh oh, better try to put it back... */
+        char *error = SDL_strdup(SDL_GetError());
         SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile_mask);
         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major);
         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor);
         SDL_RecreateWindow(window, window_flags);
+        SDL_SetError("%s", error);
+        SDL_free(error);
     }
     return NULL;
 }

+ 13 - 1
src/render/opengles2/SDL_render_gles2.c

@@ -886,7 +886,7 @@ static int GLES2_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, S
             verts->position.y = xy_[1] * scale_y;
 
             if (colorswap) {
-                Uint8 r = col_.r;
+                float r = col_.r;
                 col_.r = col_.b;
                 col_.b = r;
             }
@@ -2049,6 +2049,15 @@ static SDL_Renderer *GLES2_CreateRenderer(SDL_Window *window, SDL_PropertiesID c
     if (!renderer) {
         goto error;
     }
+    renderer->magic = &SDL_renderer_magic;
+
+    SDL_SetupRendererColorspace(renderer, create_props);
+
+    if (renderer->output_colorspace != SDL_COLORSPACE_SRGB) {
+        SDL_SetError("Unsupported output colorspace");
+        SDL_free(renderer);
+        goto error;
+    }
 
     data = (GLES2_RenderData *)SDL_calloc(1, sizeof(GLES2_RenderData));
     if (!data) {
@@ -2207,10 +2216,13 @@ static SDL_Renderer *GLES2_CreateRenderer(SDL_Window *window, SDL_PropertiesID c
 error:
     if (changed_window) {
         /* Uh oh, better try to put it back... */
+        char *error = SDL_strdup(SDL_GetError());
         SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile_mask);
         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major);
         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor);
         SDL_RecreateWindow(window, window_flags);
+        SDL_SetError("%s", error);
+        SDL_free(error);
     }
     return NULL;
 }

+ 9 - 0
src/render/ps2/SDL_render_ps2.c

@@ -630,6 +630,15 @@ static SDL_Renderer *PS2_CreateRenderer(SDL_Window *window, SDL_PropertiesID cre
     if (!renderer) {
         return NULL;
     }
+    renderer->magic = &SDL_renderer_magic;
+
+    SDL_SetupRendererColorspace(renderer, create_props);
+
+    if (renderer->output_colorspace != SDL_COLORSPACE_SRGB) {
+        SDL_SetError("Unsupported output colorspace");
+        SDL_free(renderer);
+        return NULL;
+    }
 
     data = (PS2_RenderData *)SDL_calloc(1, sizeof(*data));
     if (!data) {

+ 9 - 0
src/render/psp/SDL_render_psp.c

@@ -1312,6 +1312,15 @@ SDL_Renderer *PSP_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_pro
     if (!renderer) {
         return NULL;
     }
+    renderer->magic = &SDL_renderer_magic;
+
+    SDL_SetupRendererColorspace(renderer, create_props);
+
+    if (renderer->output_colorspace != SDL_COLORSPACE_SRGB) {
+        SDL_SetError("Unsupported output colorspace");
+        SDL_free(renderer);
+        return NULL;
+    }
 
     data = (PSP_RenderData *)SDL_calloc(1, sizeof(*data));
     if (!data) {

+ 1 - 0
src/render/software/SDL_render_sw.c

@@ -1124,6 +1124,7 @@ SDL_Renderer *SW_CreateRendererForSurface(SDL_Surface *surface)
     if (!renderer) {
         return NULL;
     }
+    renderer->magic = &SDL_renderer_magic;
 
     data = (SW_RenderData *)SDL_calloc(1, sizeof(*data));
     if (!data) {

+ 9 - 0
src/render/vitagxm/SDL_render_vita_gxm.c

@@ -220,6 +220,15 @@ SDL_Renderer *VITA_GXM_CreateRenderer(SDL_Window *window, SDL_PropertiesID creat
     if (!renderer) {
         return NULL;
     }
+    renderer->magic = &SDL_renderer_magic;
+
+    SDL_SetupRendererColorspace(renderer, create_props);
+
+    if (renderer->output_colorspace != SDL_COLORSPACE_SRGB) {
+        SDL_SetError("Unsupported output colorspace");
+        SDL_free(renderer);
+        return NULL;
+    }
 
     data = (VITA_GXM_RenderData *)SDL_calloc(1, sizeof(VITA_GXM_RenderData));
     if (!data) {

+ 9 - 11
src/video/SDL_blit.c

@@ -183,23 +183,21 @@ static SDL_BlitFunc SDL_ChooseBlitFunc(Uint32 src_format, Uint32 dst_format, int
 }
 #endif /* SDL_HAVE_BLIT_AUTO */
 
-static SDL_Colorspace GetSurfaceColorspace(SDL_Surface *surface)
-{
-    if (surface->flags & SDL_SURFACE_USES_PROPERTIES) {
-        SDL_PropertiesID props = SDL_GetSurfaceProperties(surface);
-        return (SDL_Colorspace)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_COLORSPACE_NUMBER, SDL_COLORSPACE_RGB_DEFAULT);
-    }
-    return SDL_COLORSPACE_RGB_DEFAULT;
-}
-
 /* Figure out which of many blit routines to set up on a surface */
 int SDL_CalculateBlit(SDL_Surface *surface)
 {
     SDL_BlitFunc blit = NULL;
     SDL_BlitMap *map = surface->map;
     SDL_Surface *dst = map->dst;
-    SDL_Colorspace src_colorspace = GetSurfaceColorspace(surface);
-    SDL_Colorspace dst_colorspace = GetSurfaceColorspace(dst);
+    SDL_Colorspace src_colorspace = SDL_COLORSPACE_UNKNOWN;
+    SDL_Colorspace dst_colorspace = SDL_COLORSPACE_UNKNOWN;
+
+    if (SDL_GetSurfaceColorspace(surface, &src_colorspace) < 0) {
+        return -1;
+    }
+    if (SDL_GetSurfaceColorspace(dst, &dst_colorspace) < 0) {
+        return -1;
+    }
 
     /* We don't currently support blitting to < 8 bpp surfaces */
     if (dst->format->BitsPerPixel < 8) {

+ 20 - 14
src/video/SDL_blit.h

@@ -262,13 +262,16 @@ extern SDL_BlitFunc SDL_CalculateBlitA(SDL_Surface *surface);
         a = (a * 3) / 255;                             \
         Pixel = (a << 30) | (r << 20) | (g << 10) | b; \
     }
-#define ARGB2101010_FROM_RGBAFLOAT(Pixel, r, g, b, a)  \
-    {                                                  \
-        r = SDL_clamp(r, 0.0f, 1.0f) * 1023.0f;        \
-        g = SDL_clamp(g, 0.0f, 1.0f) * 1023.0f;        \
-        b = SDL_clamp(b, 0.0f, 1.0f) * 1023.0f;        \
-        a = SDL_clamp(a, 0.0f, 1.0f) * 3.0f;           \
-        Pixel = (((Uint32)a) << 30) | (((Uint32)r) << 20) | (((Uint32)g) << 10) | (Uint32)b; \
+#define ARGB2101010_FROM_RGBAFLOAT(Pixel, r, g, b, a) \
+    {                                                 \
+        r = SDL_clamp(r, 0.0f, 1.0f) * 1023.0f;       \
+        g = SDL_clamp(g, 0.0f, 1.0f) * 1023.0f;       \
+        b = SDL_clamp(b, 0.0f, 1.0f) * 1023.0f;       \
+        a = SDL_clamp(a, 0.0f, 1.0f) * 3.0f;          \
+        Pixel = (((Uint32)SDL_roundf(a)) << 30) |     \
+                (((Uint32)SDL_roundf(r)) << 20) |     \
+                (((Uint32)SDL_roundf(g)) << 10) |     \
+                (Uint32)SDL_roundf(b);                \
     }
 #define ABGR2101010_FROM_RGBA(Pixel, r, g, b, a)       \
     {                                                  \
@@ -278,13 +281,16 @@ extern SDL_BlitFunc SDL_CalculateBlitA(SDL_Surface *surface);
         a = (a * 3) / 255;                             \
         Pixel = (a << 30) | (b << 20) | (g << 10) | r; \
     }
-#define ABGR2101010_FROM_RGBAFLOAT(Pixel, r, g, b, a)  \
-    {                                                  \
-        r = SDL_clamp(r, 0.0f, 1.0f) * 1023.0f;        \
-        g = SDL_clamp(g, 0.0f, 1.0f) * 1023.0f;        \
-        b = SDL_clamp(b, 0.0f, 1.0f) * 1023.0f;        \
-        a = SDL_clamp(a, 0.0f, 1.0f) * 3.0f;           \
-        Pixel = (((Uint32)a) << 30) | (((Uint32)b) << 20) | (((Uint32)g) << 10) | (Uint32)r; \
+#define ABGR2101010_FROM_RGBAFLOAT(Pixel, r, g, b, a) \
+    {                                                 \
+        r = SDL_clamp(r, 0.0f, 1.0f) * 1023.0f;       \
+        g = SDL_clamp(g, 0.0f, 1.0f) * 1023.0f;       \
+        b = SDL_clamp(b, 0.0f, 1.0f) * 1023.0f;       \
+        a = SDL_clamp(a, 0.0f, 1.0f) * 3.0f;          \
+        Pixel = (((Uint32)SDL_roundf(a)) << 30) |     \
+                (((Uint32)SDL_roundf(b)) << 20) |     \
+                (((Uint32)SDL_roundf(g)) << 10) |     \
+                (Uint32)SDL_roundf(r);                \
     }
 #define ASSEMBLE_RGB(buf, bpp, fmt, r, g, b)        \
     {                                               \

+ 47 - 97
src/video/SDL_blit_slow.c

@@ -366,65 +366,6 @@ static Uint16 float_to_half(float a)
     return ir;
 }
 
-static float scRGBtoNits(float v)
-{
-    return v * 80.0f;
-}
-
-static float scRGBfromNits(float v)
-{
-    return v / 80.0f;
-}
-
-static float sRGBtoNits(float v)
-{
-    if (v <= 0.04045f) {
-        v = (v / 12.92f);
-    } else {
-        v = SDL_powf((v + 0.055f) / 1.055f, 2.4f);
-    }
-    return scRGBtoNits(v);
-}
-
-static float sRGBfromNits(float v)
-{
-    v = scRGBfromNits(v);
-
-    if (v <= 0.0031308f) {
-        v = (v * 12.92f);
-    } else {
-        v = (SDL_powf(v, 1.0f / 2.4f) * 1.055f - 0.055f);
-    }
-    return v;
-}
-
-static float PQtoNits(float v)
-{
-    const float c1 = 0.8359375f;
-    const float c2 = 18.8515625f;
-    const float c3 = 18.6875f;
-    const float oo_m1 = 1.0f / 0.1593017578125f;
-    const float oo_m2 = 1.0f / 78.84375f;
-
-    float num = SDL_max(SDL_powf(v, oo_m2) - c1, 0.0f);
-    float den = c2 - c3 * SDL_powf(v, oo_m2);
-    return 10000.0f * SDL_powf(num / den, oo_m1);
-}
-
-static float PQfromNits(float v)
-{
-    const float c1 = 0.8359375f;
-    const float c2 = 18.8515625f;
-    const float c3 = 18.6875f;
-    const float m1 = 0.1593017578125f;
-    const float m2 = 78.84375f;
-
-    float y = SDL_clamp(v / 10000.0f, 0.0f, 1.0f);
-    float num = c1 + c2 * pow(y, m1);
-    float den = 1.0f + c3 * pow(y, m1);
-    return pow(num / den, m2);
-}
-
 static void ReadFloatPixel(Uint8 *pixels, SlowBlitPixelAccess access, SDL_PixelFormat *fmt, SDL_Colorspace colorspace,
                            float *outR, float *outG, float *outB, float *outA)
 {
@@ -555,20 +496,20 @@ static void ReadFloatPixel(Uint8 *pixels, SlowBlitPixelAccess access, SDL_PixelF
     /* Convert to nits so src and dst are guaranteed to be linear and in the same units */
     switch (SDL_COLORSPACETRANSFER(colorspace)) {
     case SDL_TRANSFER_CHARACTERISTICS_SRGB:
-        fR = sRGBtoNits(fR);
-        fG = sRGBtoNits(fG);
-        fB = sRGBtoNits(fB);
+        fR = SDL_sRGBtoNits(fR);
+        fG = SDL_sRGBtoNits(fG);
+        fB = SDL_sRGBtoNits(fB);
         break;
     case SDL_TRANSFER_CHARACTERISTICS_PQ:
-        fR = PQtoNits(fR);
-        fG = PQtoNits(fG);
-        fB = PQtoNits(fB);
+        fR = SDL_PQtoNits(fR);
+        fG = SDL_PQtoNits(fG);
+        fB = SDL_PQtoNits(fB);
         break;
     case SDL_TRANSFER_CHARACTERISTICS_LINEAR:
         /* Assuming scRGB for now */
-        fR = scRGBtoNits(fR);
-        fG = scRGBtoNits(fG);
-        fB = scRGBtoNits(fB);
+        fR = SDL_scRGBtoNits(fR);
+        fG = SDL_scRGBtoNits(fG);
+        fB = SDL_scRGBtoNits(fB);
         break;
     default:
         /* Unknown, leave it alone */
@@ -590,20 +531,20 @@ static void WriteFloatPixel(Uint8 *pixels, SlowBlitPixelAccess access, SDL_Pixel
     /* We converted to nits so src and dst are guaranteed to be linear and in the same units */
     switch (SDL_COLORSPACETRANSFER(colorspace)) {
     case SDL_TRANSFER_CHARACTERISTICS_SRGB:
-        fR = sRGBfromNits(fR);
-        fG = sRGBfromNits(fG);
-        fB = sRGBfromNits(fB);
+        fR = SDL_sRGBfromNits(fR);
+        fG = SDL_sRGBfromNits(fG);
+        fB = SDL_sRGBfromNits(fB);
         break;
     case SDL_TRANSFER_CHARACTERISTICS_PQ:
-        fR = PQfromNits(fR);
-        fG = PQfromNits(fG);
-        fB = PQfromNits(fB);
+        fR = SDL_PQfromNits(fR);
+        fG = SDL_PQfromNits(fG);
+        fB = SDL_PQfromNits(fB);
         break;
     case SDL_TRANSFER_CHARACTERISTICS_LINEAR:
         /* Assuming scRGB for now */
-        fR = scRGBfromNits(fR);
-        fG = scRGBfromNits(fG);
-        fB = scRGBfromNits(fB);
+        fR = SDL_scRGBfromNits(fR);
+        fG = SDL_scRGBfromNits(fG);
+        fB = SDL_scRGBfromNits(fB);
         break;
     default:
         /* Unknown, leave it alone */
@@ -612,16 +553,16 @@ static void WriteFloatPixel(Uint8 *pixels, SlowBlitPixelAccess access, SDL_Pixel
 
     switch (access) {
     case SlowBlitPixelAccess_RGB:
-        R = (Uint8)(SDL_clamp(fR, 0.0f, 1.0f) * 255.0f);
-        G = (Uint8)(SDL_clamp(fG, 0.0f, 1.0f) * 255.0f);
-        B = (Uint8)(SDL_clamp(fB, 0.0f, 1.0f) * 255.0f);
+        R = (Uint8)SDL_roundf(SDL_clamp(fR, 0.0f, 1.0f) * 255.0f);
+        G = (Uint8)SDL_roundf(SDL_clamp(fG, 0.0f, 1.0f) * 255.0f);
+        B = (Uint8)SDL_roundf(SDL_clamp(fB, 0.0f, 1.0f) * 255.0f);
         ASSEMBLE_RGB(pixels, fmt->BytesPerPixel, fmt, R, G, B);
         break;
     case SlowBlitPixelAccess_RGBA:
-        R = (Uint8)(SDL_clamp(fR, 0.0f, 1.0f) * 255.0f);
-        G = (Uint8)(SDL_clamp(fG, 0.0f, 1.0f) * 255.0f);
-        B = (Uint8)(SDL_clamp(fB, 0.0f, 1.0f) * 255.0f);
-        A = (Uint8)(SDL_clamp(fA, 0.0f, 1.0f) * 255.0f);
+        R = (Uint8)SDL_roundf(SDL_clamp(fR, 0.0f, 1.0f) * 255.0f);
+        G = (Uint8)SDL_roundf(SDL_clamp(fG, 0.0f, 1.0f) * 255.0f);
+        B = (Uint8)SDL_roundf(SDL_clamp(fB, 0.0f, 1.0f) * 255.0f);
+        A = (Uint8)SDL_roundf(SDL_clamp(fA, 0.0f, 1.0f) * 255.0f);
         ASSEMBLE_RGBA(pixels, fmt->BytesPerPixel, fmt, R, G, B, A);
         break;
     case SlowBlitPixelAccess_10Bit:
@@ -692,11 +633,11 @@ static void WriteFloatPixel(Uint8 *pixels, SlowBlitPixelAccess access, SDL_Pixel
         }
         switch (SDL_PIXELTYPE(fmt->format)) {
         case SDL_PIXELTYPE_ARRAYU16:
-            ((Uint16 *)pixels)[0] = (Uint16)(SDL_clamp(v[0], 0.0f, 1.0f) * SDL_MAX_UINT16);
-            ((Uint16 *)pixels)[1] = (Uint16)(SDL_clamp(v[1], 0.0f, 1.0f) * SDL_MAX_UINT16);
-            ((Uint16 *)pixels)[2] = (Uint16)(SDL_clamp(v[2], 0.0f, 1.0f) * SDL_MAX_UINT16);
+            ((Uint16 *)pixels)[0] = (Uint16)SDL_roundf(SDL_clamp(v[0], 0.0f, 1.0f) * SDL_MAX_UINT16);
+            ((Uint16 *)pixels)[1] = (Uint16)SDL_roundf(SDL_clamp(v[1], 0.0f, 1.0f) * SDL_MAX_UINT16);
+            ((Uint16 *)pixels)[2] = (Uint16)SDL_roundf(SDL_clamp(v[2], 0.0f, 1.0f) * SDL_MAX_UINT16);
             if (fmt->BytesPerPixel == 8) {
-                ((Uint16 *)pixels)[3] = (Uint16)(SDL_clamp(v[3], 0.0f, 1.0f) * SDL_MAX_UINT16);
+                ((Uint16 *)pixels)[3] = (Uint16)SDL_roundf(SDL_clamp(v[3], 0.0f, 1.0f) * SDL_MAX_UINT16);
             }
             break;
         case SDL_PIXELTYPE_ARRAYF16:
@@ -765,17 +706,26 @@ void SDL_Blit_Slow_Float(SDL_BlitInfo *info)
     int dstbpp = dst_fmt->BytesPerPixel;
     SlowBlitPixelAccess src_access;
     SlowBlitPixelAccess dst_access;
-    SDL_PropertiesID src_props = SDL_GetSurfaceProperties(info->src_surface);
-    SDL_Colorspace src_colorspace = (SDL_Colorspace)SDL_GetNumberProperty(src_props, SDL_PROP_SURFACE_COLORSPACE_NUMBER, SDL_COLORSPACE_RGB_DEFAULT);
-    SDL_ColorPrimaries src_primaries = SDL_COLORSPACEPRIMARIES(src_colorspace);
-    SDL_TransferCharacteristics src_transfer = SDL_COLORSPACETRANSFER(src_colorspace);
-    SDL_PropertiesID dst_props = SDL_GetSurfaceProperties(info->dst_surface);
-    SDL_Colorspace dst_colorspace = (SDL_Colorspace)SDL_GetNumberProperty(dst_props, SDL_PROP_SURFACE_COLORSPACE_NUMBER, SDL_COLORSPACE_RGB_DEFAULT);
-    SDL_ColorPrimaries dst_primaries = SDL_COLORSPACEPRIMARIES(dst_colorspace);
-    SDL_TransferCharacteristics dst_transfer = SDL_COLORSPACETRANSFER(dst_colorspace);
-    const float *color_primaries_matrix = SDL_GetColorPrimariesConversionMatrix(src_primaries, dst_primaries);
+    SDL_Colorspace src_colorspace;
+    SDL_ColorPrimaries src_primaries;
+    SDL_TransferCharacteristics src_transfer;
+    SDL_Colorspace dst_colorspace;
+    SDL_ColorPrimaries dst_primaries;
+    SDL_TransferCharacteristics dst_transfer;
+    const float *color_primaries_matrix;
     SDL_bool compress_PQ = SDL_FALSE;
 
+    if (SDL_GetSurfaceColorspace(info->src_surface, &src_colorspace) < 0 ||
+        SDL_GetSurfaceColorspace(info->dst_surface, &dst_colorspace) < 0) {
+        return;
+    }
+
+    src_primaries = SDL_COLORSPACEPRIMARIES(src_colorspace);
+    src_transfer = SDL_COLORSPACETRANSFER(src_colorspace);
+    dst_primaries = SDL_COLORSPACEPRIMARIES(dst_colorspace);
+    dst_transfer = SDL_COLORSPACETRANSFER(dst_colorspace);
+    color_primaries_matrix = SDL_GetColorPrimariesConversionMatrix(src_primaries, dst_primaries);
+
     if (src_transfer == SDL_TRANSFER_CHARACTERISTICS_PQ &&
         dst_transfer != SDL_TRANSFER_CHARACTERISTICS_PQ &&
         dst_colorspace != SDL_COLORSPACE_SCRGB) {

+ 72 - 0
src/video/SDL_pixels.c

@@ -692,6 +692,78 @@ void SDL_DestroyPixelFormat(SDL_PixelFormat *format)
     return;
 }
 
+SDL_Colorspace SDL_GetDefaultColorspaceForFormat(Uint32 format)
+{
+    if (SDL_ISPIXELFORMAT_FOURCC(format)) {
+        return SDL_COLORSPACE_BT709_FULL;
+    } else if (SDL_ISPIXELFORMAT_FLOAT(format)) {
+        return SDL_COLORSPACE_SCRGB;
+    } else if (SDL_ISPIXELFORMAT_10BIT(format)) {
+        return SDL_COLORSPACE_HDR10;
+    } else {
+        return SDL_COLORSPACE_RGB_DEFAULT;
+    }
+}
+
+float SDL_scRGBtoNits(float v)
+{
+    return v * 80.0f;
+}
+
+float SDL_scRGBfromNits(float v)
+{
+    return v / 80.0f;
+}
+
+float SDL_sRGBtoNits(float v)
+{
+    if (v <= 0.04045f) {
+        v = (v / 12.92f);
+    } else {
+        v = SDL_powf((v + 0.055f) / 1.055f, 2.4f);
+    }
+    return SDL_scRGBtoNits(v);
+}
+
+float SDL_sRGBfromNits(float v)
+{
+    v = SDL_scRGBfromNits(v);
+
+    if (v <= 0.0031308f) {
+        v = (v * 12.92f);
+    } else {
+        v = (SDL_powf(v, 1.0f / 2.4f) * 1.055f - 0.055f);
+    }
+    return v;
+}
+
+float SDL_PQtoNits(float v)
+{
+    const float c1 = 0.8359375f;
+    const float c2 = 18.8515625f;
+    const float c3 = 18.6875f;
+    const float oo_m1 = 1.0f / 0.1593017578125f;
+    const float oo_m2 = 1.0f / 78.84375f;
+
+    float num = SDL_max(SDL_powf(v, oo_m2) - c1, 0.0f);
+    float den = c2 - c3 * SDL_powf(v, oo_m2);
+    return 10000.0f * SDL_powf(num / den, oo_m1);
+}
+
+float SDL_PQfromNits(float v)
+{
+    const float c1 = 0.8359375f;
+    const float c2 = 18.8515625f;
+    const float c3 = 18.6875f;
+    const float m1 = 0.1593017578125f;
+    const float m2 = 78.84375f;
+
+    float y = SDL_clamp(v / 10000.0f, 0.0f, 1.0f);
+    float num = c1 + c2 * pow(y, m1);
+    float den = 1.0f + c3 * pow(y, m1);
+    return pow(num / den, m2);
+}
+
 SDL_Palette *SDL_CreatePalette(int ncolors)
 {
     SDL_Palette *palette;

+ 10 - 0
src/video/SDL_pixels_c.h

@@ -31,6 +31,16 @@
 /* Pixel format functions */
 extern int SDL_InitFormat(SDL_PixelFormat *format, Uint32 pixel_format);
 extern int SDL_CalculateSize(Uint32 format, int width, int height, size_t *size, size_t *pitch, SDL_bool minimalPitch);
+extern SDL_Colorspace SDL_GetDefaultColorspaceForFormat(Uint32 pixel_format);
+
+/* Colorspace conversion functions */
+extern float SDL_scRGBtoNits(float v);
+extern float SDL_scRGBfromNits(float v);
+extern float SDL_sRGBtoNits(float v);
+extern float SDL_sRGBfromNits(float v);
+extern float SDL_PQtoNits(float v);
+extern float SDL_PQfromNits(float v);
+
 
 /* Blit mapping functions */
 extern SDL_BlitMap *SDL_AllocBlitMap(void);

+ 93 - 35
src/video/SDL_surface.c

@@ -269,6 +269,36 @@ SDL_PropertiesID SDL_GetSurfaceProperties(SDL_Surface *surface)
     return props;
 }
 
+int SDL_SetSurfaceColorspace(SDL_Surface *surface, SDL_Colorspace colorspace)
+{
+    if (!surface) {
+        return SDL_InvalidParamError("surface");
+    }
+
+    return SDL_SetNumberProperty(SDL_GetSurfaceProperties(surface), SDL_PROP_SURFACE_COLORSPACE_NUMBER, colorspace);
+}
+
+int SDL_GetSurfaceColorspace(SDL_Surface *surface, SDL_Colorspace *colorspace)
+{
+    SDL_Colorspace surface_colorspace = SDL_COLORSPACE_UNKNOWN;
+
+    if (!surface) {
+        return SDL_InvalidParamError("surface");
+    }
+
+    if (surface->flags & SDL_SURFACE_USES_PROPERTIES) {
+        surface_colorspace = (SDL_Colorspace)SDL_GetNumberProperty(SDL_GetSurfaceProperties(surface), SDL_PROP_SURFACE_COLORSPACE_NUMBER, SDL_COLORSPACE_UNKNOWN);
+    }
+    if (surface_colorspace == SDL_COLORSPACE_UNKNOWN) {
+        surface_colorspace = SDL_GetDefaultColorspaceForFormat(surface->format->format);
+    }
+
+    if (colorspace) {
+        *colorspace = surface_colorspace;
+    }
+    return 0;
+}
+
 int SDL_SetSurfacePalette(SDL_Surface *surface, SDL_Palette *palette)
 {
     if (!surface) {
@@ -1159,25 +1189,10 @@ int SDL_FlipSurface(SDL_Surface *surface, SDL_FlipMode flip)
     }
 }
 
-/*
- * Creates a new surface identical to the existing surface
- */
-SDL_Surface *SDL_DuplicateSurface(SDL_Surface *surface)
-{
-    if (!surface) {
-        SDL_InvalidParamError("surface");
-        return NULL;
-    }
-
-    return SDL_ConvertSurface(surface, surface->format);
-}
-
-/*
- * Convert a surface into the specified pixel format.
- */
-SDL_Surface *SDL_ConvertSurface(SDL_Surface *surface, const SDL_PixelFormat *format)
+static SDL_Surface *SDL_ConvertSurfaceWithPixelFormatAndColorspace(SDL_Surface *surface, const SDL_PixelFormat *format, Uint32 colorspace)
 {
     SDL_Surface *convert;
+    SDL_Colorspace src_colorspace;
     Uint32 copy_flags;
     SDL_Color copy_color;
     SDL_Rect bounds;
@@ -1211,19 +1226,23 @@ SDL_Surface *SDL_ConvertSurface(SDL_Surface *surface, const SDL_PixelFormat *for
         }
     }
 
+    if (SDL_GetSurfaceColorspace(surface, &src_colorspace) < 0) {
+        return NULL;
+    }
+
     /* Create a new surface with the desired format */
     convert = SDL_CreateSurface(surface->w, surface->h, format->format);
     if (!convert) {
         return NULL;
     }
 
-    if (SDL_ISPIXELFORMAT_FOURCC(format->format) || SDL_ISPIXELFORMAT_FOURCC(surface->format->format)) {
-
-        ret = SDL_ConvertPixels(surface->w, surface->h,
-                                surface->format->format, surface->pixels, surface->pitch,
-                                convert->format->format, convert->pixels, convert->pitch);
+    if (colorspace == SDL_COLORSPACE_UNKNOWN) {
+        colorspace = src_colorspace;
+    }
+    SDL_SetSurfaceColorspace(convert, colorspace);
 
-        if (ret < 0) {
+    if (SDL_ISPIXELFORMAT_FOURCC(format->format) || SDL_ISPIXELFORMAT_FOURCC(surface->format->format)) {
+        if (SDL_ConvertPixelsAndColorspace(surface->w, surface->h, surface->format->format, src_colorspace, surface->pixels, surface->pitch, convert->format->format, colorspace, convert->pixels, convert->pitch) < 0) {
             SDL_DestroySurface(convert);
             return NULL;
         }
@@ -1430,15 +1449,35 @@ end:
     return convert;
 }
 
+SDL_Surface *SDL_DuplicateSurface(SDL_Surface *surface)
+{
+    if (!surface) {
+        SDL_InvalidParamError("surface");
+        return NULL;
+    }
+
+    return SDL_ConvertSurfaceWithPixelFormatAndColorspace(surface, surface->format, SDL_COLORSPACE_UNKNOWN);
+}
+
+SDL_Surface *SDL_ConvertSurface(SDL_Surface *surface, const SDL_PixelFormat *format)
+{
+    return SDL_ConvertSurfaceWithPixelFormatAndColorspace(surface, format, SDL_COLORSPACE_UNKNOWN);
+}
+
 SDL_Surface *SDL_ConvertSurfaceFormat(SDL_Surface *surface, Uint32 pixel_format)
 {
-    SDL_PixelFormat *fmt;
+    return SDL_ConvertSurfaceFormatAndColorspace(surface, pixel_format, SDL_COLORSPACE_UNKNOWN);
+}
+
+SDL_Surface *SDL_ConvertSurfaceFormatAndColorspace(SDL_Surface *surface, Uint32 pixel_format, SDL_Colorspace colorspace)
+{
+    SDL_PixelFormat *format;
     SDL_Surface *convert = NULL;
 
-    fmt = SDL_CreatePixelFormat(pixel_format);
-    if (fmt) {
-        convert = SDL_ConvertSurface(surface, fmt);
-        SDL_DestroyPixelFormat(fmt);
+    format = SDL_CreatePixelFormat(pixel_format);
+    if (format) {
+        convert = SDL_ConvertSurfaceWithPixelFormatAndColorspace(surface, format, colorspace);
+        SDL_DestroyPixelFormat(format);
     }
     return convert;
 }
@@ -1481,12 +1520,9 @@ static SDL_INLINE SDL_bool SDL_CreateSurfaceOnStack(int width, int height, Uint3
     return SDL_TRUE;
 }
 
-/*
- * Copy a block of pixels of one format to another format
- */
-int SDL_ConvertPixels(int width, int height,
-                      Uint32 src_format, const void *src, int src_pitch,
-                      Uint32 dst_format, void *dst, int dst_pitch)
+int SDL_ConvertPixelsAndColorspace(int width, int height,
+                      Uint32 src_format, SDL_Colorspace src_colorspace, const void *src, int src_pitch,
+                      Uint32 dst_format, SDL_Colorspace dst_colorspace, void *dst, int dst_pitch)
 {
     SDL_Surface src_surface, dst_surface;
     SDL_PixelFormat src_fmt, dst_fmt;
@@ -1523,7 +1559,7 @@ int SDL_ConvertPixels(int width, int height,
 #endif
 
     /* Fast path for same format copy */
-    if (src_format == dst_format) {
+    if (src_format == dst_format && src_colorspace == dst_colorspace) {
         int i;
         const int bpp = SDL_BYTESPERPIXEL(src_format);
         width *= bpp;
@@ -1540,10 +1576,17 @@ int SDL_ConvertPixels(int width, int height,
                                   &src_surface, &src_fmt, &src_blitmap)) {
         return -1;
     }
+    if (src_colorspace != SDL_COLORSPACE_UNKNOWN) {
+        SDL_SetNumberProperty(SDL_GetSurfaceProperties(&src_surface), SDL_PROP_SURFACE_COLORSPACE_NUMBER, src_colorspace);
+    }
+
     if (!SDL_CreateSurfaceOnStack(width, height, dst_format, dst, dst_pitch,
                                   &dst_surface, &dst_fmt, &dst_blitmap)) {
         return -1;
     }
+    if (dst_colorspace != SDL_COLORSPACE_UNKNOWN) {
+        SDL_SetNumberProperty(SDL_GetSurfaceProperties(&dst_surface), SDL_PROP_SURFACE_COLORSPACE_NUMBER, dst_colorspace);
+    }
 
     /* Set up the rect and go! */
     rect.x = 0;
@@ -1555,9 +1598,24 @@ int SDL_ConvertPixels(int width, int height,
     /* Free blitmap reference, after blitting between stack'ed surfaces */
     SDL_InvalidateMap(src_surface.map);
 
+    if (src_colorspace != SDL_COLORSPACE_UNKNOWN) {
+        SDL_DestroyProperties(SDL_GetSurfaceProperties(&src_surface));
+    }
+    if (dst_colorspace != SDL_COLORSPACE_UNKNOWN) {
+        SDL_DestroyProperties(SDL_GetSurfaceProperties(&dst_surface));
+    }
     return ret;
 }
 
+int SDL_ConvertPixels(int width, int height,
+                      Uint32 src_format, const void *src, int src_pitch,
+                      Uint32 dst_format, void *dst, int dst_pitch)
+{
+    return SDL_ConvertPixelsAndColorspace(width, height,
+                      src_format, SDL_COLORSPACE_UNKNOWN, src, src_pitch,
+                      dst_format, SDL_COLORSPACE_UNKNOWN, dst, dst_pitch);
+}
+
 /*
  * Premultiply the alpha on a block of pixels
  *

+ 64 - 28
test/testcolorspace.c

@@ -28,6 +28,8 @@
 static SDL_Window *window;
 static SDL_Renderer *renderer;
 static const char *renderer_name;
+static SDL_Colorspace colorspace = SDL_COLORSPACE_SRGB;
+static const char *colorspace_name = "sRGB";
 static int renderer_count = 0;
 static int renderer_index = 0;
 static int stage_count = 4;
@@ -50,7 +52,7 @@ static void CreateRenderer(void)
     SDL_SetProperty(props, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, window);
     SDL_SetStringProperty(props, SDL_PROP_RENDERER_CREATE_NAME_STRING, SDL_GetRenderDriver(renderer_index));
     SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_INPUT_COLORSPACE_NUMBER, SDL_COLORSPACE_SRGB);
-    SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER, SDL_COLORSPACE_SRGB);
+    SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER, colorspace);
     SDL_SetBooleanProperty(props, SDL_PROP_RENDERER_CREATE_COLORSPACE_CONVERSION_BOOLEAN, SDL_TRUE);
     renderer = SDL_CreateRendererWithProperties(props);
     SDL_DestroyProperties(props);
@@ -158,7 +160,7 @@ static void RenderClearBackground(void)
 
     float x = TEXT_START_X;
     float y = TEXT_START_Y;
-    DrawText(x, y, renderer_name);
+    DrawText(x, y, "%s %s", renderer_name, colorspace_name);
     y += TEXT_LINE_ADVANCE;
     DrawText(x, y, "Test: Clear 50%% Gray Background");
     y += TEXT_LINE_ADVANCE;
@@ -186,7 +188,7 @@ static void RenderDrawBackground(void)
 
     float x = TEXT_START_X;
     float y = TEXT_START_Y;
-    DrawText(x, y, renderer_name);
+    DrawText(x, y, "%s %s", renderer_name, colorspace_name);
     y += TEXT_LINE_ADVANCE;
     DrawText(x, y, "Test: Draw 50%% Gray Background");
     y += TEXT_LINE_ADVANCE;
@@ -236,7 +238,7 @@ static void RenderBlendDrawing(void)
 
     float x = TEXT_START_X;
     float y = TEXT_START_Y;
-    DrawText(x, y, renderer_name);
+    DrawText(x, y, "%s %s", renderer_name, colorspace_name);
     y += TEXT_LINE_ADVANCE;
     DrawText(x, y, "Test: Draw Linear Blending");
     y += TEXT_LINE_ADVANCE;
@@ -294,7 +296,7 @@ static void RenderBlendTexture(void)
 
     float x = TEXT_START_X;
     float y = TEXT_START_Y;
-    DrawText(x, y, renderer_name);
+    DrawText(x, y, "%s %s", renderer_name, colorspace_name);
     y += TEXT_LINE_ADVANCE;
     DrawText(x, y, "Test: Texture Linear Blending");
     y += TEXT_LINE_ADVANCE;
@@ -343,25 +345,27 @@ static void loop(void)
         }
     }
 
-    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
-    SDL_RenderClear(renderer);
+    if (renderer) {
+        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
+        SDL_RenderClear(renderer);
+
+        switch (stage_index) {
+        case 0:
+            RenderClearBackground();
+            break;
+        case 1:
+            RenderDrawBackground();
+            break;
+        case 2:
+            RenderBlendDrawing();
+            break;
+        case 3:
+            RenderBlendTexture();
+            break;
+        }
 
-    switch (stage_index) {
-    case 0:
-        RenderClearBackground();
-        break;
-    case 1:
-        RenderDrawBackground();
-        break;
-    case 2:
-        RenderBlendDrawing();
-        break;
-    case 3:
-        RenderBlendTexture();
-        break;
+        SDL_RenderPresent(renderer);
     }
-
-    SDL_RenderPresent(renderer);
     SDL_Delay(100);
 
 #ifdef SDL_PLATFORM_EMSCRIPTEN
@@ -371,15 +375,47 @@ static void loop(void)
 #endif
 }
 
+static void LogUsage(const char *argv0)
+{
+    SDL_Log("Usage: %s [--renderer renderer] [--colorspace colorspace]\n", argv0);
+}
+
 int main(int argc, char *argv[])
 {
-    int return_code = -1;
+    int return_code = 1;
     int i;
 
-    if (argc > 2) {
-        SDL_Log("Usage: %s [renderer]\n", argv[0]);
-        return_code = 1;
-        goto quit;
+    for (i = 1; i < argc; ++i) {
+        if (SDL_strcmp(argv[i], "--renderer") == 0) {
+            if (argv[i + 1]) {
+                renderer_name = argv[i + 1];
+                ++i;
+            } else {
+                LogUsage(argv[0]);
+                goto quit;
+            }
+        } else if (SDL_strcmp(argv[i], "--colorspace") == 0) {
+            if (argv[i + 1]) {
+                colorspace_name = argv[i + 1];
+                if (SDL_strcasecmp(colorspace_name, "srgb") == 0) {
+                    colorspace = SDL_COLORSPACE_SRGB;
+                } else if (SDL_strcasecmp(colorspace_name, "scrgb") == 0) {
+                    colorspace = SDL_COLORSPACE_SCRGB;
+                } else if (SDL_strcasecmp(colorspace_name, "hdr10") == 0) {
+                    colorspace = SDL_COLORSPACE_HDR10;
+                } else {
+                    SDL_Log("Unknown colorspace %s\n", argv[i + 1]);
+                    goto quit;
+                }
+                ++i;
+            } else {
+                LogUsage(argv[0]);
+                goto quit;
+            }
+        } else {
+            LogUsage(argv[0]);
+            goto quit;
+        }
     }
 
     window = SDL_CreateWindow("SDL colorspace test", WINDOW_WIDTH, WINDOW_HEIGHT, 0);
@@ -394,7 +430,7 @@ int main(int argc, char *argv[])
     for (i = 0; i < renderer_count; ++i) {
         const char *name = SDL_GetRenderDriver(i);
 
-        if (argv[1] && SDL_strcasecmp(argv[1], name) == 0) {
+        if (renderer_name && SDL_strcasecmp(renderer_name, name) == 0) {
             renderer_index = i;
         }
         SDL_Log("    %s\n", name);