wayland: Add color manager protocol support

Support the official wp_color_manager_v1 protocol.
This commit is contained in:
Frank Praznik
2024-10-12 12:10:32 -04:00
parent 6ef687c864
commit fadb261b66
7 changed files with 2022 additions and 6 deletions

View File

@@ -0,0 +1,224 @@
/*
Simple DirectMedia Layer
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_VIDEO_DRIVER_WAYLAND
#include "SDL_waylandcolor.h"
#include "SDL_waylandvideo.h"
#include "SDL_waylandwindow.h"
#include "color-management-v1-client-protocol.h"
typedef struct Wayland_ColorInfoState
{
struct wp_image_description_v1 *wp_image_description;
struct wp_image_description_info_v1 *wp_image_description_info;
Wayland_ColorInfo *info;
bool result;
} Wayland_ColorInfoState;
static void image_description_info_handle_done(void *data,
struct wp_image_description_info_v1 *wp_image_description_info_v1)
{
Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
if (state->wp_image_description_info) {
wp_image_description_info_v1_destroy(state->wp_image_description_info);
state->wp_image_description_info = NULL;
}
if (state->wp_image_description) {
wp_image_description_v1_destroy(state->wp_image_description);
state->wp_image_description = NULL;
}
state->result = true;
}
static void image_description_info_handle_icc_file(void *data,
struct wp_image_description_info_v1 *wp_image_description_info_v1,
int32_t icc, uint32_t icc_size)
{
Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
state->info->icc_fd = icc;
state->info->icc_size = icc_size;
}
static void image_description_info_handle_primaries(void *data,
struct wp_image_description_info_v1 *wp_image_description_info_v1,
int32_t r_x, int32_t r_y,
int32_t g_x, int32_t g_y,
int32_t b_x, int32_t b_y,
int32_t w_x, int32_t w_y)
{
// NOP
}
static void image_description_info_handle_primaries_named(void *data,
struct wp_image_description_info_v1 *wp_image_description_info_v1,
uint32_t primaries)
{
// NOP
}
static void image_description_info_handle_tf_power(void *data,
struct wp_image_description_info_v1 *wp_image_description_info_v1,
uint32_t eexp)
{
// NOP
}
static void image_description_info_handle_tf_named(void *data,
struct wp_image_description_info_v1 *wp_image_description_info_v1,
uint32_t tf)
{
// NOP
}
static void image_description_info_handle_luminances(void *data,
struct wp_image_description_info_v1 *wp_image_description_info_v1,
uint32_t min_lum,
uint32_t max_lum,
uint32_t reference_lum)
{
Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
state->info->HDR.HDR_headroom = (float)max_lum / (float)reference_lum;
}
static void image_description_info_handle_target_primaries(void *data,
struct wp_image_description_info_v1 *wp_image_description_info_v1,
int32_t r_x, int32_t r_y,
int32_t g_x, int32_t g_y,
int32_t b_x, int32_t b_y,
int32_t w_x, int32_t w_y)
{
// NOP
}
static void image_description_info_handle_target_luminance(void *data,
struct wp_image_description_info_v1 *wp_image_description_info_v1,
uint32_t min_lum,
uint32_t max_lum)
{
// NOP
}
static void image_description_info_handle_target_max_cll(void *data,
struct wp_image_description_info_v1 *wp_image_description_info_v1,
uint32_t max_cll)
{
// NOP
}
static void image_description_info_handle_target_max_fall(void *data,
struct wp_image_description_info_v1 *wp_image_description_info_v1,
uint32_t max_fall)
{
// NOP
}
static const struct wp_image_description_info_v1_listener image_description_info_listener = {
image_description_info_handle_done,
image_description_info_handle_icc_file,
image_description_info_handle_primaries,
image_description_info_handle_primaries_named,
image_description_info_handle_tf_power,
image_description_info_handle_tf_named,
image_description_info_handle_luminances,
image_description_info_handle_target_primaries,
image_description_info_handle_target_luminance,
image_description_info_handle_target_max_cll,
image_description_info_handle_target_max_fall
};
static void PumpColorspaceEvents(Wayland_ColorInfoState *state)
{
SDL_VideoData *vid = SDL_GetVideoDevice()->internal;
// Run the image description sequence to completion in its own queue.
struct wl_event_queue *queue = WAYLAND_wl_display_create_queue(vid->display);
WAYLAND_wl_proxy_set_queue((struct wl_proxy *)state->wp_image_description, queue);
while (state->wp_image_description) {
WAYLAND_wl_display_dispatch_queue(vid->display, queue);
}
WAYLAND_wl_event_queue_destroy(queue);
}
static void image_description_handle_failed(void *data,
struct wp_image_description_v1 *wp_image_description_v1,
uint32_t cause,
const char *msg)
{
Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
wp_image_description_v1_destroy(state->wp_image_description);
state->wp_image_description = NULL;
}
static void image_description_handle_ready(void *data,
struct wp_image_description_v1 *wp_image_description_v1,
uint32_t identity)
{
Wayland_ColorInfoState *state = (Wayland_ColorInfoState *)data;
// This will inherit the queue of the factory image description object.
state->wp_image_description_info = wp_image_description_v1_get_information(state->wp_image_description);
wp_image_description_info_v1_add_listener(state->wp_image_description_info, &image_description_info_listener, data);
}
static const struct wp_image_description_v1_listener image_description_listener = {
image_description_handle_failed,
image_description_handle_ready
};
bool Wayland_GetColorInfoForWindow(SDL_WindowData *window_data, Wayland_ColorInfo *info)
{
Wayland_ColorInfoState state;
SDL_zero(state);
state.info = info;
state.wp_image_description = wp_color_management_surface_feedback_v1_get_preferred(window_data->wp_color_management_surface_feedback);
wp_image_description_v1_add_listener(state.wp_image_description, &image_description_listener, &state);
PumpColorspaceEvents(&state);
return state.result;
}
bool Wayland_GetColorInfoForOutput(SDL_DisplayData *display_data, Wayland_ColorInfo *info)
{
Wayland_ColorInfoState state;
SDL_zero(state);
state.info = info;
state.wp_image_description = wp_color_management_output_v1_get_image_description(display_data->wp_color_management_output);
wp_image_description_v1_add_listener(state.wp_image_description, &image_description_listener, &state);
PumpColorspaceEvents(&state);
return state.result;
}
#endif // SDL_VIDEO_DRIVER_WAYLAND

View File

@@ -0,0 +1,41 @@
/*
Simple DirectMedia Layer
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_waylandcolor_h_
#define SDL_waylandcolor_h_
#include "../SDL_sysvideo.h"
typedef struct Wayland_ColorInfo
{
SDL_HDROutputProperties HDR;
// The ICC fd is only valid if the size is non-zero.
int icc_fd;
Uint32 icc_size;
} Wayland_ColorInfo;
extern bool Wayland_GetColorInfoForWindow(SDL_WindowData *window_data, Wayland_ColorInfo *info);
extern bool Wayland_GetColorInfoForOutput(SDL_DisplayData *display_data, Wayland_ColorInfo *info);
#endif // SDL_waylandcolor_h_

View File

@@ -27,6 +27,7 @@
#include "../../events/SDL_events_c.h"
#include "SDL_waylandclipboard.h"
#include "SDL_waylandcolor.h"
#include "SDL_waylandevents_c.h"
#include "SDL_waylandkeyboard.h"
#include "SDL_waylandmessagebox.h"
@@ -63,6 +64,7 @@
#include "xdg-output-unstable-v1-client-protocol.h"
#include "xdg-shell-client-protocol.h"
#include "xdg-toplevel-icon-v1-client-protocol.h"
#include "color-management-v1-client-protocol.h"
#ifdef HAVE_LIBDECOR_H
#include <libdecor.h>
@@ -249,7 +251,7 @@ static int SDLCALL Wayland_DisplayPositionCompare(const void *a, const void *b)
* The primary is determined by the following criteria, in order:
* - Landscape is preferred over portrait
* - The highest native resolution
* - TODO: A higher HDR range is preferred
* - A higher HDR range is preferred
* - Higher refresh is preferred (ignoring small differences)
* - Lower scale values are preferred (larger display)
*/
@@ -271,6 +273,7 @@ static int Wayland_GetPrimaryDisplay(SDL_VideoData *vid)
int best_width = 0;
int best_height = 0;
double best_scale = 0.0;
float best_headroom = 0.0f;
int best_refresh = 0;
bool best_is_landscape = false;
int best_index = 0;
@@ -286,11 +289,15 @@ static int Wayland_GetPrimaryDisplay(SDL_VideoData *vid)
if (d->pixel_width > best_width || d->pixel_height > best_height) {
have_new_best = true;
} else if (d->pixel_width == best_width && d->pixel_height == best_height) {
if (d->refresh - best_refresh > REFRESH_DELTA) { // Favor a higher refresh rate, but ignore small differences (e.g. 59.97 vs 60.1)
have_new_best = true;
} else if (d->scale_factor < best_scale && SDL_abs(d->refresh - best_refresh) <= REFRESH_DELTA) {
// Prefer a lower scale display if the difference in refresh rate is small.
if (d->HDR.HDR_headroom > best_headroom) { // Favor a higher HDR luminance range
have_new_best = true;
} else if (d->HDR.HDR_headroom == best_headroom) {
if (d->refresh - best_refresh > REFRESH_DELTA) { // Favor a higher refresh rate, but ignore small differences (e.g. 59.97 vs 60.1)
have_new_best = true;
} else if (d->scale_factor < best_scale && SDL_abs(d->refresh - best_refresh) <= REFRESH_DELTA) {
// Prefer a lower scale display if the difference in refresh rate is small.
have_new_best = true;
}
}
}
}
@@ -299,6 +306,7 @@ static int Wayland_GetPrimaryDisplay(SDL_VideoData *vid)
best_width = d->pixel_width;
best_height = d->pixel_height;
best_scale = d->scale_factor;
best_headroom = d->HDR.HDR_headroom;
best_refresh = d->refresh;
best_is_landscape = is_landscape;
best_index = i;
@@ -630,6 +638,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols)
device->SetWindowIcon = Wayland_SetWindowIcon;
device->GetWindowSizeInPixels = Wayland_GetWindowSizeInPixels;
device->GetWindowContentScale = Wayland_GetWindowContentScale;
device->GetWindowICCProfile = Wayland_GetWindowICCProfile;
device->GetDisplayForWindow = Wayland_GetDisplayForWindow;
device->DestroyWindow = Wayland_DestroyWindow;
device->SetWindowHitTest = Wayland_SetWindowHitTest;
@@ -1050,6 +1059,8 @@ static void display_handle_done(void *data,
AddEmulatedModes(internal, native_mode.w, native_mode.h);
}
SDL_SetDisplayHDRProperties(dpy, &internal->HDR);
if (internal->display == 0) {
// First time getting display info, initialize the VideoDisplay
if (internal->physical_width_mm >= internal->physical_height_mm) {
@@ -1107,6 +1118,27 @@ static const struct wl_output_listener output_listener = {
display_handle_description // Version 4
};
static void Wayland_GetOutputColorInfo(SDL_DisplayData *display)
{
Wayland_ColorInfo info;
SDL_zero(info);
if (Wayland_GetColorInfoForOutput(display, &info)) {
SDL_copyp(&display->HDR, &info.HDR);
}
}
static void handle_output_image_description_changed(void *data,
struct wp_color_management_output_v1 *wp_color_management_output_v1)
{
// wl_display.done is called after this event, so the display HDR status will be updated there.
Wayland_GetOutputColorInfo(data);
}
static const struct wp_color_management_output_v1_listener wp_color_management_output_listener = {
handle_output_image_description_changed
};
static bool Wayland_add_display(SDL_VideoData *d, uint32_t id, uint32_t version)
{
struct wl_output *output;
@@ -1136,6 +1168,11 @@ static bool Wayland_add_display(SDL_VideoData *d, uint32_t id, uint32_t version)
data->xdg_output = zxdg_output_manager_v1_get_xdg_output(data->videodata->xdg_output_manager, output);
zxdg_output_v1_add_listener(data->xdg_output, &xdg_output_listener, data);
}
if (data->videodata->wp_color_manager_v1) {
data->wp_color_management_output = wp_color_manager_v1_get_output(data->videodata->wp_color_manager_v1, output);
wp_color_management_output_v1_add_listener(data->wp_color_management_output, &wp_color_management_output_listener, data);
Wayland_GetOutputColorInfo(data);
}
return true;
}
@@ -1153,6 +1190,10 @@ static void Wayland_free_display(SDL_VideoDisplay *display, bool send_event)
SDL_free(display_data->wl_output_name);
if (display_data->wp_color_management_output) {
wp_color_management_output_v1_destroy(display_data->wp_color_management_output);
}
if (display_data->xdg_output) {
zxdg_output_v1_destroy(display_data->xdg_output);
}
@@ -1187,6 +1228,16 @@ static void Wayland_init_xdg_output(SDL_VideoData *d)
}
}
static void Wayland_InitColorManager(SDL_VideoData *d)
{
for (int i = 0; i < d->output_count; ++i) {
SDL_DisplayData *disp = d->output_list[i];
disp->wp_color_management_output = wp_color_manager_v1_get_output(disp->videodata->wp_color_manager_v1, disp->output);
wp_color_management_output_v1_add_listener(disp->wp_color_management_output, &wp_color_management_output_listener, disp);
Wayland_GetOutputColorInfo(disp);
}
}
static void handle_ping_xdg_wm_base(void *data, struct xdg_wm_base *xdg, uint32_t serial)
{
xdg_wm_base_pong(xdg, serial);
@@ -1280,6 +1331,9 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint
d->xdg_toplevel_icon_manager_v1 = wl_registry_bind(d->registry, id, &xdg_toplevel_icon_manager_v1_interface, 1);
} 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);
} else if (SDL_strcmp(interface, "xx_color_manager_v4") == 0) {
d->wp_color_manager_v1 = wl_registry_bind(d->registry, id, &wp_color_manager_v1_interface, 1);
Wayland_InitColorManager(d);
}
}
@@ -1567,6 +1621,11 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
data->frog_color_management_factory_v1 = NULL;
}
if (data->wp_color_manager_v1) {
wp_color_manager_v1_destroy(data->wp_color_manager_v1);
data->wp_color_manager_v1 = NULL;
}
if (data->compositor) {
wl_compositor_destroy(data->compositor);
data->compositor = NULL;

View File

@@ -83,6 +83,7 @@ struct SDL_VideoData
struct wp_alpha_modifier_v1 *wp_alpha_modifier_v1;
struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1;
struct frog_color_management_factory_v1 *frog_color_management_factory_v1;
struct wp_color_manager_v1 *wp_color_manager_v1;
struct zwp_tablet_manager_v2 *tablet_manager;
struct xkb_context *xkb_context;
@@ -102,6 +103,7 @@ struct SDL_DisplayData
SDL_VideoData *videodata;
struct wl_output *output;
struct zxdg_output_v1 *xdg_output;
struct wp_color_management_output_v1 *wp_color_management_output;
char *wl_output_name;
double scale_factor;
uint32_t registry_id;
@@ -111,6 +113,8 @@ struct SDL_DisplayData
SDL_DisplayOrientation orientation;
int physical_width_mm, physical_height_mm;
bool has_logical_position, has_logical_size;
bool running_colorspace_event_queue;
SDL_HDROutputProperties HDR;
SDL_DisplayID display;
SDL_VideoDisplay placeholder;
int wl_output_done_count;

View File

@@ -23,6 +23,8 @@
#ifdef SDL_VIDEO_DRIVER_WAYLAND
#include <sys/mman.h>
#include "../SDL_sysvideo.h"
#include "../../events/SDL_events_c.h"
#include "../../core/unix/SDL_appid.h"
@@ -31,6 +33,7 @@
#include "SDL_waylandwindow.h"
#include "SDL_waylandvideo.h"
#include "../../SDL_hints_c.h"
#include "SDL_waylandcolor.h"
#include "alpha-modifier-v1-client-protocol.h"
#include "xdg-shell-client-protocol.h"
@@ -43,6 +46,7 @@
#include "xdg-dialog-v1-client-protocol.h"
#include "frog-color-management-v1-client-protocol.h"
#include "xdg-toplevel-icon-v1-client-protocol.h"
#include "color-management-v1-client-protocol.h"
#ifdef HAVE_LIBDECOR_H
#include <libdecor.h>
@@ -1647,6 +1651,28 @@ static const struct frog_color_managed_surface_listener frog_surface_listener =
frog_preferred_metadata_handler
};
static void feedback_surface_preferred_changed(void *data,
struct wp_color_management_surface_feedback_v1 *wp_color_management_surface_feedback_v1,
uint32_t identity)
{
SDL_WindowData *wind = (SDL_WindowData *)data;
Wayland_ColorInfo info;
SDL_zero(info);
if (Wayland_GetColorInfoForWindow(wind, &info)) {
SDL_SetWindowHDRProperties(wind->sdlwindow, &info.HDR, true);
if (info.icc_size) {
wind->icc_fd = info.icc_fd;
wind->icc_size = info.icc_size;
SDL_SendWindowEvent(wind->sdlwindow, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0);
}
}
}
static const struct wp_color_management_surface_feedback_v1_listener color_management_surface_feedback_listener = {
feedback_surface_preferred_changed
};
static void SetKeyboardFocus(SDL_Window *window, bool set_focus)
{
SDL_Window *toplevel = window;
@@ -2593,7 +2619,10 @@ bool Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Proper
}
if (!custom_surface_role) {
if (c->frog_color_management_factory_v1) {
if (c->wp_color_manager_v1) {
data->wp_color_management_surface_feedback = wp_color_manager_v1_get_surface_feedback(c->wp_color_manager_v1, data->surface);
wp_color_management_surface_feedback_v1_add_listener(data->wp_color_management_surface_feedback, &color_management_surface_feedback_listener, data);
} else 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);
}
@@ -2875,6 +2904,26 @@ bool Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surfa
return true;
}
void *Wayland_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size)
{
SDL_WindowData *wind = window->internal;
void *ret = NULL;
if (wind->icc_size > 0) {
void *icc_map = mmap(NULL, wind->icc_size, PROT_READ, MAP_PRIVATE, wind->icc_fd, 0);
if (icc_map != MAP_FAILED) {
ret = SDL_malloc(wind->icc_size);
if (ret) {
*size = wind->icc_size;
SDL_memcpy(ret, icc_map, *size);
}
munmap(icc_map, wind->icc_size);
}
}
return ret;
}
bool Wayland_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
SDL_WindowData *wind = window->internal;
@@ -2994,6 +3043,10 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
frog_color_managed_surface_destroy(wind->frog_color_managed_surface);
}
if (wind->wp_color_management_surface_feedback) {
wp_color_management_surface_feedback_v1_destroy(wind->wp_color_management_surface_feedback);
}
SDL_free(wind->outputs);
SDL_free(wind->app_id);

View File

@@ -116,6 +116,7 @@ struct SDL_WindowData
struct wp_alpha_modifier_surface_v1 *wp_alpha_modifier_surface_v1;
struct xdg_toplevel_icon_v1 *xdg_toplevel_icon_v1;
struct frog_color_managed_surface *frog_color_managed_surface;
struct wp_color_management_surface_feedback_v1 *wp_color_management_surface_feedback;
SDL_AtomicInt swap_interval_ready;
@@ -184,6 +185,8 @@ struct SDL_WindowData
int fullscreen_deadline_count;
int maximized_restored_deadline_count;
Uint64 last_focus_event_time_ns;
int icc_fd;
Uint32 icc_size;
bool floating;
bool suspended;
bool resizing;
@@ -231,6 +234,7 @@ extern void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window);
extern bool Wayland_SuspendScreenSaver(SDL_VideoDevice *_this);
extern bool Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon);
extern float Wayland_GetWindowContentScale(SDL_VideoDevice *_this, SDL_Window *window);
extern void *Wayland_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size);
extern bool Wayland_SetWindowHitTest(SDL_Window *window, bool enabled);
extern bool Wayland_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation);

File diff suppressed because it is too large Load Diff