Compare commits

...

27 commits

Author SHA1 Message Date
Simon Ser
7dc492bde9 Merge branch 'conn-port' into 'master'
Draft: output: add wlr_output.port

See merge request wlroots/wlroots!3979
2025-10-26 08:32:08 +00:00
David Turner
879243e370 xwm: Fix double-close
When an FD is passed to xcb_connect_to_fd(), xcb takes ownership of that
FD and is responsible for closing it, which it does when
xcb_disconnect() is called.  But the xwayland handler code also keeps a
copy of the FD and closes it via safe_close() in
server_finish_process().

This double-close can cause all sorts of problems if another part of
wlroots allocates another FD between the two closes - the latter close
will close the wrong FD and things go horribly wrong (in my case leading
to use-after-free and segfaults).

Fix this by setting wm_fd[0]=-1 after calling xwm_create(), and ensuring
that xwm_create() closes the FD if startup errors occur.
2025-10-20 14:02:29 +01:00
Simon Ser
989cffe70d scene: add software fallback for gamma LUT 2025-10-18 20:36:01 +02:00
Simon Ser
3e08e3be4a gamma_control_v1: introduce fallback_gamma_size 2025-10-18 20:36:01 +02:00
Simon Ser
91f4890ec2 gamma_control_v1: add wlr_gamma_control_v1_get_color_transform() 2025-10-18 20:36:01 +02:00
Simon Ser
74ce6c22a5 output: check for color transform no-op changes
This allows callers to always set this state and not care whether
the output currently has the same color transform applied.
2025-10-18 20:36:01 +02:00
Simon Ser
0b58bddf13 render/color: add wlr_color_transform_pipeline
Useful to apply multiple transforms in sequence, e.g. sRGB inverse
EOTF followed by gamma LUTs.
2025-10-18 20:36:01 +02:00
Simon Ser
3d36ab9211 render/color: add wlr_color_transform_eval()
Makes it so the Vulkan renderer can handle arbitrary color
transforms, and doesn't need to be updated each time a new one is
added.
2025-10-18 20:35:02 +02:00
Furkan Sahin
d786e07899 backend/session: use device boot_display
shouldn't need to check for `boot_vga` if newer, more general
sysfs `boot_display` is set.
closes https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/4016
2025-10-17 17:35:51 +00:00
Simon Ser
6d63871f05 linux_drm_syncobj_v1: fix use-after-free in surface_commit_destroy()
surface_commit_destroy() accesses a field from
struct wlr_linux_drm_syncobj_surface_v1, however that struct may have
been free'd earlier:

    ==1103==ERROR: AddressSanitizer: heap-use-after-free on address 0x7cdef7a6e288 at pc 0x7feefaac335a bp 0x7ffc4de8f570 sp 0x7ffc4de8f560
    READ of size 8 at 0x7cdef7a6e288 thread T0
        #0 0x7feefaac3359 in surface_commit_destroy ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:195
        #1 0x7feefaac34cd in surface_commit_handle_surface_destroy ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:211
        #2 0x7feefbd194cf in wl_signal_emit_mutable (/usr/lib/libwayland-server.so.0+0x84cf) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1)
        #3 0x7feefaa52b22 in surface_handle_resource_destroy ../subprojects/wlroots/types/wlr_compositor.c:730
        #4 0x7feefbd1bb9f  (/usr/lib/libwayland-server.so.0+0xab9f) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1)
        #5 0x7feefaa46a18 in surface_handle_destroy ../subprojects/wlroots/types/wlr_compositor.c:65
        #6 0x7feef89afac5  (/usr/lib/libffi.so.8+0x7ac5) (BuildId: d5e3b0d8921923f35438adefa9f864745abc5e90)
        #7 0x7feef89ac76a  (/usr/lib/libffi.so.8+0x476a) (BuildId: d5e3b0d8921923f35438adefa9f864745abc5e90)
        #8 0x7feef89af06d in ffi_call (/usr/lib/libffi.so.8+0x706d) (BuildId: d5e3b0d8921923f35438adefa9f864745abc5e90)
        #9 0x7feefbd17531  (/usr/lib/libwayland-server.so.0+0x6531) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1)
        #10 0x7feefbd1cd2f  (/usr/lib/libwayland-server.so.0+0xbd2f) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1)
        #11 0x7feefbd1b181 in wl_event_loop_dispatch (/usr/lib/libwayland-server.so.0+0xa181) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1)
        #12 0x7feefbd1d296 in wl_display_run (/usr/lib/libwayland-server.so.0+0xc296) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1)
        #13 0x555bf0a55a40 in server_run ../sway/server.c:615
        #14 0x555bf0a4a654 in main ../sway/main.c:376
        #15 0x7feef9227674  (/usr/lib/libc.so.6+0x27674) (BuildId: 4fe011c94a88e8aeb6f2201b9eb369f42b4a1e9e)
        #16 0x7feef9227728 in __libc_start_main (/usr/lib/libc.so.6+0x27728) (BuildId: 4fe011c94a88e8aeb6f2201b9eb369f42b4a1e9e)
        #17 0x555bf0a03f54 in _start (/home/leo/code/stuff/sway/build/sway/sway+0x390f54) (BuildId: e3d4e653af1aa0885f0426c403e16fc87c086d33)

    0x7cdef7a6e288 is located 8 bytes inside of 176-byte region [0x7cdef7a6e280,0x7cdef7a6e330)
    freed by thread T0 here:
        #0 0x7feefb71f79d in free /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:51
        #1 0x7feefaac29f1 in surface_destroy ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:84
        #2 0x7feefaac2e47 in surface_handle_resource_destroy ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:143
        #3 0x7feefbd1bb9f  (/usr/lib/libwayland-server.so.0+0xab9f) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1)
        #4 0x7feefaac2a12 in surface_handle_destroy ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:89
        #5 0x7feef89afac5  (/usr/lib/libffi.so.8+0x7ac5) (BuildId: d5e3b0d8921923f35438adefa9f864745abc5e90)

    previously allocated by thread T0 here:
        #0 0x7feefb7205dd in calloc /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:74
        #1 0x7feefaac4abd in manager_handle_get_surface ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:313
        #2 0x7feef89afac5  (/usr/lib/libffi.so.8+0x7ac5) (BuildId: d5e3b0d8921923f35438adefa9f864745abc5e90)

Fix this by storing the struct wlr_surface in the field.

Closes: https://github.com/swaywm/sway/issues/8917
2025-10-17 09:05:53 +00:00
tokyo4j
19c5d22beb util/box.c: use 1/256 instead of 1/65536 in wlr_box_closest_point()
This fixes the issue that a scrollbar in a maximized GTK/Chromium window
cannot be dragged when cursor is on the right/bottom edge of the output.

The issue was caused by rounding in `wl_fixed_from_double()` ([1]); if
`wlr_cursor_move()` constrains the x-position of the cursor to
`(output width)-1/65536`, `wl_fixed_from_double()` converts it to just
`(output width)`, which is perceived as outside of the window by
GTK/Chromium.

Using 1/256 (minimal unit of `wl_fixed_t`) instead of 1/65536 avoids
this rounding issue.

[1]: f246e619d1
2025-10-16 13:46:27 +00:00
Furkan Sahin
06275103f2 input-method-v2: Destroy keyboard grab before input method
Fixes race condition in where the keyboard grab tries to reference the
input manager after it's been set to null.
2025-10-16 12:07:47 +00:00
Simon Ser
03e7966650 ci: fix VKMS lookup after faux bus migration
VKMS has been migrated to the new faux bus. This causes breakage
in CI, because we used the platform bus to find the right device.

udev hasn't been updated yet to support the faux bus, so just use
sysfs instead.
2025-10-16 11:04:25 +02:00
llyyr
5529aae3e6 wlr_scene: fix direct scanout for gamma2.2 buffers
Fixes incorrectly rejecting scanout for gamma2.2 buffers when the output
has no image description set. This happens on `hdr off` mode on sway.

