Added the concept of colorspace to the SDL renderer

This allows color operations to happen in linear space between sRGB input and sRGB output. This is currently supported on the direct3d11, direct3d12 and opengl renderers.

This is a good resource on blending in linear space vs sRGB space:
https://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/

Also added testcolorspace to verify colorspace changes
main
Sam Lantinga 2024-01-29 18:32:27 -08:00
parent 554f0625d3
commit f257eb4481
9 changed files with 648 additions and 40 deletions

View File

@ -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) \

View File

@ -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"

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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); */

View File

@ -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)

420
test/testcolorspace.c Normal file
View File

@ -0,0 +1,420 @@
/*
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
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 <SDL3/SDL.h>
#include <SDL3/SDL_test.h>
#include <SDL3/SDL_main.h>
#ifdef SDL_PLATFORM_EMSCRIPTEN
#include <emscripten/emscripten.h>
#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;
}