SDL: Don't assume HDR headroom for HDR10 surfaces

From 7f9ff6277c789bdd4d475601f1a0c45d7c1b3b0b Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 2 Mar 2024 15:02:17 -0800
Subject: [PATCH] Don't assume HDR headroom for HDR10 surfaces

Applications that support HDR will set the correct values for their content.
---
 include/SDL3/SDL_surface.h | 20 ++------------------
 src/video/SDL_surface.c    |  5 -----
 test/testcolorspace.c      |  3 ---
 test/testffmpeg.c          | 23 ++++++++++++++---------
 4 files changed, 16 insertions(+), 35 deletions(-)

diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h
index 5c8184e44fa57..4064354979ca9 100644
--- a/include/SDL3/SDL_surface.h
+++ b/include/SDL3/SDL_surface.h
@@ -210,27 +210,13 @@ extern DECLSPEC void SDLCALL SDL_DestroySurface(SDL_Surface *surface);
  *   floating point formats, SDL_COLORSPACE_HDR10 for 10-bit formats,
  *   SDL_COLORSPACE_SRGB for other RGB surfaces and SDL_COLORSPACE_BT709_FULL
  *   for YUV surfaces.
- * - `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 content. MaxCLL is usually measured off the final delivered
- *   content after mastering. If one uses the full light level of the HDR
- *   mastering display and adds a hard clip at its maximum value, MaxCLL would
- *   be equal to the peak luminance of the mastering monitor. This defaults to
- *   400 for HDR10 surfaces.
- * - `SDL_PROP_SURFACE_MAXFALL_NUMBER`: MaxFALL (Maximum Frame Average Light
- *   Level) indicates the maximum value of the frame average light level (in
- *   cd/m2 or nits) of the content. MaxFALL is calculated by averaging the
- *   decoded luminance values of all the pixels within a frame. MaxFALL is
- *   usually much lower than MaxCLL.
  * - `SDL_PROP_SURFACE_SDR_WHITE_POINT_FLOAT`: for HDR10 and floating point
  *   surfaces, this defines the value of 100% diffuse white, with higher
  *   values being displayed in the High Dynamic Range headroom. This defaults
- *   to 100 for HDR10 surfaces and 1.0 for other surfaces.
+ *   to 203 for HDR10 surfaces and 1.0 for floating point surfaces.
  * - `SDL_PROP_SURFACE_HDR_HEADROOM_FLOAT`: for HDR10 and floating point
  *   surfaces, this defines the maximum dynamic range used by the content, in
- *   terms of the SDR white point. This defaults to
- *   SDL_PROP_SURFACE_MAXCLL_NUMBER / SDL_PROP_SURFACE_SDR_WHITE_POINT_FLOAT,
- *   or 4.0, for HDR10 surfaces.
+ *   terms of the SDR white point. This defaults to 0.0, which disables tone mapping.
  * - `SDL_PROP_SURFACE_TONEMAP_OPERATOR_STRING`: the tone mapping operator
  *   used when compressing from a surface with high dynamic range to another
  *   with lower dynamic range. Currently this supports "chrome", which uses
@@ -250,8 +236,6 @@ extern DECLSPEC void SDLCALL SDL_DestroySurface(SDL_Surface *surface);
 extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetSurfaceProperties(SDL_Surface *surface);
 
 #define SDL_PROP_SURFACE_COLORSPACE_NUMBER                  "SDL.surface.colorspace"
-#define SDL_PROP_SURFACE_MAXCLL_NUMBER                      "SDL.surface.maxCLL"
-#define SDL_PROP_SURFACE_MAXFALL_NUMBER                     "SDL.surface.maxFALL"
 #define SDL_PROP_SURFACE_SDR_WHITE_POINT_FLOAT              "SDL.surface.SDR_white_point"
 #define SDL_PROP_SURFACE_HDR_HEADROOM_FLOAT                 "SDL.surface.HDR_headroom"
 #define SDL_PROP_SURFACE_TONEMAP_OPERATOR_STRING            "SDL.surface.tonemap"
diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c
index 16de3758228a7..e003f31e53cfb 100644
--- a/src/video/SDL_surface.c
+++ b/src/video/SDL_surface.c
@@ -355,11 +355,6 @@ float SDL_GetSurfaceHDRHeadroom(SDL_Surface *surface, SDL_Colorspace colorspace)
         } else {
             props = 0;
         }
-        if (transfer == SDL_TRANSFER_CHARACTERISTICS_PQ) {
-            /* The official definition is 10000, but PQ game content is often mastered for 400 or 1000 nits */
-            const int DEFAULT_PQ_MAXCLL = 1000;
-            default_value = (float)SDL_GetNumberProperty(props, SDL_PROP_SURFACE_MAXCLL_NUMBER, DEFAULT_PQ_MAXCLL) / SDL_GetSurfaceSDRWhitePoint(surface, colorspace);
-        }
         return SDL_GetFloatProperty(props, SDL_PROP_SURFACE_HDR_HEADROOM_FLOAT, default_value);
     }
     return 1.0f;
diff --git a/test/testcolorspace.c b/test/testcolorspace.c
index 7fb262b161c31..ee1c610b1e4ff 100644
--- a/test/testcolorspace.c
+++ b/test/testcolorspace.c
@@ -157,9 +157,6 @@ static SDL_bool ReadPixel(int x, int y, SDL_Color *c)
 
     surface = SDL_RenderReadPixels(renderer, &r);
     if (surface) {
-        /* We don't want to do any HDR -> SDR tone mapping */
-        SDL_SetFloatProperty(SDL_GetSurfaceProperties(surface), SDL_PROP_SURFACE_HDR_HEADROOM_FLOAT, 0.0f);
-
         if (SDL_ReadSurfacePixel(surface, 0, 0, &c->r, &c->g, &c->b, &c->a) == 0) {
             result = SDL_TRUE;
         } else {
diff --git a/test/testffmpeg.c b/test/testffmpeg.c
index 1770dca4506fb..4c3ef85b9abd5 100644
--- a/test/testffmpeg.c
+++ b/test/testffmpeg.c
@@ -436,20 +436,25 @@ static SDL_PropertiesID CreateVideoTextureProperties(AVFrame *frame, Uint32 form
 {
     AVFrameSideData *pSideData;
     SDL_PropertiesID props;
+    SDL_Colorspace colorspace = GetFrameColorspace(frame);
+
+    /* ITU-R BT.2408-6 recommends using an SDR white point of 203 nits, which is more likely for game content */
+    static const float k_flSDRWhitePoint = 203.0f;
+    float flMaxLuminance = k_flSDRWhitePoint;
 
     props = SDL_CreateProperties();
-    SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, GetFrameColorspace(frame));
+    SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, colorspace);
     pSideData = av_frame_get_side_data(frame, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
     if (pSideData) {
-        /* ITU-R BT.2408-6 recommends using an SDR white point of 203 nits, which is more likely for game content */
-        static const float k_flSDRWhitePoint = 203.0f;
-
         AVMasteringDisplayMetadata *pMasteringDisplayMetadata = (AVMasteringDisplayMetadata *)pSideData->data;
-        float flMaxLuminance = (float)pMasteringDisplayMetadata->max_luminance.num / pMasteringDisplayMetadata->max_luminance.den;
-        if (flMaxLuminance > k_flSDRWhitePoint) {
-            SDL_SetFloatProperty(props, SDL_PROP_TEXTURE_CREATE_SDR_WHITE_POINT_FLOAT, k_flSDRWhitePoint);
-            SDL_SetFloatProperty(props, SDL_PROP_TEXTURE_CREATE_HDR_HEADROOM_FLOAT, flMaxLuminance / k_flSDRWhitePoint);
-        }
+        flMaxLuminance = (float)pMasteringDisplayMetadata->max_luminance.num / pMasteringDisplayMetadata->max_luminance.den;
+    } else if (SDL_COLORSPACETRANSFER(colorspace) == SDL_TRANSFER_CHARACTERISTICS_PQ) {
+        /* The official definition is 10000, but PQ game content is often mastered for 400 or 1000 nits */
+        flMaxLuminance = 1000.0f;
+    }
+    if (flMaxLuminance > k_flSDRWhitePoint) {
+        SDL_SetFloatProperty(props, SDL_PROP_TEXTURE_CREATE_SDR_WHITE_POINT_FLOAT, k_flSDRWhitePoint);
+        SDL_SetFloatProperty(props, SDL_PROP_TEXTURE_CREATE_HDR_HEADROOM_FLOAT, flMaxLuminance / k_flSDRWhitePoint);
     }
     SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, format);
     SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, access);