From 28f8e8d147d70a333ed3f5129c44522ed7b495e8 Mon Sep 17 00:00:00 2001 From: Matthias Geiger Date: Fri, 14 Jul 2023 15:07:44 +0200 Subject: [PATCH] New upstream version 0.4.1 --- .editorconfig | 2 +- README.md | 7 +- alloc.c | 91 +++++++++-- device.c | 19 ++- example/common.c | 13 +- example/compositor.c | 26 ++-- example/dynamic.c | 16 +- example/multi-output.c | 10 +- example/simple.c | 10 +- gcovr.cfg | 3 + include/libliftoff.h | 25 ++- include/private.h | 27 +++- layer.c | 109 +++++++++++++ log.c | 3 +- meson.build | 13 +- output.c | 4 +- plane.c | 175 ++++++++++++++++++--- test/bench.c | 27 ++-- test/libdrm_mock.c | 198 +++++++++++++++++++++--- test/libdrm_mock.h | 15 +- test/meson.build | 9 ++ test/test_alloc.c | 16 +- test/test_candidate.c | 98 ++++++++++++ test/test_dynamic.c | 74 +++++++-- test/test_priority.c | 12 +- test/test_prop.c | 340 +++++++++++++++++++++++++++++------------ 26 files changed, 1098 insertions(+), 244 deletions(-) create mode 100644 gcovr.cfg create mode 100644 test/test_candidate.c diff --git a/.editorconfig b/.editorconfig index 761b471..2dff42c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,4 +7,4 @@ trim_trailing_whitespace = true insert_final_newline = true indent_style = tab indent_size = 8 -max_line_length = 80 +max_line_length = 100 diff --git a/README.md b/README.md index bcc473f..615a2f6 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,16 @@ Lightweight KMS plane library. libliftoff eases the use of KMS planes from userspace without standing in your way. Users create "virtual planes" called layers, set KMS properties on them, -and libliftoff will pick planes for these layers if possible. +and libliftoff will pick hardware planes for these layers if possible. Resources: * [Blog post introducing the project][intro-post] * [FOSDEM 2020 talk][fosdem-2020] +libliftoff is used in production by [gamescope] on Steam Deck devices, and work +is underway to integrate it with [wlroots]. + ## Building Depends on libdrm. Requires universal planes and atomic. @@ -76,3 +79,5 @@ MIT [fosdem-2020]: https://fosdem.org/2020/schedule/event/kms_planes/ [gitlab]: https://gitlab.freedesktop.org/emersion/libliftoff [weston-contributing]: https://gitlab.freedesktop.org/wayland/weston/-/blob/master/CONTRIBUTING.md +[gamescope]: https://github.com/Plagman/gamescope +[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots diff --git a/alloc.c b/alloc.c index fcc54ae..8ca536e 100644 --- a/alloc.c +++ b/alloc.c @@ -439,7 +439,7 @@ output_choose_layers(struct liftoff_output *output, struct alloc_result *result, step->log_prefix, plane->id, step->plane_idx + 1, result->planes_len); liftoff_list_for_each(layer, &output->layers, link) { - if (layer->plane != NULL || layer->force_composition) { + if (layer->plane != NULL) { continue; } if (!layer_is_visible(layer)) { @@ -461,6 +461,18 @@ output_choose_layers(struct liftoff_output *output, struct alloc_result *result, return ret; } + layer_add_candidate_plane(layer, plane); + + /* If composition is forced, wait until after the + * layer_add_candidate_plane() call to reject the plane: we want + * to return a meaningful list of candidate planes so that the + * API user has the opportunity to re-allocate its buffers with + * scanout-capable ones. Same deal for the FB check. */ + if (layer->force_composition || !plane_check_layer_fb(plane, layer)) { + drmModeAtomicSetCursor(result->req, cursor); + continue; + } + ret = device_test_commit(device, result->req, result->flags); if (ret == 0) { liftoff_log(LIFTOFF_DEBUG, @@ -507,7 +519,6 @@ apply_current(struct liftoff_device *device, drmModeAtomicReq *req) liftoff_list_for_each(plane, &device->planes, link) { ret = plane_apply(plane, plane->layer, req); - assert(ret != -EINVAL); if (ret != 0) { drmModeAtomicSetCursor(req, cursor); return ret; @@ -517,6 +528,19 @@ apply_current(struct liftoff_device *device, drmModeAtomicReq *req) return 0; } +static bool +fb_info_needs_realloc(const drmModeFB2 *a, const drmModeFB2 *b) +{ + if (a->width != b->width || a->height != b->height || + a->pixel_format != b->pixel_format || a->modifier != b->modifier) { + return true; + } + + /* TODO: consider checking pitch and offset? */ + + return false; +} + static bool layer_needs_realloc(struct liftoff_layer *layer) { @@ -524,26 +548,45 @@ layer_needs_realloc(struct liftoff_layer *layer) struct liftoff_layer_property *prop; if (layer->changed) { + liftoff_log(LIFTOFF_DEBUG, "Cannot re-use previous allocation: " + "layer property added or force composition changed"); return true; } for (i = 0; i < layer->props_len; i++) { prop = &layer->props[i]; - if (prop->value == prop->prev_value) { - continue; - } /* If FB_ID changes from non-zero to zero, we don't need to * display this layer anymore, so we may be able to re-use its * plane for another layer. If FB_ID changes from zero to * non-zero, we might be able to find a plane for this layer. - * If FB_ID changes from non-zero to non-zero, we can try to - * re-use the previous allocation. */ + * If FB_ID changes from non-zero to non-zero and the FB + * attributes didn't change, we can try to re-use the previous + * allocation. */ if (strcmp(prop->name, "FB_ID") == 0) { + if (prop->value == 0 && prop->prev_value == 0) { + continue; + } + if (prop->value == 0 || prop->prev_value == 0) { + liftoff_log(LIFTOFF_DEBUG, "Cannot re-use previous allocation: " + "layer enabled or disabled"); return true; } - /* TODO: check format/modifier is the same? */ + + if (fb_info_needs_realloc(&layer->fb_info, + &layer->prev_fb_info)) { + liftoff_log(LIFTOFF_DEBUG, "Cannot re-use previous allocation: " + "FB info changed"); + return true; + } + + continue; + } + + /* For all properties except FB_ID, we can skip realloc if the + * value didn't change. */ + if (prop->value == prop->prev_value) { continue; } @@ -553,6 +596,8 @@ layer_needs_realloc(struct liftoff_layer *layer) if (strcmp(prop->name, "alpha") == 0) { if (prop->value == 0 || prop->prev_value == 0 || prop->value == 0xFFFF || prop->prev_value == 0xFFFF) { + liftoff_log(LIFTOFF_DEBUG, "Cannot re-use previous allocation: " + "alpha changed"); return true; } continue; @@ -567,6 +612,8 @@ layer_needs_realloc(struct liftoff_layer *layer) /* TODO: if CRTC_{X,Y,W,H} changed but intersection with other * layers hasn't changed, don't realloc */ + liftoff_log(LIFTOFF_DEBUG, "Cannot re-use previous allocation: " + "property \"%s\" changed", prop->name); return true; } @@ -584,6 +631,8 @@ reuse_previous_alloc(struct liftoff_output *output, drmModeAtomicReq *req, device = output->device; if (output->layers_changed) { + liftoff_log(LIFTOFF_DEBUG, "Cannot re-use previous allocation: " + "a layer has been added or removed"); return -EINVAL; } @@ -624,10 +673,10 @@ update_layers_priority(struct liftoff_device *device) { struct liftoff_output *output; struct liftoff_layer *layer; + bool period_elapsed; device->page_flip_counter++; - bool period_elapsed = - device->page_flip_counter >= LIFTOFF_PRIORITY_PERIOD; + period_elapsed = device->page_flip_counter >= LIFTOFF_PRIORITY_PERIOD; if (period_elapsed) { device->page_flip_counter = 0; } @@ -639,6 +688,22 @@ update_layers_priority(struct liftoff_device *device) } } +static void +update_layers_fb_info(struct liftoff_output *output) +{ + struct liftoff_layer *layer; + + /* We don't know what the library user did in-between + * liftoff_output_apply() calls. They might've removed the FB and + * re-created a completely different one which happens to have the same + * FB ID. */ + liftoff_list_for_each(layer, &output->layers, link) { + memset(&layer->fb_info, 0, sizeof(layer->fb_info)); + layer_cache_fb_info(layer); + /* TODO: propagate error? */ + } +} + static void log_reuse(struct liftoff_output *output) { @@ -697,6 +762,7 @@ liftoff_output_apply(struct liftoff_output *output, drmModeAtomicReq *req, device = output->device; update_layers_priority(device); + update_layers_fb_info(output); ret = reuse_previous_alloc(output, req, flags); if (ret == 0) { @@ -705,6 +771,11 @@ liftoff_output_apply(struct liftoff_output *output, drmModeAtomicReq *req, } log_no_reuse(output); + /* Reset layers' candidate planes */ + liftoff_list_for_each(layer, &output->layers, link) { + layer_reset_candidate_planes(layer); + } + device->test_commit_counter = 0; output_log_layers(output); diff --git a/device.c b/device.c index 6d6e21a..fcbfbed 100644 --- a/device.c +++ b/device.c @@ -10,6 +10,7 @@ liftoff_device_create(int drm_fd) { struct liftoff_device *device; drmModeRes *drm_res; + drmModePlaneRes *drm_plane_res; device = calloc(1, sizeof(*device)); if (device == NULL) { @@ -34,19 +35,27 @@ liftoff_device_create(int drm_fd) return NULL; } - device->crtcs = malloc(drm_res->count_crtcs * sizeof(uint32_t)); + device->crtcs_len = (size_t)drm_res->count_crtcs; + device->crtcs = malloc(device->crtcs_len * sizeof(device->crtcs[0])); if (device->crtcs == NULL) { liftoff_log_errno(LIFTOFF_ERROR, "malloc"); drmModeFreeResources(drm_res); liftoff_device_destroy(device); return NULL; } - device->crtcs_len = drm_res->count_crtcs; - memcpy(device->crtcs, drm_res->crtcs, - drm_res->count_crtcs * sizeof(uint32_t)); + memcpy(device->crtcs, drm_res->crtcs, device->crtcs_len * sizeof(device->crtcs[0])); drmModeFreeResources(drm_res); + drm_plane_res = drmModeGetPlaneResources(device->drm_fd); + if (drm_plane_res == NULL) { + liftoff_log_errno(LIFTOFF_ERROR, "drmModeGetPlaneResources"); + liftoff_device_destroy(device); + return NULL; + } + device->planes_cap = drm_plane_res->count_planes; + drmModeFreePlaneResources(drm_plane_res); + return device; } @@ -97,7 +106,7 @@ device_test_commit(struct liftoff_device *device, drmModeAtomicReq *req, device->test_commit_counter++; - flags &= ~DRM_MODE_PAGE_FLIP_EVENT; + flags &= ~(uint32_t)DRM_MODE_PAGE_FLIP_EVENT; do { ret = drmModeAtomicCommit(device->drm_fd, req, DRM_MODE_ATOMIC_TEST_ONLY | flags, diff --git a/example/common.c b/example/common.c index 79befa5..0613217 100644 --- a/example/common.c +++ b/example/common.c @@ -87,10 +87,14 @@ dumb_fb_init(struct dumb_fb *fb, int drm_fd, uint32_t format, uint32_t width, { int ret; uint32_t fb_id; + struct drm_mode_create_dumb create; + uint32_t handles[4] = {0}; + uint32_t strides[4] = {0}; + uint32_t offsets[4] = { 0 }; assert(format == DRM_FORMAT_ARGB8888 || format == DRM_FORMAT_XRGB8888); - struct drm_mode_create_dumb create = { + create = (struct drm_mode_create_dumb) { .width = width, .height = height, .bpp = 32, @@ -101,9 +105,8 @@ dumb_fb_init(struct dumb_fb *fb, int drm_fd, uint32_t format, uint32_t width, return false; } - uint32_t handles[4] = { create.handle }; - uint32_t strides[4] = { create.pitch }; - uint32_t offsets[4] = { 0 }; + handles[0] = create.handle; + strides[0] = create.pitch; ret = drmModeAddFB2(drm_fd, width, height, format, handles, strides, offsets, &fb_id, 0); if (ret < 0) { @@ -131,7 +134,7 @@ dumb_fb_map(struct dumb_fb *fb, int drm_fd) } return mmap(0, fb->size, PROT_READ | PROT_WRITE, MAP_SHARED, drm_fd, - map.offset); + (off_t)map.offset); } void diff --git a/example/compositor.c b/example/compositor.c index e04c969..3dde899 100644 --- a/example/compositor.c +++ b/example/compositor.c @@ -26,8 +26,8 @@ static const uint32_t colors[] = { }; static struct liftoff_layer * -add_layer(int drm_fd, struct liftoff_output *output, int x, int y, int width, - int height, bool with_alpha, bool white, struct dumb_fb *fb) +add_layer(int drm_fd, struct liftoff_output *output, int x, int y, uint32_t width, + uint32_t height, bool with_alpha, bool white, struct dumb_fb *fb) { static size_t color_idx = 0; uint32_t color; @@ -51,8 +51,8 @@ add_layer(int drm_fd, struct liftoff_output *output, int x, int y, int width, layer = liftoff_layer_create(output); liftoff_layer_set_property(layer, "FB_ID", fb->id); - liftoff_layer_set_property(layer, "CRTC_X", x); - liftoff_layer_set_property(layer, "CRTC_Y", y); + liftoff_layer_set_property(layer, "CRTC_X", (uint64_t)x); + liftoff_layer_set_property(layer, "CRTC_Y", (uint64_t)y); liftoff_layer_set_property(layer, "CRTC_W", width); liftoff_layer_set_property(layer, "CRTC_H", height); liftoff_layer_set_property(layer, "SRC_X", 0); @@ -74,12 +74,12 @@ composite(int drm_fd, struct dumb_fb *dst_fb, struct dumb_fb *src_fb, int dst_x, dst = dumb_fb_map(dst_fb, drm_fd); src = dumb_fb_map(src_fb, drm_fd); - src_width = src_fb->width; + src_width = (int)src_fb->width; if (dst_x < 0) { dst_x = 0; } if (dst_x + src_width > (int)dst_fb->width) { - src_width = dst_fb->width - dst_x; + src_width = (int)dst_fb->width - dst_x; } for (i = 0; i < (int)src_fb->height; i++) { @@ -87,10 +87,10 @@ composite(int drm_fd, struct dumb_fb *dst_fb, struct dumb_fb *src_fb, int dst_x, if (y < 0 || y >= (int)dst_fb->height) { continue; } - memcpy(dst + dst_fb->stride * y + - dst_x * sizeof(uint32_t), - src + src_fb->stride * i, - src_width * sizeof(uint32_t)); + memcpy(dst + dst_fb->stride * (size_t)y + + (size_t)dst_x * sizeof(uint32_t), + src + src_fb->stride * (size_t)i, + (size_t)src_width * sizeof(uint32_t)); } munmap(dst, dst_fb->size); @@ -122,7 +122,7 @@ main(int argc, char *argv[]) while ((opt = getopt(argc, argv, "l:h")) != -1) { switch (opt) { case 'l': - layers_len = atoi(optarg); + layers_len = (size_t)atoi(optarg); break; default: fprintf(stderr, @@ -182,7 +182,7 @@ main(int argc, char *argv[]) layers[0] = add_layer(drm_fd, output, 0, 0, crtc->mode.hdisplay, crtc->mode.vdisplay, false, true, &fbs[0]); for (i = 1; i < layers_len; i++) { - layers[i] = add_layer(drm_fd, output, 100 * i, 100 * i, + layers[i] = add_layer(drm_fd, output, 100 * (int)i, 100 * (int)i, 256, 256, i % 2, false, &fbs[i]); } @@ -205,7 +205,7 @@ main(int argc, char *argv[]) for (i = 1; i < layers_len; i++) { if (liftoff_layer_needs_composition(layers[i])) { composite(drm_fd, &composition_fb, &fbs[i], - i * 100, i * 100); + (int)i * 100, (int)i * 100); } } diff --git a/example/dynamic.c b/example/dynamic.c index 0ff0388..5247c5f 100644 --- a/example/dynamic.c +++ b/example/dynamic.c @@ -35,7 +35,7 @@ static size_t active_layer_idx = 2; static bool init_layer(int drm_fd, struct example_layer *layer, - struct liftoff_output *output, int width, int height, + struct liftoff_output *output, uint32_t width, uint32_t height, bool with_alpha) { static size_t color_idx = 0; @@ -60,7 +60,7 @@ init_layer(int drm_fd, struct example_layer *layer, layer->color[color_idx % 3] = color_value; color_idx++; if (color_idx % 3 == 0) { - color_value -= 0.1; + color_value -= 0.1f; } return true; @@ -83,8 +83,8 @@ draw_layer(int drm_fd, struct example_layer *layer) dumb_fb_fill(fb, drm_fd, color); liftoff_layer_set_property(layer->layer, "FB_ID", fb->id); - liftoff_layer_set_property(layer->layer, "CRTC_X", layer->x); - liftoff_layer_set_property(layer->layer, "CRTC_Y", layer->y); + liftoff_layer_set_property(layer->layer, "CRTC_X", (uint64_t)layer->x); + liftoff_layer_set_property(layer->layer, "CRTC_Y", (uint64_t)layer->y); } static bool @@ -101,8 +101,8 @@ draw(void) inc = (active_layer->dec + 1) % 3; - active_layer->color[inc] += 0.05; - active_layer->color[active_layer->dec] -= 0.05; + active_layer->color[inc] += 0.05f; + active_layer->color[active_layer->dec] -= 0.05f; if (active_layer->color[active_layer->dec] < 0.0f) { active_layer->color[inc] = 1.0f; @@ -199,8 +199,8 @@ main(int argc, char *argv[]) crtc->mode.vdisplay, false); for (i = 1; i < LAYERS_LEN; i++) { init_layer(drm_fd, &layers[i], output, 100, 100, i % 2); - layers[i].x = 100 * i; - layers[i].y = 100 * i; + layers[i].x = 100 * (int)i; + layers[i].y = 100 * (int)i; } for (i = 0; i < LAYERS_LEN; i++) { diff --git a/example/multi-output.c b/example/multi-output.c index cf86479..b6b0656 100644 --- a/example/multi-output.c +++ b/example/multi-output.c @@ -25,8 +25,8 @@ static const uint32_t colors[] = { }; static struct liftoff_layer * -add_layer(int drm_fd, struct liftoff_output *output, int x, int y, int width, - int height, bool with_alpha) +add_layer(int drm_fd, struct liftoff_output *output, int x, int y, uint32_t width, + uint32_t height, bool with_alpha) { static bool first = true; static size_t color_idx = 0; @@ -53,8 +53,8 @@ add_layer(int drm_fd, struct liftoff_output *output, int x, int y, int width, layer = liftoff_layer_create(output); liftoff_layer_set_property(layer, "FB_ID", fb.id); - liftoff_layer_set_property(layer, "CRTC_X", x); - liftoff_layer_set_property(layer, "CRTC_Y", y); + liftoff_layer_set_property(layer, "CRTC_X", (uint64_t)x); + liftoff_layer_set_property(layer, "CRTC_Y", (uint64_t)y); liftoff_layer_set_property(layer, "CRTC_W", width); liftoff_layer_set_property(layer, "CRTC_H", height); liftoff_layer_set_property(layer, "SRC_X", 0); @@ -145,7 +145,7 @@ main(int argc, char *argv[]) crtc->mode.vdisplay, false); for (j = 1; j < LAYERS_PER_OUTPUT; j++) { layers[layers_len++] = add_layer(drm_fd, output, - 100 * j, 100 * j, + 100 * (int)j, 100 * (int)j, 256, 256, j % 2); } } diff --git a/example/simple.c b/example/simple.c index 54adf68..5ea5f74 100644 --- a/example/simple.c +++ b/example/simple.c @@ -23,8 +23,8 @@ static const uint32_t colors[] = { }; static struct liftoff_layer * -add_layer(int drm_fd, struct liftoff_output *output, int x, int y, int width, - int height, bool with_alpha) +add_layer(int drm_fd, struct liftoff_output *output, int x, int y, uint32_t width, + uint32_t height, bool with_alpha) { static bool first = true; static size_t color_idx = 0; @@ -51,8 +51,8 @@ add_layer(int drm_fd, struct liftoff_output *output, int x, int y, int width, layer = liftoff_layer_create(output); liftoff_layer_set_property(layer, "FB_ID", fb.id); - liftoff_layer_set_property(layer, "CRTC_X", x); - liftoff_layer_set_property(layer, "CRTC_Y", y); + liftoff_layer_set_property(layer, "CRTC_X", (uint64_t)x); + liftoff_layer_set_property(layer, "CRTC_Y", (uint64_t)y); liftoff_layer_set_property(layer, "CRTC_W", width); liftoff_layer_set_property(layer, "CRTC_H", height); liftoff_layer_set_property(layer, "SRC_X", 0); @@ -120,7 +120,7 @@ main(int argc, char *argv[]) layers[0] = add_layer(drm_fd, output, 0, 0, crtc->mode.hdisplay, crtc->mode.vdisplay, false); for (i = 1; i < LAYERS_LEN; i++) { - layers[i] = add_layer(drm_fd, output, 100 * i, 100 * i, + layers[i] = add_layer(drm_fd, output, 100 * (int)i, 100 * (int)i, 256, 256, i % 2); } diff --git a/gcovr.cfg b/gcovr.cfg new file mode 100644 index 0000000..d99a455 --- /dev/null +++ b/gcovr.cfg @@ -0,0 +1,3 @@ +exclude = subprojects/ +exclude = test/ +exclude = example/ diff --git a/include/libliftoff.h b/include/libliftoff.h index 00baa2b..99cd8a4 100644 --- a/include/libliftoff.h +++ b/include/libliftoff.h @@ -63,12 +63,12 @@ uint32_t liftoff_plane_get_id(struct liftoff_plane *plane); /** - * Build a layer to plane mapping and append the plane configuration to `req`. + * Build a layer to plane mapping and append the plane configuration to req. * - * Callers are expected to commit `req` afterwards and can figure out which - * layers need composition via `liftoff_layer_needs_composition`. + * Callers are expected to commit req afterwards and can figure out which + * layers need composition via liftoff_layer_needs_composition(). * - * `flags` is the atomic commit flags the caller intends to use. + * flags is the atomic commit flags the caller intends to use. * * Zero is returned on success, negative errno on error. */ @@ -108,7 +108,7 @@ liftoff_output_set_composition_layer(struct liftoff_output *output, * * An output doesn't need composition if all visible layers could be mapped to a * plane. In other words, if an output needs composition, at least one layer - * will return true when `liftoff_layer_needs_composition` is called. + * will return true when liftoff_layer_needs_composition() is called. */ bool liftoff_output_needs_composition(struct liftoff_output *output); @@ -179,6 +179,21 @@ liftoff_layer_needs_composition(struct liftoff_layer *layer); struct liftoff_plane * liftoff_layer_get_plane(struct liftoff_layer *layer); +/** + * Check whether a plane is a candidate for this layer. + * + * A plane is a candidate if it could potentially be used for the layer with + * a buffer with the same size. The buffer may need to be re-allocated with + * formats and modifiers accepted by the plane. + * + * This can be used to implemented a feedback loop: if a layer isn't mapped to + * a plane, loop over the candidate planes, and re-allocate the layer's FB + * according to the IN_FORMATS property. + */ +bool +liftoff_layer_is_candidate_plane(struct liftoff_layer *layer, + struct liftoff_plane *plane); + enum liftoff_log_priority { LIFTOFF_SILENT, LIFTOFF_ERROR, diff --git a/include/private.h b/include/private.h index 4b237d0..46ae2be 100644 --- a/include/private.h +++ b/include/private.h @@ -18,6 +18,8 @@ struct liftoff_device { uint32_t *crtcs; size_t crtcs_len; + size_t planes_cap; /* max number of planes */ + int page_flip_counter; int test_commit_counter; }; @@ -48,9 +50,13 @@ struct liftoff_layer { struct liftoff_plane *plane; + /* Array of plane IDs with a length of liftoff_device.planes_cap */ + uint32_t *candidate_planes; + int current_priority, pending_priority; /* prop added or force_composition changed */ bool changed; + drmModeFB2 fb_info, prev_fb_info; /* cached FB info */ }; struct liftoff_layer_property { @@ -66,17 +72,13 @@ struct liftoff_plane { /* TODO: formats */ struct liftoff_list link; /* liftoff_device.planes */ - struct liftoff_plane_property *props; + drmModePropertyRes **props; size_t props_len; + drmModePropertyBlobRes *in_formats_blob; struct liftoff_layer *layer; }; -struct liftoff_plane_property { - char name[DRM_PROP_NAME_LEN]; - uint32_t id; -}; - struct liftoff_rect { int x, y; int width, height; @@ -104,13 +106,26 @@ layer_update_priority(struct liftoff_layer *layer, bool make_current); bool layer_has_fb(struct liftoff_layer *layer); +void +layer_add_candidate_plane(struct liftoff_layer *layer, + struct liftoff_plane *plane); + +void +layer_reset_candidate_planes(struct liftoff_layer *layer); + bool layer_is_visible(struct liftoff_layer *layer); +int +layer_cache_fb_info(struct liftoff_layer *layer); + int plane_apply(struct liftoff_plane *plane, struct liftoff_layer *layer, drmModeAtomicReq *req); +bool +plane_check_layer_fb(struct liftoff_plane *plane, struct liftoff_layer *layer); + void output_log_layers(struct liftoff_output *output); diff --git a/layer.c b/layer.c index 2e14b2a..73a8186 100644 --- a/layer.c +++ b/layer.c @@ -1,6 +1,9 @@ +#include #include #include #include +#include +#include #include "private.h" struct liftoff_layer * @@ -14,6 +17,13 @@ liftoff_layer_create(struct liftoff_output *output) return NULL; } layer->output = output; + layer->candidate_planes = calloc(sizeof(layer->candidate_planes[0]), + output->device->planes_cap); + if (layer->candidate_planes == NULL) { + liftoff_log_errno(LIFTOFF_ERROR, "calloc"); + free(layer); + return NULL; + } liftoff_list_insert(output->layers.prev, &layer->link); output->layers_changed = true; return layer; @@ -34,6 +44,7 @@ liftoff_layer_destroy(struct liftoff_layer *layer) layer->output->composition_layer = NULL; } free(layer->props); + free(layer->candidate_planes); liftoff_list_remove(&layer->link); free(layer); } @@ -178,6 +189,7 @@ layer_mark_clean(struct liftoff_layer *layer) size_t i; layer->changed = false; + layer->prev_fb_info = layer->fb_info; for (i = 0; i < layer->props_len; i++) { layer->props[i].prev_value = layer->props[i].value; @@ -239,3 +251,100 @@ layer_is_visible(struct liftoff_layer *layer) return layer_has_fb(layer); } } + +int +layer_cache_fb_info(struct liftoff_layer *layer) +{ + struct liftoff_layer_property *fb_id_prop; + drmModeFB2 *fb_info; + size_t i, j, num_planes; + int ret; + + fb_id_prop = layer_get_property(layer, "FB_ID"); + if (fb_id_prop == NULL || fb_id_prop->value == 0) { + memset(&layer->fb_info, 0, sizeof(layer->fb_info)); + return 0; + } + + if (layer->fb_info.fb_id == fb_id_prop->value) { + return 0; + } + + fb_info = drmModeGetFB2(layer->output->device->drm_fd, fb_id_prop->value); + if (fb_info == NULL) { + if (errno == EINVAL) { + return 0; /* old kernel */ + } + return -errno; + } + + /* drmModeGetFB2() always creates new GEM handles -- close these, we + * won't use them and we don't want to leak them */ + num_planes = sizeof(fb_info->handles) / sizeof(fb_info->handles[0]); + for (i = 0; i < num_planes; i++) { + if (fb_info->handles[i] == 0) { + continue; + } + + ret = drmCloseBufferHandle(layer->output->device->drm_fd, + fb_info->handles[i]); + if (ret != 0) { + liftoff_log_errno(LIFTOFF_ERROR, "drmCloseBufferHandle"); + continue; + } + + /* Make sure we don't double-close a handle */ + for (j = i + 1; j < num_planes; j++) { + if (fb_info->handles[j] == fb_info->handles[i]) { + fb_info->handles[j] = 0; + } + } + fb_info->handles[i] = 0; + } + + layer->fb_info = *fb_info; + drmModeFreeFB2(fb_info); + return 0; +} + +bool +liftoff_layer_is_candidate_plane(struct liftoff_layer *layer, + struct liftoff_plane *plane) +{ + size_t i; + + for (i = 0; i < layer->output->device->planes_cap; i++) { + if (layer->candidate_planes[i] == plane->id) { + return true; + } + } + + return false; +} + +void +layer_add_candidate_plane(struct liftoff_layer *layer, + struct liftoff_plane *plane) +{ + size_t i; + ssize_t empty_slot = -1; + + for (i = 0; i < layer->output->device->planes_cap; i++) { + if (layer->candidate_planes[i] == plane->id) { + return; + } + if (empty_slot < 0 && layer->candidate_planes[i] == 0) { + empty_slot = (ssize_t)i; + } + } + + assert(empty_slot >= 0); + layer->candidate_planes[empty_slot] = plane->id; +} + +void +layer_reset_candidate_planes(struct liftoff_layer *layer) +{ + memset(layer->candidate_planes, 0, + sizeof(layer->candidate_planes[0]) * layer->output->device->planes_cap); +} diff --git a/log.c b/log.c index 1ac1176..5df6dd8 100644 --- a/log.c +++ b/log.c @@ -38,11 +38,12 @@ log_has(enum liftoff_log_priority priority) void liftoff_log(enum liftoff_log_priority priority, const char *fmt, ...) { + va_list args; + if (!log_has(priority)) { return; } - va_list args; va_start(args, fmt); log_handler(priority, fmt, args); va_end(args); diff --git a/meson.build b/meson.build index 20d6352..c1711bf 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'libliftoff', 'c', - version: '0.3.0', + version: '0.4.1', license: 'MIT', meson_version: '>=0.52.0', default_options: [ @@ -17,6 +17,9 @@ add_project_arguments(cc.get_supported_arguments([ '-Wundef', '-Wmissing-prototypes', '-Walloca', + '-Wdeclaration-after-statement', + '-Wfloat-conversion', + '-Wsign-conversion', '-Wno-missing-braces', '-Wno-unused-parameter', @@ -24,7 +27,7 @@ add_project_arguments(cc.get_supported_arguments([ liftoff_inc = include_directories('include') -drm = dependency('libdrm', include_type: 'system') +drm = dependency('libdrm', include_type: 'system', version: '>= 2.4.108') liftoff_deps = [drm] @@ -51,15 +54,19 @@ liftoff = declare_dependency( dependencies: liftoff_deps, ) +if meson.version().version_compare('>= 0.54.0') + meson.override_dependency('libliftoff', liftoff) +endif + install_headers('include/libliftoff.h') pkgconfig = import('pkgconfig') pkgconfig.generate( liftoff_lib, - version: meson.project_version(), filebase: meson.project_name(), name: meson.project_name(), description: 'KMS plane library', + url: 'https://gitlab.freedesktop.org/emersion/libliftoff', ) subdir('example') diff --git a/output.c b/output.c index 32e6a1a..2acbd61 100644 --- a/output.c +++ b/output.c @@ -15,7 +15,7 @@ liftoff_output_create(struct liftoff_device *device, uint32_t crtc_id) crtc_index = -1; for (i = 0; i < device->crtcs_len; i++) { if (device->crtcs[i] == crtc_id) { - crtc_index = i; + crtc_index = (ssize_t)i; break; } } @@ -29,7 +29,7 @@ liftoff_output_create(struct liftoff_device *device, uint32_t crtc_id) } output->device = device; output->crtc_id = crtc_id; - output->crtc_index = crtc_index; + output->crtc_index = (size_t)crtc_index; liftoff_list_init(&output->layers); liftoff_list_insert(&device->outputs, &output->link); return output; diff --git a/plane.c b/plane.c index 8f881c2..7e2d408 100644 --- a/plane.c +++ b/plane.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "private.h" static int @@ -39,8 +40,7 @@ liftoff_plane_create(struct liftoff_device *device, uint32_t id) drmModePlane *drm_plane; drmModeObjectProperties *drm_props; uint32_t i; - drmModePropertyRes *drm_prop; - struct liftoff_plane_property *prop; + drmModePropertyRes *prop; uint64_t value; bool has_type = false, has_zpos = false; @@ -74,25 +74,20 @@ liftoff_plane_create(struct liftoff_device *device, uint32_t id) liftoff_log_errno(LIFTOFF_ERROR, "drmModeObjectGetProperties"); return NULL; } - plane->props = calloc(drm_props->count_props, - sizeof(struct liftoff_plane_property)); + plane->props = calloc(drm_props->count_props, sizeof(plane->props[0])); if (plane->props == NULL) { liftoff_log_errno(LIFTOFF_ERROR, "calloc"); drmModeFreeObjectProperties(drm_props); return NULL; } for (i = 0; i < drm_props->count_props; i++) { - drm_prop = drmModeGetProperty(device->drm_fd, - drm_props->props[i]); - if (drm_prop == NULL) { + prop = drmModeGetProperty(device->drm_fd, drm_props->props[i]); + if (prop == NULL) { liftoff_log_errno(LIFTOFF_ERROR, "drmModeGetProperty"); drmModeFreeObjectProperties(drm_props); return NULL; } - prop = &plane->props[i]; - memcpy(prop->name, drm_prop->name, sizeof(prop->name)); - prop->id = drm_prop->prop_id; - drmModeFreeProperty(drm_prop); + plane->props[i] = prop; plane->props_len++; value = drm_props->prop_values[i]; @@ -102,6 +97,13 @@ liftoff_plane_create(struct liftoff_device *device, uint32_t id) } else if (strcmp(prop->name, "zpos") == 0) { plane->zpos = value; has_zpos = true; + } else if (strcmp(prop->name, "IN_FORMATS") == 0) { + plane->in_formats_blob = drmModeGetPropertyBlob(device->drm_fd, + value); + if (plane->in_formats_blob == NULL) { + liftoff_log_errno(LIFTOFF_ERROR, "drmModeGetPropertyBlob"); + return NULL; + } } } drmModeFreeObjectProperties(drm_props); @@ -144,11 +146,23 @@ liftoff_plane_create(struct liftoff_device *device, uint32_t id) void liftoff_plane_destroy(struct liftoff_plane *plane) { + size_t i; + + if (plane == NULL) { + return; + } + if (plane->layer != NULL) { plane->layer->plane = NULL; } + + for (i = 0; i < plane->props_len; i++) { + drmModeFreeProperty(plane->props[i]); + } + liftoff_list_remove(&plane->link); free(plane->props); + drmModeFreePropertyBlob(plane->in_formats_blob); free(plane); } @@ -158,26 +172,100 @@ liftoff_plane_get_id(struct liftoff_plane *plane) return plane->id; } -static struct liftoff_plane_property * +static const drmModePropertyRes * plane_get_property(struct liftoff_plane *plane, const char *name) { size_t i; for (i = 0; i < plane->props_len; i++) { - if (strcmp(plane->props[i].name, name) == 0) { - return &plane->props[i]; + if (strcmp(plane->props[i]->name, name) == 0) { + return plane->props[i]; } } return NULL; } +static int +check_range_prop(const drmModePropertyRes *prop, uint64_t value) +{ + if (value < prop->values[0] || value > prop->values[1]) { + return -EINVAL; + } + return 0; +} + +static int +check_enum_prop(const drmModePropertyRes *prop, uint64_t value) +{ + int i; + + for (i = 0; i < prop->count_enums; i++) { + if (prop->enums[i].value == value) { + return 0; + } + } + return -EINVAL; +} + +static int +check_bitmask_prop(const drmModePropertyRes *prop, uint64_t value) +{ + int i; + uint64_t mask; + + mask = 0; + for (i = 0; i < prop->count_enums; i++) { + mask |= (uint64_t)1 << prop->enums[i].value; + } + + if ((value & ~mask) != 0) { + return -EINVAL; + } + return 0; +} + +static int +check_signed_range_prop(const drmModePropertyRes *prop, uint64_t value) +{ + if ((int64_t) value < (int64_t) prop->values[0] || + (int64_t) value > (int64_t) prop->values[1]) { + return -EINVAL; + } + return 0; +} + static int plane_set_prop(struct liftoff_plane *plane, drmModeAtomicReq *req, - struct liftoff_plane_property *prop, uint64_t value) + const drmModePropertyRes *prop, uint64_t value) { int ret; - ret = drmModeAtomicAddProperty(req, plane->id, prop->id, value); + if (prop->flags & DRM_MODE_PROP_IMMUTABLE) { + return -EINVAL; + } + + /* Manually check the property value if we can: this may avoid + * unnecessary test commits */ + ret = 0; + switch (drmModeGetPropertyType(prop)) { + case DRM_MODE_PROP_RANGE: + ret = check_range_prop(prop, value); + break; + case DRM_MODE_PROP_ENUM: + ret = check_enum_prop(prop, value); + break; + case DRM_MODE_PROP_BITMASK: + ret = check_bitmask_prop(prop, value); + break; + case DRM_MODE_PROP_SIGNED_RANGE: + ret = check_signed_range_prop(prop, value); + break; + } + if (ret != 0) { + return ret; + } + + ret = drmModeAtomicAddProperty(req, plane->id, prop->prop_id, value); if (ret < 0) { liftoff_log(LIFTOFF_ERROR, "drmModeAtomicAddProperty: %s", strerror(-ret)); @@ -191,7 +279,7 @@ static int set_plane_prop_str(struct liftoff_plane *plane, drmModeAtomicReq *req, const char *name, uint64_t value) { - struct liftoff_plane_property *prop; + const drmModePropertyRes *prop; prop = plane_get_property(plane, name); if (prop == NULL) { @@ -204,6 +292,57 @@ set_plane_prop_str(struct liftoff_plane *plane, drmModeAtomicReq *req, return plane_set_prop(plane, req, prop, value); } +bool +plane_check_layer_fb(struct liftoff_plane *plane, struct liftoff_layer *layer) +{ + const struct drm_format_modifier_blob *set; + const uint32_t *formats; + const struct drm_format_modifier *modifiers; + size_t i; + ssize_t format_index, modifier_index; + int format_shift; + + /* TODO: add support for legacy format list with implicit modifier */ + if (layer->fb_info.fb_id == 0 || + !(layer->fb_info.flags & DRM_MODE_FB_MODIFIERS) || + plane->in_formats_blob == NULL) { + return true; /* not enough information to reject */ + } + + set = plane->in_formats_blob->data; + + formats = (void *)((char *)set + set->formats_offset); + format_index = -1; + for (i = 0; i < set->count_formats; ++i) { + if (formats[i] == layer->fb_info.pixel_format) { + format_index = (ssize_t)i; + break; + } + } + if (format_index < 0) { + return false; + } + + modifiers = (void *)((char *)set + set->modifiers_offset); + modifier_index = -1; + for (i = 0; i < set->count_modifiers; i++) { + if (modifiers[i].modifier == layer->fb_info.modifier) { + modifier_index = (ssize_t)i; + break; + } + } + if (modifier_index < 0) { + return false; + } + + if ((size_t)format_index < modifiers[modifier_index].offset || + (size_t)format_index >= modifiers[modifier_index].offset + 64) { + return false; + } + format_shift = format_index - (int)modifiers[modifier_index].offset; + return (modifiers[modifier_index].formats & ((uint64_t)1 << format_shift)) != 0; +} + int plane_apply(struct liftoff_plane *plane, struct liftoff_layer *layer, drmModeAtomicReq *req) @@ -211,7 +350,7 @@ plane_apply(struct liftoff_plane *plane, struct liftoff_layer *layer, int cursor, ret; size_t i; struct liftoff_layer_property *layer_prop; - struct liftoff_plane_property *plane_prop; + const drmModePropertyRes *plane_prop; cursor = drmModeAtomicGetCursor(req); diff --git a/test/bench.c b/test/bench.c index d744e51..851caa4 100644 --- a/test/bench.c +++ b/test/bench.c @@ -12,7 +12,7 @@ #define MAX_LAYERS 128 static struct liftoff_layer * -add_layer(struct liftoff_output *output, int x, int y, int width, int height) +add_layer(struct liftoff_output *output, int x, int y, uint32_t width, uint32_t height) { uint32_t fb_id; struct liftoff_layer *layer; @@ -20,14 +20,14 @@ add_layer(struct liftoff_output *output, int x, int y, int width, int height) layer = liftoff_layer_create(output); fb_id = liftoff_mock_drm_create_fb(layer); liftoff_layer_set_property(layer, "FB_ID", fb_id); - liftoff_layer_set_property(layer, "CRTC_X", x); - liftoff_layer_set_property(layer, "CRTC_Y", y); - liftoff_layer_set_property(layer, "CRTC_W", width); - liftoff_layer_set_property(layer, "CRTC_H", height); + liftoff_layer_set_property(layer, "CRTC_X", (uint64_t)x); + liftoff_layer_set_property(layer, "CRTC_Y", (uint64_t)y); + liftoff_layer_set_property(layer, "CRTC_W", (uint64_t)width); + liftoff_layer_set_property(layer, "CRTC_H", (uint64_t)height); liftoff_layer_set_property(layer, "SRC_X", 0); liftoff_layer_set_property(layer, "SRC_Y", 0); - liftoff_layer_set_property(layer, "SRC_W", width << 16); - liftoff_layer_set_property(layer, "SRC_H", height << 16); + liftoff_layer_set_property(layer, "SRC_W", (uint64_t)width << 16); + liftoff_layer_set_property(layer, "SRC_H", (uint64_t)height << 16); return layer; } @@ -40,23 +40,24 @@ main(int argc, char *argv[]) struct timespec start, end; struct liftoff_mock_plane *mock_planes[MAX_PLANES]; size_t i, j; - int plane_type; + uint64_t plane_type; int drm_fd; struct liftoff_device *device; struct liftoff_output *output; struct liftoff_layer *layers[MAX_LAYERS]; drmModeAtomicReq *req; int ret; + double dur_ms; planes_len = 5; layers_len = 10; while ((opt = getopt(argc, argv, "p:l:")) != -1) { switch (opt) { case 'p': - planes_len = atoi(optarg); + planes_len = (size_t)atoi(optarg); break; case 'l': - layers_len = atoi(optarg); + layers_len = (size_t)atoi(optarg); break; default: fprintf(stderr, "usage: %s [-p planes] [-l layers]\n", @@ -84,7 +85,7 @@ main(int argc, char *argv[]) for (i = 0; i < layers_len; i++) { /* Planes don't intersect, so the library can arrange them in * any order. Testing all combinations takes more time. */ - layers[i] = add_layer(output, i * 100, i * 100, 100, 100); + layers[i] = add_layer(output, (int)i * 100, (int)i * 100, 100, 100); for (j = 0; j < planes_len; j++) { if (j == 1) { /* Make the lowest plane above the primary plane @@ -108,8 +109,8 @@ main(int argc, char *argv[]) clock_gettime(CLOCK_MONOTONIC, &end); - double dur_ms = ((double)end.tv_sec - (double)start.tv_sec) * 1000 + - ((double)end.tv_nsec - (double)start.tv_nsec) / 1000000; + dur_ms = ((double)end.tv_sec - (double)start.tv_sec) * 1000 + + ((double)end.tv_nsec - (double)start.tv_nsec) / 1000000; printf("Plane allocation took %fms\n", dur_ms); printf("Plane allocation performed %zu atomic test commits\n", liftoff_mock_commit_count); diff --git a/test/libdrm_mock.c b/test/libdrm_mock.c index a7b2587..b1c9668 100644 --- a/test/libdrm_mock.c +++ b/test/libdrm_mock.c @@ -14,7 +14,10 @@ #define MAX_PLANE_PROPS 64 #define MAX_REQ_PROPS 1024 -uint32_t liftoff_mock_drm_crtc_id = 0xCC000000; +#define OBJ_INDEX_MASK 0x00FFFFFF +#define OBJ_TYPE_MASK 0xFF000000 + +uint32_t liftoff_mock_drm_crtc_id = DRM_MODE_OBJECT_CRTC & OBJ_TYPE_MASK; size_t liftoff_mock_commit_count = 0; bool liftoff_mock_require_primary_plane = false; @@ -30,6 +33,11 @@ struct liftoff_mock_prop { uint64_t value; }; +struct liftoff_mock_fb { + struct liftoff_layer *layer; + drmModeFB2 info; +}; + struct _drmModeAtomicReq { struct liftoff_mock_prop props[MAX_REQ_PROPS]; int cursor; @@ -37,7 +45,8 @@ struct _drmModeAtomicReq { static int mock_pipe[2] = {-1, -1}; static struct liftoff_mock_plane mock_planes[MAX_PLANES]; -static struct liftoff_layer *mock_fbs[MAX_LAYERS]; +static struct liftoff_mock_fb mock_fbs[MAX_LAYERS]; +static drmModePropertyBlobRes mock_blobs[MAX_PLANES]; enum plane_prop { PLANE_TYPE, @@ -66,6 +75,20 @@ static drmModePropertyRes plane_props[MAX_PLANE_PROPS] = {0}; static size_t plane_props_len = 0; +static uint32_t +object_id(size_t index, uint32_t type) +{ + assert((type & OBJ_TYPE_MASK) != 0); + return index | (type & OBJ_TYPE_MASK); +} + +static size_t +object_index(uint32_t id, uint32_t type) +{ + assert((id & OBJ_TYPE_MASK) == (type & OBJ_TYPE_MASK)); + return id & OBJ_INDEX_MASK; +} + static void assert_drm_fd(int fd) { @@ -89,7 +112,7 @@ register_prop(const drmModePropertyRes *prop) assert(plane_props_len < MAX_PLANE_PROPS); dst = &plane_props[plane_props_len]; memcpy(dst, prop, sizeof(*dst)); - dst->prop_id = 0xB0000000 + plane_props_len; + dst->prop_id = object_id(plane_props_len, DRM_MODE_OBJECT_PROPERTY); plane_props_len++; return dst->prop_id; @@ -126,7 +149,7 @@ liftoff_mock_drm_open(void) } struct liftoff_mock_plane * -liftoff_mock_drm_create_plane(int type) +liftoff_mock_drm_create_plane(uint64_t type) { struct liftoff_mock_plane *plane; size_t i; @@ -142,7 +165,7 @@ liftoff_mock_drm_create_plane(int type) i++; } - plane->id = 0xEE000000 + i; + plane->id = object_id(i, DRM_MODE_OBJECT_PLANE); plane->prop_values[PLANE_TYPE] = type; for (size_t i = 0; i < basic_plane_props_len; i++) { @@ -184,19 +207,39 @@ liftoff_mock_plane_add_compatible_layer(struct liftoff_mock_plane *plane, abort(); // unreachable } +uint32_t +liftoff_mock_plane_get_id(struct liftoff_mock_plane *plane) +{ + return plane->id; +} + uint32_t liftoff_mock_drm_create_fb(struct liftoff_layer *layer) { size_t i; + uint32_t fb_id; i = 0; - while (mock_fbs[i] != 0) { + while (mock_fbs[i].layer != NULL) { i++; } - mock_fbs[i] = layer; + fb_id = object_id(i, DRM_MODE_OBJECT_FB); + + mock_fbs[i] = (struct liftoff_mock_fb) { + .layer = layer, + }; + + return fb_id; +} + +void +liftoff_mock_drm_set_fb_info(const drmModeFB2 *fb_info) +{ + size_t i; - return 0xFB000000 + i; + i = object_index(fb_info->fb_id, DRM_MODE_OBJECT_FB); + mock_fbs[i].info = *fb_info; } static bool @@ -206,7 +249,7 @@ mock_atomic_req_get_property(drmModeAtomicReq *req, uint32_t obj_id, ssize_t i; uint32_t prop_id; - prop_id = 0xB0000000 + prop; + prop_id = object_id(prop, DRM_MODE_OBJECT_PROPERTY); for (i = req->cursor - 1; i >= 0; i--) { if (req->props[i].obj_id == obj_id && req->props[i].prop_id == prop_id) { @@ -227,12 +270,10 @@ mock_fb_get_layer(uint32_t fb_id) return NULL; } - assert((fb_id & 0xFF000000) == 0xFB000000); - - i = fb_id & 0x00FFFFFF; + i = object_index(fb_id, DRM_MODE_OBJECT_FB); assert(i < MAX_LAYERS); - return mock_fbs[i]; + return mock_fbs[i].layer; } struct liftoff_layer * @@ -246,9 +287,7 @@ get_prop_index(uint32_t id) { size_t i; - assert((id & 0xFF000000) == 0xB0000000); - - i = id & 0x00FFFFFF; + i = object_index(id, DRM_MODE_OBJECT_PROPERTY); assert(i < plane_props_len); return i; @@ -256,18 +295,57 @@ get_prop_index(uint32_t id) uint32_t liftoff_mock_plane_add_property(struct liftoff_mock_plane *plane, - const drmModePropertyRes *prop) + const drmModePropertyRes *prop, + uint64_t value) { uint32_t prop_id; prop_id = register_prop(prop); plane->enabled_props[get_prop_index(prop_id)] = true; - if (prop->count_values == 1) { - plane->prop_values[get_prop_index(prop_id)] = prop->values[0]; - } + plane->prop_values[get_prop_index(prop_id)] = value; return prop_id; } +static uint32_t +register_blob(size_t size, const void *data) +{ + size_t i; + uint32_t blob_id; + void *copy; + + i = 0; + while (mock_blobs[i].id != 0) { + i++; + assert(i < sizeof(mock_blobs) / sizeof(mock_blobs[0])); + } + + copy = malloc(size); + memcpy(copy, data, size); + + blob_id = object_id(i, DRM_MODE_OBJECT_BLOB); + mock_blobs[i] = (drmModePropertyBlobRes) { + .id = blob_id, + .length = size, + .data = copy, + }; + return blob_id; +} + +void +liftoff_mock_plane_add_in_formats(struct liftoff_mock_plane *plane, + const struct drm_format_modifier_blob *data, + size_t size) +{ + uint32_t blob_id; + drmModePropertyRes prop = {0}; + + blob_id = register_blob(size, data); + + strncpy(prop.name, "IN_FORMATS", sizeof(prop.name) - 1); + /* TODO: fill flags */ + liftoff_mock_plane_add_property(plane, &prop, blob_id); +} + static void apply_atomic_req(drmModeAtomicReq *req) { @@ -529,3 +607,83 @@ drmModeAtomicSetCursor(drmModeAtomicReq *req, int cursor) { req->cursor = cursor; } + +drmModeFB2 * +drmModeGetFB2(int fd, uint32_t fb_id) +{ + size_t i; + drmModeFB2 *fb2; + + i = object_index(fb_id, DRM_MODE_OBJECT_FB); + assert(i < MAX_LAYERS); + + if (mock_fbs[i].info.fb_id == 0) { + errno = EINVAL; + return NULL; + } + + fb2 = malloc(sizeof(*fb2)); + if (fb2 == NULL) { + return NULL; + } + *fb2 = mock_fbs[i].info; + return fb2; +} + +void +drmModeFreeFB2(drmModeFB2 *fb2) +{ + free(fb2); +} + +int +drmCloseBufferHandle(int fd, uint32_t handle) +{ + errno = EINVAL; + return -EINVAL; +} + +drmModePropertyBlobRes * +drmModeGetPropertyBlob(int fd, uint32_t blob_id) +{ + size_t i; + drmModePropertyBlobRes *blob; + void *data; + + i = object_index(blob_id, DRM_MODE_OBJECT_BLOB); + assert(i < sizeof(mock_blobs) / sizeof(mock_blobs[0])); + + if (mock_blobs[i].id == 0) { + errno = EINVAL; + return NULL; + } + + blob = malloc(sizeof(*blob)); + if (blob == NULL) { + return NULL; + } + + data = malloc(mock_blobs[i].length); + if (data == NULL) { + free(blob); + return NULL; + } + memcpy(data, mock_blobs[i].data, mock_blobs[i].length); + + *blob = (drmModePropertyBlobRes) { + .id = blob_id, + .length = mock_blobs[i].length, + .data = data, + }; + return blob; +} + +void +drmModeFreePropertyBlob(drmModePropertyBlobRes *blob) +{ + if (blob == NULL) { + return; + } + free(blob->data); + free(blob); +} diff --git a/test/libdrm_mock.h b/test/libdrm_mock.h index bda493b..1457a63 100644 --- a/test/libdrm_mock.h +++ b/test/libdrm_mock.h @@ -3,6 +3,7 @@ #include #include +#include #include extern uint32_t liftoff_mock_drm_crtc_id; @@ -22,8 +23,11 @@ liftoff_mock_drm_open(void); uint32_t liftoff_mock_drm_create_fb(struct liftoff_layer *layer); +void +liftoff_mock_drm_set_fb_info(const drmModeFB2 *fb_info); + struct liftoff_mock_plane * -liftoff_mock_drm_create_plane(int type); +liftoff_mock_drm_create_plane(uint64_t type); struct liftoff_mock_plane * liftoff_mock_drm_get_plane(uint32_t id); @@ -33,9 +37,16 @@ liftoff_mock_plane_add_compatible_layer(struct liftoff_mock_plane *plane, struct liftoff_layer *layer); struct liftoff_layer * liftoff_mock_plane_get_layer(struct liftoff_mock_plane *plane); +uint32_t +liftoff_mock_plane_get_id(struct liftoff_mock_plane *plane); uint32_t liftoff_mock_plane_add_property(struct liftoff_mock_plane *plane, - const drmModePropertyRes *prop); + const drmModePropertyRes *prop, + uint64_t value); +void +liftoff_mock_plane_add_in_formats(struct liftoff_mock_plane *plane, + const struct drm_format_modifier_blob *data, + size_t size); #endif diff --git a/test/meson.build b/test/meson.build index 354e6a4..46f36e1 100644 --- a/test/meson.build +++ b/test/meson.build @@ -54,6 +54,7 @@ tests = { 'dynamic': [ 'same', 'change-fb', + 'change-fb-modifier', 'unset-fb', 'set-fb', 'add-layer', @@ -77,6 +78,14 @@ tests = { 'immutable-zpos', 'unmatched', 'unset', + 'in-formats', + 'range', + 'signed-range', + 'enum', + 'bitmask', + ], + 'candidate': [ + 'basic', ], } diff --git a/test/test_alloc.c b/test/test_alloc.c index 84eeae1..d786c47 100644 --- a/test/test_alloc.c +++ b/test/test_alloc.c @@ -15,20 +15,20 @@ add_layer(struct liftoff_output *output, int x, int y, int width, int height) layer = liftoff_layer_create(output); fb_id = liftoff_mock_drm_create_fb(layer); liftoff_layer_set_property(layer, "FB_ID", fb_id); - liftoff_layer_set_property(layer, "CRTC_X", x); - liftoff_layer_set_property(layer, "CRTC_Y", y); - liftoff_layer_set_property(layer, "CRTC_W", width); - liftoff_layer_set_property(layer, "CRTC_H", height); + liftoff_layer_set_property(layer, "CRTC_X", (uint64_t)x); + liftoff_layer_set_property(layer, "CRTC_Y", (uint64_t)y); + liftoff_layer_set_property(layer, "CRTC_W", (uint64_t)width); + liftoff_layer_set_property(layer, "CRTC_H", (uint64_t)height); liftoff_layer_set_property(layer, "SRC_X", 0); liftoff_layer_set_property(layer, "SRC_Y", 0); - liftoff_layer_set_property(layer, "SRC_W", width << 16); - liftoff_layer_set_property(layer, "SRC_H", height << 16); + liftoff_layer_set_property(layer, "SRC_W", (uint64_t)width << 16); + liftoff_layer_set_property(layer, "SRC_H", (uint64_t)height << 16); return layer; } struct test_plane { - int type; + uint64_t type; }; struct test_prop { @@ -820,7 +820,7 @@ run_test(const struct test_case *test) plane_index_got = -1; for (j = 0; j < test_setup_len; j++) { if (mock_planes[j] == mock_plane) { - plane_index_got = j; + plane_index_got = (ssize_t)j; break; } } diff --git a/test/test_candidate.c b/test/test_candidate.c new file mode 100644 index 0000000..b7697de --- /dev/null +++ b/test/test_candidate.c @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include +#include "libdrm_mock.h" + +static struct liftoff_layer * +add_layer(struct liftoff_output *output, int x, int y, int width, int height) +{ + uint32_t fb_id; + struct liftoff_layer *layer; + + layer = liftoff_layer_create(output); + fb_id = liftoff_mock_drm_create_fb(layer); + liftoff_layer_set_property(layer, "FB_ID", fb_id); + liftoff_layer_set_property(layer, "CRTC_X", (uint64_t)x); + liftoff_layer_set_property(layer, "CRTC_Y", (uint64_t)y); + liftoff_layer_set_property(layer, "CRTC_W", (uint64_t)width); + liftoff_layer_set_property(layer, "CRTC_H", (uint64_t)height); + liftoff_layer_set_property(layer, "SRC_X", 0); + liftoff_layer_set_property(layer, "SRC_Y", 0); + liftoff_layer_set_property(layer, "SRC_W", (uint64_t)width << 16); + liftoff_layer_set_property(layer, "SRC_H", (uint64_t)height << 16); + + return layer; +} + +static void +test_basic(void) +{ + struct liftoff_mock_plane *mock_plane_ok, *mock_plane_ko; + int drm_fd; + struct liftoff_device *device; + struct liftoff_output *output; + struct liftoff_layer *layer; + struct liftoff_plane *plane_ok, *plane_ko; + drmModeAtomicReq *req; + drmModePropertyRes prop = {0}; + int ret; + + mock_plane_ok = liftoff_mock_drm_create_plane(DRM_PLANE_TYPE_OVERLAY); + mock_plane_ko = liftoff_mock_drm_create_plane(DRM_PLANE_TYPE_OVERLAY); + + /* Only add the COLOR_RANGE property to mock_plane_ok only. libliftoff + * should mark that one as a possible candidate, but not the other + * one. */ + strncpy(prop.name, "COLOR_RANGE", sizeof(prop.name) - 1); + liftoff_mock_plane_add_property(mock_plane_ok, &prop, 0); + + drm_fd = liftoff_mock_drm_open(); + device = liftoff_device_create(drm_fd); + assert(device != NULL); + + plane_ok = liftoff_plane_create(device, liftoff_mock_plane_get_id(mock_plane_ok)); + plane_ko = liftoff_plane_create(device, liftoff_mock_plane_get_id(mock_plane_ko)); + + output = liftoff_output_create(device, liftoff_mock_drm_crtc_id); + layer = add_layer(output, 0, 0, 1920, 1080); + liftoff_layer_set_property(layer, "COLOR_RANGE", 0); + + req = drmModeAtomicAlloc(); + ret = liftoff_output_apply(output, req, 0); + assert(ret == 0); + ret = drmModeAtomicCommit(drm_fd, req, 0, NULL); + assert(ret == 0); + assert(liftoff_mock_plane_get_layer(mock_plane_ok) == NULL); + assert(liftoff_mock_plane_get_layer(mock_plane_ko) == NULL); + assert(liftoff_layer_is_candidate_plane(layer, plane_ok)); + assert(!liftoff_layer_is_candidate_plane(layer, plane_ko)); + drmModeAtomicFree(req); + + liftoff_device_destroy(device); + close(drm_fd); +} + +int +main(int argc, char *argv[]) +{ + const char *test_name; + + liftoff_log_set_priority(LIFTOFF_DEBUG); + + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return 1; + } + test_name = argv[1]; + + if (strcmp(test_name, "basic") == 0) { + test_basic(); + } else { + fprintf(stderr, "no such test: %s\n", test_name); + return 1; + } + + return 0; +} diff --git a/test/test_dynamic.c b/test/test_dynamic.c index c932b2b..048783c 100644 --- a/test/test_dynamic.c +++ b/test/test_dynamic.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -28,14 +29,14 @@ add_layer(struct liftoff_output *output, int x, int y, int width, int height) layer = liftoff_layer_create(output); fb_id = liftoff_mock_drm_create_fb(layer); liftoff_layer_set_property(layer, "FB_ID", fb_id); - liftoff_layer_set_property(layer, "CRTC_X", x); - liftoff_layer_set_property(layer, "CRTC_Y", y); - liftoff_layer_set_property(layer, "CRTC_W", width); - liftoff_layer_set_property(layer, "CRTC_H", height); + liftoff_layer_set_property(layer, "CRTC_X", (uint64_t)x); + liftoff_layer_set_property(layer, "CRTC_Y", (uint64_t)y); + liftoff_layer_set_property(layer, "CRTC_W", (uint64_t)width); + liftoff_layer_set_property(layer, "CRTC_H", (uint64_t)height); liftoff_layer_set_property(layer, "SRC_X", 0); liftoff_layer_set_property(layer, "SRC_Y", 0); - liftoff_layer_set_property(layer, "SRC_W", width << 16); - liftoff_layer_set_property(layer, "SRC_H", height << 16); + liftoff_layer_set_property(layer, "SRC_W", (uint64_t)width << 16); + liftoff_layer_set_property(layer, "SRC_H", (uint64_t)height << 16); return layer; } @@ -100,16 +101,66 @@ run_same(struct context *ctx) static void run_change_fb(struct context *ctx) { + uint32_t fb_id; + drmModeFB2 fb_info; + + fb_id = liftoff_mock_drm_create_fb(ctx->layer); + fb_info = (drmModeFB2) { + .fb_id = fb_id, + .width = 1920, + .height = 1080, + .flags = DRM_MODE_FB_MODIFIERS, + .pixel_format = DRM_FORMAT_ARGB8888, + .modifier = DRM_FORMAT_MOD_LINEAR, + }; + liftoff_mock_drm_set_fb_info(&fb_info); + liftoff_layer_set_property(ctx->layer, "FB_ID", fb_id); + first_commit(ctx); assert(liftoff_mock_plane_get_layer(ctx->mock_plane) == ctx->layer); - liftoff_layer_set_property(ctx->layer, "FB_ID", - liftoff_mock_drm_create_fb(ctx->layer)); + /* Create a new FB with the exact same FB info as the first one. */ + fb_id = liftoff_mock_drm_create_fb(ctx->layer); + fb_info.fb_id = fb_id; + liftoff_mock_drm_set_fb_info(&fb_info); + liftoff_layer_set_property(ctx->layer, "FB_ID", fb_id); second_commit(ctx, true); assert(liftoff_mock_plane_get_layer(ctx->mock_plane) == ctx->layer); } +static void +run_change_fb_modifier(struct context *ctx) +{ + uint32_t fb_id; + drmModeFB2 fb_info; + + fb_id = liftoff_mock_drm_create_fb(ctx->layer); + fb_info = (drmModeFB2) { + .fb_id = fb_id, + .width = 1920, + .height = 1080, + .flags = DRM_MODE_FB_MODIFIERS, + .pixel_format = DRM_FORMAT_ARGB8888, + .modifier = I915_FORMAT_MOD_Y_TILED, + }; + liftoff_mock_drm_set_fb_info(&fb_info); + liftoff_layer_set_property(ctx->layer, "FB_ID", fb_id); + + first_commit(ctx); + assert(liftoff_mock_plane_get_layer(ctx->mock_plane) == ctx->layer); + + /* Simulate the situation where the previous FB gets removed, and a new + * one gets re-created with the same FB ID but a different modifier. + * This should prevent the first configuration from being re-used. */ + fb_info.modifier = I915_FORMAT_MOD_X_TILED; + liftoff_mock_drm_set_fb_info(&fb_info); + liftoff_layer_set_property(ctx->layer, "FB_ID", fb_id); + + second_commit(ctx, false); + assert(liftoff_mock_plane_get_layer(ctx->mock_plane) == ctx->layer); +} + static void run_unset_fb(struct context *ctx) { @@ -274,6 +325,7 @@ run_change_fb_damage_clips(struct context *ctx) static const struct test_case tests[] = { { .name = "same", .run = run_same }, { .name = "change-fb", .run = run_change_fb }, + { .name = "change-fb-modifier", .run = run_change_fb_modifier }, { .name = "unset-fb", .run = run_unset_fb }, { .name = "set-fb", .run = run_set_fb }, { .name = "add-layer", .run = run_add_layer }, @@ -308,17 +360,17 @@ run(const struct test_case *test) prop_name = "alpha"; prop = (drmModePropertyRes){0}; strncpy(prop.name, prop_name, sizeof(prop.name) - 1); - liftoff_mock_plane_add_property(ctx.mock_plane, &prop); + liftoff_mock_plane_add_property(ctx.mock_plane, &prop, 0); prop_name = "IN_FENCE_FD"; prop = (drmModePropertyRes){0}; strncpy(prop.name, prop_name, sizeof(prop.name) - 1); - liftoff_mock_plane_add_property(ctx.mock_plane, &prop); + liftoff_mock_plane_add_property(ctx.mock_plane, &prop, (uint64_t)-1); prop_name = "FB_DAMAGE_CLIPS"; prop = (drmModePropertyRes){0}; strncpy(prop.name, prop_name, sizeof(prop.name) - 1); - liftoff_mock_plane_add_property(ctx.mock_plane, &prop); + liftoff_mock_plane_add_property(ctx.mock_plane, &prop, 0); ctx.drm_fd = liftoff_mock_drm_open(); device = liftoff_device_create(ctx.drm_fd); diff --git a/test/test_priority.c b/test/test_priority.c index 1c24ec4..598dfcc 100644 --- a/test/test_priority.c +++ b/test/test_priority.c @@ -17,14 +17,14 @@ add_layer(struct liftoff_output *output, int x, int y, int width, int height) layer = liftoff_layer_create(output); fb_id = liftoff_mock_drm_create_fb(layer); liftoff_layer_set_property(layer, "FB_ID", fb_id); - liftoff_layer_set_property(layer, "CRTC_X", x); - liftoff_layer_set_property(layer, "CRTC_Y", y); - liftoff_layer_set_property(layer, "CRTC_W", width); - liftoff_layer_set_property(layer, "CRTC_H", height); + liftoff_layer_set_property(layer, "CRTC_X", (uint64_t)x); + liftoff_layer_set_property(layer, "CRTC_Y", (uint64_t)y); + liftoff_layer_set_property(layer, "CRTC_W", (uint64_t)width); + liftoff_layer_set_property(layer, "CRTC_H", (uint64_t)height); liftoff_layer_set_property(layer, "SRC_X", 0); liftoff_layer_set_property(layer, "SRC_Y", 0); - liftoff_layer_set_property(layer, "SRC_W", width << 16); - liftoff_layer_set_property(layer, "SRC_H", height << 16); + liftoff_layer_set_property(layer, "SRC_W", (uint64_t)width << 16); + liftoff_layer_set_property(layer, "SRC_H", (uint64_t)height << 16); return layer; } diff --git a/test/test_prop.c b/test/test_prop.c index 396a8ec..de29030 100644 --- a/test/test_prop.c +++ b/test/test_prop.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -14,18 +15,32 @@ add_layer(struct liftoff_output *output, int x, int y, int width, int height) layer = liftoff_layer_create(output); fb_id = liftoff_mock_drm_create_fb(layer); liftoff_layer_set_property(layer, "FB_ID", fb_id); - liftoff_layer_set_property(layer, "CRTC_X", x); - liftoff_layer_set_property(layer, "CRTC_Y", y); - liftoff_layer_set_property(layer, "CRTC_W", width); - liftoff_layer_set_property(layer, "CRTC_H", height); + liftoff_layer_set_property(layer, "CRTC_X", (uint64_t)x); + liftoff_layer_set_property(layer, "CRTC_Y", (uint64_t)y); + liftoff_layer_set_property(layer, "CRTC_W", (uint64_t)width); + liftoff_layer_set_property(layer, "CRTC_H", (uint64_t)height); liftoff_layer_set_property(layer, "SRC_X", 0); liftoff_layer_set_property(layer, "SRC_Y", 0); - liftoff_layer_set_property(layer, "SRC_W", width << 16); - liftoff_layer_set_property(layer, "SRC_H", height << 16); + liftoff_layer_set_property(layer, "SRC_W", (uint64_t)width << 16); + liftoff_layer_set_property(layer, "SRC_H", (uint64_t)height << 16); return layer; } +static void +commit(int drm_fd, struct liftoff_output *output) +{ + drmModeAtomicReq *req; + int ret; + + req = drmModeAtomicAlloc(); + ret = liftoff_output_apply(output, req, 0); + assert(ret == 0); + ret = drmModeAtomicCommit(drm_fd, req, 0, NULL); + assert(ret == 0); + drmModeAtomicFree(req); +} + static int test_prop_default(const char *prop_name) { @@ -35,16 +50,15 @@ test_prop_default(const char *prop_name) struct liftoff_device *device; struct liftoff_output *output; struct liftoff_layer *layer; - drmModeAtomicReq *req; - int ret; + uint64_t require_prop_value, default_value; + drmModePropertyRes prop = {0}; mock_plane_without_prop = liftoff_mock_drm_create_plane(DRM_PLANE_TYPE_OVERLAY); mock_plane_with_prop = liftoff_mock_drm_create_plane(DRM_PLANE_TYPE_OVERLAY); - /* Value that requires the prop for the plane allocation to succeed */ - uint64_t require_prop_value; - /* Value that doesn't require the prop to be present */ - uint64_t default_value; + /* require_prop_value is a value that requires the prop for the plane + * allocation to succeed. default_value is a value that requires the + * prop for the plane allocation to succeed. */ if (strcmp(prop_name, "alpha") == 0) { require_prop_value = (uint16_t)(0.5 * 0xFFFF); default_value = 0xFFFF; /* opaque */ @@ -58,9 +72,8 @@ test_prop_default(const char *prop_name) /* We need to setup mock plane properties before creating the liftoff * device */ - drmModePropertyRes prop = {0}; strncpy(prop.name, prop_name, sizeof(prop.name) - 1); - liftoff_mock_plane_add_property(mock_plane_with_prop, &prop); + liftoff_mock_plane_add_property(mock_plane_with_prop, &prop, 0); drm_fd = liftoff_mock_drm_open(); device = liftoff_device_create(drm_fd); @@ -75,47 +88,22 @@ test_prop_default(const char *prop_name) /* First test that the layer doesn't get assigned to the plane without * the prop when using a non-default value */ - - req = drmModeAtomicAlloc(); - liftoff_layer_set_property(layer, prop.name, require_prop_value); - - ret = liftoff_output_apply(output, req, 0); - assert(ret == 0); - ret = drmModeAtomicCommit(drm_fd, req, 0, NULL); - assert(ret == 0); + commit(drm_fd, output); assert(liftoff_layer_get_plane(layer) == NULL); - drmModeAtomicFree(req); /* The layer should get assigned to the plane without the prop when * using the default value */ - - req = drmModeAtomicAlloc(); - liftoff_layer_set_property(layer, prop.name, default_value); - - ret = liftoff_output_apply(output, req, 0); - assert(ret == 0); - ret = drmModeAtomicCommit(drm_fd, req, 0, NULL); - assert(ret == 0); + commit(drm_fd, output); assert(liftoff_layer_get_plane(layer) != NULL); - drmModeAtomicFree(req); /* The layer should get assigned to the plane with the prop when using * a non-default value */ - liftoff_mock_plane_add_compatible_layer(mock_plane_with_prop, layer); - - req = drmModeAtomicAlloc(); - liftoff_layer_set_property(layer, prop.name, require_prop_value); - - ret = liftoff_output_apply(output, req, 0); - assert(ret == 0); - ret = drmModeAtomicCommit(drm_fd, req, 0, NULL); - assert(ret == 0); + commit(drm_fd, output); assert(liftoff_layer_get_plane(layer) != NULL); - drmModeAtomicFree(req); liftoff_device_destroy(device); close(drm_fd); @@ -133,13 +121,11 @@ test_ignore_alpha(void) struct liftoff_device *device; struct liftoff_output *output; struct liftoff_layer *layer; - drmModeAtomicReq *req; - int ret; mock_plane = liftoff_mock_drm_create_plane(DRM_PLANE_TYPE_PRIMARY); strncpy(prop.name, "alpha", sizeof(prop.name) - 1); - liftoff_mock_plane_add_property(mock_plane, &prop); + liftoff_mock_plane_add_property(mock_plane, &prop, 0); drm_fd = liftoff_mock_drm_open(); device = liftoff_device_create(drm_fd); @@ -153,14 +139,9 @@ test_ignore_alpha(void) liftoff_mock_plane_add_compatible_layer(mock_plane, layer); - req = drmModeAtomicAlloc(); - ret = liftoff_output_apply(output, req, 0); - assert(ret == 0); - ret = drmModeAtomicCommit(drm_fd, req, 0, NULL); - assert(ret == 0); + commit(drm_fd, output); assert(liftoff_mock_plane_get_layer(mock_plane) == NULL); assert(!liftoff_layer_needs_composition(layer)); - drmModeAtomicFree(req); liftoff_device_destroy(device); close(drm_fd); @@ -173,27 +154,20 @@ test_immutable_zpos(void) { struct liftoff_mock_plane *mock_plane1, *mock_plane2; drmModePropertyRes prop = {0}; - uint64_t prop_value; int drm_fd; struct liftoff_device *device; struct liftoff_output *output; struct liftoff_layer *layer1, *layer2; - drmModeAtomicReq *req; - int ret; mock_plane1 = liftoff_mock_drm_create_plane(DRM_PLANE_TYPE_OVERLAY); mock_plane2 = liftoff_mock_drm_create_plane(DRM_PLANE_TYPE_OVERLAY); strncpy(prop.name, "zpos", sizeof(prop.name) - 1); prop.flags = DRM_MODE_PROP_IMMUTABLE; - prop.count_values = 1; - prop.values = &prop_value; /* Plane 2 is always on top of plane 1, and this is immutable */ - prop_value = 1; - liftoff_mock_plane_add_property(mock_plane1, &prop); - prop_value = 2; - liftoff_mock_plane_add_property(mock_plane2, &prop); + liftoff_mock_plane_add_property(mock_plane1, &prop, 1); + liftoff_mock_plane_add_property(mock_plane2, &prop, 2); drm_fd = liftoff_mock_drm_open(); device = liftoff_device_create(drm_fd); @@ -215,27 +189,17 @@ test_immutable_zpos(void) liftoff_layer_set_property(layer1, "zpos", 42); liftoff_layer_set_property(layer2, "zpos", 43); - req = drmModeAtomicAlloc(); - ret = liftoff_output_apply(output, req, 0); - assert(ret == 0); - ret = drmModeAtomicCommit(drm_fd, req, 0, NULL); - assert(ret == 0); + commit(drm_fd, output); assert(liftoff_mock_plane_get_layer(mock_plane1) == layer1); assert(liftoff_mock_plane_get_layer(mock_plane2) == layer2); - drmModeAtomicFree(req); /* Layer 1 on top of layer 2 */ liftoff_layer_set_property(layer1, "zpos", 43); liftoff_layer_set_property(layer2, "zpos", 42); - req = drmModeAtomicAlloc(); - ret = liftoff_output_apply(output, req, 0); - assert(ret == 0); - ret = drmModeAtomicCommit(drm_fd, req, 0, NULL); - assert(ret == 0); + commit(drm_fd, output); assert(liftoff_mock_plane_get_layer(mock_plane1) == layer2); assert(liftoff_mock_plane_get_layer(mock_plane2) == layer1); - drmModeAtomicFree(req); liftoff_device_destroy(device); close(drm_fd); @@ -251,8 +215,6 @@ test_unmatched_prop(void) struct liftoff_device *device; struct liftoff_output *output; struct liftoff_layer *layer; - drmModeAtomicReq *req; - int ret; mock_plane = liftoff_mock_drm_create_plane(DRM_PLANE_TYPE_PRIMARY); @@ -268,13 +230,8 @@ test_unmatched_prop(void) liftoff_mock_plane_add_compatible_layer(mock_plane, layer); - req = drmModeAtomicAlloc(); - ret = liftoff_output_apply(output, req, 0); - assert(ret == 0); - ret = drmModeAtomicCommit(drm_fd, req, 0, NULL); - assert(ret == 0); + commit(drm_fd, output); assert(liftoff_mock_plane_get_layer(mock_plane) == NULL); - drmModeAtomicFree(req); liftoff_device_destroy(device); close(drm_fd); @@ -290,8 +247,6 @@ test_unset_prop(void) struct liftoff_device *device; struct liftoff_output *output; struct liftoff_layer *layer; - drmModeAtomicReq *req; - int ret; mock_plane = liftoff_mock_drm_create_plane(DRM_PLANE_TYPE_PRIMARY); @@ -308,23 +263,13 @@ test_unset_prop(void) liftoff_mock_plane_add_compatible_layer(mock_plane, layer); - req = drmModeAtomicAlloc(); - ret = liftoff_output_apply(output, req, 0); - assert(ret == 0); - ret = drmModeAtomicCommit(drm_fd, req, 0, NULL); - assert(ret == 0); + commit(drm_fd, output); assert(liftoff_mock_plane_get_layer(mock_plane) == NULL); - drmModeAtomicFree(req); liftoff_layer_unset_property(layer, "asdf"); - req = drmModeAtomicAlloc(); - ret = liftoff_output_apply(output, req, 0); - assert(ret == 0); - ret = drmModeAtomicCommit(drm_fd, req, 0, NULL); - assert(ret == 0); + commit(drm_fd, output); assert(liftoff_mock_plane_get_layer(mock_plane) == layer); - drmModeAtomicFree(req); liftoff_device_destroy(device); close(drm_fd); @@ -332,10 +277,203 @@ test_unset_prop(void) return 0; } +struct single_format_modifier_blob { + struct drm_format_modifier_blob base; + uint32_t formats[1]; + struct drm_format_modifier modifiers[1]; +}; + +static int +test_in_formats(void) +{ + struct liftoff_mock_plane *mock_plane; + int drm_fd; + struct liftoff_device *device; + struct liftoff_output *output; + struct liftoff_layer *layer; + struct single_format_modifier_blob in_formats; + uint32_t fb_id; + drmModeFB2 fb_info; + + /* Create an IN_FORMATS property which only supports + * DRM_FORMAT_ARGB8888 + DRM_FORMAT_MOD_LINEAR */ + in_formats = (struct single_format_modifier_blob) { + .base = { + .version = 1, + .count_formats = 1, + .formats_offset = offsetof(struct single_format_modifier_blob, formats), + .count_modifiers = 1, + .modifiers_offset = offsetof(struct single_format_modifier_blob, modifiers), + }, + .formats = { DRM_FORMAT_ARGB8888 }, + .modifiers = { + { .formats = 0x01, .modifier = DRM_FORMAT_MOD_LINEAR }, + }, + }; + + mock_plane = liftoff_mock_drm_create_plane(DRM_PLANE_TYPE_PRIMARY); + liftoff_mock_plane_add_in_formats(mock_plane, &in_formats.base, sizeof(in_formats)); + + drm_fd = liftoff_mock_drm_open(); + device = liftoff_device_create(drm_fd); + assert(device != NULL); + + liftoff_device_register_all_planes(device); + + output = liftoff_output_create(device, liftoff_mock_drm_crtc_id); + layer = add_layer(output, 0, 0, 1920, 1080); + fb_id = liftoff_mock_drm_create_fb(layer); + fb_info = (drmModeFB2) { + .fb_id = fb_id, + .width = 1920, + .height = 1080, + .flags = DRM_MODE_FB_MODIFIERS, + .pixel_format = DRM_FORMAT_ARGB8888, + .modifier = I915_FORMAT_MOD_X_TILED, + }; + liftoff_mock_drm_set_fb_info(&fb_info); + liftoff_layer_set_property(layer, "FB_ID", fb_id); + + liftoff_mock_plane_add_compatible_layer(mock_plane, layer); + + /* First commit: even if the layer is compatible with the plane, + * libliftoff shouldn't try to use the plane because the FB modifier + * isn't in IN_FORMATS */ + commit(drm_fd, output); + assert(liftoff_mock_plane_get_layer(mock_plane) == NULL); + + fb_id = liftoff_mock_drm_create_fb(layer); + fb_info.fb_id = fb_id; + fb_info.modifier = DRM_FORMAT_MOD_LINEAR; + liftoff_mock_drm_set_fb_info(&fb_info); + liftoff_layer_set_property(layer, "FB_ID", fb_id); + + /* Second commit: the new FB modifier is in IN_FORMATS */ + commit(drm_fd, output); + assert(liftoff_mock_plane_get_layer(mock_plane) == layer); + + liftoff_device_destroy(device); + close(drm_fd); + + return 0; +} + +static int +test_type(const drmModePropertyRes *prop, uint64_t valid_value, + uint64_t invalid_value) +{ + struct liftoff_mock_plane *mock_plane; + int drm_fd; + struct liftoff_device *device; + struct liftoff_output *output; + struct liftoff_layer *layer; + + mock_plane = liftoff_mock_drm_create_plane(DRM_PLANE_TYPE_PRIMARY); + liftoff_mock_plane_add_property(mock_plane, prop, 0); + + drm_fd = liftoff_mock_drm_open(); + device = liftoff_device_create(drm_fd); + assert(device != NULL); + + liftoff_device_register_all_planes(device); + + output = liftoff_output_create(device, liftoff_mock_drm_crtc_id); + layer = add_layer(output, 0, 0, 1920, 1080); + + liftoff_mock_plane_add_compatible_layer(mock_plane, layer); + + /* First commit with a valid prop value */ + liftoff_layer_set_property(layer, prop->name, valid_value); + commit(drm_fd, output); + assert(liftoff_mock_plane_get_layer(mock_plane) != NULL); + + /* Second commit with an invalid prop value: even if the plane is + * compatible with the layer, the invalid value should be checked by + * libliftoff and rejected before any test commit happens */ + liftoff_layer_set_property(layer, prop->name, invalid_value); + commit(drm_fd, output); + assert(liftoff_mock_plane_get_layer(mock_plane) == NULL); + + liftoff_device_destroy(device); + close(drm_fd); + + return 0; +} + +static int +test_range(void) +{ + drmModePropertyRes prop = {0}; + uint64_t range[2]; + + range[0] = 0; + range[1] = 0xFFFF; + strncpy(prop.name, "alpha", sizeof(prop.name) - 1); + prop.flags = DRM_MODE_PROP_RANGE; + prop.count_values = 2; + prop.values = range; + + return test_type(&prop, 42, 0xFFFF + 42); +} + +static int +test_signed_range(void) +{ + drmModePropertyRes prop = {0}; + uint64_t range[2]; + + range[0] = (uint64_t)-1; + range[1] = INT64_MAX; + strncpy(prop.name, "IN_FENCE_FD", sizeof(prop.name) - 1); + prop.flags = DRM_MODE_PROP_SIGNED_RANGE; + prop.count_values = 2; + prop.values = range; + + return test_type(&prop, (uint64_t)-1, (uint64_t)-2); +} + +static int +test_enum(void) +{ + drmModePropertyRes prop = {0}; + struct drm_mode_property_enum enums[3] = {0}; + + enums[0].value = 0; + strncpy(enums[0].name, "rotate-0", sizeof(enums[0].name) - 1); + enums[1].value = 1; + strncpy(enums[1].name, "rotate-90", sizeof(enums[1].name) - 1); + enums[2].value = 2; + strncpy(enums[2].name, "rotate-180", sizeof(enums[2].name) - 1); + strncpy(prop.name, "rotation", sizeof(prop.name) - 1); + prop.flags = DRM_MODE_PROP_BITMASK; + prop.enums = enums; + prop.count_enums = 3; + + return test_type(&prop, DRM_MODE_ROTATE_90, DRM_MODE_REFLECT_X); +} + +static int +test_bitmask(void) +{ + drmModePropertyRes prop = {0}; + struct drm_mode_property_enum enums[2] = {0}; + + enums[0].value = 0; + strncpy(enums[0].name, "YCbCr limited range", sizeof(enums[0].name) - 1); + enums[1].value = 1; + strncpy(enums[1].name, "YCbCr full range", sizeof(enums[1].name) - 1); + strncpy(prop.name, "COLOR_RANGE", sizeof(prop.name) - 1); + prop.flags = DRM_MODE_PROP_ENUM; + prop.enums = enums; + prop.count_enums = 2; + + return test_type(&prop, 1, 2); +} + int main(int argc, char *argv[]) { - const char *test_name; + const char *test_name, *default_test_prefix; liftoff_log_set_priority(LIFTOFF_DEBUG); @@ -345,7 +483,7 @@ main(int argc, char *argv[]) } test_name = argv[1]; - const char default_test_prefix[] = "default-"; + default_test_prefix = "default-"; if (strncmp(test_name, default_test_prefix, strlen(default_test_prefix)) == 0) { return test_prop_default(test_name + strlen(default_test_prefix)); @@ -357,6 +495,16 @@ main(int argc, char *argv[]) return test_unmatched_prop(); } else if (strcmp(test_name, "unset") == 0) { return test_unset_prop(); + } else if (strcmp(test_name, "in-formats") == 0) { + return test_in_formats(); + } else if (strcmp(test_name, "range") == 0) { + return test_range(); + } else if (strcmp(test_name, "signed-range") == 0) { + return test_signed_range(); + } else if (strcmp(test_name, "enum") == 0) { + return test_enum(); + } else if (strcmp(test_name, "bitmask") == 0) { + return test_bitmask(); } else { fprintf(stderr, "no such test: %s\n", test_name); return 1;