diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index 5ce3a96b4..2cdcedae2 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -94,14 +94,13 @@ typedef enum SDL_EventType /* Display events */ /* 0x150 was SDL_DISPLAYEVENT, reserve the number for sdl2-compat */ - SDL_EVENT_DISPLAY_ORIENTATION = 0x151, /**< Display orientation has changed to data1 */ - SDL_EVENT_DISPLAY_ADDED, /**< Display has been added to the system */ - SDL_EVENT_DISPLAY_REMOVED, /**< Display has been removed from the system */ - SDL_EVENT_DISPLAY_MOVED, /**< Display has changed position */ + SDL_EVENT_DISPLAY_ORIENTATION = 0x151, /**< Display orientation has changed to data1 */ + SDL_EVENT_DISPLAY_ADDED, /**< Display has been added to the system */ + SDL_EVENT_DISPLAY_REMOVED, /**< Display has been removed from the system */ + SDL_EVENT_DISPLAY_MOVED, /**< Display has changed position */ SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED, /**< Display has changed content scale */ - SDL_EVENT_DISPLAY_HDR_STATE_CHANGED, /**< Display HDR properties have changed */ SDL_EVENT_DISPLAY_FIRST = SDL_EVENT_DISPLAY_ORIENTATION, - SDL_EVENT_DISPLAY_LAST = SDL_EVENT_DISPLAY_HDR_STATE_CHANGED, + SDL_EVENT_DISPLAY_LAST = SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED, /* Window events */ /* 0x200 was SDL_WINDOWEVENT, reserve the number for sdl2-compat */ @@ -134,6 +133,7 @@ typedef enum SDL_EventType associated with it are invalid */ SDL_EVENT_WINDOW_PEN_ENTER, /**< Window has gained focus of the pressure-sensitive pen with ID "data1" */ SDL_EVENT_WINDOW_PEN_LEAVE, /**< Window has lost focus of the pressure-sensitive pen with ID "data1" */ + SDL_EVENT_WINDOW_HDR_STATE_CHANGED, /**< Window HDR properties have changed */ SDL_EVENT_WINDOW_FIRST = SDL_EVENT_WINDOW_SHOWN, SDL_EVENT_WINDOW_LAST = SDL_EVENT_WINDOW_PEN_LEAVE, diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 0263d9e46..6c4486b7e 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -411,17 +411,9 @@ extern SDL_DECLSPEC SDL_DisplayID SDLCALL SDL_GetPrimaryDisplay(void); * The following read-only properties are provided by SDL: * * - `SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN`: true if the display has HDR - * headroom above the SDR white point. This property can change dynamically - * when SDL_EVENT_DISPLAY_HDR_STATE_CHANGED is sent. - * - `SDL_PROP_DISPLAY_SDR_WHITE_POINT_FLOAT`: the value of SDR white in the - * SDL_COLORSPACE_SRGB_LINEAR colorspace. On Windows this corresponds to the - * SDR white level in scRGB colorspace, and on Apple platforms this is - * always 1.0 for EDR content. This property can change dynamically when - * SDL_EVENT_DISPLAY_HDR_STATE_CHANGED is sent. - * - `SDL_PROP_DISPLAY_HDR_HEADROOM_FLOAT`: the additional high dynamic range - * that can be displayed, in terms of the SDR white point. When HDR is not - * enabled, this will be 1.0. This property can change dynamically when - * SDL_EVENT_DISPLAY_HDR_STATE_CHANGED is sent. + * headroom above the SDR white point. This is for informational and diagnostic + * purposes only, as not all platforms provide this information at the display + * level. * * On KMS/DRM: * @@ -443,8 +435,6 @@ extern SDL_DECLSPEC SDL_DisplayID SDLCALL SDL_GetPrimaryDisplay(void); extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetDisplayProperties(SDL_DisplayID displayID); #define SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN "SDL.display.HDR_enabled" -#define SDL_PROP_DISPLAY_SDR_WHITE_POINT_FLOAT "SDL.display.SDR_white_point" -#define SDL_PROP_DISPLAY_HDR_HEADROOM_FLOAT "SDL.display.HDR_headroom" #define SDL_PROP_DISPLAY_KMSDRM_PANEL_ORIENTATION_NUMBER "SDL.display.KMSDRM.panel_orientation" /** @@ -1113,6 +1103,18 @@ extern SDL_DECLSPEC SDL_Window *SDLCALL SDL_GetWindowParent(SDL_Window *window); * * - `SDL_PROP_WINDOW_SHAPE_POINTER`: the surface associated with a shaped * window + * - `SDL_PROP_WINDOW_HDR_ENABLED_BOOLEAN`: true if the window has HDR + * headroom above the SDR white point. This property can change dynamically + * when SDL_EVENT_WINDOW_HDR_STATE_CHANGED is sent. + * - `SDL_PROP_WINDOW_SDR_WHITE_LEVEL_FLOAT`: the value of SDR white in the + * SDL_COLORSPACE_SRGB_LINEAR colorspace. On Windows this corresponds to the + * SDR white level in scRGB colorspace, and on Apple platforms this is + * always 1.0 for EDR content. This property can change dynamically when + * SDL_EVENT_WINDOW_HDR_STATE_CHANGED is sent. + * - `SDL_PROP_WINDOW_HDR_HEADROOM_FLOAT`: the additional high dynamic range + * that can be displayed, in terms of the SDR white point. When HDR is not + * enabled, this will be 1.0. This property can change dynamically when + * SDL_EVENT_WINDOW_HDR_STATE_CHANGED is sent. * * On Android: * @@ -1216,6 +1218,9 @@ extern SDL_DECLSPEC SDL_Window *SDLCALL SDL_GetWindowParent(SDL_Window *window); extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window *window); #define SDL_PROP_WINDOW_SHAPE_POINTER "SDL.window.shape" +#define SDL_PROP_WINDOW_HDR_ENABLED_BOOLEAN "SDL.window.HDR_enabled" +#define SDL_PROP_WINDOW_SDR_WHITE_LEVEL_FLOAT "SDL.window.SDR_white_level" +#define SDL_PROP_WINDOW_HDR_HEADROOM_FLOAT "SDL.window.HDR_headroom" #define SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER "SDL.window.android.window" #define SDL_PROP_WINDOW_ANDROID_SURFACE_POINTER "SDL.window.android.surface" #define SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER "SDL.window.uikit.window" diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c index d9f26af09..7dd7ff752 100644 --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -285,7 +285,6 @@ static void SDL_LogEvent(const SDL_Event *event) SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_REMOVED); SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_MOVED); SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED); - SDL_DISPLAYEVENT_CASE(SDL_EVENT_DISPLAY_HDR_STATE_CHANGED); #undef SDL_DISPLAYEVENT_CASE #define SDL_WINDOWEVENT_CASE(x) \ @@ -319,6 +318,7 @@ static void SDL_LogEvent(const SDL_Event *event) SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_ENTER_FULLSCREEN); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_LEAVE_FULLSCREEN); SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_DESTROYED); + SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_HDR_STATE_CHANGED); #undef SDL_WINDOWEVENT_CASE #define PRINT_KEYDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%u)", (uint)event->kdevice.timestamp, (uint)event->kdevice.which) diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index 31d2dfde3..c5e126d6c 100644 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -737,16 +737,11 @@ static void UpdateMainViewDimensions(SDL_Renderer *renderer) static void UpdateHDRProperties(SDL_Renderer *renderer) { - SDL_DisplayID displayID = SDL_GetDisplayForWindow(renderer->window); - SDL_PropertiesID display_props; + SDL_PropertiesID window_props; SDL_PropertiesID renderer_props; - if (!displayID) { - return; - } - - display_props = SDL_GetDisplayProperties(displayID); - if (!display_props) { + window_props = SDL_GetWindowProperties(renderer->window); + if (!window_props) { return; } @@ -758,8 +753,8 @@ static void UpdateHDRProperties(SDL_Renderer *renderer) renderer->color_scale /= renderer->SDR_white_point; if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) { - renderer->SDR_white_point = SDL_GetFloatProperty(display_props, SDL_PROP_DISPLAY_SDR_WHITE_POINT_FLOAT, 1.0f); - renderer->HDR_headroom = SDL_GetFloatProperty(display_props, SDL_PROP_DISPLAY_HDR_HEADROOM_FLOAT, 1.0f); + renderer->SDR_white_point = SDL_GetFloatProperty(window_props, SDL_PROP_WINDOW_SDR_WHITE_LEVEL_FLOAT, 1.0f); + renderer->HDR_headroom = SDL_GetFloatProperty(window_props, SDL_PROP_WINDOW_HDR_HEADROOM_FLOAT, 1.0f); } else { renderer->SDR_white_point = 1.0f; renderer->HDR_headroom = 1.0f; @@ -836,7 +831,7 @@ static int SDLCALL SDL_RendererEventWatch(void *userdata, SDL_Event *event) UpdateHDRProperties(renderer); } } - } else if (event->type == SDL_EVENT_DISPLAY_HDR_STATE_CHANGED) { + } else if (event->type == SDL_EVENT_WINDOW_HDR_STATE_CHANGED) { UpdateHDRProperties(renderer); } diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index fa6493f6a..e3b44258a 100644 --- a/src/test/SDL_test_common.c +++ b/src/test/SDL_test_common.c @@ -1576,12 +1576,6 @@ static void SDLTest_PrintEvent(const SDL_Event *event) event->display.displayID, (int)(scale * 100.0f)); } break; - case SDL_EVENT_DISPLAY_HDR_STATE_CHANGED: - { - SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " HDR %s", - event->display.displayID, event->display.data1 ? "enabled" : "disabled"); - } - break; case SDL_EVENT_DISPLAY_MOVED: SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " changed position", event->display.displayID); @@ -1668,6 +1662,9 @@ static void SDLTest_PrintEvent(const SDL_Event *event) case SDL_EVENT_WINDOW_DESTROYED: SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " destroyed", event->window.windowID); break; + case SDL_EVENT_WINDOW_HDR_STATE_CHANGED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " HDR %s", event->window.windowID, event->window.data1 ? "enabled" : "disabled"); + break; case SDL_EVENT_KEYBOARD_ADDED: SDL_Log("SDL EVENT: Keyboard %" SDL_PRIu32 " attached", event->kdevice.which); diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 13a388a75..9add48fca 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -34,6 +34,12 @@ typedef struct SDL_DisplayData SDL_DisplayData; typedef struct SDL_DisplayModeData SDL_DisplayModeData; typedef struct SDL_WindowData SDL_WindowData; +typedef struct +{ + float SDR_white_level; + float HDR_headroom; +} SDL_HDROutputProperties; + /* Define the SDL window structure, corresponding to toplevel windows */ struct SDL_Window { @@ -82,6 +88,7 @@ struct SDL_Window SDL_DisplayMode requested_fullscreen_mode; SDL_DisplayMode current_fullscreen_mode; + SDL_HDROutputProperties HDR; float opacity; @@ -119,12 +126,6 @@ struct SDL_Window #define SDL_WINDOW_IS_POPUP(W) \ (((W)->flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU)) != 0) -typedef struct -{ - float SDR_white_point; - float HDR_headroom; -} SDL_HDRDisplayProperties; - /* * Define the SDL display structure. * This corresponds to physical monitors attached to the system. @@ -141,7 +142,7 @@ struct SDL_VideoDisplay SDL_DisplayOrientation natural_orientation; SDL_DisplayOrientation current_orientation; float content_scale; - SDL_HDRDisplayProperties HDR; + SDL_HDROutputProperties HDR; SDL_Window *fullscreen_window; @@ -160,7 +161,8 @@ typedef enum VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS = 0x04, VIDEO_DEVICE_CAPS_FULLSCREEN_ONLY = 0x08, VIDEO_DEVICE_CAPS_SENDS_DISPLAY_CHANGES = 0x10, - VIDEO_DEVICE_CAPS_DISABLE_MOUSE_WARP_ON_FULLSCREEN_TRANSITIONS = 0x20 + VIDEO_DEVICE_CAPS_DISABLE_MOUSE_WARP_ON_FULLSCREEN_TRANSITIONS = 0x20, + VIDEO_DEVICE_CAPS_SENDS_HDR_CHANGES = 0x40 } DeviceCaps; /* Fullscreen operations */ @@ -511,7 +513,7 @@ extern void SDL_ResetFullscreenDisplayModes(SDL_VideoDisplay *display); extern void SDL_SetDesktopDisplayMode(SDL_VideoDisplay *display, const SDL_DisplayMode *mode); extern void SDL_SetCurrentDisplayMode(SDL_VideoDisplay *display, const SDL_DisplayMode *mode); extern void SDL_SetDisplayContentScale(SDL_VideoDisplay *display, float scale); -extern void SDL_SetDisplayHDRProperties(SDL_VideoDisplay *display, const SDL_HDRDisplayProperties *HDR); +extern void SDL_SetDisplayHDRProperties(SDL_VideoDisplay *display, const SDL_HDROutputProperties *HDR); extern int SDL_SetDisplayModeForDisplay(SDL_VideoDisplay *display, SDL_DisplayMode *mode); extern SDL_VideoDisplay *SDL_GetVideoDisplay(SDL_DisplayID display); extern SDL_DisplayID SDL_GetDisplayForWindowPosition(SDL_Window *window); @@ -521,6 +523,7 @@ extern int SDL_GetDisplayIndex(SDL_DisplayID displayID); extern SDL_DisplayData *SDL_GetDisplayDriverData(SDL_DisplayID display); extern SDL_DisplayData *SDL_GetDisplayDriverDataForWindow(SDL_Window *window); extern int SDL_GetMessageBoxCount(void); +extern void SDL_SetWindowHDRProperties(SDL_Window *window, const SDL_HDROutputProperties *HDR, SDL_bool send_event); extern void SDL_GL_DeduceMaxSupportedESProfile(int *major, int *minor); diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 9032cd7f9..2cfbfb96d 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -195,6 +195,11 @@ static SDL_bool SDL_DisableMouseWarpOnFullscreenTransitions(SDL_VideoDevice *_th return !!(_this->device_caps & VIDEO_DEVICE_CAPS_DISABLE_MOUSE_WARP_ON_FULLSCREEN_TRANSITIONS); } +static SDL_bool SDL_DriverSendsHDRChanges(SDL_VideoDevice *_this) +{ + return !!(_this->device_caps & VIDEO_DEVICE_CAPS_SENDS_HDR_CHANGES); +} + /* Hint to treat all window ops as synchronous */ static SDL_bool syncHint; @@ -763,23 +768,11 @@ SDL_DisplayID SDL_AddVideoDisplay(const SDL_VideoDisplay *display, SDL_bool send new_display->fullscreen_modes[i].displayID = id; } - props = SDL_GetDisplayProperties(id); + new_display->HDR.HDR_headroom = SDL_max(display->HDR.HDR_headroom, 1.0f); + new_display->HDR.SDR_white_level = SDL_max(display->HDR.SDR_white_level, 1.0f); - if (display->HDR.HDR_headroom > 1.0f) { - SDL_SetBooleanProperty(props, SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN, SDL_TRUE); - } else { - SDL_SetBooleanProperty(props, SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN, SDL_FALSE); - } - if (display->HDR.SDR_white_point <= 1.0f) { - SDL_SetFloatProperty(props, SDL_PROP_DISPLAY_SDR_WHITE_POINT_FLOAT, 1.0f); - } else { - SDL_SetFloatProperty(props, SDL_PROP_DISPLAY_SDR_WHITE_POINT_FLOAT, display->HDR.SDR_white_point); - } - if (display->HDR.HDR_headroom <= 1.0f) { - SDL_SetFloatProperty(props, SDL_PROP_DISPLAY_HDR_HEADROOM_FLOAT, 1.0f); - } else { - SDL_SetFloatProperty(props, SDL_PROP_DISPLAY_HDR_HEADROOM_FLOAT, display->HDR.HDR_headroom); - } + props = SDL_GetDisplayProperties(id); + SDL_SetBooleanProperty(props, SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN, new_display->HDR.HDR_headroom > 1.0f); SDL_UpdateDesktopBounds(); @@ -1053,37 +1046,42 @@ float SDL_GetDisplayContentScale(SDL_DisplayID displayID) return display->content_scale; } -void SDL_SetDisplayHDRProperties(SDL_VideoDisplay *display, const SDL_HDRDisplayProperties *HDR) +void SDL_SetWindowHDRProperties(SDL_Window *window, const SDL_HDROutputProperties *HDR, SDL_bool send_event) +{ + if (window->HDR.HDR_headroom != HDR->HDR_headroom || window->HDR.SDR_white_level != window->HDR.SDR_white_level) { + SDL_PropertiesID window_props = SDL_GetWindowProperties(window); + + SDL_SetFloatProperty(window_props, SDL_PROP_WINDOW_HDR_HEADROOM_FLOAT, SDL_max(HDR->HDR_headroom, 1.0f)); + SDL_SetFloatProperty(window_props, SDL_PROP_WINDOW_SDR_WHITE_LEVEL_FLOAT, SDL_max(HDR->SDR_white_level, 1.0f)); + SDL_SetBooleanProperty(window_props, SDL_PROP_WINDOW_HDR_ENABLED_BOOLEAN, HDR->HDR_headroom > 1.0f); + SDL_copyp(&window->HDR, HDR); + + if (send_event) { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_HDR_STATE_CHANGED, HDR->HDR_headroom > 1.0f, 0); + } + } +} + +void SDL_SetDisplayHDRProperties(SDL_VideoDisplay *display, const SDL_HDROutputProperties *HDR) { - SDL_PropertiesID props = SDL_GetDisplayProperties(display->id); SDL_bool changed = SDL_FALSE; - if (HDR->SDR_white_point != display->HDR.SDR_white_point) { - if (HDR->SDR_white_point <= 1.0f) { - SDL_SetFloatProperty(props, SDL_PROP_DISPLAY_SDR_WHITE_POINT_FLOAT, 1.0f); - } else { - SDL_SetFloatProperty(props, SDL_PROP_DISPLAY_SDR_WHITE_POINT_FLOAT, HDR->SDR_white_point); - } + if (HDR->SDR_white_level != display->HDR.SDR_white_level) { + display->HDR.SDR_white_level = SDL_max(HDR->SDR_white_level, 1.0f); changed = SDL_TRUE; } if (HDR->HDR_headroom != display->HDR.HDR_headroom) { - if (HDR->HDR_headroom > 1.0f) { - SDL_SetBooleanProperty(props, SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN, SDL_TRUE); - } else { - SDL_SetBooleanProperty(props, SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN, SDL_FALSE); - } - if (HDR->HDR_headroom <= 1.0f) { - SDL_SetFloatProperty(props, SDL_PROP_DISPLAY_HDR_HEADROOM_FLOAT, 1.0f); - } else { - SDL_SetFloatProperty(props, SDL_PROP_DISPLAY_HDR_HEADROOM_FLOAT, HDR->HDR_headroom); - } + display->HDR.HDR_headroom = SDL_max(HDR->HDR_headroom, 1.0f); changed = SDL_TRUE; } SDL_copyp(&display->HDR, HDR); - if (changed) { - SDL_bool enabled = SDL_GetBooleanProperty(props, SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN, SDL_FALSE); - SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_HDR_STATE_CHANGED, enabled); + if (changed && !SDL_DriverSendsHDRChanges(_this)) { + for (SDL_Window *w = display->device->windows; w; w = w->next) { + if (SDL_GetDisplayForWindow(w) == display->id) { + SDL_SetWindowHDRProperties(w, &display->HDR, SDL_TRUE); + } + } } } @@ -2247,8 +2245,10 @@ SDL_Window *SDL_CreateWindowWithProperties(SDL_PropertiesID props) window->undefined_x = undefined_x; window->undefined_y = undefined_y; + SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window); + SDL_SetWindowHDRProperties(window, &display->HDR, SDL_FALSE); + if (flags & SDL_WINDOW_FULLSCREEN || IsFullscreenOnly(_this)) { - SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window); SDL_Rect bounds; SDL_GetDisplayBounds(display->id, &bounds); diff --git a/src/video/cocoa/SDL_cocoamodes.m b/src/video/cocoa/SDL_cocoamodes.m index 862bc56b5..638e82d6b 100644 --- a/src/video/cocoa/SDL_cocoamodes.m +++ b/src/video/cocoa/SDL_cocoamodes.m @@ -290,9 +290,9 @@ static char *Cocoa_GetDisplayName(CGDirectDisplayID displayID) return displayName; } -static void Cocoa_GetHDRProperties(CGDirectDisplayID displayID, SDL_HDRDisplayProperties *HDR) +static void Cocoa_GetHDRProperties(CGDirectDisplayID displayID, SDL_HDROutputProperties *HDR) { - HDR->SDR_white_point = 1.0f; + HDR->SDR_white_level = 1.0f; HDR->HDR_headroom = 1.0f; #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 /* Added in the 10.15 SDK */ @@ -397,7 +397,7 @@ void Cocoa_InitModes(SDL_VideoDevice *_this) void Cocoa_UpdateDisplays(SDL_VideoDevice *_this) { - SDL_HDRDisplayProperties HDR; + SDL_HDROutputProperties HDR; int i; for (i = 0; i < _this->num_displays; ++i) { diff --git a/src/video/uikit/SDL_uikitmodes.m b/src/video/uikit/SDL_uikitmodes.m index 2c3329c86..8b8ada13d 100644 --- a/src/video/uikit/SDL_uikitmodes.m +++ b/src/video/uikit/SDL_uikitmodes.m @@ -242,7 +242,7 @@ int UIKit_AddDisplay(UIScreen *uiscreen, SDL_bool send_event) } display.desktop_mode = mode; - display.HDR.SDR_white_point = 1.0f; + display.HDR.SDR_white_level = 1.0f; display.HDR.HDR_headroom = 1.0f; #ifndef SDL_PLATFORM_TVOS diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 0567ae5bd..aedc1b239 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -46,6 +46,7 @@ #include "alpha-modifier-v1-client-protocol.h" #include "cursor-shape-v1-client-protocol.h" #include "fractional-scale-v1-client-protocol.h" +#include "frog-color-management-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h" #include "input-timestamps-unstable-v1-client-protocol.h" #include "kde-output-order-v1-client-protocol.h" @@ -526,7 +527,8 @@ static SDL_VideoDevice *Wayland_CreateDevice(void) VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT | VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS | VIDEO_DEVICE_CAPS_SENDS_DISPLAY_CHANGES | - VIDEO_DEVICE_CAPS_DISABLE_MOUSE_WARP_ON_FULLSCREEN_TRANSITIONS; + VIDEO_DEVICE_CAPS_DISABLE_MOUSE_WARP_ON_FULLSCREEN_TRANSITIONS | + VIDEO_DEVICE_CAPS_SENDS_HDR_CHANGES; return device; } @@ -1114,6 +1116,8 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint } else if (SDL_strcmp(interface, "kde_output_order_v1") == 0) { d->kde_output_order = wl_registry_bind(d->registry, id, &kde_output_order_v1_interface, 1); kde_output_order_v1_add_listener(d->kde_output_order, &kde_output_order_listener, d); + } else if (SDL_strcmp(interface, "frog_color_management_factory_v1") == 0) { + d->frog_color_management_factory_v1 = wl_registry_bind(d->registry, id, &frog_color_management_factory_v1_interface, 1); } } @@ -1384,6 +1388,11 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this) data->kde_output_order = NULL; } + if (data->frog_color_management_factory_v1) { + frog_color_management_factory_v1_destroy(data->frog_color_management_factory_v1); + data->frog_color_management_factory_v1 = NULL; + } + if (data->compositor) { wl_compositor_destroy(data->compositor); data->compositor = NULL; diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h index f5cc778b9..6802fcf0a 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/src/video/wayland/SDL_waylandvideo.h @@ -83,6 +83,7 @@ struct SDL_VideoData struct xdg_wm_dialog_v1 *xdg_wm_dialog_v1; struct wp_alpha_modifier_v1 *wp_alpha_modifier_v1; struct kde_output_order_v1 *kde_output_order; + struct frog_color_management_factory_v1 *frog_color_management_factory_v1; struct xkb_context *xkb_context; struct SDL_WaylandInput *input; diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index 756308f98..cd0d16399 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -41,6 +41,7 @@ #include "fractional-scale-v1-client-protocol.h" #include "xdg-foreign-unstable-v2-client-protocol.h" #include "xdg-dialog-v1-client-protocol.h" +#include "frog-color-management-v1-client-protocol.h" #ifdef HAVE_LIBDECOR_H #include @@ -1486,6 +1487,47 @@ static const struct wp_fractional_scale_v1_listener fractional_scale_listener = handle_preferred_fractional_scale }; +static void frog_preferred_metadata_handler(void *data, struct frog_color_managed_surface *frog_color_managed_surface, uint32_t transfer_function, + uint32_t output_display_primary_red_x, uint32_t output_display_primary_red_y, + uint32_t output_display_primary_green_x, uint32_t output_display_primary_green_y, + uint32_t output_display_primary_blue_x, uint32_t output_display_primary_blue_y, + uint32_t output_white_point_x, uint32_t output_white_point_y, + uint32_t max_luminance, uint32_t min_luminance, + uint32_t max_full_frame_luminance) +{ + SDL_WindowData *wind = (SDL_WindowData *)data; + SDL_HDROutputProperties HDR; + + SDL_zero(HDR); + + switch (transfer_function) { + case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ: + /* ITU-R BT.2408-7 (Sept 2023) has the reference PQ white level at 203 nits, + * while older Dolby documentation claims a reference level of 100 nits. + * + * Use 203 nits for now. + */ + HDR.HDR_headroom = max_luminance / 203.0f; + break; + case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_SCRGB_LINEAR: + HDR.HDR_headroom = max_luminance / 80.0f; + break; + case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_UNDEFINED: + case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_SRGB: + case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_GAMMA_22: + default: + HDR.HDR_headroom = 1.0f; + break; + } + + HDR.SDR_white_level = 1.0f; + SDL_SetWindowHDRProperties(wind->sdlwindow, &HDR, SDL_TRUE); +} + +static const struct frog_color_managed_surface_listener frog_surface_listener = { + frog_preferred_metadata_handler +}; + static void SetKeyboardFocus(SDL_Window *window) { SDL_Window *kb_focus = SDL_GetKeyboardFocus(); @@ -2366,9 +2408,16 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert } } - if (!custom_surface_role && c->wp_alpha_modifier_v1) { - data->wp_alpha_modifier_surface_v1 = wp_alpha_modifier_v1_get_surface(c->wp_alpha_modifier_v1, data->surface); - wp_alpha_modifier_surface_v1_set_multiplier(data->wp_alpha_modifier_surface_v1, SDL_MAX_UINT32); + if (!custom_surface_role) { + if (c->frog_color_management_factory_v1) { + data->frog_color_managed_surface = frog_color_management_factory_v1_get_color_managed_surface(c->frog_color_management_factory_v1, data->surface); + frog_color_managed_surface_add_listener(data->frog_color_managed_surface, &frog_surface_listener, data); + } + + if (c->wp_alpha_modifier_v1) { + data->wp_alpha_modifier_surface_v1 = wp_alpha_modifier_v1_get_surface(c->wp_alpha_modifier_v1, data->surface); + wp_alpha_modifier_surface_v1_set_multiplier(data->wp_alpha_modifier_surface_v1, SDL_MAX_UINT32); + } } /* Must be called before EGL configuration to set the drawable backbuffer size. */ @@ -2701,6 +2750,10 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) wp_alpha_modifier_surface_v1_destroy(wind->wp_alpha_modifier_surface_v1); } + if (wind->frog_color_managed_surface) { + frog_color_managed_surface_destroy(wind->frog_color_managed_surface); + } + SDL_free(wind->outputs); SDL_free(wind->app_id); diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index 21c7e5b86..489c0edaa 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -98,6 +98,7 @@ struct SDL_WindowData struct zxdg_exported_v2 *exported; struct xdg_dialog_v1 *xdg_dialog_v1; struct wp_alpha_modifier_surface_v1 *wp_alpha_modifier_surface_v1; + struct frog_color_managed_surface *frog_color_managed_surface; SDL_AtomicInt swap_interval_ready; diff --git a/src/video/windows/SDL_windowsmodes.c b/src/video/windows/SDL_windowsmodes.c index 2aee71adc..adc8ed860 100644 --- a/src/video/windows/SDL_windowsmodes.c +++ b/src/video/windows/SDL_windowsmodes.c @@ -443,7 +443,7 @@ static float WIN_GetSDRWhitePoint(SDL_VideoDevice *_this, HMONITOR hMonitor) { DISPLAYCONFIG_PATH_INFO path_info; SDL_VideoData *videodata = _this->driverdata; - float SDR_white_point = 1.0f; + float SDR_white_level = 1.0f; if (WIN_GetMonitorPathInfo(videodata, hMonitor, &path_info)) { DISPLAYCONFIG_SDR_WHITE_LEVEL white_level; @@ -456,13 +456,13 @@ static float WIN_GetSDRWhitePoint(SDL_VideoDevice *_this, HMONITOR hMonitor) /* WIN_GetMonitorPathInfo() succeeded: DisplayConfigGetDeviceInfo is not NULL */ if (videodata->DisplayConfigGetDeviceInfo(&white_level.header) == ERROR_SUCCESS && white_level.SDRWhiteLevel > 0) { - SDR_white_point = (white_level.SDRWhiteLevel / 1000.0f); + SDR_white_level = (white_level.SDRWhiteLevel / 1000.0f); } } - return SDR_white_point; + return SDR_white_level; } -static void WIN_GetHDRProperties(SDL_VideoDevice *_this, HMONITOR hMonitor, SDL_HDRDisplayProperties *HDR) +static void WIN_GetHDRProperties(SDL_VideoDevice *_this, HMONITOR hMonitor, SDL_HDROutputProperties *HDR) { DXGI_OUTPUT_DESC1 desc; @@ -470,8 +470,8 @@ static void WIN_GetHDRProperties(SDL_VideoDevice *_this, HMONITOR hMonitor, SDL_ if (WIN_GetMonitorDESC1(hMonitor, &desc)) { if (desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) { - HDR->SDR_white_point = WIN_GetSDRWhitePoint(_this, hMonitor); - HDR->HDR_headroom = (desc.MaxLuminance / 80.0f) / HDR->SDR_white_point; + HDR->SDR_white_level = WIN_GetSDRWhitePoint(_this, hMonitor); + HDR->HDR_headroom = (desc.MaxLuminance / 80.0f) / HDR->SDR_white_level; } } } @@ -529,7 +529,6 @@ static void WIN_AddDisplay(SDL_VideoDevice *_this, HMONITOR hMonitor, const MONI if (!_this->setting_display_mode) { SDL_VideoDisplay *existing_display = _this->displays[i]; SDL_Rect bounds; - SDL_HDRDisplayProperties HDR; SDL_ResetFullscreenDisplayModes(existing_display); SDL_SetDesktopDisplayMode(existing_display, &mode); @@ -544,6 +543,7 @@ static void WIN_AddDisplay(SDL_VideoDevice *_this, HMONITOR hMonitor, const MONI SDL_SendDisplayEvent(existing_display, SDL_EVENT_DISPLAY_ORIENTATION, current_orientation); SDL_SetDisplayContentScale(existing_display, content_scale); #ifdef HAVE_DXGI1_6_H + SDL_HDROutputProperties HDR; WIN_GetHDRProperties(_this, hMonitor, &HDR); SDL_SetDisplayHDRProperties(existing_display, &HDR); #endif diff --git a/test/testcolorspace.c b/test/testcolorspace.c index 1e62e2684..9b080b314 100644 --- a/test/testcolorspace.c +++ b/test/testcolorspace.c @@ -59,8 +59,8 @@ static void UpdateHDRState(void) SDL_PropertiesID props; SDL_bool HDR_enabled; - props = SDL_GetDisplayProperties(SDL_GetDisplayForWindow(window)); - HDR_enabled = SDL_GetBooleanProperty(props, SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN, SDL_FALSE); + props = SDL_GetWindowProperties(window); + HDR_enabled = SDL_GetBooleanProperty(props, SDL_PROP_WINDOW_HDR_ENABLED_BOOLEAN, SDL_FALSE); SDL_Log("HDR %s\n", HDR_enabled ? "enabled" : "disabled"); @@ -523,7 +523,7 @@ static void loop(void) default: break; } - } else if (event.type == SDL_EVENT_DISPLAY_HDR_STATE_CHANGED) { + } else if (event.type == SDL_EVENT_WINDOW_HDR_STATE_CHANGED) { UpdateHDRState(); } else if (event.type == SDL_EVENT_QUIT) { done = 1; diff --git a/test/testdisplayinfo.c b/test/testdisplayinfo.c index fa9ed7b43..a7e7e8db8 100644 --- a/test/testdisplayinfo.c +++ b/test/testdisplayinfo.c @@ -66,12 +66,14 @@ int main(int argc, char *argv[]) for (i = 0; i < num_displays; i++) { SDL_DisplayID dpy = displays[i]; + SDL_PropertiesID props = SDL_GetDisplayProperties(dpy); SDL_Rect rect = { 0, 0, 0, 0 }; int m, num_modes = 0; + const SDL_bool has_HDR = SDL_GetBooleanProperty(props, SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN, SDL_FALSE); SDL_GetDisplayBounds(dpy, &rect); modes = SDL_GetFullscreenDisplayModes(dpy, &num_modes); - SDL_Log("%" SDL_PRIu32 ": \"%s\" (%dx%d at %d,%d), content scale %.2f, %d fullscreen modes.\n", dpy, SDL_GetDisplayName(dpy), rect.w, rect.h, rect.x, rect.y, SDL_GetDisplayContentScale(dpy), num_modes); + SDL_Log("%" SDL_PRIu32 ": \"%s\" (%dx%d at %d,%d), content scale %.2f, %d fullscreen modes, HDR capable: %s.\n", dpy, SDL_GetDisplayName(dpy), rect.w, rect.h, rect.x, rect.y, SDL_GetDisplayContentScale(dpy), num_modes, has_HDR ? "yes" : "no"); mode = SDL_GetCurrentDisplayMode(dpy); if (mode) { diff --git a/test/testffmpeg.c b/test/testffmpeg.c index 63f5195fb..6bb30186b 100644 --- a/test/testffmpeg.c +++ b/test/testffmpeg.c @@ -155,7 +155,7 @@ static SDL_bool CreateWindowAndRenderer(SDL_WindowFlags window_flags, const char if (useVulkan) { SetupVulkanRenderProperties(vulkan_context, props); } - if (SDL_GetBooleanProperty(SDL_GetDisplayProperties(SDL_GetDisplayForWindow(window)), SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN, SDL_FALSE)) { + if (SDL_GetBooleanProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_HDR_ENABLED_BOOLEAN, SDL_FALSE)) { /* Try to create an HDR capable renderer */ SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER, SDL_COLORSPACE_SRGB_LINEAR); renderer = SDL_CreateRendererWithProperties(props); diff --git a/wayland-protocols/frog-color-management-v1.xml b/wayland-protocols/frog-color-management-v1.xml new file mode 100644 index 000000000..3d6c2f5d0 --- /dev/null +++ b/wayland-protocols/frog-color-management-v1.xml @@ -0,0 +1,356 @@ + + + + + Copyright © 2023 Joshua Ashton for Valve Software + Copyright © 2023 Xaver Hugl + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + The aim of this color management extension is to get HDR games working quickly, + and have an easy way to test implementations in the wild before the upstream + protocol is ready to be merged. + For that purpose it's intentionally limited and cut down and does not serve + all uses cases. + + + + + The color management factory singleton creates color managed surface objects. + + + + + + + + + + + + + + + + Interface for changing surface color management and HDR state. + + An implementation must: support every part of the version + of the frog_color_managed_surface interface it exposes. + Including all known enums associated with a given version. + + + + + Destroying the color managed surface resets all known color + state for the surface back to 'undefined' implementation-specific + values. + + + + + + Extended information on the transfer functions described + here can be found in the Khronos Data Format specification: + + https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + Extended information on render intents described + here can be found in ICC.1:2022: + + https://www.color.org/specification/ICC.1-2022-05.pdf + + + + + + + NOTE: On a surface with "perceptual" (default) render intent, handling of the container's color volume + is implementation-specific, and may differ between different transfer functions it is paired with: + ie. sRGB + 709 rendering may have it's primaries widened to more of the available display's gamut + to be be more pleasing for the viewer. + Compared to scRGB Linear + 709 being treated faithfully as 709 + (including utilizing negatives out of the 709 gamut triangle) + + + + + + + Forwards HDR metadata from the client to the compositor. + + HDR Metadata Infoframe as per CTA 861.G spec. + + Usage of this HDR metadata is implementation specific and + outside of the scope of this protocol. + + + + Mastering Red Color Primary X Coordinate of the Data. + + Coded as unsigned 16-bit values in units of + 0.00002, where 0x0000 represents zero and 0xC350 + represents 1.0000. + + + + + Mastering Red Color Primary Y Coordinate of the Data. + + Coded as unsigned 16-bit values in units of + 0.00002, where 0x0000 represents zero and 0xC350 + represents 1.0000. + + + + + Mastering Green Color Primary X Coordinate of the Data. + + Coded as unsigned 16-bit values in units of + 0.00002, where 0x0000 represents zero and 0xC350 + represents 1.0000. + + + + + Mastering Green Color Primary Y Coordinate of the Data. + + Coded as unsigned 16-bit values in units of + 0.00002, where 0x0000 represents zero and 0xC350 + represents 1.0000. + + + + + Mastering Blue Color Primary X Coordinate of the Data. + + Coded as unsigned 16-bit values in units of + 0.00002, where 0x0000 represents zero and 0xC350 + represents 1.0000. + + + + + Mastering Blue Color Primary Y Coordinate of the Data. + + Coded as unsigned 16-bit values in units of + 0.00002, where 0x0000 represents zero and 0xC350 + represents 1.0000. + + + + + Mastering White Point X Coordinate of the Data. + + These are coded as unsigned 16-bit values in units of + 0.00002, where 0x0000 represents zero and 0xC350 + represents 1.0000. + + + + + Mastering White Point Y Coordinate of the Data. + + These are coded as unsigned 16-bit values in units of + 0.00002, where 0x0000 represents zero and 0xC350 + represents 1.0000. + + + + + Max Mastering Display Luminance. + This value is coded as an unsigned 16-bit value in units of 1 cd/m2, + where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. + + + + + Min Mastering Display Luminance. + This value is coded as an unsigned 16-bit value in units of + 0.0001 cd/m2, where 0x0001 represents 0.0001 cd/m2 and 0xFFFF + represents 6.5535 cd/m2. + + + + + Max Content Light Level. + This value is coded as an unsigned 16-bit value in units of 1 cd/m2, + where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. + + + + + Max Frame Average Light Level. + This value is coded as an unsigned 16-bit value in units of 1 cd/m2, + where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. + + + + + + + Current preferred metadata for a surface. + The application should use this information to tone-map its buffers + to this target before committing. + + This metadata does not necessarily correspond to any physical output, but + rather what the compositor thinks would be best for a given surface. + + + + Specifies a known transfer function that corresponds to the + output the surface is targeting. + + + + + Output Red Color Primary X Coordinate of the Data. + + Coded as unsigned 16-bit values in units of + 0.00002, where 0x0000 represents zero and 0xC350 + represents 1.0000. + + + + + Output Red Color Primary Y Coordinate of the Data. + + Coded as unsigned 16-bit values in units of + 0.00002, where 0x0000 represents zero and 0xC350 + represents 1.0000. + + + + + Output Green Color Primary X Coordinate of the Data. + + Coded as unsigned 16-bit values in units of + 0.00002, where 0x0000 represents zero and 0xC350 + represents 1.0000. + + + + + Output Green Color Primary Y Coordinate of the Data. + + Coded as unsigned 16-bit values in units of + 0.00002, where 0x0000 represents zero and 0xC350 + represents 1.0000. + + + + + Output Blue Color Primary X Coordinate of the Data. + + Coded as unsigned 16-bit values in units of + 0.00002, where 0x0000 represents zero and 0xC350 + represents 1.0000. + + + + + Output Blue Color Primary Y Coordinate of the Data. + + Coded as unsigned 16-bit values in units of + 0.00002, where 0x0000 represents zero and 0xC350 + represents 1.0000. + + + + + Output White Point X Coordinate of the Data. + + These are coded as unsigned 16-bit values in units of + 0.00002, where 0x0000 represents zero and 0xC350 + represents 1.0000. + + + + + Output White Point Y Coordinate of the Data. + + These are coded as unsigned 16-bit values in units of + 0.00002, where 0x0000 represents zero and 0xC350 + represents 1.0000. + + + + + Max Output Luminance + The max luminance in nits that the output is capable of rendering in small areas. + Content should: not exceed this value to avoid clipping. + + This value is coded as an unsigned 16-bit value in units of 1 cd/m2, + where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. + + + + + Min Output Luminance + The min luminance that the output is capable of rendering. + Content should: not exceed this value to avoid clipping. + + This value is coded as an unsigned 16-bit value in units of + 0.0001 cd/m2, where 0x0001 represents 0.0001 cd/m2 and 0xFFFF + represents 6.5535 cd/m2. + + + + + Max Full Frame Luminance + The max luminance in nits that the output is capable of rendering for the + full frame sustained. + + This value is coded as an unsigned 16-bit value in units of 1 cd/m2, + where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. + + + + +