Also refactor the scanout check into its own function while at it to
make it easier to follow.
2025-10-05 23:53:25 +05:30
llyyr
6e1c8748ff render: introduce bt.1886 transfer function 2025-10-04 18:13:37 +05:30
Félix Poisot
d8fb7adcf0 scene, render: use Gamma 2.2 TF as default 2025-10-03 19:48:12 +00:00
Félix Poisot
c2d9ae2142 render: introduce Gamma 2.2 color transform 2025-10-03 19:39:17 +00:00
Simon Ser
6978509f64 Revert "wlr_scene: fix tf/prim comparison for scanout attempt"
This reverts commit dde07b6840.

This is incorrect as discussed here:
https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5163#note_3118744
2025-10-03 20:42:21 +02:00
llyyr
2252854297 wlr_scene: return scene_direct_scanout_result instead of bool 2025-10-02 13:51:41 +05:30
llyyr
dde07b6840 wlr_scene: fix tf/prim comparison for scanout attempt
We were incorrectly doing comparison with `!= 0` to detect non-sRGB
tf/primaries. Since these enums are bit flags, the default sRGB values
are 1, not 0, so sRGB buffers were incorrectly rejected.

Fixes: bf40f396bf ("scene: grab image description from output state")
2025-10-02 11:57:52 +05:30
Simon Ser
406aa5f7f5 backend/session: fix crash on udev device remove event
libwayland adds phantom listeners here:
d81525a235/src/wayland-server.c (L2378)

Closes: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3982
2025-10-01 17:05:10 +02:00
Simon Ser
2ec4012559 backend/drm: avoid error message when EDID is missing
We'd attempt to parse an EDID even when the connector has no EDID,
printing "Failed to parse EDID" in logs. Instead, don't attempt to
parse the EDID and print a more appropriate log message.
2025-10-01 15:14:46 +02:00
Simon Ser
d039ad8da3 backend/wayland: continue reading on hangup
If we stop immediately, we won't see any wl_display.error events.
Make sure we've read everything before handling hangup.
2025-09-30 09:25:14 -04:00
Simon Ser
3f0d338643 backend/wayland: log when getting disconnected from remote display
It can be a bit confusing to understand why a compositor is shutting
down on its own. Log a message when we get disconnected from the
parent compositor to explain the cause.
2025-09-30 09:25:14 -04:00
Simon Ser
60d72724cd render/color: fix bounds check in lut_1d_get()
i == len is out-of-bounds.

Fixes: 74217a4d93 ("render/color: introduce COLOR_TRANSFORM_LUT_3X1D")
2025-09-30 09:34:40 +02:00
Simon Ser
ade7fee5d2 backend/drm: populate wlr_output.port 2025-01-23 19:21:04 +01:00
Simon Ser
85ab6b7eb2 output: add wlr_output.port 2025-01-23 19:18:09 +01:00
32 changed files with 577 additions and 105 deletions

View file

@ -41,9 +41,10 @@ tasks:
cd wlroots/build-gcc/tinywl
sudo modprobe vkms
udevadm settle
card="/dev/dri/$(ls /sys/devices/faux/vkms/drm/ | grep ^card)"
export WLR_BACKENDS=drm
export WLR_RENDERER=pixman
export WLR_DRM_DEVICES=/dev/dri/by-path/platform-vkms-card
export WLR_DRM_DEVICES="$card"
export UBSAN_OPTIONS=halt_on_error=1
sudo chmod ugo+rw /dev/dri/by-path/platform-vkms-card
sudo chmod ugo+rw "$card"
sudo -E seatd-launch -- ./tinywl -s 'kill $PPID' || [ $? = 143 ]

View file

@ -186,6 +186,10 @@ static uint8_t convert_cta861_eotf(enum wlr_color_transfer_function tf) {
return 2;
case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR:
abort(); // unsupported
case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22:
abort(); // unsupported
case WLR_COLOR_TRANSFER_FUNCTION_BT1886:
abort(); // unsupported
}
abort(); // unreachable
}

View file

