aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.builds/alpine.yml1
-rw-r--r--.builds/archlinux.yml1
-rw-r--r--.builds/freebsd.yml1
-rw-r--r--README.md2
-rw-r--r--backend/drm/atomic.c4
-rw-r--r--backend/drm/drm.c32
-rw-r--r--backend/drm/libliftoff.c347
-rw-r--r--backend/drm/meson.build13
-rw-r--r--docs/env_vars.md2
-rw-r--r--include/backend/drm/drm.h6
-rw-r--r--include/backend/drm/iface.h9
11 files changed, 415 insertions, 3 deletions
diff --git a/.builds/alpine.yml b/.builds/alpine.yml
index 68679403..6df07fab 100644
--- a/.builds/alpine.yml
+++ b/.builds/alpine.yml
@@ -4,6 +4,7 @@ packages:
- ffmpeg-dev
- glslang
- libinput-dev
+ - libliftoff-dev
- libxkbcommon-dev
- mesa-dev
- meson
diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml
index 2d62d0d1..16d89331 100644
--- a/.builds/archlinux.yml
+++ b/.builds/archlinux.yml
@@ -3,6 +3,7 @@ packages:
- clang
- ffmpeg
- libinput
+ - libliftoff
- libxkbcommon
- mesa
- meson
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml
index a9650d64..872e01fe 100644
--- a/.builds/freebsd.yml
+++ b/.builds/freebsd.yml
@@ -7,6 +7,7 @@ packages:
- devel/pkgconf
- graphics/glslang
- graphics/libdrm
+ - graphics/libliftoff
- graphics/mesa-libs
- graphics/png
- graphics/vulkan-headers
diff --git a/README.md b/README.md
index ae8a6ad9..e269ff14 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,7 @@ Install dependencies:
* pixman
* [libseat] (optional, for the session)
* [hwdata] (optional, for the DRM backend)
+* [libliftoff] (optional, for the DRM backend)
If you choose to enable X11 support:
@@ -80,4 +81,5 @@ See [CONTRIBUTING.md].
[wrapper libraries]: https://gitlab.freedesktop.org/wlroots/wlroots/-/wikis/Projects-which-use-wlroots#wrapper-libraries
[libseat]: https://git.sr.ht/~kennylevinsen/seatd
[hwdata]: https://github.com/vcrhonek/hwdata
+[libliftoff]: https://gitlab.freedesktop.org/emersion/libliftoff
[CONTRIBUTING.md]: https://gitlab.freedesktop.org/wlroots/wlroots/-/blob/master/CONTRIBUTING.md
diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c
index 0dacff23..c8ffaff6 100644
--- a/backend/drm/atomic.c
+++ b/backend/drm/atomic.c
@@ -93,7 +93,7 @@ static void atomic_add(struct atomic *atom, uint32_t id, uint32_t prop, uint64_t
}
}
-static bool create_mode_blob(struct wlr_drm_backend *drm,
+bool create_mode_blob(struct wlr_drm_backend *drm,
struct wlr_drm_connector *conn,
const struct wlr_drm_connector_state *state, uint32_t *blob_id) {
if (!state->active) {
@@ -110,7 +110,7 @@ static bool create_mode_blob(struct wlr_drm_backend *drm,
return true;
}
-static bool create_gamma_lut_blob(struct wlr_drm_backend *drm,
+bool create_gamma_lut_blob(struct wlr_drm_backend *drm,
size_t size, const uint16_t *lut, uint32_t *blob_id) {
if (size == 0) {
*blob_id = 0;
diff --git a/backend/drm/drm.c b/backend/drm/drm.c
index f342249f..bfbe6cbd 100644
--- a/backend/drm/drm.c
+++ b/backend/drm/drm.c
@@ -30,6 +30,11 @@
#include "render/swapchain.h"
#include "render/wlr_renderer.h"
#include "util/env.h"
+#include "config.h"
+
+#if HAVE_LIBLIFTOFF
+#include <libliftoff.h>
+#endif
// Output state which needs a KMS commit to be applied
static const uint32_t COMMIT_OUTPUT_STATE =
@@ -76,7 +81,20 @@ bool check_drm_features(struct wlr_drm_backend *drm) {
return false;
}
- if (env_parse_bool("WLR_DRM_NO_ATOMIC")) {
+ if (env_parse_bool("WLR_DRM_FORCE_LIBLIFTOFF")) {
+#if HAVE_LIBLIFTOFF
+ wlr_log(WLR_INFO,
+ "WLR_DRM_FORCE_LIBLIFTOFF set, forcing libliftoff interface");
+ if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_ATOMIC, 1) != 0) {
+ wlr_log_errno(WLR_ERROR, "drmSetClientCap(ATOMIC) failed");
+ return false;
+ }
+ drm->iface = &liftoff_iface;
+#else
+ wlr_log(WLR_ERROR, "libliftoff interface not available");
+ return false;
+#endif
+ } else if (env_parse_bool("WLR_DRM_NO_ATOMIC")) {
wlr_log(WLR_DEBUG,
"WLR_DRM_NO_ATOMIC set, forcing legacy DRM interface");
drm->iface = &legacy_iface;
@@ -121,6 +139,7 @@ static bool init_plane(struct wlr_drm_backend *drm,
p->type = type;
p->id = drm_plane->plane_id;
p->props = props;
+ p->initial_crtc_id = drm_plane->crtc_id;
for (size_t i = 0; i < drm_plane->count_formats; ++i) {
// Force a LINEAR layout for the cursor if the driver doesn't support
@@ -263,6 +282,10 @@ bool init_drm_resources(struct wlr_drm_backend *drm) {
goto error_crtcs;
}
+ if (drm->iface->init != NULL && !drm->iface->init(drm)) {
+ goto error_crtcs;
+ }
+
drmModeFreeResources(res);
return true;
@@ -279,6 +302,10 @@ void finish_drm_resources(struct wlr_drm_backend *drm) {
return;
}
+ if (drm->iface->finish != NULL) {
+ drm->iface->finish(drm);
+ }
+
for (size_t i = 0; i < drm->num_crtcs; ++i) {
struct wlr_drm_crtc *crtc = &drm->crtcs[i];
@@ -295,6 +322,9 @@ void finish_drm_resources(struct wlr_drm_backend *drm) {
for (size_t i = 0; i < drm->num_planes; ++i) {
struct wlr_drm_plane *plane = &drm->planes[i];
wlr_drm_format_set_finish(&plane->formats);
+#if HAVE_LIBLIFTOFF
+ liftoff_plane_destroy(plane->liftoff);
+#endif
}
free(drm->planes);
diff --git a/backend/drm/libliftoff.c b/backend/drm/libliftoff.c
new file mode 100644
index 00000000..c6322c73
--- /dev/null
+++ b/backend/drm/libliftoff.c
@@ -0,0 +1,347 @@
+#define _POSIX_C_SOURCE 200809L
+#include <fcntl.h>
+#include <libliftoff.h>
+#include <unistd.h>
+#include <wlr/util/log.h>
+
+#include "backend/drm/drm.h"
+#include "backend/drm/iface.h"
+
+static bool init(struct wlr_drm_backend *drm) {
+ // TODO: lower log level
+ liftoff_log_set_priority(LIFTOFF_DEBUG);
+
+ int drm_fd = fcntl(drm->fd, F_DUPFD_CLOEXEC, 0);
+ if (drm_fd < 0) {
+ wlr_log_errno(WLR_ERROR, "fcntl(F_DUPFD_CLOEXEC) failed");
+ return false;
+ }
+
+ drm->liftoff = liftoff_device_create(drm_fd);
+ if (!drm->liftoff) {
+ wlr_log(WLR_ERROR, "Failed to create liftoff device");
+ close(drm_fd);
+ return false;
+ }
+
+ for (size_t i = 0; i < drm->num_planes; i++) {
+ struct wlr_drm_plane *plane = &drm->planes[i];
+ if (plane->initial_crtc_id != 0) {
+ continue;
+ }
+ plane->liftoff = liftoff_plane_create(drm->liftoff, plane->id);
+ if (plane->liftoff == NULL) {
+ wlr_log(WLR_ERROR, "Failed to create liftoff plane");
+ return false;
+ }
+ }
+
+ for (size_t i = 0; i < drm->num_crtcs; i++) {
+ struct wlr_drm_crtc *crtc = &drm->crtcs[i];
+
+ crtc->liftoff = liftoff_output_create(drm->liftoff, crtc->id);
+ if (!crtc->liftoff) {
+ wlr_log(WLR_ERROR, "Failed to create liftoff output");
+ return false;
+ }
+
+ if (crtc->primary) {
+ crtc->primary->liftoff_layer = liftoff_layer_create(crtc->liftoff);
+ if (!crtc->primary->liftoff_layer) {
+ wlr_log(WLR_ERROR, "Failed to create liftoff layer for primary plane");
+ return false;
+ }
+ }
+
+ if (crtc->cursor) {
+ crtc->cursor->liftoff_layer = liftoff_layer_create(crtc->liftoff);
+ if (!crtc->cursor->liftoff_layer) {
+ wlr_log(WLR_ERROR, "Failed to create liftoff layer for cursor plane");
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+static bool register_planes_for_crtc(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc) {
+ // When performing the first modeset on a CRTC, we need to be a bit careful
+ // when it comes to planes: we don't want to allow libliftoff to make use
+ // of planes currently already in-use on another CRTC. We need to wait for
+ // a modeset to happen on the other CRTC before being able to use these.
+ for (size_t i = 0; i < drm->num_planes; i++) {
+ struct wlr_drm_plane *plane = &drm->planes[i];
+ if (plane->liftoff != NULL || plane->initial_crtc_id != crtc->id) {
+ continue;
+ }
+ plane->liftoff = liftoff_plane_create(drm->liftoff, plane->id);
+ if (plane->liftoff == NULL) {
+ wlr_log(WLR_ERROR, "Failed to create liftoff plane");
+ return false;
+ }
+ }
+ return true;
+}
+
+static void finish(struct wlr_drm_backend *drm) {
+ for (size_t i = 0; i < drm->num_crtcs; i++) {
+ struct wlr_drm_crtc *crtc = &drm->crtcs[i];
+
+ if (crtc->primary) {
+ liftoff_layer_destroy(crtc->primary->liftoff_layer);
+ }
+ if (crtc->cursor) {
+ liftoff_layer_destroy(crtc->cursor->liftoff_layer);
+ }
+
+ liftoff_output_destroy(crtc->liftoff);
+ }
+
+ liftoff_device_destroy(drm->liftoff);
+}
+
+static bool add_prop(drmModeAtomicReq *req, uint32_t obj,
+ uint32_t prop, uint64_t val) {
+ if (drmModeAtomicAddProperty(req, obj, prop, val) < 0) {
+ wlr_log_errno(WLR_ERROR, "drmModeAtomicAddProperty failed");
+ return false;
+ }
+ return true;
+}
+
+static void commit_blob(struct wlr_drm_backend *drm,
+ uint32_t *current, uint32_t next) {
+ if (*current == next) {
+ return;
+ }
+ if (*current != 0) {
+ drmModeDestroyPropertyBlob(drm->fd, *current);
+ }
+ *current = next;
+}
+
+static void rollback_blob(struct wlr_drm_backend *drm,
+ uint32_t *current, uint32_t next) {
+ if (*current == next) {
+ return;
+ }
+ if (next != 0) {
+ drmModeDestroyPropertyBlob(drm->fd, next);
+ }
+}
+
+static bool set_plane_props(struct wlr_drm_plane *plane,
+ int32_t x, int32_t y, uint64_t zpos) {
+ struct wlr_drm_fb *fb = plane_get_next_fb(plane);
+ if (fb == NULL) {
+ wlr_log(WLR_ERROR, "Failed to acquire FB");
+ return false;
+ }
+
+ uint32_t width = fb->wlr_buf->width;
+ uint32_t height = fb->wlr_buf->height;
+
+ // The SRC_* properties are in 16.16 fixed point
+ struct liftoff_layer *layer = plane->liftoff_layer;
+ return liftoff_layer_set_property(layer, "zpos", zpos) == 0 &&
+ liftoff_layer_set_property(layer, "SRC_X", 0) == 0 &&
+ liftoff_layer_set_property(layer, "SRC_Y", 0) == 0 &&
+ liftoff_layer_set_property(layer, "SRC_W", (uint64_t)width << 16) == 0 &&
+ liftoff_layer_set_property(layer, "SRC_H", (uint64_t)height << 16) == 0 &&
+ liftoff_layer_set_property(layer, "CRTC_X", (uint64_t)x) == 0 &&
+ liftoff_layer_set_property(layer, "CRTC_Y", (uint64_t)y) == 0 &&
+ liftoff_layer_set_property(layer, "CRTC_W", width) == 0 &&
+ liftoff_layer_set_property(layer, "CRTC_H", height) == 0 &&
+ liftoff_layer_set_property(layer, "FB_ID", fb->id) == 0;
+}
+
+static bool disable_plane(struct wlr_drm_plane *plane) {
+ return liftoff_layer_set_property(plane->liftoff_layer, "FB_ID", 0) == 0;
+}
+
+static bool crtc_commit(struct wlr_drm_connector *conn,
+ const struct wlr_drm_connector_state *state, uint32_t flags,
+ bool test_only) {
+ struct wlr_drm_backend *drm = conn->backend;
+ struct wlr_output *output = &conn->output;
+ struct wlr_drm_crtc *crtc = conn->crtc;
+
+ bool modeset = state->modeset;
+ bool active = state->active;
+
+ if (modeset && !register_planes_for_crtc(drm, crtc)) {
+ return false;
+ }
+
+ uint32_t mode_id = crtc->mode_id;
+ if (modeset) {
+ if (!create_mode_blob(drm, conn, state, &mode_id)) {
+ return false;
+ }
+ }
+
+ uint32_t gamma_lut = crtc->gamma_lut;
+ if (state->base->committed & WLR_OUTPUT_STATE_GAMMA_LUT) {
+ // Fallback to legacy gamma interface when gamma properties are not
+ // available (can happen on older Intel GPUs that support gamma but not
+ // degamma).
+ if (crtc->props.gamma_lut == 0) {
+ if (!drm_legacy_crtc_set_gamma(drm, crtc,
+ state->base->gamma_lut_size,
+ state->base->gamma_lut)) {
+ return false;
+ }
+ } else {
+ if (!create_gamma_lut_blob(drm, state->base->gamma_lut_size,
+ state->base->gamma_lut, &gamma_lut)) {
+ return false;
+ }
+ }
+ }
+
+ uint32_t fb_damage_clips = 0;
+ if ((state->base->committed & WLR_OUTPUT_STATE_DAMAGE) &&
+ pixman_region32_not_empty((pixman_region32_t *)&state->base->damage) &&
+ crtc->primary->props.fb_damage_clips != 0) {
+ int rects_len;
+ const pixman_box32_t *rects = pixman_region32_rectangles(
+ (pixman_region32_t *)&state->base->damage, &rects_len);
+ if (drmModeCreatePropertyBlob(drm->fd, rects,
+ sizeof(*rects) * rects_len, &fb_damage_clips) != 0) {
+ wlr_log_errno(WLR_ERROR, "Failed to create FB_DAMAGE_CLIPS property blob");
+ }
+ }
+
+ bool prev_vrr_enabled =
+ output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED;
+ bool vrr_enabled = prev_vrr_enabled;
+ if ((state->base->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) &&
+ drm_connector_supports_vrr(conn)) {
+ vrr_enabled = state->base->adaptive_sync_enabled;
+ }
+
+ if (test_only) {
+ flags |= DRM_MODE_ATOMIC_TEST_ONLY;
+ }
+ if (modeset) {
+ flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
+ } else if (!test_only && (state->base->committed & WLR_OUTPUT_STATE_BUFFER)) {
+ // The wlr_output API requires non-modeset commits with a new buffer to
+ // wait for the frame event. However compositors often perform
+ // non-modesets commits without a new buffer without waiting for the
+ // frame event. In that case we need to make the KMS commit blocking,
+ // otherwise the kernel will error out with EBUSY.
+ flags |= DRM_MODE_ATOMIC_NONBLOCK;
+ }
+
+ drmModeAtomicReq *req = drmModeAtomicAlloc();
+ if (req == NULL) {
+ wlr_log(WLR_ERROR, "drmModeAtomicAlloc failed");
+ return false;
+ }
+
+ bool ok = add_prop(req, conn->id, conn->props.crtc_id,
+ active ? crtc->id : 0);
+ if (modeset && active && conn->props.link_status != 0) {
+ ok = ok && add_prop(req, conn->id, conn->props.link_status,
+ DRM_MODE_LINK_STATUS_GOOD);
+ }
+ if (active && conn->props.content_type != 0) {
+ ok = ok && add_prop(req, conn->id, conn->props.content_type,
+ DRM_MODE_CONTENT_TYPE_GRAPHICS);
+ }
+ // TODO: set "max bpc"
+ ok = ok &&
+ add_prop(req, crtc->id, crtc->props.mode_id, mode_id) &&
+ add_prop(req, crtc->id, crtc->props.active, active);
+ if (active) {
+ if (crtc->props.gamma_lut != 0) {
+ ok = ok && add_prop(req, crtc->id, crtc->props.gamma_lut, gamma_lut);
+ }
+ if (crtc->props.vrr_enabled != 0) {
+ ok = ok && add_prop(req, crtc->id, crtc->props.vrr_enabled, vrr_enabled);
+ }
+ ok = ok && set_plane_props(crtc->primary, 0, 0, 0);
+ liftoff_layer_set_property(crtc->primary->liftoff_layer,
+ "FB_DAMAGE_CLIPS", fb_damage_clips);
+ if (crtc->cursor) {
+ if (drm_connector_is_cursor_visible(conn)) {
+ ok = ok && set_plane_props(crtc->cursor,
+ conn->cursor_x, conn->cursor_y, 1);
+ } else {
+ ok = ok && disable_plane(crtc->cursor);
+ }
+ }
+ } else {
+ ok = ok && disable_plane(crtc->primary);
+ if (crtc->cursor) {
+ ok = ok && disable_plane(crtc->cursor);
+ }
+ }
+
+ if (!ok) {
+ goto out;
+ }
+
+ int ret = liftoff_output_apply(crtc->liftoff, req, flags);
+ if (ret != 0) {
+ wlr_drm_conn_log(conn, test_only ? WLR_DEBUG : WLR_ERROR,
+ "liftoff_output_apply failed: %s", strerror(-ret));
+ ok = false;
+ goto out;
+ }
+
+ if (liftoff_layer_needs_composition(crtc->primary->liftoff_layer)) {
+ wlr_drm_conn_log(conn, WLR_DEBUG, "Failed to scan-out primary plane");
+ ok = false;
+ goto out;
+ }
+ if (crtc->cursor &&
+ liftoff_layer_needs_composition(crtc->cursor->liftoff_layer)) {
+ wlr_drm_conn_log(conn, WLR_DEBUG, "Failed to scan-out cursor plane");
+ ok = false;
+ goto out;
+ }
+
+ ret = drmModeAtomicCommit(drm->fd, req, flags, drm);
+ if (ret != 0) {
+ wlr_drm_conn_log_errno(conn, test_only ? WLR_DEBUG : WLR_ERROR,
+ "Atomic commit failed");
+ ok = false;
+ goto out;
+ }
+
+out:
+ drmModeAtomicFree(req);
+
+ if (ok && !test_only) {
+ commit_blob(drm, &crtc->mode_id, mode_id);
+ commit_blob(drm, &crtc->gamma_lut, gamma_lut);
+
+ if (vrr_enabled != prev_vrr_enabled) {
+ output->adaptive_sync_status = vrr_enabled ?
+ WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED :
+ WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED;
+ wlr_drm_conn_log(conn, WLR_DEBUG, "VRR %s",
+ vrr_enabled ? "enabled" : "disabled");
+ }
+ } else {
+ rollback_blob(drm, &crtc->mode_id, mode_id);
+ rollback_blob(drm, &crtc->gamma_lut, gamma_lut);
+ }
+
+ if (fb_damage_clips != 0 &&
+ drmModeDestroyPropertyBlob(drm->fd, fb_damage_clips) != 0) {
+ wlr_log_errno(WLR_ERROR, "Failed to destroy FB_DAMAGE_CLIPS property blob");
+ }
+
+ return ok;
+}
+
+const struct wlr_drm_interface liftoff_iface = {
+ .init = init,
+ .finish = finish,
+ .crtc_commit = crtc_commit,
+};
diff --git a/backend/drm/meson.build b/backend/drm/meson.build
index 9abf1350..8d1c1845 100644
--- a/backend/drm/meson.build
+++ b/backend/drm/meson.build
@@ -5,6 +5,13 @@ hwdata = dependency(
not_found_message: 'Required for the DRM backend.',
)
+libliftoff = dependency(
+ 'libliftoff',
+ version: '>=0.2.0',
+ fallback: 'libliftoff',
+ required: false,
+)
+
if not (hwdata.found() and features['session'])
subdir_done()
endif
@@ -32,4 +39,10 @@ wlr_files += files(
'util.c',
)
+if libliftoff.found()
+ wlr_files += files('libliftoff.c')
+endif
+
features += { 'drm-backend': true }
+internal_features += { 'libliftoff': libliftoff.found() }
+wlr_deps += libliftoff
diff --git a/docs/env_vars.md b/docs/env_vars.md
index 39daf9fd..e36cdc78 100644
--- a/docs/env_vars.md
+++ b/docs/env_vars.md
@@ -24,6 +24,8 @@ wlroots reads these environment variables
mode setting
* *WLR_DRM_NO_MODIFIERS*: set to 1 to always allocate planes without modifiers,
this can fix certain modeset failures because of bandwidth restrictions.
+* *WLR_DRM_FORCE_LIBLIFTOFF*: set to 1 to force libliftoff (by default,
+ libliftoff is never used)
## Headless backend
diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h
index 0c07811b..f2cd9c50 100644
--- a/include/backend/drm/drm.h
+++ b/include/backend/drm/drm.h
@@ -30,11 +30,16 @@ struct wlr_drm_plane {
struct wlr_drm_format_set formats;
union wlr_drm_plane_props props;
+
+ uint32_t initial_crtc_id;
+ struct liftoff_plane *liftoff;
+ struct liftoff_layer *liftoff_layer;
};
struct wlr_drm_crtc {
uint32_t id;
struct wlr_drm_lease *lease;
+ struct liftoff_output *liftoff;
// Atomic modesetting only
uint32_t mode_id;
@@ -60,6 +65,7 @@ struct wlr_drm_backend {
int fd;
char *name;
struct wlr_device *dev;
+ struct liftoff_device *liftoff;
size_t num_crtcs;
struct wlr_drm_crtc *crtcs;
diff --git a/include/backend/drm/iface.h b/include/backend/drm/iface.h
index d52bbd3d..c4bd62e4 100644
--- a/include/backend/drm/iface.h
+++ b/include/backend/drm/iface.h
@@ -13,6 +13,8 @@ struct wlr_drm_connector_state;
// Used to provide atomic or legacy DRM functions
struct wlr_drm_interface {
+ bool (*init)(struct wlr_drm_backend *drm);
+ void (*finish)(struct wlr_drm_backend *drm);
// Commit all pending changes on a CRTC.
bool (*crtc_commit)(struct wlr_drm_connector *conn,
const struct wlr_drm_connector_state *state, uint32_t flags,
@@ -21,8 +23,15 @@ struct wlr_drm_interface {
extern const struct wlr_drm_interface atomic_iface;
extern const struct wlr_drm_interface legacy_iface;
+extern const struct wlr_drm_interface liftoff_iface;
bool drm_legacy_crtc_set_gamma(struct wlr_drm_backend *drm,
struct wlr_drm_crtc *crtc, size_t size, uint16_t *lut);
+bool create_mode_blob(struct wlr_drm_backend *drm,
+ struct wlr_drm_connector *conn,
+ const struct wlr_drm_connector_state *state, uint32_t *blob_id);
+bool create_gamma_lut_blob(struct wlr_drm_backend *drm,
+ size_t size, const uint16_t *lut, uint32_t *blob_id);
+
#endif