diff --git a/include/SDL3/SDL_pixels.h b/include/SDL3/SDL_pixels.h index 57e640fff..b92e42992 100644 --- a/include/SDL3/SDL_pixels.h +++ b/include/SDL3/SDL_pixels.h @@ -196,8 +196,14 @@ typedef enum (SDL_PIXELORDER(format) == SDL_PACKEDORDER_BGRA)))) #define SDL_ISPIXELFORMAT_10BIT(format) \ - ((SDL_PIXELTYPE(format) == SDL_PIXELTYPE_PACKED32) && \ - (SDL_PIXELLAYOUT(format) == SDL_PACKEDLAYOUT_2101010)) + (!SDL_ISPIXELFORMAT_FOURCC(format) && \ + ((SDL_PIXELTYPE(format) == SDL_PIXELTYPE_PACKED32) && \ + (SDL_PIXELLAYOUT(format) == SDL_PACKEDLAYOUT_2101010))) + +#define SDL_ISPIXELFORMAT_FLOAT(format) \ + (!SDL_ISPIXELFORMAT_FOURCC(format) && \ + ((SDL_PIXELTYPE(format) == SDL_PIXELTYPE_ARRAYF16) || \ + (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_ARRAYF32))) /* The flag is set to 1 because 0x1? is not in the printable ASCII range */ #define SDL_ISPIXELFORMAT_FOURCC(format) \ diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h index 41b87b441..7552672f8 100644 --- a/include/SDL3/SDL_render.h +++ b/include/SDL3/SDL_render.h @@ -240,9 +240,14 @@ extern DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window *window, co * is displayed, if you want a software renderer without a window * - `SDL_PROP_RENDERER_CREATE_NAME_STRING`: the name of the rendering driver * to use, if a specific one is desired + * - `SDL_PROP_RENDERER_CREATE_INPUT_COLORSPACE_NUMBER`: an SDL_ColorSpace value describing the colorspace for input colors, defaults to SDL_COLORSPACE_SRGB + * - `SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER`: an SDL_ColorSpace value describing the colorspace for output to the display, defaults to SDL_COLORSPACE_SRGB + * - `SDL_PROP_RENDERER_CREATE_COLORSPACE_CONVERSION_BOOLEAN`: true if you want conversion between the input colorspace and the output colorspace, defaults to SDL_TRUE * - `SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN`: true if you want * present synchronized with the refresh rate * + * Note that enabling colorspace conversion between sRGB input and sRGB output implies that the rendering is done in a linear colorspace for more correct blending results. If colorspace conversion is disabled, then input colors are passed directly through to the output. + * * \param props the properties to use * \returns a valid rendering context or NULL if there was an error; call * SDL_GetError() for more information. @@ -256,10 +261,13 @@ extern DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window *window, co */ extern DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRendererWithProperties(SDL_PropertiesID props); -#define SDL_PROP_RENDERER_CREATE_WINDOW_POINTER "window" -#define SDL_PROP_RENDERER_CREATE_SURFACE_POINTER "surface" -#define SDL_PROP_RENDERER_CREATE_NAME_STRING "name" -#define SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN "present_vsync" +#define SDL_PROP_RENDERER_CREATE_WINDOW_POINTER "window" +#define SDL_PROP_RENDERER_CREATE_SURFACE_POINTER "surface" +#define SDL_PROP_RENDERER_CREATE_NAME_STRING "name" +#define SDL_PROP_RENDERER_CREATE_INPUT_COLORSPACE_NUMBER "input_colorspace" +#define SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER "output_colorspace" +#define SDL_PROP_RENDERER_CREATE_COLORSPACE_CONVERSION_BOOLEAN "colorspace_conversion" +#define SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_BOOLEAN "present_vsync" /** * Create a 2D software rendering context for a surface. @@ -447,6 +455,7 @@ extern DECLSPEC SDL_Texture *SDLCALL SDL_CreateTextureFromSurface(SDL_Renderer * * * These are the supported properties: * + * - `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 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 * - `SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER`: one of the enumerated values in @@ -524,6 +533,7 @@ extern DECLSPEC SDL_Texture *SDLCALL SDL_CreateTextureFromSurface(SDL_Renderer * */ extern DECLSPEC SDL_Texture *SDLCALL SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_PropertiesID props); +#define SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER "colorspace" #define SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER "format" #define SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER "access" #define SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER "width" @@ -550,6 +560,8 @@ extern DECLSPEC SDL_Texture *SDLCALL SDL_CreateTextureWithProperties(SDL_Rendere * * The following read-only properties are provided by SDL: * + * - `SDL_PROP_TEXTURE_COLORSPACE_NUMBER`: an SDL_ColorSpace value describing the colorspace used by the texture + * * With the direct3d11 renderer: * * - `SDL_PROP_TEXTURE_D3D11_TEXTURE_POINTER`: the ID3D11Texture2D associated @@ -609,6 +621,7 @@ extern DECLSPEC SDL_Texture *SDLCALL SDL_CreateTextureWithProperties(SDL_Rendere */ extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetTextureProperties(SDL_Texture *texture); +#define SDL_PROP_TEXTURE_COLORSPACE_NUMBER "SDL.texture.colorspace" #define SDL_PROP_TEXTURE_D3D11_TEXTURE_POINTER "SDL.texture.d3d11.texture" #define SDL_PROP_TEXTURE_D3D11_TEXTURE_U_POINTER "SDL.texture.d3d11.texture_u" #define SDL_PROP_TEXTURE_D3D11_TEXTURE_V_POINTER "SDL.texture.d3d11.texture_v" diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index 9bb163f52..9a943739a 100644 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -125,6 +125,66 @@ static const SDL_RenderDriver *render_drivers[] = { char SDL_renderer_magic; char SDL_texture_magic; + +void SDL_SetupRendererColorspace(SDL_Renderer *renderer, SDL_PropertiesID props) +{ + renderer->input_colorspace = (SDL_Colorspace)SDL_GetNumberProperty(props, SDL_PROP_RENDERER_CREATE_INPUT_COLORSPACE_NUMBER, SDL_COLORSPACE_SRGB); + renderer->output_colorspace = (SDL_Colorspace)SDL_GetNumberProperty(props, SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER, SDL_COLORSPACE_SRGB); + renderer->colorspace_conversion = SDL_GetBooleanProperty(props, SDL_PROP_RENDERER_CREATE_COLORSPACE_CONVERSION_BOOLEAN, SDL_TRUE); +} + +static float sRGBtoLinear(float v) +{ + return v <= 0.04045f ? (v / 12.92f) : SDL_powf(((v + 0.055f) / 1.055f), 2.4f); +} + +static float sRGBfromLinear(float v) +{ + return v <= 0.0031308f ? (v * 12.92f) : (SDL_powf(v, 1.0f / 2.4f) * 1.055f - 0.055f); +} + +void SDL_ConvertToLinear(SDL_Renderer *renderer, SDL_FColor *color) +{ + if (!renderer->colorspace_conversion) { + return; + } + + switch (SDL_COLORSPACETRANSFER(renderer->input_colorspace)) { + case SDL_TRANSFER_CHARACTERISTICS_SRGB: + color->r = sRGBtoLinear(color->r); + color->g = sRGBtoLinear(color->g); + color->b = sRGBtoLinear(color->b); + break; + case SDL_TRANSFER_CHARACTERISTICS_LINEAR: + /* No conversion needed */ + break; + default: + /* Unsupported */ + break; + } +} + +void SDL_ConvertFromLinear(SDL_Renderer *renderer, SDL_FColor *color) +{ + if (!renderer->colorspace_conversion) { + return; + } + + switch (SDL_COLORSPACETRANSFER(renderer->input_colorspace)) { + case SDL_TRANSFER_CHARACTERISTICS_SRGB: + color->r = sRGBfromLinear(color->r); + color->g = sRGBfromLinear(color->g); + color->b = sRGBfromLinear(color->b); + break; + case SDL_TRANSFER_CHARACTERISTICS_LINEAR: + /* No conversion needed */ + break; + default: + /* Unsupported */ + break; + } +} + static SDL_INLINE void DebugLogRenderCommands(const SDL_RenderCommand *cmd) { #if 0 @@ -1115,6 +1175,19 @@ 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; @@ -1122,6 +1195,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_bool texture_is_fourcc_and_target; CHECK_RENDERER_MAGIC(renderer, NULL); @@ -1148,11 +1222,15 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert SDL_SetError("Texture dimensions are limited to %dx%d", renderer->info.max_texture_width, renderer->info.max_texture_height); return NULL; } + + default_colorspace = SDL_GetDefaultTextureColorspace(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->format = format; texture->access = access; texture->w = w; @@ -1176,9 +1254,9 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert renderer->textures = texture; /* FOURCC format cannot be used directly by renderer back-ends for target texture */ - texture_is_fourcc_and_target = (access == SDL_TEXTUREACCESS_TARGET && SDL_ISPIXELFORMAT_FOURCC(texture->format)); + texture_is_fourcc_and_target = (access == SDL_TEXTUREACCESS_TARGET && SDL_ISPIXELFORMAT_FOURCC(format)); - if (texture_is_fourcc_and_target == SDL_FALSE && IsSupportedFormat(renderer, format)) { + if (!texture_is_fourcc_and_target && IsSupportedFormat(renderer, format)) { if (renderer->CreateTexture(renderer, texture, props) < 0) { SDL_DestroyTexture(texture); return NULL; @@ -1186,7 +1264,7 @@ SDL_Texture *SDL_CreateTextureWithProperties(SDL_Renderer *renderer, SDL_Propert } else { int closest_format; - if (texture_is_fourcc_and_target == SDL_FALSE) { + if (!texture_is_fourcc_and_target) { closest_format = GetClosestSupportedFormat(renderer, format); } else { closest_format = renderer->info.texture_formats[0]; @@ -1255,6 +1333,8 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s int i; Uint32 format = SDL_PIXELFORMAT_UNKNOWN; SDL_Texture *texture; + SDL_PropertiesID props; + Uint32 default_colorspace, colorspace; CHECK_RENDERER_MAGIC(renderer, NULL); @@ -1320,8 +1400,16 @@ SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *s } } - texture = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_STATIC, - surface->w, surface->h); + default_colorspace = SDL_GetDefaultTextureColorspace(format); + colorspace = (Uint32)SDL_GetNumberProperty(SDL_GetSurfaceProperties(surface), SDL_PROP_SURFACE_COLORSPACE_NUMBER, default_colorspace); + + props = SDL_CreateProperties(); + SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, colorspace); + SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, format); + SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STATIC); + SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, surface->w); + SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, surface->h); + texture = SDL_CreateTextureWithProperties(renderer, props); if (!texture) { return NULL; } @@ -1441,6 +1529,7 @@ int SDL_SetTextureColorModFloat(SDL_Texture *texture, float r, float g, float b) texture->color.r = r; texture->color.g = g; texture->color.b = b; + SDL_ConvertToLinear(texture->renderer, &texture->color); if (texture->native) { return SDL_SetTextureColorModFloat(texture->native, r, g, b); } @@ -1469,16 +1558,21 @@ int SDL_GetTextureColorMod(SDL_Texture *texture, Uint8 *r, Uint8 *g, Uint8 *b) int SDL_GetTextureColorModFloat(SDL_Texture *texture, float *r, float *g, float *b) { + SDL_FColor color; + CHECK_TEXTURE_MAGIC(texture, -1); + color = texture->color; + SDL_ConvertFromLinear(texture->renderer, &color); + if (r) { - *r = texture->color.r; + *r = color.r; } if (g) { - *g = texture->color.g; + *g = color.g; } if (b) { - *b = texture->color.b; + *b = color.b; } return 0; } @@ -2689,6 +2783,7 @@ int SDL_SetRenderDrawColorFloat(SDL_Renderer *renderer, float r, float g, float renderer->color.g = g; renderer->color.b = b; renderer->color.a = a; + SDL_ConvertToLinear(renderer, &renderer->color); return 0; } @@ -2717,19 +2812,24 @@ int SDL_GetRenderDrawColor(SDL_Renderer *renderer, Uint8 *r, Uint8 *g, Uint8 *b, int SDL_GetRenderDrawColorFloat(SDL_Renderer *renderer, float *r, float *g, float *b, float *a) { + SDL_FColor color; + CHECK_RENDERER_MAGIC(renderer, -1); + color = renderer->color; + SDL_ConvertFromLinear(renderer, &color); + if (r) { - *r = renderer->color.r; + *r = color.r; } if (g) { - *g = renderer->color.g; + *g = color.g; } if (b) { - *b = renderer->color.b; + *b = color.b; } if (a) { - *a = renderer->color.a; + *a = color.a; } return 0; } diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h index c54f00aee..58536876f 100644 --- a/src/render/SDL_sysrender.h +++ b/src/render/SDL_sysrender.h @@ -63,6 +63,7 @@ typedef struct SDL_RenderViewState struct SDL_Texture { const void *magic; + SDL_Colorspace colorspace; /**< The colorspace of the texture */ Uint32 format; /**< The pixel format of the texture */ int access; /**< SDL_TextureAccess */ int w; /**< The width of the texture */ @@ -249,6 +250,10 @@ struct SDL_Renderer SDL_Texture *target; SDL_Mutex *target_mutex; + SDL_Colorspace input_colorspace; + SDL_Colorspace output_colorspace; + SDL_bool colorspace_conversion; + SDL_FColor color; /**< Color for drawing operations values */ SDL_BlendMode blendMode; /**< The drawing blend mode */ @@ -294,6 +299,13 @@ extern SDL_RenderDriver PSP_RenderDriver; extern SDL_RenderDriver SW_RenderDriver; extern SDL_RenderDriver VITA_GXM_RenderDriver; +/* Setup colorspace conversion */ +extern void SDL_SetupRendererColorspace(SDL_Renderer *renderer, SDL_PropertiesID props); + +/* Colorspace conversion functions */ +extern void SDL_ConvertToLinear(SDL_Renderer *renderer, SDL_FColor *color); +extern void SDL_ConvertFromLinear(SDL_Renderer *renderer, SDL_FColor *color); + /* Blend mode functions */ extern SDL_BlendFactor SDL_GetBlendModeSrcColorFactor(SDL_BlendMode blendMode); extern SDL_BlendFactor SDL_GetBlendModeDstColorFactor(SDL_BlendMode blendMode); diff --git a/src/render/direct3d11/SDL_render_d3d11.c b/src/render/direct3d11/SDL_render_d3d11.c index 65ce0155b..8966bda40 100644 --- a/src/render/direct3d11/SDL_render_d3d11.c +++ b/src/render/direct3d11/SDL_render_d3d11.c @@ -198,20 +198,28 @@ Uint32 D3D11_DXGIFormatToSDLPixelFormat(DXGI_FORMAT dxgiFormat) { switch (dxgiFormat) { case DXGI_FORMAT_B8G8R8A8_UNORM: + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: return SDL_PIXELFORMAT_ARGB8888; case DXGI_FORMAT_B8G8R8X8_UNORM: + case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: return SDL_PIXELFORMAT_XRGB8888; default: return SDL_PIXELFORMAT_UNKNOWN; } } -static DXGI_FORMAT SDLPixelFormatToDXGITextureFormat(Uint32 sdlFormat) +static DXGI_FORMAT SDLPixelFormatToDXGITextureFormat(Uint32 format, Uint32 colorspace, SDL_bool colorspace_conversion) { - switch (sdlFormat) { + switch (format) { case SDL_PIXELFORMAT_ARGB8888: + if (colorspace_conversion && colorspace == SDL_COLORSPACE_SRGB) { + return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; + } return DXGI_FORMAT_B8G8R8A8_UNORM; case SDL_PIXELFORMAT_XRGB8888: + if (colorspace_conversion && colorspace == SDL_COLORSPACE_SRGB) { + return DXGI_FORMAT_B8G8R8X8_UNORM_SRGB; + } return DXGI_FORMAT_B8G8R8X8_UNORM; case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: @@ -224,12 +232,18 @@ static DXGI_FORMAT SDLPixelFormatToDXGITextureFormat(Uint32 sdlFormat) } } -static DXGI_FORMAT SDLPixelFormatToDXGIMainResourceViewFormat(Uint32 sdlFormat) +static DXGI_FORMAT SDLPixelFormatToDXGIMainResourceViewFormat(Uint32 format, Uint32 colorspace, SDL_bool colorspace_conversion) { - switch (sdlFormat) { + switch (format) { case SDL_PIXELFORMAT_ARGB8888: + if (colorspace_conversion && colorspace == SDL_COLORSPACE_SRGB) { + return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; + } return DXGI_FORMAT_B8G8R8A8_UNORM; case SDL_PIXELFORMAT_XRGB8888: + if (colorspace_conversion && colorspace == SDL_COLORSPACE_SRGB) { + return DXGI_FORMAT_B8G8R8X8_UNORM_SRGB; + } return DXGI_FORMAT_B8G8R8X8_UNORM; case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: @@ -988,9 +1002,17 @@ 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; + } + desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; result = ID3D11Device_CreateRenderTargetView(data->d3dDevice, (ID3D11Resource *)backBuffer, - NULL, + &desc, &data->mainRenderTargetView); if (FAILED(result)) { WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device::CreateRenderTargetView"), result); @@ -1083,7 +1105,7 @@ static int D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL D3D11_RenderData *rendererData = (D3D11_RenderData *)renderer->driverdata; D3D11_TextureData *textureData; HRESULT result; - DXGI_FORMAT textureFormat = SDLPixelFormatToDXGITextureFormat(texture->format); + DXGI_FORMAT textureFormat = SDLPixelFormatToDXGITextureFormat(texture->format, texture->colorspace, renderer->colorspace_conversion); D3D11_TEXTURE2D_DESC textureDesc; D3D11_SHADER_RESOURCE_VIEW_DESC resourceViewDesc; @@ -1182,7 +1204,7 @@ static int D3D11_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL } #endif /* SDL_HAVE_YUV */ SDL_zero(resourceViewDesc); - resourceViewDesc.Format = SDLPixelFormatToDXGIMainResourceViewFormat(texture->format); + resourceViewDesc.Format = SDLPixelFormatToDXGIMainResourceViewFormat(texture->format, texture->colorspace, renderer->colorspace_conversion); resourceViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; resourceViewDesc.Texture2D.MostDetailedMip = 0; resourceViewDesc.Texture2D.MipLevels = textureDesc.MipLevels; @@ -2441,6 +2463,8 @@ SDL_Renderer *D3D11_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_p return NULL; } + SDL_SetupRendererColorspace(renderer, create_props); + data->identity = MatrixIdentity(); renderer->WindowEvent = D3D11_WindowEvent; diff --git a/src/render/direct3d12/SDL_render_d3d12.c b/src/render/direct3d12/SDL_render_d3d12.c index 8c6a18003..10fe44350 100644 --- a/src/render/direct3d12/SDL_render_d3d12.c +++ b/src/render/direct3d12/SDL_render_d3d12.c @@ -273,20 +273,28 @@ Uint32 D3D12_DXGIFormatToSDLPixelFormat(DXGI_FORMAT dxgiFormat) { switch (dxgiFormat) { case DXGI_FORMAT_B8G8R8A8_UNORM: + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: return SDL_PIXELFORMAT_ARGB8888; case DXGI_FORMAT_B8G8R8X8_UNORM: + case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: return SDL_PIXELFORMAT_XRGB8888; default: return SDL_PIXELFORMAT_UNKNOWN; } } -static DXGI_FORMAT SDLPixelFormatToDXGITextureFormat(Uint32 sdlFormat) +static DXGI_FORMAT SDLPixelFormatToDXGITextureFormat(Uint32 format, Uint32 colorspace, SDL_bool colorspace_conversion) { - switch (sdlFormat) { + switch (format) { case SDL_PIXELFORMAT_ARGB8888: + if (colorspace_conversion && colorspace == SDL_COLORSPACE_SRGB) { + return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; + } return DXGI_FORMAT_B8G8R8A8_UNORM; case SDL_PIXELFORMAT_XRGB8888: + if (colorspace_conversion && colorspace == SDL_COLORSPACE_SRGB) { + return DXGI_FORMAT_B8G8R8X8_UNORM_SRGB; + } return DXGI_FORMAT_B8G8R8X8_UNORM; case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: @@ -299,12 +307,18 @@ static DXGI_FORMAT SDLPixelFormatToDXGITextureFormat(Uint32 sdlFormat) } } -static DXGI_FORMAT SDLPixelFormatToDXGIMainResourceViewFormat(Uint32 sdlFormat) +static DXGI_FORMAT SDLPixelFormatToDXGIMainResourceViewFormat(Uint32 format, Uint32 colorspace, SDL_bool colorspace_conversion) { - switch (sdlFormat) { + switch (format) { case SDL_PIXELFORMAT_ARGB8888: + if (colorspace_conversion && colorspace == SDL_COLORSPACE_SRGB) { + return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; + } return DXGI_FORMAT_B8G8R8A8_UNORM; case SDL_PIXELFORMAT_XRGB8888: + if (colorspace_conversion && colorspace == SDL_COLORSPACE_SRGB) { + return DXGI_FORMAT_B8G8R8X8_UNORM_SRGB; + } return DXGI_FORMAT_B8G8R8X8_UNORM; case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: @@ -1340,7 +1354,11 @@ static HRESULT D3D12_CreateWindowSizeDependentResources(SDL_Renderer *renderer) #endif SDL_zero(rtvDesc); - rtvDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + 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.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; SDL_zero(rtvDescriptor); @@ -1450,7 +1468,7 @@ static int D3D12_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL D3D12_RenderData *rendererData = (D3D12_RenderData *)renderer->driverdata; D3D12_TextureData *textureData; HRESULT result; - DXGI_FORMAT textureFormat = SDLPixelFormatToDXGITextureFormat(texture->format); + DXGI_FORMAT textureFormat = SDLPixelFormatToDXGITextureFormat(texture->format, texture->colorspace, renderer->colorspace_conversion); D3D12_RESOURCE_DESC textureDesc; D3D12_HEAP_PROPERTIES heapProps; D3D12_SHADER_RESOURCE_VIEW_DESC resourceViewDesc; @@ -1563,7 +1581,7 @@ static int D3D12_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL #endif /* SDL_HAVE_YUV */ SDL_zero(resourceViewDesc); resourceViewDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; - resourceViewDesc.Format = SDLPixelFormatToDXGIMainResourceViewFormat(texture->format); + resourceViewDesc.Format = SDLPixelFormatToDXGIMainResourceViewFormat(texture->format, texture->colorspace, renderer->colorspace_conversion); resourceViewDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; resourceViewDesc.Texture2D.MipLevels = textureDesc.MipLevels; @@ -2985,6 +3003,8 @@ SDL_Renderer *D3D12_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_p return NULL; } + SDL_SetupRendererColorspace(renderer, create_props); + data->identity = MatrixIdentity(); renderer->WindowEvent = D3D12_WindowEvent; diff --git a/src/render/opengl/SDL_render_gl.c b/src/render/opengl/SDL_render_gl.c index cac1fb3a9..17c438b49 100644 --- a/src/render/opengl/SDL_render_gl.c +++ b/src/render/opengl/SDL_render_gl.c @@ -49,8 +49,6 @@ http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html */ -static const float inv255f = 1.0f / 255.0f; - typedef struct GL_FBOList GL_FBOList; struct GL_FBOList @@ -403,19 +401,27 @@ static SDL_bool GL_SupportsBlendMode(SDL_Renderer *renderer, SDL_BlendMode blend return SDL_TRUE; } -static SDL_bool convert_format(GL_RenderData *renderdata, Uint32 pixel_format, +static SDL_bool convert_format(Uint32 pixel_format, Uint32 colorspace, SDL_bool colorspace_conversion, GLint *internalFormat, GLenum *format, GLenum *type) { switch (pixel_format) { case SDL_PIXELFORMAT_ARGB8888: case SDL_PIXELFORMAT_XRGB8888: - *internalFormat = GL_RGBA8; + if (colorspace_conversion && colorspace == SDL_COLORSPACE_SRGB) { + *internalFormat = GL_SRGB8_ALPHA8; + } else { + *internalFormat = GL_RGBA8; + } *format = GL_BGRA; *type = GL_UNSIGNED_INT_8_8_8_8_REV; break; case SDL_PIXELFORMAT_ABGR8888: case SDL_PIXELFORMAT_XBGR8888: - *internalFormat = GL_RGBA8; + if (colorspace_conversion && colorspace == SDL_COLORSPACE_SRGB) { + *internalFormat = GL_SRGB8_ALPHA8; + } else { + *internalFormat = GL_RGBA8; + } *format = GL_RGBA; *type = GL_UNSIGNED_INT_8_8_8_8_REV; break; @@ -460,8 +466,8 @@ static int GL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_Pr return SDL_SetError("Render targets not supported by OpenGL"); } - if (!convert_format(renderdata, texture->format, &internalFormat, - &format, &type)) { + if (!convert_format(texture->format, texture->colorspace, renderer->colorspace_conversion, + &internalFormat, &format, &type)) { return SDL_SetError("Texture format %s not supported by OpenGL", SDL_GetPixelFormatName(texture->format)); } @@ -1475,7 +1481,8 @@ static int GL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect, GL_ActivateRenderer(renderer); - if (!convert_format(data, temp_format, &internalFormat, &format, &type)) { + if (!convert_format(temp_format, renderer->input_colorspace, renderer->colorspace_conversion, + &internalFormat, &format, &type)) { return SDL_SetError("Texture format %s not supported by OpenGL", SDL_GetPixelFormatName(temp_format)); } @@ -1709,6 +1716,8 @@ static SDL_Renderer *GL_CreateRenderer(SDL_Window *window, SDL_PropertiesID crea goto error; } + SDL_SetupRendererColorspace(renderer, create_props); + renderer->WindowEvent = GL_WindowEvent; renderer->SupportsBlendMode = GL_SupportsBlendMode; renderer->CreateTexture = GL_CreateTexture; @@ -1910,6 +1919,9 @@ static SDL_Renderer *GL_CreateRenderer(SDL_Window *window, SDL_PropertiesID crea data->glDisable(data->textype); data->glClearColor(1.0f, 1.0f, 1.0f, 1.0f); data->glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + if (renderer->colorspace_conversion && renderer->output_colorspace == SDL_COLORSPACE_SRGB) { + data->glEnable(GL_FRAMEBUFFER_SRGB); + } /* This ended up causing video discrepancies between OpenGL and Direct3D */ /* data->glEnable(GL_LINE_SMOOTH); */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 014e29d1c..2fc1d953d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -350,6 +350,7 @@ files2headers(gamepad_image_headers files2headers(icon_bmp_header icon.bmp) add_sdl_test_executable(testaudio MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testaudio.c) +add_sdl_test_executable(testcolorspace SOURCES testcolorspace.c) add_sdl_test_executable(testfile NONINTERACTIVE SOURCES testfile.c) add_sdl_test_executable(testcontroller TESTUTILS SOURCES testcontroller.c gamepadutils.c ${gamepad_image_headers}) add_sdl_test_executable(testgeometry TESTUTILS SOURCES testgeometry.c) diff --git a/test/testcolorspace.c b/test/testcolorspace.c new file mode 100644 index 000000000..ad427c672 --- /dev/null +++ b/test/testcolorspace.c @@ -0,0 +1,420 @@ +/* + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely. +*/ + +#include +#include +#include + +#ifdef SDL_PLATFORM_EMSCRIPTEN +#include +#endif + +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 + +#define TEXT_START_X 6.0f +#define TEXT_START_Y 6.0f +#define TEXT_LINE_ADVANCE FONT_CHARACTER_SIZE * 2 + +static SDL_Window *window; +static SDL_Renderer *renderer; +static const char *renderer_name; +static int renderer_count = 0; +static int renderer_index = 0; +static int stage_count = 4; +static int stage_index = 0; +static int done; + +static void FreeRenderer(void) +{ + SDLTest_CleanupTextDrawing(); + SDL_DestroyRenderer(renderer); + renderer = NULL; +} + +static void CreateRenderer(void) +{ + SDL_PropertiesID props; + SDL_RendererInfo info; + + props = SDL_CreateProperties(); + 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_SetBooleanProperty(props, SDL_PROP_RENDERER_CREATE_COLORSPACE_CONVERSION_BOOLEAN, SDL_TRUE); + renderer = SDL_CreateRendererWithProperties(props); + SDL_DestroyProperties(props); + if (!renderer) { + SDL_Log("Couldn't create renderer: %s\n", SDL_GetError()); + return; + } + + SDL_GetRendererInfo(renderer, &info); + SDL_Log("Created renderer %s\n", info.name); + renderer_name = info.name; +} + +static void NextRenderer( void ) +{ + if (renderer_count <= 0) { + return; + } + + ++renderer_index; + if (renderer_index == renderer_count) { + renderer_index = 0; + } + FreeRenderer(); + CreateRenderer(); +} + +static void PrevRenderer(void) +{ + if (renderer_count <= 0) { + return; + } + + --renderer_index; + if (renderer_index == -1) { + renderer_index += renderer_count; + } + FreeRenderer(); + CreateRenderer(); +} + +static void NextStage(void) +{ + if (stage_count <= 0) { + return; + } + + ++stage_index; + if (stage_index == stage_count) { + stage_index = 0; + } +} + +static void PrevStage(void) +{ + --stage_index; + if (stage_index == -1) { + stage_index += stage_count; + } +} + +static SDL_bool ReadPixel(int x, int y, SDL_Color *c) +{ + SDL_Rect r; + r.x = x; + r.y = y; + r.w = 1; + r.h = 1; + if (SDL_RenderReadPixels(renderer, &r, SDL_PIXELFORMAT_RGBA32, c, sizeof(*c)) < 0) { + SDL_Log("Couldn't read back pixels: %s\n", SDL_GetError()); + return SDL_FALSE; + } + return SDL_TRUE; +} + +static void DrawText(float x, float y, const char *fmt, ...) +{ + char *text; + + va_list ap; + va_start(ap, fmt); + SDL_vasprintf(&text, fmt, ap); + va_end(ap); + + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDLTest_DrawString(renderer, x + 1.0f, y + 1.0f, text); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDLTest_DrawString(renderer, x, y, text); + SDL_free(text); +} + +static void RenderClearBackground(void) +{ + /* Draw a 50% gray background. + * This will be darker when using sRGB colors and lighter using linear colors + */ + SDL_SetRenderDrawColor(renderer, 128, 128, 128, 255); + SDL_RenderClear(renderer); + + /* Check the renderered pixels */ + SDL_Color c; + if (!ReadPixel(0, 0, &c)) { + return; + } + + float x = TEXT_START_X; + float y = TEXT_START_Y; + DrawText(x, y, renderer_name); + y += TEXT_LINE_ADVANCE; + DrawText(x, y, "Test: Clear 50%% Gray Background"); + y += TEXT_LINE_ADVANCE; + DrawText(x, y, "Background color written: 0x808080, read: 0x%.2x%.2x%.2x", c.r, c.g, c.b); + y += TEXT_LINE_ADVANCE; + if (c.r != 128) { + DrawText(x, y, "Incorrect background color, unknown reason"); + y += TEXT_LINE_ADVANCE; + } +} + +static void RenderDrawBackground(void) +{ + /* Draw a 50% gray background. + * This will be darker when using sRGB colors and lighter using linear colors + */ + SDL_SetRenderDrawColor(renderer, 128, 128, 128, 255); + SDL_RenderFillRect(renderer, NULL); + + /* Check the renderered pixels */ + SDL_Color c; + if (!ReadPixel(0, 0, &c)) { + return; + } + + float x = TEXT_START_X; + float y = TEXT_START_Y; + DrawText(x, y, renderer_name); + y += TEXT_LINE_ADVANCE; + DrawText(x, y, "Test: Draw 50%% Gray Background"); + y += TEXT_LINE_ADVANCE; + DrawText(x, y, "Background color written: 0x808080, read: 0x%.2x%.2x%.2x", c.r, c.g, c.b); + y += TEXT_LINE_ADVANCE; + if (c.r != 128) { + DrawText(x, y, "Incorrect background color, unknown reason"); + y += TEXT_LINE_ADVANCE; + } +} + +static void RenderBlendDrawing(void) +{ + SDL_Color a = { 238, 70, 166, 255 }; /* red square */ + SDL_Color b = { 147, 255, 0, 255 }; /* green square */ + SDL_FRect rect; + + /* Draw a green square blended over a red square + * This will have different effects based on whether sRGB colorspaces and sRGB vs linear blending is used. + */ + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderClear(renderer); + rect.x = WINDOW_WIDTH / 3; + rect.y = 0; + rect.w = WINDOW_WIDTH / 3; + rect.h = WINDOW_HEIGHT; + SDL_SetRenderDrawColor(renderer, a.r, a.g, a.b, a.a); + SDL_RenderFillRect(renderer, &rect); + + rect.x = 0; + rect.y = WINDOW_HEIGHT / 3; + rect.w = WINDOW_WIDTH; + rect.h = WINDOW_HEIGHT / 6; + SDL_SetRenderDrawColor(renderer, b.r, b.g, b.b, b.a); + SDL_RenderFillRect(renderer, &rect); + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(renderer, b.r, b.g, b.b, 128); + rect.y += WINDOW_HEIGHT / 6; + SDL_RenderFillRect(renderer, &rect); + + SDL_Color ar, br, cr; + if (!ReadPixel(WINDOW_WIDTH / 2, 0, &ar) || + !ReadPixel(WINDOW_WIDTH / 2, WINDOW_HEIGHT / 3, &br) || + !ReadPixel(WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2, &cr)) { + return; + } + + float x = TEXT_START_X; + float y = TEXT_START_Y; + DrawText(x, y, renderer_name); + y += TEXT_LINE_ADVANCE; + DrawText(x, y, "Test: Draw Linear Blending"); + y += TEXT_LINE_ADVANCE; + if (cr.r == 199 && cr.g == 193 && cr.b == 121) { + DrawText(x, y, "Correct blend color in linear space"); + } else if ((cr.r == 192 && cr.g == 163 && cr.b == 83) || + (cr.r == 191 && cr.g == 162 && cr.b == 82)) { + DrawText(x, y, "Incorrect blend color, blending in sRGB space"); + } else { + DrawText(x, y, "Incorrect blend color, unknown reason"); + } + y += TEXT_LINE_ADVANCE; +} + +static void RenderBlendTexture(void) +{ + SDL_Color color_a = { 238, 70, 166, 255 }; /* red square */ + SDL_Color color_b = { 147, 255, 0, 255 }; /* green square */ + SDL_Texture *a; + SDL_Texture *b; + SDL_FRect rect; + + /* Draw a green square blended over a red square + * This will have different effects based on whether sRGB colorspaces and sRGB vs linear blending is used. + */ + a = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, 1, 1); + SDL_UpdateTexture(a, NULL, &color_a, sizeof(color_a)); + b = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, 1, 1); + SDL_UpdateTexture(b, NULL, &color_b, sizeof(color_b)); + + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderClear(renderer); + rect.x = WINDOW_WIDTH / 3; + rect.y = 0; + rect.w = WINDOW_WIDTH / 3; + rect.h = WINDOW_HEIGHT; + SDL_RenderTexture(renderer, a, NULL, &rect); + + rect.x = 0; + rect.y = WINDOW_HEIGHT / 3; + rect.w = WINDOW_WIDTH; + rect.h = WINDOW_HEIGHT / 6; + SDL_RenderTexture(renderer, b, NULL, &rect); + rect.y += WINDOW_HEIGHT / 6; + SDL_SetTextureBlendMode(b, SDL_BLENDMODE_BLEND); + SDL_SetTextureAlphaModFloat(b, 128 / 255.0f); + SDL_RenderTexture(renderer, b, NULL, &rect); + + SDL_Color ar, br, cr; + if (!ReadPixel(WINDOW_WIDTH / 2, 0, &ar) || + !ReadPixel(WINDOW_WIDTH / 2, WINDOW_HEIGHT / 3, &br) || + !ReadPixel(WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2, &cr)) { + return; + } + + float x = TEXT_START_X; + float y = TEXT_START_Y; + DrawText(x, y, renderer_name); + y += TEXT_LINE_ADVANCE; + DrawText(x, y, "Test: Texture Linear Blending"); + y += TEXT_LINE_ADVANCE; + if (cr.r == 199 && cr.g == 193 && cr.b == 121) { + DrawText(x, y, "Correct blend color in linear space"); + } else if ((cr.r == 192 && cr.g == 163 && cr.b == 83) || + (cr.r == 191 && cr.g == 162 && cr.b == 82)) { + DrawText(x, y, "Incorrect blend color, blending in sRGB space"); + } else { + DrawText(x, y, "Incorrect blend color, unknown reason"); + } + y += TEXT_LINE_ADVANCE; + + SDL_DestroyTexture(a); + SDL_DestroyTexture(b); +} + +static void loop(void) +{ + SDL_Event event; + + /* Check for events */ + while (SDL_PollEvent(&event)) { + if (event.type == SDL_EVENT_KEY_DOWN) { + switch (event.key.keysym.sym) { + case SDLK_ESCAPE: + done = 1; + case SDLK_SPACE: + case SDLK_RIGHT: + NextStage(); + break; + case SDLK_LEFT: + PrevStage(); + break; + case SDLK_DOWN: + NextRenderer(); + break; + case SDLK_UP: + PrevRenderer(); + break; + default: + break; + } + } else if (event.type == SDL_EVENT_QUIT) { + done = 1; + } + } + + 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; + } + + SDL_RenderPresent(renderer); + SDL_Delay(100); + +#ifdef SDL_PLATFORM_EMSCRIPTEN + if (done) { + emscripten_cancel_main_loop(); + } +#endif +} + +int main(int argc, char *argv[]) +{ + int return_code = -1; + int i; + + if (argc > 2) { + SDL_Log("Usage: %s [renderer]\n", argv[0]); + return_code = 1; + goto quit; + } + + window = SDL_CreateWindow("SDL colorspace test", WINDOW_WIDTH, WINDOW_HEIGHT, 0); + if (!window) { + SDL_Log("Couldn't create window: %s\n", SDL_GetError()); + return_code = 2; + goto quit; + } + + renderer_count = SDL_GetNumRenderDrivers(); + SDL_Log("There are %d render drivers:\n", renderer_count); + for (i = 0; i < renderer_count; ++i) { + const char *name = SDL_GetRenderDriver(i); + + if (argv[1] && SDL_strcasecmp(argv[1], name) == 0) { + renderer_index = i; + } + SDL_Log(" %s\n", name); + } + CreateRenderer(); + + /* Main render loop */ + done = 0; + +#ifdef SDL_PLATFORM_EMSCRIPTEN + emscripten_set_main_loop(loop, 0, 1); +#else + while (!done) { + loop(); + } +#endif + return_code = 0; +quit: + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return return_code; +}