From e10457d52f4bbef993679e6b9e2696b16977b790 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Tue, 21 Jul 2026 14:10:09 -0700 Subject: [PATCH] testcolorspace: added an HDR10 gradient --- test/testcolorspace.c | 140 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 2 deletions(-) diff --git a/test/testcolorspace.c b/test/testcolorspace.c index 777bf59a35..1dac709cfa 100644 --- a/test/testcolorspace.c +++ b/test/testcolorspace.c @@ -30,6 +30,7 @@ static const char *colorspace_name = "sRGB"; static int renderer_count = 0; static int renderer_index = 0; static int stage_index = 0; +static float SDR_white_level = 1.0f; static float HDR_headroom = 1.0f; enum @@ -42,6 +43,7 @@ enum StageBlendTexture, StageGradientDrawing, StageGradientTexture, + StageHDR10Texture, StageCount }; @@ -70,7 +72,11 @@ static void UpdateHDRState(void) colorspace != SDL_COLORSPACE_HDR10) { SDL_Log("Run with --colorspace linear to display HDR colors"); } + SDR_white_level = SDL_GetFloatProperty(props, SDL_PROP_RENDERER_SDR_WHITE_POINT_FLOAT, 1.0f); HDR_headroom = SDL_GetFloatProperty(props, SDL_PROP_RENDERER_HDR_HEADROOM_FLOAT, 1.0f); + } else { + SDR_white_level = 1.0f; + HDR_headroom = 1.0f; } } @@ -167,6 +173,41 @@ static bool ReadPixel(int x, int y, SDL_Color *c) return result; } +typedef enum +{ + TextAlignLeft, + TextAlignRight, + TextAlignCenter +} TextAlignment; + +static void DrawAlignedText(float x, float y, TextAlignment align, const char *fmt, ...) +{ + char *text; + + va_list ap; + va_start(ap, fmt); + SDL_vasprintf(&text, fmt, ap); + va_end(ap); + + size_t text_width = SDL_strlen(text) * SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE; + switch (align) { + case TextAlignLeft: + break; + case TextAlignRight: + x -= text_width; + break; + case TextAlignCenter: + x -= text_width / 2.0f; + break; + } + + 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 DrawText(float x, float y, const char *fmt, ...) { char *text; @@ -376,7 +417,7 @@ static void RenderBlendDrawing(void) DrawText(x, y, "Correct blend color, blending in sRGB space"); } else if ((cr.r == 214 && cr.g == 156 && cr.b == 113) || (cr.r == 215 && cr.g == 155 && cr.b == 113)) { - DrawText(x, y, "Incorrect blend color, blending in PQ space"); + DrawText(x, y, "Correct blend color, blending in PQ space"); } else { DrawText(x, y, "Incorrect blend color, unknown reason"); } @@ -438,7 +479,7 @@ static void RenderBlendTexture(void) DrawText(x, y, "Correct blend color, blending in sRGB space"); } else if ((cr.r == 214 && cr.g == 156 && cr.b == 113) || (cr.r == 215 && cr.g == 155 && cr.b == 113)) { - DrawText(x, y, "Incorrect blend color, blending in PQ space"); + DrawText(x, y, "Correct blend color, blending in PQ space"); } else { DrawText(x, y, "Incorrect blend color, unknown reason"); } @@ -589,6 +630,98 @@ static void RenderGradientTexture(void) y += 64.0f; } +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 * SDL_powf(y, m1); + float den = 1.0f + c3 * SDL_powf(y, m1); + return SDL_powf(num / den, m2); +} + +static SDL_Texture *CreateHDR10Texture(int width, float max_nits) +{ + SDL_Texture *texture; + Uint32 *pixels; + + /* ABGR2101010 textures are in the HDR10 colorspace by default */ + SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, SDL_PIXELFORMAT_ABGR2101010); + SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STATIC); + SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, width); + SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, 1); + // The white point for HDR10 textures is the SDR white level in nits + if (SDR_white_level > 1.0f) { + // We'll match the current display SDR white level so we don't get any scaling here + SDL_SetFloatProperty(props, SDL_PROP_TEXTURE_CREATE_SDR_WHITE_POINT_FLOAT, SDR_white_level * 80.0f); + } else { + // ITU-R BT.2408-6 recommends using an SDR white point of 203 nits. + SDL_SetFloatProperty(props, SDL_PROP_TEXTURE_CREATE_SDR_WHITE_POINT_FLOAT, 203.0f); + } + texture = SDL_CreateTextureWithProperties(renderer, props); + SDL_DestroyProperties(props); + if (!texture) { + return NULL; + } + + pixels = (Uint32 *)SDL_malloc(width * sizeof(Uint32)); + if (pixels) { + int i; + + for (i = 0; i < width; ++i) { + float nits = (max_nits * i) / width; + Uint32 v = (Uint32)SDL_roundf(PQfromNits(nits) * 1023.0f); + pixels[i] = 0xC0000000 | (v << 20) | (v << 10) | v; + } + SDL_UpdateTexture(texture, NULL, pixels, width * sizeof(Uint32)); + SDL_free(pixels); + } + return texture; +} + +static void DrawHDR10Texture(float x, float y, float width, float height, float max_nits) +{ + SDL_FRect rect = { x, y, width, height }; + SDL_Texture *texture = CreateHDR10Texture((int)width, max_nits); + SDL_RenderTexture(renderer, texture, NULL, &rect); + SDL_DestroyTexture(texture); +} + +static void RenderHDR10Texture(void) +{ + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderClear(renderer); + + float x = TEXT_START_X; + float y = TEXT_START_Y; + DrawText(x, y, "%s %s", renderer_name, colorspace_name); + y += TEXT_LINE_ADVANCE; + DrawText(x, y, "Test: Texture HDR10 ramp"); + y += TEXT_LINE_ADVANCE; + y += TEXT_LINE_ADVANCE; + + DrawText(x, y, "HDR10"); + y += TEXT_LINE_ADVANCE; + + /* The gradient texture is in the linear colorspace, so we can use the HDR_headroom value directly */ + const float max_nits = 1000.0f; + float gradient_width = WINDOW_WIDTH - 2 * x; + DrawHDR10Texture(x, y, gradient_width, 64.0f, max_nits); + y += 64.0f; + y += 4.0f; + DrawAlignedText(x, y, TextAlignLeft, "Nits:"); + DrawAlignedText(x + gradient_width / 4, y, TextAlignCenter, "%g", max_nits / 4); + DrawAlignedText(x + gradient_width / 2, y, TextAlignCenter, "%g", max_nits / 2); + DrawAlignedText(x + gradient_width * 400 / max_nits, y, TextAlignCenter, "400"); + DrawAlignedText(x + gradient_width * 3 / 4, y, TextAlignCenter, "%g", max_nits * 3 / 4); + DrawAlignedText(x + gradient_width, y, TextAlignRight, "%g", max_nits); +} + SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { /* Check for events */ @@ -650,6 +783,9 @@ SDL_AppResult SDL_AppIterate(void *appstate) case StageGradientTexture: RenderGradientTexture(); break; + case StageHDR10Texture: + RenderHDR10Texture(); + break; } SDL_RenderPresent(renderer);