@ -11,6 +11,7 @@
#include <xf86drm.h>
#include "backend/drm/drm.h"
#include "backend/drm/fb.h"
#include "backend/drm/util.h"
#include "render/drm_format_set.h"
struct wlr_drm_backend *get_drm_backend_from_backend(
@ -225,6 +226,9 @@ struct wlr_backend *wlr_drm_backend_create(struct wlr_session *session,
wlr_log(WLR_INFO, "Initializing DRM backend for %s (%s)", name, version->name);
drmFreeVersion(version);
drmDevice *dev_info = NULL;
drmGetDevice2(dev->fd, 0, &dev_info);
struct wlr_drm_backend *drm = calloc(1, sizeof(*drm));
if (!drm) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
@ -243,6 +247,9 @@ struct wlr_backend *wlr_drm_backend_create(struct wlr_session *session,
drm->fd = dev->fd;
drm->name = name;
drm->bus = get_drm_bus_str(dev_info);
drmFreeDevice(&dev_info);
if (parent != NULL) {
drm->parent = get_drm_backend_from_backend(parent);

View file

@ -1603,6 +1603,92 @@ static drmModeModeInfo *connector_get_current_mode(struct wlr_drm_connector *wlr
}
}
static struct wlr_drm_connector *find_drm_connector_by_id(struct wlr_drm_backend *drm, uint32_t id) {
struct wlr_drm_connector *conn = NULL;
wl_list_for_each(conn, &drm->connectors, link) {
if (conn->id == id) {
return conn;
}
}
return NULL;
}
static bool connector_write_root_port(struct wlr_drm_connector *wlr_conn, FILE *f) {
struct wlr_drm_backend *drm = wlr_conn->backend;
size_t seq_num = 0;
struct wlr_drm_connector *c;
wl_list_for_each(c, &drm->connectors, link) {
seq_num++;
if (c == wlr_conn) {
break;
}
}
return fprintf(f, "%s/connector-%zu", drm->bus, seq_num) > 0;
}
static bool connector_write_port(struct wlr_drm_connector *wlr_conn, FILE *f);
static bool connector_write_nested_port(struct wlr_drm_connector *wlr_conn, FILE *f) {
struct wlr_drm_backend *drm = wlr_conn->backend;
bool ok = false;
size_t size = 0;
void *path_prop_data = get_drm_prop_blob(drm->fd, wlr_conn->id, wlr_conn->props.path, &size);
if (path_prop_data == NULL) {
goto out;
}
uint32_t parent_conn_id = 0;
const char *child_path = NULL;
if (!parse_dp_mst_path(path_prop_data, &parent_conn_id, &child_path)) {
goto out;
}
struct wlr_drm_connector *parent = find_drm_connector_by_id(drm, parent_conn_id);
if (parent == NULL) {
goto out;
}
if (!connector_write_port(parent, f)) {
goto out;
}
ok = fprintf(f, "/mst-%s", child_path) > 0;
out:
free(path_prop_data);
return ok;
}
static bool connector_write_port(struct wlr_drm_connector *wlr_conn, FILE *f) {
if (wlr_conn->props.path != 0) {
return connector_write_nested_port(wlr_conn, f);
} else {
return connector_write_root_port(wlr_conn, f);
}
}
static char *connector_get_port(struct wlr_drm_connector *wlr_conn) {
if (wlr_conn->backend->bus == NULL) {
return NULL;
}
char *str = NULL;
size_t str_size = 0;
FILE *f = open_memstream(&str, &str_size);
if (f == NULL) {
return NULL;
}
bool ok = connector_write_port(wlr_conn, f);
if (fclose(f) != 0 || !ok) {
return NULL;
}
return str;
}
static bool connect_drm_connector(struct wlr_drm_connector *wlr_conn,
const drmModeConnector *drm_conn) {
struct wlr_drm_backend *drm = wlr_conn->backend;
@ -1717,7 +1803,11 @@ static bool connect_drm_connector(struct wlr_drm_connector *wlr_conn,
size_t edid_len = 0;
uint8_t *edid = get_drm_prop_blob(drm->fd,
wlr_conn->id, wlr_conn->props.edid, &edid_len);
parse_edid(wlr_conn, edid_len, edid);
if (edid_len > 0) {
parse_edid(wlr_conn, edid_len, edid);
} else {
wlr_log(WLR_DEBUG, "Connector has no EDID");
}
free(edid);
char *subconnector = NULL;
@ -1741,6 +1831,9 @@ static bool connect_drm_connector(struct wlr_drm_connector *wlr_conn,
wlr_output_set_description(output, description);
free(subconnector);
output->port = connector_get_port(wlr_conn);
wlr_conn->status = DRM_MODE_CONNECTED;
return true;
}

View file

@ -281,3 +281,44 @@ void generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay,
};
snprintf(mode->name, sizeof(mode->name), "%dx%d", hdisplay, vdisplay);
}
char *get_drm_bus_str(const drmDevice *dev) {
char buf[128];
switch (dev->bustype) {
case DRM_BUS_PCI:;
const drmPciBusInfo *pci = dev->businfo.pci;
snprintf(buf, sizeof(buf), "pci-%04" PRIx16 ":%02" PRIx8 ":%02" PRIx8 ".%" PRIu8,
pci->domain, pci->bus, pci->dev, pci->func);
return strdup(buf);
case DRM_BUS_PLATFORM:;
const drmPlatformBusInfo *platform = dev->businfo.platform;
size_t str_size = strlen("platform-") + strlen(platform->fullname) + 1;
char *str = malloc(str_size);
if (str == NULL) {
return NULL;
}
snprintf(str, str_size, "platform-%s", platform->fullname);
return str;
}
return NULL;
}
bool parse_dp_mst_path(const char *path, uint32_t *parent_conn_id, const char **child_path) {
const char prefix[] = "mst:";
if (strncmp(path, prefix, strlen(prefix)) != 0) {
return false;
}
path = &path[strlen(prefix)];
char *id_end;
errno = 0;
unsigned long id = strtoul(path, &id_end, 10);
if (errno != 0 || id_end[0] != '-' || id > UINT32_MAX) {
wlr_log(WLR_ERROR, "Malformed PATH DP-MST property: invalid parent connector ID");
return false;
}
*parent_conn_id = (uint32_t)id;
*child_path = &id_end[1];
return true;
}

View file

@ -367,7 +367,10 @@ void wlr_session_close_file(struct wlr_session *session,
}
assert(wl_list_empty(&dev->events.change.listener_list));
assert(wl_list_empty(&dev->events.remove.listener_list));
// TODO: assert that the "remove" listener list is empty as well. Listeners
// will typically call wlr_session_close_file() in response, and
// wl_signal_emit_mutable() installs two phantom listeners, so we'd count
// these two.
close(dev->fd);
wl_list_remove(&dev->link);
@ -516,8 +519,6 @@ ssize_t wlr_session_find_gpus(struct wlr_session *session,
break;
}
bool is_boot_vga = false;
const char *path = udev_list_entry_get_name(entry);
struct udev_device *dev = udev_device_new_from_syspath(session->udev, path);
if (!dev) {
@ -533,14 +534,20 @@ ssize_t wlr_session_find_gpus(struct wlr_session *session,
continue;
}
// This is owned by 'dev', so we don't need to free it
struct udev_device *pci =
udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL);
bool is_primary = false;
const char *boot_display = udev_device_get_sysattr_value(dev, "boot_display");
if (boot_display && strcmp(boot_display, "1") == 0) {
is_primary = true;
} else {
// This is owned by 'dev', so we don't need to free it
struct udev_device *pci =
udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL);
if (pci) {
const char *id = udev_device_get_sysattr_value(pci, "boot_vga");
if (id && strcmp(id, "1") == 0) {
is_boot_vga = true;
if (pci) {
const char *id = udev_device_get_sysattr_value(pci, "boot_vga");
if (id && strcmp(id, "1") == 0) {
is_primary = true;
}
}
}
@ -554,7 +561,7 @@ ssize_t wlr_session_find_gpus(struct wlr_session *session,
udev_device_unref(dev);
ret[i] = wlr_dev;
if (is_boot_vga) {
if (is_primary) {
struct wlr_device *tmp = ret[0];
ret[0] = ret[i];
ret[i] = tmp;

View file

@ -55,14 +55,6 @@ struct wlr_wl_backend *get_wl_backend_from_backend(struct wlr_backend *wlr_backe
static int dispatch_events(int fd, uint32_t mask, void *data) {
struct wlr_wl_backend *wl = data;
if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) {
if (mask & WL_EVENT_ERROR) {
wlr_log(WLR_ERROR, "Failed to read from remote Wayland display");
}
wlr_backend_destroy(&wl->backend);
return 0;
}
int count = 0;
if (mask & WL_EVENT_READABLE) {
count = wl_display_dispatch(wl->remote_display);
@ -75,6 +67,18 @@ static int dispatch_events(int fd, uint32_t mask, void *data) {
wl_display_flush(wl->remote_display);
}
// Make sure we've consumed all data before disconnecting due to hangup,
// so that we process any wl_display.error events
if (!(mask & WL_EVENT_READABLE) && (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR))) {
if (mask & WL_EVENT_ERROR) {
wlr_log(WLR_ERROR, "Failed to read from remote Wayland display");
} else {
wlr_log(WLR_DEBUG, "Disconnected from remote Wayland display");
}
wlr_backend_destroy(&wl->backend);
return 0;
}
if (count < 0) {
wlr_log(WLR_ERROR, "Failed to dispatch remote Wayland display");
wlr_backend_destroy(&wl->backend);

View file

@ -92,7 +92,7 @@ struct wlr_drm_backend {
bool addfb2_modifiers;
int fd;
char *name;
char *name, *bus;
struct wlr_device *dev;
struct liftoff_device *liftoff;

View file

@ -38,4 +38,7 @@ void match_connectors_with_crtcs(size_t num_conns,
size_t num_crtcs, const uint32_t prev_crtcs[static restrict num_crtcs],
uint32_t new_crtcs[static restrict num_crtcs]);
char *get_drm_bus_str(const drmDevice *dev);
bool parse_dp_mst_path(const char *path, uint32_t *parent_conn_id, const char **child_path);
#endif

View file

@ -9,6 +9,7 @@ enum wlr_color_transform_type {
COLOR_TRANSFORM_INVERSE_EOTF,
COLOR_TRANSFORM_LCMS2,
COLOR_TRANSFORM_LUT_3X1D,
COLOR_TRANSFORM_PIPELINE,
};
struct wlr_color_transform {
@ -39,6 +40,13 @@ struct wlr_color_transform_lut_3x1d {
size_t dim;
};
struct wlr_color_transform_pipeline {
struct wlr_color_transform base;
struct wlr_color_transform **transforms;
size_t len;
};
void wlr_color_transform_init(struct wlr_color_transform *tr,
enum wlr_color_transform_type type);
@ -72,12 +80,6 @@ struct wlr_color_transform_inverse_eotf *wlr_color_transform_inverse_eotf_from_b
struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base(
struct wlr_color_transform *tr);
/**
* Evaluate a 3x1D LUT color transform for a given RGB triplet.
*/
void color_transform_lut_3x1d_eval(struct wlr_color_transform_lut_3x1d *tr,
float out[static 3], const float in[static 3]);
/**
* Obtain primaries values from a well-known primaries name.
*/

View file

@ -153,6 +153,8 @@ enum wlr_vk_texture_transform {
WLR_VK_TEXTURE_TRANSFORM_IDENTITY = 0,
WLR_VK_TEXTURE_TRANSFORM_SRGB = 1,
WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ = 2,
WLR_VK_TEXTURE_TRANSFORM_GAMMA22 = 3,
WLR_VK_TEXTURE_TRANSFORM_BT1886 = 4,
};
enum wlr_vk_shader_source {
@ -167,6 +169,8 @@ enum wlr_vk_output_transform {
WLR_VK_OUTPUT_TRANSFORM_INVERSE_SRGB = 1,
WLR_VK_OUTPUT_TRANSFORM_INVERSE_ST2084_PQ = 2,
WLR_VK_OUTPUT_TRANSFORM_LUT3D = 3,
WLR_VK_OUTPUT_TRANSFORM_INVERSE_GAMMA22 = 4,
WLR_VK_OUTPUT_TRANSFORM_INVERSE_BT1886 = 5,
};
struct wlr_vk_pipeline_key {
@ -199,6 +203,8 @@ struct wlr_vk_render_format_setup {
VkPipeline output_pipe_srgb;
VkPipeline output_pipe_pq;
VkPipeline output_pipe_lut3d;
VkPipeline output_pipe_gamma22;
VkPipeline output_pipe_bt1886;
struct wlr_vk_renderer *renderer;
struct wl_list pipelines; // struct wlr_vk_pipeline.link

View file

@ -28,6 +28,8 @@ enum wlr_color_transfer_function {
WLR_COLOR_TRANSFER_FUNCTION_SRGB = 1 << 0,
WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ = 1 << 1,
WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR = 1 << 2,
WLR_COLOR_TRANSFER_FUNCTION_GAMMA22 = 1 << 3,
WLR_COLOR_TRANSFER_FUNCTION_BT1886 = 1 << 4,
};
/**
@ -139,6 +141,13 @@ struct wlr_color_transform *wlr_color_transform_init_linear_to_inverse_eotf(
struct wlr_color_transform *wlr_color_transform_init_lut_3x1d(size_t dim,
const uint16_t *r, const uint16_t *g, const uint16_t *b);
/**
* Initialize a color transformation to apply a sequence of color transforms
* one after another.
*/
struct wlr_color_transform *wlr_color_transform_init_pipeline(
struct wlr_color_transform **transforms, size_t len);
/**
* Increase the reference count of the color transform by 1.
*/
@ -150,4 +159,10 @@ struct wlr_color_transform *wlr_color_transform_ref(struct wlr_color_transform *
*/
void wlr_color_transform_unref(struct wlr_color_transform *tr);
/**
* Evaluate a color transform for a given RGB triplet.
*/
void wlr_color_transform_eval(struct wlr_color_transform *tr,
float out[static 3], const float in[static 3]);
#endif

View file

@ -31,8 +31,9 @@ struct wlr_render_timer;
struct wlr_buffer_pass_options {
/* Timer to measure the duration of the render pass */
struct wlr_render_timer *timer;
/* Color transform to apply to the output of the render pass,
* leave NULL to indicate sRGB/no custom transform */
/* Color transform to apply to the output of the render pass.
* Leave NULL to indicate the default transform (Gamma 2.2 encoding for
* sRGB monitors) */
struct wlr_color_transform *color_transform;
/** Primaries describing the color volume of the destination buffer */
const struct wlr_color_primaries *primaries;

View file

@ -10,6 +10,11 @@ struct wlr_gamma_control_manager_v1 {
struct wl_global *global;
struct wl_list controls; // wlr_gamma_control_v1.link
// Fallback to use when an struct wlr_output doesn't support gamma LUTs.
// Can be used to apply gamma LUTs via a struct wlr_renderer. Leave zero to
// indicate that the fallback is unsupported.
size_t fallback_gamma_size;
struct {
struct wl_signal destroy;
struct wl_signal set_gamma; // struct wlr_gamma_control_manager_v1_set_gamma_event
@ -49,6 +54,8 @@ struct wlr_gamma_control_v1 *wlr_gamma_control_manager_v1_get_control(
struct wlr_gamma_control_manager_v1 *manager, struct wlr_output *output);
bool wlr_gamma_control_v1_apply(struct wlr_gamma_control_v1 *gamma_control,
struct wlr_output_state *output_state);
struct wlr_color_transform *wlr_gamma_control_v1_get_color_transform(
struct wlr_gamma_control_v1 *gamma_control);
void wlr_gamma_control_v1_send_failed_and_destroy(struct wlr_gamma_control_v1 *gamma_control);
#endif

View file

@ -186,6 +186,7 @@ struct wlr_output {
char *name;
char *description; // may be NULL
char *make, *model, *serial; // may be NULL
char *port; // may be NULL
int32_t phys_width, phys_height; // mm
// Note: some backends may have zero modes
@ -267,6 +268,7 @@ struct wlr_output {
struct {
struct wl_listener display_destroy;
struct wlr_output_image_description image_description_value;
struct wlr_color_transform *color_transform;
} WLR_PRIVATE;
};

View file

@ -252,6 +252,11 @@ struct wlr_scene_output {
bool gamma_lut_changed;
struct wlr_gamma_control_v1 *gamma_lut;
struct wlr_color_transform *gamma_lut_color_transform;
struct wlr_color_transform *prev_gamma_lut_color_transform;
struct wlr_color_transform *prev_supplied_color_transform;
struct wlr_color_transform *prev_combined_color_transform;
struct wl_listener output_commit;
struct wl_listener output_damage;

View file

@ -164,6 +164,7 @@ struct wlr_xwm {
struct wl_listener drop_focus_destroy;
};
// xwm_create takes ownership of wm_fd and will close it under all circumstances.
struct wlr_xwm *xwm_create(struct wlr_xwayland *wlr_xwayland, int wm_fd);
void xwm_destroy(struct wlr_xwm *xwm);

View file

@ -62,6 +62,33 @@ struct wlr_color_transform *wlr_color_transform_init_lut_3x1d(size_t dim,
return &tx->base;
}
struct wlr_color_transform *wlr_color_transform_init_pipeline(
struct wlr_color_transform **transforms, size_t len) {
assert(len > 0);
struct wlr_color_transform **copy = calloc(len, sizeof(copy[0]));
if (copy == NULL) {
return NULL;
}
struct wlr_color_transform_pipeline *tx = calloc(1, sizeof(*tx));
if (!tx) {
free(copy);
return NULL;
}
wlr_color_transform_init(&tx->base, COLOR_TRANSFORM_PIPELINE);
// TODO: flatten nested pipeline transforms
for (size_t i = 0; i < len; i++) {
copy[i] = wlr_color_transform_ref(transforms[i]);
}
tx->transforms = copy;
tx->len = len;
return &tx->base;
}
static void color_transform_destroy(struct wlr_color_transform *tr) {
switch (tr->type) {
case COLOR_TRANSFORM_INVERSE_EOTF:
@ -73,6 +100,14 @@ static void color_transform_destroy(struct wlr_color_transform *tr) {
struct wlr_color_transform_lut_3x1d *lut_3x1d = color_transform_lut_3x1d_from_base(tr);
free(lut_3x1d->lut_3x1d);
break;
case COLOR_TRANSFORM_PIPELINE:;
struct wlr_color_transform_pipeline *pipeline =
wl_container_of(tr, pipeline, base);
for (size_t i = 0; i < pipeline->len; i++) {
wlr_color_transform_unref(pipeline->transforms[i]);
}
free(pipeline->transforms);
break;
}
wlr_addon_set_finish(&tr->addons);
free(tr);
@ -108,8 +143,67 @@ struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base(
return lut_3x1d;
}
static float srgb_eval_inverse_eotf(float x) {
// See https://www.w3.org/Graphics/Color/srgb
if (x <= 0.0031308) {
return 12.92 * x;
} else {
return 1.055 * powf(x, 1.0 / 2.4) - 0.055;
}
}
static float st2084_pq_eval_inverse_eotf(float x) {
// H.273 TransferCharacteristics code point 16
float c1 = 0.8359375;
float c2 = 18.8515625;
float c3 = 18.6875;
float m = 78.84375;
float n = 0.1593017578125;
if (x < 0) {
x = 0;
}
if (x > 1) {
x = 1;
}
float pow_n = powf(x, n);
return powf((c1 + c2 * pow_n) / (1 + c3 * pow_n), m);
}
static float bt1886_eval_inverse_eotf(float x) {
float lb = powf(0.0001, 1.0 / 2.4);
float lw = powf(1.0, 1.0 / 2.4);
float a = powf(lw - lb, 2.4);
float b = lb / (lw - lb);
return powf(x / a, 1.0 / 2.4) - b;
}
static float transfer_function_eval_inverse_eotf(
enum wlr_color_transfer_function tf, float x) {
switch (tf) {
case WLR_COLOR_TRANSFER_FUNCTION_SRGB:
return srgb_eval_inverse_eotf(x);
case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ:
return st2084_pq_eval_inverse_eotf(x);
case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR:
return x;
case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22:
return powf(x, 1.0 / 2.2);
case WLR_COLOR_TRANSFER_FUNCTION_BT1886:
return bt1886_eval_inverse_eotf(x);
}
abort(); // unreachable
}
static void color_transform_inverse_eotf_eval(
struct wlr_color_transform_inverse_eotf *tr,
float out[static 3], const float in[static 3]) {
for (size_t i = 0; i < 3; i++) {
out[i] = transfer_function_eval_inverse_eotf(tr->tf, in[i]);
}
}
static float lut_1d_get(const uint16_t *lut, size_t len, size_t i) {
if (i > len) {
if (i >= len) {
i = len - 1;
}
return (float) lut[i] / UINT16_MAX;
@ -125,13 +219,38 @@ static float lut_1d_eval(const uint16_t *lut, size_t len, float x) {
return a * (1 - frac_part) + b * frac_part;
}
void color_transform_lut_3x1d_eval(struct wlr_color_transform_lut_3x1d *tr,
static void color_transform_lut_3x1d_eval(struct wlr_color_transform_lut_3x1d *tr,
float out[static 3], const float in[static 3]) {
for (size_t i = 0; i < 3; i++) {
out[i] = lut_1d_eval(&tr->lut_3x1d[tr->dim * i], tr->dim, in[i]);
}
}
void wlr_color_transform_eval(struct wlr_color_transform *tr,
float out[static 3], const float in[static 3]) {
switch (tr->type) {
case COLOR_TRANSFORM_INVERSE_EOTF:
color_transform_inverse_eotf_eval(wlr_color_transform_inverse_eotf_from_base(tr), out, in);
break;
case COLOR_TRANSFORM_LCMS2:
color_transform_lcms2_eval(color_transform_lcms2_from_base(tr), out, in);
break;
case COLOR_TRANSFORM_LUT_3X1D:
color_transform_lut_3x1d_eval(color_transform_lut_3x1d_from_base(tr), out, in);
break;
case COLOR_TRANSFORM_PIPELINE:;
struct wlr_color_transform_pipeline *pipeline =
wl_container_of(tr, pipeline, base);
float color[3];
memcpy(color, in, sizeof(color));
for (size_t i = 0; i < pipeline->len; i++) {
wlr_color_transform_eval(pipeline->transforms[i], color, color);
}
memcpy(out, color, sizeof(color));
break;
}
}
void wlr_color_primaries_from_named(struct wlr_color_primaries *out,
enum wlr_color_named_primaries named) {
switch (named) {
@ -202,6 +321,13 @@ void wlr_color_transfer_function_get_default_luminance(enum wlr_color_transfer_f
.reference = 203,
};
break;
case WLR_COLOR_TRANSFER_FUNCTION_BT1886:
*lum = (struct wlr_color_luminances){
.min = 0.01,
.max = 100,
.reference = 100,
};
break;
default:
*lum = (struct wlr_color_luminances){
.min = 0.2,

View file

@ -57,10 +57,7 @@ static void convert_pixman_box_to_vk_rect(const pixman_box32_t *box, VkRect2D *r
}
static float color_to_linear(float non_linear) {
// See https://www.w3.org/Graphics/Color/srgb
return (non_linear > 0.04045) ?
pow((non_linear + 0.055) / 1.055, 2.4) :
non_linear / 12.92;
return pow(non_linear, 2.2);
}
static float color_to_linear_premult(float non_linear, float alpha) {
@ -229,7 +226,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) {
if (pass->color_transform && pass->color_transform->type != COLOR_TRANSFORM_INVERSE_EOTF) {
pipeline = render_buffer->two_pass.render_setup->output_pipe_lut3d;
} else {
enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_SRGB;
enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22;
if (pass->color_transform && pass->color_transform->type == COLOR_TRANSFORM_INVERSE_EOTF) {
struct wlr_color_transform_inverse_eotf *inverse_eotf =
wlr_color_transform_inverse_eotf_from_base(pass->color_transform);
@ -246,6 +243,12 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) {
case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ:
pipeline = render_buffer->two_pass.render_setup->output_pipe_pq;
break;
case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22:
pipeline = render_buffer->two_pass.render_setup->output_pipe_gamma22;
break;
case WLR_COLOR_TRANSFER_FUNCTION_BT1886:
pipeline = render_buffer->two_pass.render_setup->output_pipe_bt1886;
break;
}
struct wlr_color_luminances srgb_lum, dst_lum;
@ -776,7 +779,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass,
enum wlr_color_transfer_function tf = options->transfer_function;
if (tf == 0) {
tf = WLR_COLOR_TRANSFER_FUNCTION_SRGB;
tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22;
}
bool srgb_image_view = false;
@ -796,6 +799,12 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass,
case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ:
tex_transform = WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ;
break;
case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22:
tex_transform = WLR_VK_TEXTURE_TRANSFORM_GAMMA22;
break;
case WLR_COLOR_TRANSFER_FUNCTION_BT1886:
tex_transform = WLR_VK_TEXTURE_TRANSFORM_BT1886;
break;
}
struct wlr_vk_pipeline *pipe = setup_get_or_create_pipeline(
@ -840,7 +849,8 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass,
}
float luminance_multiplier = 1;
if (tf != WLR_COLOR_TRANSFER_FUNCTION_SRGB) {
if (tf != WLR_COLOR_TRANSFER_FUNCTION_SRGB
&& tf != WLR_COLOR_TRANSFER_FUNCTION_GAMMA22) {
struct wlr_color_luminances src_lum, srgb_lum;
wlr_color_transfer_function_get_default_luminance(tf, &src_lum);
wlr_color_transfer_function_get_default_luminance(
@ -954,19 +964,6 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer,
*ds = VK_NULL_HANDLE;
*ds_pool = NULL;
struct wlr_color_transform_lcms2 *tr_lcms2 = NULL;
struct wlr_color_transform_lut_3x1d *tr_lut_3x1d = NULL;
switch (tr->type) {
case COLOR_TRANSFORM_INVERSE_EOTF:
abort(); // unreachable
case COLOR_TRANSFORM_LCMS2:
tr_lcms2 = color_transform_lcms2_from_base(tr);
break;
case COLOR_TRANSFORM_LUT_3X1D:
tr_lut_3x1d = color_transform_lut_3x1d_from_base(tr);
break;
}
// R32G32B32 is not a required Vulkan format
// TODO: use it when available
VkFormat format = VK_FORMAT_R32G32B32A32_SFLOAT;
@ -1064,11 +1061,7 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer,
b_index * sample_range,
};
float rgb_out[3];
if (tr_lcms2 != NULL) {
color_transform_lcms2_eval(tr_lcms2, rgb_out, rgb_in);
} else {
color_transform_lut_3x1d_eval(tr_lut_3x1d, rgb_out, rgb_in);
}
wlr_color_transform_eval(tr, rgb_out, rgb_in);
dst[dst_offset] = rgb_out[0];
dst[dst_offset + 1] = rgb_out[1];
@ -1179,7 +1172,7 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend
}
} else {
// This is the default when unspecified
inv_eotf = WLR_COLOR_TRANSFER_FUNCTION_SRGB;
inv_eotf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22;
}
bool using_linear_pathway = inv_eotf == WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR;

View file

@ -175,6 +175,8 @@ static void destroy_render_format_setup(struct wlr_vk_renderer *renderer,
vkDestroyPipeline(dev, setup->output_pipe_srgb, NULL);
vkDestroyPipeline(dev, setup->output_pipe_pq, NULL);
vkDestroyPipeline(dev, setup->output_pipe_lut3d, NULL);
vkDestroyPipeline(dev, setup->output_pipe_gamma22, NULL);
vkDestroyPipeline(dev, setup->output_pipe_bt1886, NULL);
struct wlr_vk_pipeline *pipeline, *tmp_pipeline;
wl_list_for_each_safe(pipeline, tmp_pipeline, &setup->pipelines, link) {
@ -2345,6 +2347,16 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup(
&setup->output_pipe_pq, WLR_VK_OUTPUT_TRANSFORM_INVERSE_ST2084_PQ)) {
goto error;
}
if (!init_blend_to_output_pipeline(
renderer, setup->render_pass, renderer->output_pipe_layout,
&setup->output_pipe_gamma22, WLR_VK_OUTPUT_TRANSFORM_INVERSE_GAMMA22)) {
goto error;
}
if (!init_blend_to_output_pipeline(
renderer, setup->render_pass, renderer->output_pipe_layout,
&setup->output_pipe_bt1886, WLR_VK_OUTPUT_TRANSFORM_INVERSE_BT1886)) {
goto error;
}
} else {
assert(format->vk_srgb);
VkAttachmentDescription attachment = {

View file

@ -22,6 +22,8 @@ layout (constant_id = 0) const int OUTPUT_TRANSFORM = 0;
#define OUTPUT_TRANSFORM_INVERSE_SRGB 1
#define OUTPUT_TRANSFORM_INVERSE_ST2084_PQ 2
#define OUTPUT_TRANSFORM_LUT_3D 3
#define OUTPUT_TRANSFORM_INVERSE_GAMMA22 4
#define OUTPUT_TRANSFORM_INVERSE_BT1886 5
float linear_channel_to_srgb(float x) {
return max(min(x * 12.92, 0.04045), 1.055 * pow(x, 1. / 2.4) - 0.055);
@ -46,6 +48,14 @@ vec3 linear_color_to_pq(vec3 color) {
return pow((vec3(c1) + c2 * pow_n) / (vec3(1) + c3 * pow_n), vec3(m));
}
vec3 linear_color_to_bt1886(vec3 color) {
float lb = pow(0.0001, 1.0 / 2.4);
float lw = pow(1.0, 1.0 / 2.4);
float a = pow(lw - lb, 2.4);
float b = lb / (lw - lb);
return pow(color / a, vec3(1.0 / 2.4)) - vec3(b);
}
void main() {
vec4 in_color = subpassLoad(in_color).rgba;
@ -71,6 +81,10 @@ void main() {
} else if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_INVERSE_SRGB) {
// Produce sRGB encoded values
rgb = linear_color_to_srgb(rgb);
} else if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_INVERSE_GAMMA22) {
rgb = pow(rgb, vec3(1. / 2.2));
} else if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_INVERSE_BT1886) {
rgb = linear_color_to_bt1886(rgb);
}
// Back to pre-multiplied alpha

View file

@ -18,6 +18,8 @@ layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0;
#define TEXTURE_TRANSFORM_IDENTITY 0
#define TEXTURE_TRANSFORM_SRGB 1
#define TEXTURE_TRANSFORM_ST2084_PQ 2
#define TEXTURE_TRANSFORM_GAMMA22 3
#define TEXTURE_TRANSFORM_BT1886 4
float srgb_channel_to_linear(float x) {
return mix(x / 12.92,
@ -44,6 +46,14 @@ vec3 pq_color_to_linear(vec3 color) {
return pow(num / denom, vec3(inv_m1));
}
vec3 bt1886_color_to_linear(vec3 color) {
float lb = pow(0.0001, 1.0 / 2.4);
float lw = pow(1.0, 1.0 / 2.4);
float a = pow(lw - lb, 2.4);
float b = lb / (lw - lb);
return a * pow(color + vec3(b), vec3(2.4));
}
void main() {
vec4 in_color = textureLod(tex, uv, 0);
@ -60,6 +70,10 @@ void main() {
rgb = srgb_color_to_linear(rgb);
} else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_ST2084_PQ) {
rgb = pq_color_to_linear(rgb);
} else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_GAMMA22) {
rgb = pow(rgb, vec3(2.2));
} else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_BT1886) {
rgb = bt1886_color_to_linear(rgb);
}
rgb *= data.luminance_multiplier;

View file

@ -242,6 +242,15 @@ static void output_apply_state(struct wlr_output *output,
}
}
if (state->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) {
wlr_color_transform_unref(output->color_transform);
if (state->color_transform != NULL) {
output->color_transform = wlr_color_transform_ref(state->color_transform);
} else {
output->color_transform = NULL;
}
}
bool geometry_updated = state->committed &
(WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_TRANSFORM |
WLR_OUTPUT_STATE_SUBPIXEL);
@ -407,6 +416,7 @@ void wlr_output_finish(struct wlr_output *output) {
wlr_swapchain_destroy(output->cursor_swapchain);
wlr_buffer_unlock(output->cursor_front_buffer);
wlr_color_transform_unref(output->color_transform);
wlr_swapchain_destroy(output->swapchain);
@ -423,6 +433,7 @@ void wlr_output_finish(struct wlr_output *output) {
free(output->make);
free(output->model);
free(output->serial);
free(output->port);
}
void wlr_output_destroy(struct wlr_output *output) {
@ -515,8 +526,7 @@ const struct wlr_output_image_description *output_pending_image_description(
* Returns a bitfield of the unchanged fields.
*
* Some fields are not checked: damage always changes in-between frames, the
* gamma LUT is too expensive to check, the contents of the buffer might have
* changed, etc.
* contents of the buffer might have changed, etc.
*/
static uint32_t output_compare_state(struct wlr_output *output,
const struct wlr_output_state *state) {
@ -562,6 +572,10 @@ static uint32_t output_compare_state(struct wlr_output *output,
output->subpixel == state->subpixel) {
fields |= WLR_OUTPUT_STATE_SUBPIXEL;
}
if ((state->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) &&
output->color_transform == state->color_transform) {
fields |= WLR_OUTPUT_STATE_COLOR_TRANSFORM;
}
return fields;
}

View file

@ -37,10 +37,12 @@ static struct wlr_output *get_surface_frame_pacing_output(struct wlr_surface *su
static bool get_tf_preference(enum wlr_color_transfer_function tf) {
switch (tf) {
case WLR_COLOR_TRANSFER_FUNCTION_SRGB:
case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22:
return 0;
case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ:
return 1;
case WLR_COLOR_TRANSFER_FUNCTION_BT1886:
case WLR_COLOR_TRANSFER_FUNCTION_SRGB:
case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR:
return -1;
}
@ -60,7 +62,7 @@ static bool get_primaries_preference(enum wlr_color_named_primaries primaries) {
static void get_surface_preferred_image_description(struct wlr_surface *surface,
struct wlr_image_description_v1_data *out) {
struct wlr_output_image_description preferred = {
.transfer_function = WLR_COLOR_TRANSFER_FUNCTION_SRGB,
.transfer_function = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22,
.primaries = WLR_COLOR_NAMED_PRIMARIES_SRGB,
};
@ -248,7 +250,7 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) {
opacity = (float)alpha_modifier_state->multiplier;
}
enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_SRGB;
enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22;
enum wlr_color_named_primaries primaries = WLR_COLOR_NAMED_PRIMARIES_SRGB;
const struct wlr_image_description_v1_data *img_desc =
wlr_surface_get_image_description_v1_data(surface);

View file

@ -1530,6 +1530,8 @@ static void scene_handle_gamma_control_manager_v1_set_gamma(struct wl_listener *
output->gamma_lut_changed = true;
output->gamma_lut = event->control;
wlr_color_transform_unref(output->gamma_lut_color_transform);
output->gamma_lut_color_transform = wlr_gamma_control_v1_get_color_transform(event->control);
wlr_output_schedule_frame(output->output);
}
@ -1547,6 +1549,8 @@ static void scene_handle_gamma_control_manager_v1_destroy(struct wl_listener *li
wl_list_for_each(output, &scene->outputs, link) {
output->gamma_lut_changed = false;
output->gamma_lut = NULL;
wlr_color_transform_unref(output->gamma_lut_color_transform);
output->gamma_lut_color_transform = NULL;
}
}
@ -1766,6 +1770,10 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) {
wl_list_remove(&scene_output->output_damage.link);
wl_list_remove(&scene_output->output_needs_frame.link);
wlr_drm_syncobj_timeline_unref(scene_output->in_timeline);
wlr_color_transform_unref(scene_output->gamma_lut_color_transform);
wlr_color_transform_unref(scene_output->prev_gamma_lut_color_transform);
wlr_color_transform_unref(scene_output->prev_supplied_color_transform);
wlr_color_transform_unref(scene_output->prev_combined_color_transform);
wl_array_release(&scene_output->render_list);
free(scene_output);
}
@ -1925,6 +1933,27 @@ static void scene_buffer_send_dmabuf_feedback(const struct wlr_scene *scene,
wlr_linux_dmabuf_feedback_v1_finish(&feedback);
}
static bool color_management_is_scanout_allowed(const struct wlr_output_image_description *img_desc,
const struct wlr_scene_buffer *buffer) {
// Disallow scanout if the output has colorimetry information but buffer
// doesn't; allow it only if the output also lacks it.
if (buffer->transfer_function == 0 && buffer->primaries == 0) {
return img_desc == NULL;
}
// If the output has colorimetry information, the buffer must match it for
// direct scanout to be allowed.
if (img_desc != NULL) {
return img_desc->transfer_function == buffer->transfer_function &&
img_desc->primaries == buffer->primaries;
}
// If the output doesn't have colorimetry image description set, we can only
// scan out buffers with default colorimetry (gamma2.2 transfer and sRGB
// primaries) used in wlroots.
return buffer->transfer_function == WLR_COLOR_TRANSFER_FUNCTION_GAMMA22 &&
buffer->primaries == WLR_COLOR_NAMED_PRIMARIES_SRGB;
}
enum scene_direct_scanout_result {
// This scene node is not a candidate for scanout
SCANOUT_INELIGIBLE,
@ -1982,20 +2011,8 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout(
}
const struct wlr_output_image_description *img_desc = output_pending_image_description(scene_output->output, state);
if (buffer->transfer_function != 0 || buffer->primaries != 0) {
if (img_desc == NULL || img_desc->transfer_function != buffer->transfer_function ||
img_desc->primaries != buffer->primaries) {
return false;
}
} else if (img_desc != NULL) {
return false;
}
if (buffer->transfer_function != 0 && buffer->transfer_function != WLR_COLOR_TRANSFER_FUNCTION_SRGB) {
return false;
}
if (buffer->primaries != 0 && buffer->primaries != WLR_COLOR_NAMED_PRIMARIES_SRGB) {
return false;
if (!color_management_is_scanout_allowed(img_desc, buffer)) {
return SCANOUT_INELIGIBLE;
}
// We want to ensure optimal buffer selection, but as direct-scanout can be enabled and disabled
@ -2095,16 +2112,15 @@ static void scene_output_state_attempt_gamma(struct wlr_scene_output *scene_outp
return;
}
if (!wlr_gamma_control_v1_apply(scene_output->gamma_lut, &gamma_pending)) {
wlr_output_state_finish(&gamma_pending);
return;
}
wlr_output_state_set_color_transform(&gamma_pending, scene_output->gamma_lut_color_transform);
scene_output->gamma_lut_changed = false;
if (!wlr_output_test_state(scene_output->output, &gamma_pending)) {
wlr_gamma_control_v1_send_failed_and_destroy(scene_output->gamma_lut);
scene_output->gamma_lut = NULL;
wlr_color_transform_unref(scene_output->gamma_lut_color_transform);
scene_output->gamma_lut_color_transform = NULL;
wlr_output_state_finish(&gamma_pending);
return;
}
@ -2113,6 +2129,41 @@ static void scene_output_state_attempt_gamma(struct wlr_scene_output *scene_outp
wlr_output_state_finish(&gamma_pending);
}
static struct wlr_color_transform *scene_output_combine_color_transforms(
struct wlr_scene_output *scene_output, struct wlr_color_transform *supplied) {
struct wlr_color_transform *gamma_lut = scene_output->gamma_lut_color_transform;
assert(gamma_lut != NULL);
if (gamma_lut == scene_output->prev_gamma_lut_color_transform &&
supplied == scene_output->prev_supplied_color_transform) {
return wlr_color_transform_ref(scene_output->prev_combined_color_transform);
}
struct wlr_color_transform *combined;
if (supplied == NULL) {
combined = wlr_color_transform_ref(gamma_lut);
} else {
struct wlr_color_transform *transforms[] = {
gamma_lut,
supplied,
};
size_t transforms_len = sizeof(transforms) / sizeof(transforms[0]);
combined = wlr_color_transform_init_pipeline(transforms, transforms_len);
if (combined == NULL) {
return NULL;
}
}
wlr_color_transform_unref(scene_output->prev_gamma_lut_color_transform);
scene_output->prev_gamma_lut_color_transform = wlr_color_transform_ref(gamma_lut);
wlr_color_transform_unref(scene_output->prev_supplied_color_transform);
scene_output->prev_supplied_color_transform = supplied ? wlr_color_transform_ref(supplied) : NULL;
wlr_color_transform_unref(scene_output->prev_combined_color_transform);
scene_output->prev_combined_color_transform = wlr_color_transform_ref(combined);
return combined;
}
bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
struct wlr_output_state *state, const struct wlr_scene_output_state_options *options) {
struct wlr_scene_output_state_options default_options = {0};
@ -2136,6 +2187,16 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
enum wlr_scene_debug_damage_option debug_damage =
scene_output->scene->debug_damage_option;
bool render_gamma_lut = false;
if (wlr_output_get_gamma_size(output) == 0 && output->renderer->features.output_color_transform) {
if (scene_output->gamma_lut_color_transform != scene_output->prev_gamma_lut_color_transform) {
scene_output_damage_whole(scene_output);
}
if (scene_output->gamma_lut_color_transform != NULL) {
render_gamma_lut = true;
}
}
struct render_data render_data = {
.transform = output->transform,
.scale = output->scale,
@ -2236,7 +2297,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
// - There are no color transforms that need to be applied
// - Damage highlight debugging is not enabled
enum scene_direct_scanout_result scanout_result = SCANOUT_INELIGIBLE;
if (options->color_transform == NULL && list_len == 1
if (options->color_transform == NULL && !render_gamma_lut && list_len == 1
&& debug_damage != WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) {
scanout_result = scene_entry_try_direct_scanout(&list_data[0], state, &render_data);
}
@ -2310,6 +2371,17 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
color_transform = wlr_color_transform_ref(options->color_transform);
}
if (render_gamma_lut) {
struct wlr_color_transform *combined =
scene_output_combine_color_transforms(scene_output, color_transform);
wlr_color_transform_unref(color_transform);
if (combined == NULL) {
wlr_buffer_unlock(buffer);
return false;
}
color_transform = combined;
}
scene_output->in_point++;
struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(output->renderer, buffer,
&(struct wlr_buffer_pass_options){
@ -2432,7 +2504,9 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
scene_output->in_point);
}
scene_output_state_attempt_gamma(scene_output, state);
if (!render_gamma_lut) {
scene_output_state_attempt_gamma(scene_output, state);
}
return true;
}

View file

@ -212,7 +212,7 @@ static void cm_output_handle_get_image_description(struct wl_client *client,
}
struct wlr_image_description_v1_data data = {
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22,
.primaries_named = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
};
const struct wlr_output_image_description *image_desc = cm_output->output->image_description;
@ -777,7 +777,7 @@ static void manager_handle_get_surface_feedback(struct wl_client *client,
surface_feedback->surface = surface;
surface_feedback->data = (struct wlr_image_description_v1_data){
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22,
.primaries_named = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
};
@ -993,6 +993,10 @@ wlr_color_manager_v1_transfer_function_to_wlr(enum wp_color_manager_v1_transfer_
return WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ;
case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR:
return WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR;
case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22:
return WLR_COLOR_TRANSFER_FUNCTION_GAMMA22;
case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886:
return WLR_COLOR_TRANSFER_FUNCTION_BT1886;
default:
abort();
}
@ -1007,6 +1011,10 @@ wlr_color_manager_v1_transfer_function_from_wlr(enum wlr_color_transfer_function
return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ;
case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR:
return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR;
case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22:
return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22;
case WLR_COLOR_TRANSFER_FUNCTION_BT1886:
return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886;
}
abort();
}

View file

@ -157,6 +157,9 @@ static void gamma_control_manager_get_gamma_control(struct wl_client *client,
}
size_t gamma_size = wlr_output_get_gamma_size(output);
if (gamma_size == 0) {
gamma_size = manager->fallback_gamma_size;
}
if (gamma_size == 0) {
zwlr_gamma_control_v1_send_failed(resource);
return;
@ -262,15 +265,24 @@ struct wlr_gamma_control_v1 *wlr_gamma_control_manager_v1_get_control(
return NULL;
}
struct wlr_color_transform *wlr_gamma_control_v1_get_color_transform(
struct wlr_gamma_control_v1 *gamma_control) {
if (gamma_control == NULL || gamma_control->table == NULL) {
return NULL;
}
const uint16_t *r = gamma_control->table;
const uint16_t *g = gamma_control->table + gamma_control->ramp_size;
const uint16_t *b = gamma_control->table + 2 * gamma_control->ramp_size;
return wlr_color_transform_init_lut_3x1d(gamma_control->ramp_size, r, g, b);
}
bool wlr_gamma_control_v1_apply(struct wlr_gamma_control_v1 *gamma_control,
struct wlr_output_state *output_state) {
struct wlr_color_transform *tr = NULL;
if (gamma_control != NULL && gamma_control->table != NULL) {
const uint16_t *r = gamma_control->table;
const uint16_t *g = gamma_control->table + gamma_control->ramp_size;
const uint16_t *b = gamma_control->table + 2 * gamma_control->ramp_size;
tr = wlr_color_transform_init_lut_3x1d(gamma_control->ramp_size, r, g, b);
tr = wlr_gamma_control_v1_get_color_transform(gamma_control);
if (tr == NULL) {
return false;
}

View file

@ -56,6 +56,7 @@ static void input_method_destroy(struct wlr_input_method_v2 *input_method) {
popup_surface, tmp, &input_method->popup_surfaces, link) {
popup_surface_destroy(popup_surface);
}
wlr_input_method_keyboard_grab_v2_destroy(input_method->keyboard_grab);
wl_signal_emit_mutable(&input_method->events.destroy, NULL);
assert(wl_list_empty(&input_method->events.commit.listener_list));
@ -65,7 +66,6 @@ static void input_method_destroy(struct wlr_input_method_v2 *input_method) {
wl_list_remove(wl_resource_get_link(input_method->resource));
wl_list_remove(&input_method->seat_client_destroy.link);
wlr_input_method_keyboard_grab_v2_destroy(input_method->keyboard_grab);
input_state_reset(&input_method->pending);
input_state_reset(&input_method->current);
free(input_method);
@ -271,8 +271,7 @@ void wlr_input_method_keyboard_grab_v2_destroy(
if (!keyboard_grab) {
return;
}
wl_signal_emit_mutable(&keyboard_grab->events.destroy, keyboard_grab);
wl_signal_emit_mutable(&keyboard_grab->events.destroy, NULL);
assert(wl_list_empty(&keyboard_grab->events.destroy.listener_list));
keyboard_grab->input_method->keyboard_grab = NULL;

View file

@ -26,7 +26,7 @@ struct wlr_linux_drm_syncobj_surface_v1 {
};
struct wlr_linux_drm_syncobj_surface_v1_commit {
struct wlr_linux_drm_syncobj_surface_v1 *surface;
struct wlr_surface *surface;
struct wlr_drm_syncobj_timeline_waiter waiter;
uint32_t cached_seq;
@ -192,7 +192,7 @@ static struct wlr_linux_drm_syncobj_surface_v1 *surface_from_wlr_surface(
}
static void surface_commit_destroy(struct wlr_linux_drm_syncobj_surface_v1_commit *commit) {
wlr_surface_unlock_cached(commit->surface->surface, commit->cached_seq);
wlr_surface_unlock_cached(commit->surface, commit->cached_seq);
wl_list_remove(&commit->surface_destroy.link);
wlr_drm_syncobj_timeline_waiter_finish(&commit->waiter);
free(commit);
@ -237,7 +237,7 @@ static bool lock_surface_commit(struct wlr_linux_drm_syncobj_surface_v1 *surface
return false;
}
commit->surface = surface;
commit->surface = surface->surface;
commit->cached_seq = wlr_surface_lock_pending(surface->surface);
commit->surface_destroy.notify = surface_commit_handle_surface_destroy;

View file

@ -19,16 +19,15 @@ void wlr_box_closest_point(const struct wlr_box *box, double x, double y,
//
// In order to be consistent with e.g. wlr_box_contains_point(),
// this function returns a point inside the bottom and right edges
// of the box by at least 1/65536 of a unit (pixel). 1/65536 is
// of the box by at least 1/256 of a unit (pixel). 1/256 is
// small enough to avoid a "dead zone" with high-resolution mice
// but large enough to avoid rounding to zero (due to loss of
// significant digits) in simple floating-point calculations.
// but large enough to avoid rounding to zero in wl_fixed_from_double().
// find the closest x point
if (x < box->x) {
*dest_x = box->x;
} else if (x > box->x + box->width - 1/65536.0) {
*dest_x = box->x + box->width - 1/65536.0;
} else if (x > box->x + box->width - 1/256.0) {
*dest_x = box->x + box->width - 1/256.0;
} else {
*dest_x = x;
}
@ -36,8 +35,8 @@ void wlr_box_closest_point(const struct wlr_box *box, double x, double y,
// find closest y point
if (y < box->y) {
*dest_y = box->y;
} else if (y > box->y + box->height - 1/65536.0) {
*dest_y = box->y + box->height - 1/65536.0;
} else if (y > box->y + box->height - 1/256.0) {
*dest_y = box->y + box->height - 1/256.0;
} else {
*dest_y = y;
}

View file

@ -42,6 +42,9 @@ static void handle_server_start(struct wl_listener *listener, void *data) {
static void xwayland_mark_ready(struct wlr_xwayland *xwayland) {
assert(xwayland->server->wm_fd[0] >= 0);
xwayland->xwm = xwm_create(xwayland, xwayland->server->wm_fd[0]);
// xwm_create takes ownership of wm_fd[0] under all circumstances
xwayland->server->wm_fd[0] = -1;
if (!xwayland->xwm) {
return;
}

View file

@ -2530,6 +2530,7 @@ void xwm_set_cursor(struct wlr_xwm *xwm, const uint8_t *pixels, uint32_t stride,
struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) {
struct wlr_xwm *xwm = calloc(1, sizeof(*xwm));
if (xwm == NULL) {
close(wm_fd);
return NULL;
}
@ -2544,11 +2545,13 @@ struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) {
xwm->ping_timeout = 10000;
// xcb_connect_to_fd takes ownership of the FD regardless of success/failure
xwm->xcb_conn = xcb_connect_to_fd(wm_fd, NULL);
int rc = xcb_connection_has_error(xwm->xcb_conn);
if (rc) {
wlr_log(WLR_ERROR, "xcb connect failed: %d", rc);
xcb_disconnect(xwm->xcb_conn);
free(xwm);
return NULL;
}