From a1ddc25b459a5ce8b75393d4aa0a364e7e16e144 Mon Sep 17 00:00:00 2001 From: EBADBEEF Date: Wed, 23 Aug 2023 15:02:02 -0700 Subject: [PATCH 1/5] output: expose output_apply_state() to header - need a way to apply the work of commit without side-effects - related to 8f67446cc736be1c10ff48e83dda3e98ae816dc2 and f042de3f516ec13fe9500b27a024330843941060 --- include/types/wlr_output.h | 1 + types/output/output.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/include/types/wlr_output.h b/include/types/wlr_output.h index d59b05f0b..7f9a716ae 100644 --- a/include/types/wlr_output.h +++ b/include/types/wlr_output.h @@ -28,6 +28,7 @@ void output_defer_present(struct wlr_output *output, struct wlr_output_event_pre bool output_prepare_commit(struct wlr_output *output, const struct wlr_output_state *state); void output_apply_commit(struct wlr_output *output, const struct wlr_output_state *state); void output_send_commit_event(struct wlr_output *output, const struct wlr_output_state *state); +void output_apply_state(struct wlr_output *output, const struct wlr_output_state *state); void output_state_get_buffer_src_box(const struct wlr_output_state *state, struct wlr_fbox *out); diff --git a/types/output/output.c b/types/output/output.c index 1fb0347a0..827f27691 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -210,7 +210,7 @@ void wlr_output_set_description(struct wlr_output *output, const char *desc) { wl_signal_emit_mutable(&output->events.description, output); } -static void output_apply_state(struct wlr_output *output, +void output_apply_state(struct wlr_output *output, const struct wlr_output_state *state) { if (state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { output->render_format = state->render_format; From cbf921ccbb7e3f6966e23a6ec2f1aecf3f328352 Mon Sep 17 00:00:00 2001 From: EBADBEEF Date: Tue, 30 May 2023 19:39:48 -0700 Subject: [PATCH 2/5] output: introduce wlr_output_group An output group is an output implementation that contains one or more separate outputs as children. The children can be grouped together to turn into one big output (for a tiled display). Updates: - use upstream src_box/dst_box - provide wlr_backend_impl, set features.timeline=true if all children have it - fix output destroy (happens on vt switch) by properly removing output from group registry - add output->impl->test because otherwise trying direct scan out can fail and stop updating the screen (scene_entry_try_direct_scanout) - use wlr_output_finish() on destroy - remove output->impl->get_buffer_caps (but keep the logic that buffer_caps is the intersection of all children) --- include/wlr/types/wlr_output_group.h | 63 +++ types/meson.build | 1 + types/wlr_output_group.c | 667 +++++++++++++++++++++++++++ 3 files changed, 731 insertions(+) create mode 100644 include/wlr/types/wlr_output_group.h create mode 100644 types/wlr_output_group.c diff --git a/include/wlr/types/wlr_output_group.h b/include/wlr/types/wlr_output_group.h new file mode 100644 index 000000000..e4ca528e1 --- /dev/null +++ b/include/wlr/types/wlr_output_group.h @@ -0,0 +1,63 @@ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_OUTPUT_GROUP_H +#define WLR_TYPES_WLR_OUTPUT_GROUP_H + +#include +#include +#include +#include +#include +#include + +struct wlr_output_group_tile_info { + uint32_t group_id; + uint32_t is_single_monitor; + uint32_t num_h; + uint32_t num_v; + uint32_t h_loc; + uint32_t v_loc; + uint32_t h_size; + uint32_t v_size; +}; + +struct wlr_output_group_child { + struct wlr_output *output; + struct wlr_output_group *group; + struct wlr_fbox src_box; + struct wlr_box dst_box; + struct wlr_output_group_tile_info tile_info; + uint32_t index; + struct wlr_output_mode *tiled_mode; + struct wl_listener present; + struct wl_listener frame; + struct wl_listener needs_frame; + struct wl_listener output_destroy; + struct wl_list link; +}; + +struct wlr_output_group { + struct wlr_output output; + /* private data below */ + int queued_frame_count; + int num_children; + struct wlr_output_mode *tiled_mode; + struct wl_list children; //wlr_output_group_child.link + struct wl_list mirrors; //wlr_output_group_child.link + struct wlr_drm_format_set cursor_formats; + struct wlr_drm_format_set primary_formats; + struct wl_event_source *ready; + struct wl_list link; + struct wlr_backend backend; + struct wlr_output_cursor_size *cursor_sizes; + size_t cursor_sizes_len; +}; + +struct wlr_output_group *wlr_output_group_create(void); +struct wlr_output_group *wlr_output_group_match_tile(struct wlr_output_group_tile_info *tile_info); +void wlr_output_group_add_tile(struct wlr_output_group *group, struct wlr_output *output, + struct wlr_output_group_tile_info *tile_info); + +#endif diff --git a/types/meson.build b/types/meson.build index 402fd3e11..595741727 100644 --- a/types/meson.build +++ b/types/meson.build @@ -67,6 +67,7 @@ wlr_files += files( 'wlr_linux_drm_syncobj_v1.c', 'wlr_output_layer.c', 'wlr_output_layout.c', + 'wlr_output_group.c', 'wlr_output_management_v1.c', 'wlr_output_power_management_v1.c', 'wlr_output_swapchain_manager.c', diff --git a/types/wlr_output_group.c b/types/wlr_output_group.c new file mode 100644 index 000000000..74ad9773a --- /dev/null +++ b/types/wlr_output_group.c @@ -0,0 +1,667 @@ +#define _POSIX_C_SOURCE 200809L +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "backend/drm/drm.h" +#include "render/drm_format_set.h" +#include "types/wlr_output.h" + +static const struct wlr_output_impl output_impl; +static const struct wlr_backend_impl backend_impl; +static void output_group_child_destroy(struct wlr_output_group_child *child); +static void output_group_state_change(void *data); + +// A global registry for output groups. +static struct wl_list priv_registry; +static struct wl_list *registry = NULL; + +static bool backend_is_group(struct wlr_backend *b) { + return (b->impl == &backend_impl); +} + +static struct wlr_output_group *group_from_output(struct wlr_output *output) { + assert(output->impl == &output_impl); + return (struct wlr_output_group *)output; +} + +static struct wlr_output_group *group_from_backend(struct wlr_backend *wlr_backend) { + assert(backend_is_group(wlr_backend)); + struct wlr_output_group *group = wl_container_of(wlr_backend, group, backend); + return group; +} + +static int backend_get_drm_fd(struct wlr_backend *backend) { + struct wlr_output_group *group = group_from_backend(backend); + struct wlr_output_group_child *primary = + wl_container_of(group->children.next, primary, link); + struct wlr_backend *child_backend = primary->output->backend; + if (child_backend->impl->get_drm_fd) + return child_backend->impl->get_drm_fd(child_backend); + return -1; +} + +static void handle_present(struct wl_listener *listener, void *user_data) { + struct wlr_output_event_present *event = (struct wlr_output_event_present *) user_data; + struct wlr_output_group_child *child = wl_container_of(listener, child, present); + struct wlr_output_group *group = child->group; + if (group->queued_frame_count > 0) { + group->queued_frame_count -= 1; + } + if (group->queued_frame_count == 0) { + wlr_output_send_present(&group->output, event); + } +} + +static void handle_frame(struct wl_listener *listener, void *user_data) { + struct wlr_output_group_child *child = wl_container_of(listener, child, frame); + struct wlr_output_group *group = child->group; + /* present happens before frame so trust that backend already did present */ + if (group->queued_frame_count == 0) { + wl_signal_emit_mutable(&group->output.events.frame, &group->output); + } +} + +static void handle_needs_frame(struct wl_listener *listener, void *user_data) { + struct wlr_output *output = (struct wlr_output *) user_data; + struct wlr_output_group_child *child = wl_container_of(listener, child, needs_frame); + struct wlr_output_group *group = child->group; + /* if any output raises needs_frame, re-raise it */ + output->needs_frame = false; + wlr_output_update_needs_frame(&group->output); +} + +static void handle_output_destroy(struct wl_listener *listener, void *user_data) { + struct wlr_output_group_child *child = wl_container_of(listener, child, output_destroy); + output_group_child_destroy(child); +} + +struct wlr_output_group_mode { + struct wlr_output_mode mode; + struct wlr_output_mode *original_mode; +}; + +#define GROUP_PREFIX "GROUP-" +static bool clone_output(struct wlr_output_group *group, struct wlr_output *src_output, + struct wlr_output_group_tile_info *tile_info) { + struct wlr_output *dst_output = &group->output; + + size_t new_name_len = sizeof(GROUP_PREFIX) + strlen(src_output->name); + char *new_name = malloc(new_name_len); + if (!new_name) + return false; + snprintf(new_name, new_name_len, "%s%s", GROUP_PREFIX, src_output->name); + + wlr_output_init(dst_output, &group->backend, &output_impl, src_output->event_loop, NULL); + wlr_output_set_description(dst_output, src_output->description); + dst_output->name = new_name; + dst_output->make = strdup(src_output->make); + dst_output->model = strdup(src_output->model); + dst_output->serial = strdup(src_output->serial); + dst_output->phys_width = src_output->phys_width; + dst_output->phys_height = src_output->phys_height; + dst_output->current_mode = src_output->current_mode; + dst_output->width = src_output->width; + dst_output->height = src_output->height; + dst_output->refresh = src_output->refresh; + dst_output->enabled = src_output->enabled; + dst_output->scale = src_output->scale; + dst_output->subpixel = src_output->subpixel; + dst_output->transform = src_output->transform; + dst_output->adaptive_sync_status = src_output->adaptive_sync_status; + + /* Clone all child modes while but keep references to the original. This is + * needed because the drm backend uses its own mode container + * (wlr_drm_mode) to link a wlr_mode to a drmModeModeInfo. */ + struct wlr_output_mode *mode; + wl_list_for_each_reverse(mode, &src_output->modes, link) { + struct wlr_output_group_mode *group_mode = calloc(1, sizeof(*group_mode)); + memcpy(&group_mode->mode, mode, sizeof(*mode)); + group_mode->original_mode = mode; + wl_list_insert(&dst_output->modes, &group_mode->mode.link); + } + return true; +} + +struct wlr_output_group *wlr_output_group_match_tile(struct wlr_output_group_tile_info *tile_info) { + if (!registry) { + return NULL; + } + struct wlr_output_group *group; + wl_list_for_each(group, registry, link) { + struct wlr_output_group_child *child = wl_container_of(group->children.next, child, link); + if (child->tile_info.group_id == tile_info->group_id) { + return group; + } + } + return NULL; +} + +void wlr_output_group_add_tile(struct wlr_output_group *group, struct wlr_output *output, + struct wlr_output_group_tile_info *tile_info) { + struct wlr_output_group_child *child = calloc(1, sizeof(*child)); + assert(tile_info->group_id != 0); + child->output = output; + + child->output_destroy.notify = handle_output_destroy; + wl_signal_add(&output->events.destroy, &child->output_destroy); + child->frame.notify = handle_frame; + wl_signal_add(&output->events.frame, &child->frame); + + child->present.notify = handle_present; + wl_signal_add(&output->events.present, &child->present); + child->needs_frame.notify = handle_needs_frame; + wl_signal_add(&output->events.needs_frame, &child->needs_frame); + + child->group = group; + child->tile_info = *tile_info; + + /* index is like array v,h: + * #1 [0,0], #2 [0,1], #3 [0,2], + * #4 [1,0], #5 [1,1], #6 [1,2], + * #7 [2,0], #8 [2,1], #9 [2,2], + */ + child->index = (tile_info->v_loc * tile_info->num_h) + tile_info->h_loc; + + /* sorted insert to keep children in order for calculating tiled mode */ + struct wlr_output_group_child *cur; + wl_list_for_each(cur, &group->children, link) { + if (child->index < cur->index) { + break; + } + } + wl_list_insert(cur->link.prev, &child->link); + + if (child->output->backend->features.timeline == false) { + group->backend.features.timeline = false; + } + + group->backend.buffer_caps &= child->output->backend->buffer_caps; + + if (group->ready == NULL) { + group->ready = wl_event_loop_add_idle(child->output->event_loop, + output_group_state_change, group); + } +} + +struct wlr_output_group *wlr_output_group_create(void) { + if (registry == NULL) { + wl_list_init(&priv_registry); + registry = &priv_registry; + } + struct wlr_output_group *group = calloc(1, sizeof(*group)); + wl_list_insert(registry, &group->link); + wl_list_init(&group->children); + wlr_backend_init(&group->backend, &backend_impl); + group->backend.features.timeline = true; + group->backend.buffer_caps = ~0; + return group; +} + +static void output_group_destroy(struct wlr_output *output) { + struct wlr_output_group *group = group_from_output(output); + struct wlr_output_group_child *child,*child_tmp; + wlr_output_finish(output); + wl_list_for_each_safe(child, child_tmp, &group->children, link) { + output_group_child_destroy(child); + } + struct wlr_output_group_mode *mode, *mode_tmp; + wl_list_for_each_safe(mode, mode_tmp, &group->output.modes, mode.link) { + wl_list_remove(&mode->mode.link); + free(mode); + } + if (group->ready != NULL) { + wl_event_source_remove(group->ready); + } + free(group->cursor_sizes); + wl_list_remove(&group->link); + free(group); +} + +static void output_group_child_destroy(struct wlr_output_group_child *child) { + struct wlr_output_group *group = child->group; + wlr_log(WLR_DEBUG, "removing child %s from group %s", + child->output->name, group->output.name); + wl_list_remove(&child->present.link); + wl_list_remove(&child->needs_frame.link); + wl_list_remove(&child->frame.link); + wl_list_remove(&child->output_destroy.link); + wl_list_remove(&child->link); + /* Schedule a group state change event. When all children are removed, the + * output will be destroyed. */ + if (group->ready == NULL) { + group->ready = wl_event_loop_add_idle(child->output->event_loop, + output_group_state_change, group); + } + free(child); +} + +static bool output_group_commit_helper(struct wlr_output *parent, const struct wlr_output_state *state, bool test) { + struct wlr_output_group *group = group_from_output(parent); + bool ret = false; + bool failed = false; + bool in_tiled_mode = false; + + if(state->committed & WLR_OUTPUT_STATE_MODE) { + if (state->mode == group->tiled_mode) { + in_tiled_mode = true; + } + } else { + if (parent->current_mode == group->tiled_mode) { + in_tiled_mode = true; + } + } + + /* TODO: it is probably possible to figure out crop/scaling for children */ + if (state->committed & WLR_OUTPUT_STATE_BUFFER) { + if (state->buffer_dst_box.x != 0 || state->buffer_dst_box.y != 0 || + state->buffer_src_box.x != 0.0 || state->buffer_src_box.y != 0.0) { + wlr_log(WLR_DEBUG, "crop/scaling not implemented in output group"); + return false; + } + } + + struct wlr_output_group_child *child; + bool single_output_enabled = false; + int frame_count = 0; + wl_list_for_each(child, &group->children, link) { + struct wlr_output *output = child->output; + struct wlr_output_state state_copy = *state; + struct wlr_output_state *pending = &state_copy; + + /* commit_seq important for presentation feedback! */ + output->commit_seq = parent->commit_seq; + + if (in_tiled_mode) { + frame_count += 1; + pending->buffer_src_box = child->src_box; + pending->buffer_dst_box = child->dst_box; + pending->mode = child->tiled_mode; + if (output->enabled == false && !(pending->committed & WLR_OUTPUT_STATE_ENABLED)) { + pending->committed |= WLR_OUTPUT_STATE_ENABLED; + pending->enabled = true; + } + } else { + frame_count = 1; + if (output->enabled == true || (pending->committed & WLR_OUTPUT_STATE_ENABLED && pending->enabled == true)) { + if (single_output_enabled == false) { + /* first child gets turned on */ + if ((pending->committed & WLR_OUTPUT_STATE_MODE) && (pending->mode_type == WLR_OUTPUT_STATE_MODE_FIXED)) { + struct wlr_output_group_mode *group_mode = wl_container_of(pending->mode, group_mode, mode); + pending->mode = group_mode->original_mode; + } + single_output_enabled = true; + } else { + /* rest of the children get forced off */ + pending->committed = WLR_OUTPUT_STATE_ENABLED; + pending->enabled = false; + } + } + } + + if (output->enabled == false && !(pending->committed & WLR_OUTPUT_STATE_ENABLED)) { + continue; + } + + /* The parent group output is going through wlr_output_commit_state() + * and managing state. Skip directly to the children output + * implementation commit. */ + + if (test) { + if (output->impl->test) { + if (false == output->impl->test(output, pending)) { + return false; + } + } + } else { + ret = output->impl->commit(output, pending); + if(ret == false) { + failed = true; + wlr_log(WLR_DEBUG, "commit failed on %s", output->name); + } + if (!failed) { + output_apply_state(output, pending); + if (output->frame_pending) { + parent->frame_pending = true; + } + } + } + } + + if (test) { + return true; + } else if (failed) { + /* Do not present any frame where any children failed */ + group->queued_frame_count = -1; + } else { + /* Synchronize all children outputs to prevent tearing. Make + * sure we get all the children frame/present events before + * forwarding that to the group output. */ + group->queued_frame_count = frame_count; + } + + return !failed; +} + +static bool output_group_commit(struct wlr_output *parent, const struct wlr_output_state *state) { + return output_group_commit_helper(parent, state, false); +} + +static bool output_group_test(struct wlr_output *parent, const struct wlr_output_state *state) { + return output_group_commit_helper(parent, state, true); +} + +static size_t output_group_get_gamma_size(struct wlr_output *output) { + struct wlr_output_group *group = group_from_output(output); + size_t gamma_size = 0; + size_t tmp_gamma_size = 0; + struct wlr_output_group_child *child; + wl_list_for_each(child, &group->children, link) { + if (child->output->impl->get_gamma_size) { + tmp_gamma_size = child->output->impl->get_gamma_size(child->output); + } + if (gamma_size == 0) { + gamma_size = tmp_gamma_size; + } + if (tmp_gamma_size == 0 || tmp_gamma_size != gamma_size) { + return 0; + } + } + return gamma_size; +} + +static bool output_group_set_cursor(struct wlr_output *output, + struct wlr_buffer *buffer, int hotspot_x, int hotspot_y) { + struct wlr_output_group *group = group_from_output(output); + struct wlr_output_group_child *child; + wl_list_for_each(child, &group->children, link) { + if (child->output->enabled) { + child->output->impl->set_cursor(child->output, buffer, hotspot_x, hotspot_y); + } + }; + return true; +} + +static bool output_group_move_cursor(struct wlr_output *output, + int x, int y) { + struct wlr_output_group *group = group_from_output(output); + struct wlr_output *parent = &group->output; + struct wlr_output_group_child *child; + /* copied from backend/drm.c ;-) */ + struct wlr_box box = { .x = x, .y = y }; + int width, height; + enum wl_output_transform transform = wlr_output_transform_invert(parent->transform); + wlr_output_transformed_resolution(output, &width, &height); + wlr_box_transform(&box, &box, transform, width, height); + wl_list_for_each(child, &group->children, link) { + if (child->output->enabled) { + child->output->impl->move_cursor(child->output, box.x - child->src_box.x, box.y - child->src_box.y); + } + }; + return true; +} + +static const struct wlr_output_cursor_size *output_group_get_cursor_sizes(struct wlr_output *output, size_t *len) { + struct wlr_output_group *group = group_from_output(output); + *len = group->cursor_sizes_len; + return group->cursor_sizes; +} + +static const struct wlr_drm_format_set *output_group_get_cursor_formats( + struct wlr_output *output, uint32_t buffer_caps) { + struct wlr_output_group *group = group_from_output(output); + struct wlr_output_group_child *child; + bool first = true; + wl_list_for_each(child, &group->children, link) { + if (!child->output->impl->get_cursor_formats) { + wlr_drm_format_set_finish(&group->cursor_formats); + break; + } + const struct wlr_drm_format_set *set = + child->output->impl->get_cursor_formats(child->output, buffer_caps); + if (first) { + wlr_drm_format_set_copy(&group->cursor_formats, set); + first = false; + } else { + wlr_drm_format_set_intersect(&group->cursor_formats, &group->cursor_formats, set); + } + } + return &group->cursor_formats; +} + +static const struct wlr_drm_format_set *output_group_get_primary_formats( + struct wlr_output *output, uint32_t buffer_caps) { + struct wlr_output_group *group = group_from_output(output); + bool first = true; + struct wlr_output_group_child *child; + wl_list_for_each(child, &group->children, link) { + if (!child->output->impl->get_primary_formats) { + wlr_drm_format_set_finish(&group->primary_formats); + break; + } + const struct wlr_drm_format_set *set = + child->output->impl->get_primary_formats(child->output, buffer_caps); + if (first) { + wlr_drm_format_set_copy(&group->primary_formats, set); + first = false; + } else { + wlr_drm_format_set_intersect(&group->primary_formats, &group->primary_formats, set); + } + } + return &group->primary_formats; +} + +static void calculate_and_allocate_tiled_mode(struct wlr_output_group *group) { + struct wlr_output_group_mode *group_mode = calloc(1, sizeof(*group_mode)); + uint32_t x_start = 0, y_start = 0; + struct wlr_output_group_child *child; + + wl_list_for_each(child, &group->children, link) { + struct wlr_output_group_tile_info *tile_info = &child->tile_info; + + /* this depends on iterating through the children in tile index order and + * assumes the dimensions work */ + if (tile_info->v_loc == 0) { + group_mode->mode.width += tile_info->h_size; + } + if (tile_info->h_loc == 0) { + group_mode->mode.height += tile_info->v_size; + } + + /* Generate the crop for this specific tile. The source buffer is shared + * between all tiles and each child output takes a subset of the shared + * buffer. */ + child->src_box.x = x_start; + child->src_box.y = y_start; + child->src_box.width = tile_info->h_size; + child->src_box.height = tile_info->v_size; + + child->dst_box.x = 0; + child->dst_box.y = 0; + child->dst_box.width = tile_info->h_size; + child->dst_box.height = tile_info->v_size; + + if (tile_info->h_loc == (tile_info->num_h-1)) { + x_start = 0; + y_start += tile_info->v_size; + } else { + x_start += tile_info->h_size; + } + + struct wlr_output_mode *mode; + wl_list_for_each(mode, &child->output->modes, link) { + if (mode->width == (int32_t)tile_info->h_size && mode->height == (int32_t)tile_info->v_size) { + child->tiled_mode = mode; + if ((group_mode->mode.refresh == 0) || (mode->refresh < group_mode->mode.refresh)) { + /* slowest refresh wins */ + group_mode->mode.refresh = mode->refresh; + } + break; + } + } + } + //TODO: set aspect ratio? + group_mode->mode.picture_aspect_ratio = WLR_OUTPUT_MODE_ASPECT_RATIO_NONE; + group_mode->mode.preferred = true; + group->tiled_mode = &group_mode->mode; + wl_list_insert(&group->output.modes, &group_mode->mode.link); +} + +static bool is_cursor_size_in_all_children(struct wlr_output_group *group, struct wlr_output_group_child *myself, const struct wlr_output_cursor_size *size) { + struct wlr_output_group_child *child; + wl_list_for_each(child, &group->children, link) { + size_t len = 0; + if (child == myself) { + continue; + } + if (!child->output->impl->get_cursor_sizes) { + continue; + } + const struct wlr_output_cursor_size *sizes = child->output->impl->get_cursor_sizes(child->output, &len); + bool found = false; + for (size_t i=0; i < len && found == false; ++i) { + if (sizes[i].width == size->width && sizes[i].height == size->height) { + found = true; + } + }; + if (!found) { + return false; + } + } + return true; +} + +static void allocate_cursor_sizes(struct wlr_output_group *group) { + struct wlr_output_group_child *child; + size_t cur_idx = 0; + + /* Save the intersection of hardware cursor sizes. */ + wl_list_for_each(child, &group->children, link) { + size_t len = 0; + if (!child->output->impl->get_cursor_sizes) { + continue; + } + const struct wlr_output_cursor_size *sizes = child->output->impl->get_cursor_sizes(child->output, &len); + group->cursor_sizes = calloc(len, sizeof(struct wlr_output_cursor_size)); + for (size_t i=0; icursor_sizes[cur_idx] = sizes[i]; + cur_idx += 1; + } + } + group->cursor_sizes_len = cur_idx; + + break; + } +} + +static void output_group_state_change(void *data) { + struct wlr_output_group *group = data; + int num_children = wl_list_length(&group->children); + bool need_init = false; + bool need_destroy = false; + if (group->num_children > 0) { + need_destroy = true; + } + + if (num_children > 0) { + need_init = true; + } + + if (need_destroy) { + struct wlr_output_group *old_group = group; + if (need_init) { + struct wlr_output_group *new_group = wlr_output_group_create(); + struct wlr_output_group_child *child, *child_tmp; + + /* prevent re-entering */ + new_group->ready = old_group->ready; + + /* disable old group */ + const struct wlr_output_state pending = (struct wlr_output_state) { + .committed = WLR_OUTPUT_STATE_ENABLED, + .allow_reconfiguration = true, + .enabled = false, + }; + wlr_output_commit_state(&old_group->output, &pending); + + /* move children to new group */ + wl_list_for_each_safe(child, child_tmp, &group->children, link) { + wlr_output_group_add_tile(new_group, child->output, &child->tile_info); + output_group_child_destroy(child); + } + + /* old group will get free'd during output destroy */ + group = new_group; + } + + wlr_output_destroy(&old_group->output); + } + + group->ready = NULL; + group->num_children = num_children; + + if (!need_init) { + return; + } + + /* the first child is the primary */ + struct wlr_output_group_child *primary = + wl_container_of(group->children.next, primary, link); + + if (!clone_output(group, primary->output, &primary->tile_info)) + return; + + /* try to support a partial tiled array */ + int num_children_needed; + if (!primary->tile_info.is_single_monitor) { + num_children_needed = (primary->tile_info.num_h * primary->tile_info.num_v); + } else { + num_children_needed = 1; + } + + if (num_children >= num_children_needed) { + calculate_and_allocate_tiled_mode(group); + } + allocate_cursor_sizes(group); + + wlr_log(WLR_INFO, "created output group %s (%dx%d mm)", + group->output.name, group->output.phys_width, group->output.phys_height); + + struct wlr_output_mode *mode; + wl_list_for_each(mode, &group->output.modes, link) { + wlr_log(WLR_DEBUG, " mode %dx%d@%d %s %s", + mode->width, mode->height, mode->refresh, + mode->preferred?"(preferred)":"", + (mode == group->tiled_mode)?"(tiled)":""); + } + wl_signal_emit_mutable(&primary->output->backend->events.new_output, &group->output); +} + +static const struct wlr_output_impl output_impl = { + .destroy = output_group_destroy, + .test = output_group_test, + .commit = output_group_commit, + .get_gamma_size = output_group_get_gamma_size, + .set_cursor = output_group_set_cursor, + .move_cursor = output_group_move_cursor, + .get_cursor_formats = output_group_get_cursor_formats, + .get_cursor_sizes = output_group_get_cursor_sizes, + .get_primary_formats = output_group_get_primary_formats, +}; + +static const struct wlr_backend_impl backend_impl = { + .start = NULL, + .destroy = NULL, + .get_drm_fd = backend_get_drm_fd, + .test = NULL, + .commit = NULL, +}; From 2a697512d7d0479a9a9a6616789024c3f7a11dba Mon Sep 17 00:00:00 2001 From: EBADBEEF Date: Tue, 30 May 2023 19:42:51 -0700 Subject: [PATCH 3/5] backend/drm: parse TILE property --- backend/drm/drm.c | 7 ++++++ backend/drm/properties.c | 1 + backend/drm/util.c | 41 ++++++++++++++++++++++++++++++++ include/backend/drm/drm.h | 3 +++ include/backend/drm/properties.h | 1 + include/backend/drm/util.h | 1 + 6 files changed, 54 insertions(+) diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 15cb181cf..8f2aad88b 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -1720,6 +1721,12 @@ static bool connect_drm_connector(struct wlr_drm_connector *wlr_conn, parse_edid(wlr_conn, edid_len, edid); free(edid); + size_t tile_len = 0; + uint8_t *tile = get_drm_prop_blob(drm->fd, + wlr_conn->id, wlr_conn->props.tile, &tile_len); + parse_tile(wlr_conn, tile_len, tile); + free(tile); + char *subconnector = NULL; if (wlr_conn->props.subconnector) { subconnector = get_drm_prop_enum(drm->fd, diff --git a/backend/drm/properties.c b/backend/drm/properties.c index 314023954..875f9fb4f 100644 --- a/backend/drm/properties.c +++ b/backend/drm/properties.c @@ -27,6 +27,7 @@ static const struct prop_info connector_info[] = { { "EDID", INDEX(edid) }, { "HDR_OUTPUT_METADATA", INDEX(hdr_output_metadata) }, { "PATH", INDEX(path) }, + { "TILE", INDEX(tile) }, { "content type", INDEX(content_type) }, { "link-status", INDEX(link_status) }, { "max bpc", INDEX(max_bpc) }, diff --git a/backend/drm/util.c b/backend/drm/util.c index dd2b37351..ccb562a99 100644 --- a/backend/drm/util.c +++ b/backend/drm/util.c @@ -97,6 +97,47 @@ void parse_edid(struct wlr_drm_connector *conn, size_t len, const uint8_t *data) di_info_destroy(info); } +void parse_tile(struct wlr_drm_connector *conn, size_t len, const uint8_t *data) { + struct wlr_output_group_tile_info *tile_info = &conn->tile_info; + memset(tile_info, 0, sizeof(*tile_info)); + if (len == 0) + return; + + // Reference: + // - include/linux/drm/drm_connector.h tile_blob_ptr + // - drivers/gpu/drm/drm_edid.c drm_parse_tiled_block() + // + // Note: group_id is always > 0 + int ret = sscanf((char*)data, "%d:%d:%d:%d:%d:%d:%d:%d", + &tile_info->group_id, + &tile_info->is_single_monitor, + &tile_info->num_h, + &tile_info->num_v, + &tile_info->h_loc, + &tile_info->v_loc, + &tile_info->h_size, + &tile_info->v_size); + if(ret != 8) { + wlr_log(WLR_ERROR, "Unable to understand tile information for " + "connector %s", conn->name); + return; + } + + wlr_log(WLR_INFO, "Connector '%s' TILE information: " + "group ID %d, single monitor %d, total %d horizontal tiles, " + "total %d vertical tiles, horizontal tile %d, vertical tile " + "%d, width %d, height %d", + conn->name, + tile_info->group_id, + tile_info->is_single_monitor, + tile_info->num_h, + tile_info->num_v, + tile_info->h_loc, + tile_info->v_loc, + tile_info->h_size, + tile_info->v_size); +} + const char *drm_connector_status_str(drmModeConnection status) { switch (status) { case DRM_MODE_CONNECTED: diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h index af4231f54..7c829b5ca 100644 --- a/include/backend/drm/drm.h +++ b/include/backend/drm/drm.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "backend/drm/iface.h" #include "backend/drm/properties.h" @@ -219,6 +220,8 @@ struct wlr_drm_connector { uint32_t hdr_output_metadata; int32_t refresh; + + struct wlr_output_group_tile_info tile_info; }; struct wlr_drm_backend *get_drm_backend_from_backend( diff --git a/include/backend/drm/properties.h b/include/backend/drm/properties.h index c02d655ba..c07d3268b 100644 --- a/include/backend/drm/properties.h +++ b/include/backend/drm/properties.h @@ -22,6 +22,7 @@ struct wlr_drm_connector_props { uint32_t panel_orientation; // not guaranteed to exist uint32_t content_type; // not guaranteed to exist uint32_t max_bpc; // not guaranteed to exist + uint32_t tile; // not guaranteed to exist // atomic-modesetting only diff --git a/include/backend/drm/util.h b/include/backend/drm/util.h index 9ba5f435e..95321cb2e 100644 --- a/include/backend/drm/util.h +++ b/include/backend/drm/util.h @@ -14,6 +14,7 @@ enum wlr_output_mode_aspect_ratio get_picture_aspect_ratio(const drmModeModeInfo const char *get_pnp_manufacturer(const char code[static 3]); // Populates the make/model/phys_{width,height} of output from the edid data void parse_edid(struct wlr_drm_connector *conn, size_t len, const uint8_t *data); +void parse_tile(struct wlr_drm_connector *conn, size_t len, const uint8_t *data); const char *drm_connector_status_str(drmModeConnection status); void generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay, float vrefresh); From e9124b8a9a4a10b95013b9a32f8c379f735a2e53 Mon Sep 17 00:00:00 2001 From: EBADBEEF Date: Tue, 30 May 2023 19:43:16 -0700 Subject: [PATCH 4/5] backend/drm: automatically use wlr_output_group for tiled outputs --- backend/drm/drm.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 8f2aad88b..3cfd0f71a 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -1873,10 +1873,20 @@ void scan_drm_connectors(struct wlr_drm_backend *drm, for (size_t i = 0; i < new_outputs_len; ++i) { struct wlr_drm_connector *conn = new_outputs[i]; - - wlr_drm_conn_log(conn, WLR_INFO, "Requesting modeset"); - wl_signal_emit_mutable(&drm->backend.events.new_output, - &conn->output); + if(conn->tile_info.group_id) { + struct wlr_output_group *group = wlr_output_group_match_tile(&conn->tile_info); + if (group) { + wlr_drm_conn_log(conn, WLR_INFO, "Adding %s to existing group", conn->name); + } else { + wlr_drm_conn_log(conn, WLR_INFO, "Creating output group for %s", conn->name); + group = wlr_output_group_create(); + } + wlr_output_group_add_tile(group, &conn->output, &conn->tile_info); + } else { + wlr_drm_conn_log(conn, WLR_INFO, "Requesting modeset"); + wl_signal_emit_mutable(&drm->backend.events.new_output, + &conn->output); + } } } From 879243e370de6167d2c49510396f937b1a93fab5 Mon Sep 17 00:00:00 2001 From: David Turner Date: Mon, 20 Oct 2025 13:55:00 +0100 Subject: [PATCH 5/5] 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. --- include/xwayland/xwm.h | 1 + xwayland/xwayland.c | 3 +++ xwayland/xwm.c | 3 +++ 3 files changed, 7 insertions(+) diff --git a/include/xwayland/xwm.h b/include/xwayland/xwm.h index e460bbb63..73f440d29 100644 --- a/include/xwayland/xwm.h +++ b/include/xwayland/xwm.h @@ -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); diff --git a/xwayland/xwayland.c b/xwayland/xwayland.c index 5d51df074..3aa47bac2 100644 --- a/xwayland/xwayland.c +++ b/xwayland/xwayland.c @@ -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; } diff --git a/xwayland/xwm.c b/xwayland/xwm.c index a82e8b145..2bb4e4c64 100644 --- a/xwayland/xwm.c +++ b/xwayland/xwm.c @@ -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; }