diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index da1119b27..4501a0434 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -170,7 +170,7 @@ static void plane_disable(struct atomic *atom, struct wlr_drm_plane *plane) { atomic_add(atom, id, props->crtc_id, 0); } -static void set_plane_props(struct atomic *atom, struct wlr_drm_backend *drm, +static void set_plane_props(struct atomic *atom, struct wlr_box source_box, struct wlr_drm_plane *plane, uint32_t crtc_id, int32_t x, int32_t y) { uint32_t id = plane->id; const union wlr_drm_plane_props *props = &plane->props; @@ -180,12 +180,14 @@ static void set_plane_props(struct atomic *atom, struct wlr_drm_backend *drm, goto error; } - uint32_t width = fb->wlr_buf->width; - uint32_t height = fb->wlr_buf->height; + uint32_t width = source_box.width ? (uint32_t)source_box.width : + (uint32_t)fb->wlr_buf->width; + uint32_t height = source_box.height ? (uint32_t)source_box.height : + (uint32_t)fb->wlr_buf->height; // The src_* properties are in 16.16 fixed point - atomic_add(atom, id, props->src_x, 0); - atomic_add(atom, id, props->src_y, 0); + atomic_add(atom, id, props->src_x, (uint64_t)source_box.x << 16); + atomic_add(atom, id, props->src_y, (uint64_t)source_box.y << 16); atomic_add(atom, id, props->src_w, (uint64_t)width << 16); atomic_add(atom, id, props->src_h, (uint64_t)height << 16); atomic_add(atom, id, props->crtc_w, width); @@ -292,20 +294,36 @@ static bool atomic_crtc_commit(struct wlr_drm_connector *conn, atomic_add(&atom, crtc->id, crtc->props.mode_id, mode_id); atomic_add(&atom, crtc->id, crtc->props.active, active); if (active) { + struct wlr_box source_box; + if (state->base->committed & WLR_OUTPUT_STATE_SOURCE_BOX) { + /** + * Grab source box from output in order to crop the + * buffer. + */ + source_box = state->base->source_box; + } + else { + // Use dummy source box + source_box.x = source_box.y = source_box.width = + source_box.height = 0; + } if (crtc->props.gamma_lut != 0) { atomic_add(&atom, crtc->id, crtc->props.gamma_lut, gamma_lut); } if (crtc->props.vrr_enabled != 0) { atomic_add(&atom, crtc->id, crtc->props.vrr_enabled, vrr_enabled); } - set_plane_props(&atom, drm, crtc->primary, crtc->id, 0, 0); + set_plane_props(&atom, source_box, crtc->primary, crtc->id, 0, 0); if (crtc->primary->props.fb_damage_clips != 0) { atomic_add(&atom, crtc->primary->id, crtc->primary->props.fb_damage_clips, fb_damage_clips); } if (crtc->cursor) { if (drm_connector_is_cursor_visible(conn)) { - set_plane_props(&atom, drm, crtc->cursor, crtc->id, + // Ensure source_box is unset for cursor plane + source_box.x = source_box.y = source_box.width = + source_box.height = 0; + set_plane_props(&atom, source_box, crtc->cursor, crtc->id, conn->cursor_x, conn->cursor_y); } else { plane_disable(&atom, crtc->cursor); diff --git a/backend/drm/drm.c b/backend/drm/drm.c index d5e6c21e6..fb12926d9 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -1272,6 +1272,12 @@ void scan_drm_connectors(struct wlr_drm_backend *drm, 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 4f4951771..feb586cb5 100644 --- a/backend/drm/properties.c +++ b/backend/drm/properties.c @@ -25,6 +25,7 @@ static const struct prop_info connector_info[] = { { "DPMS", INDEX(dpms) }, { "EDID", INDEX(edid) }, { "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 3f460317f..10f2f5e76 100644 --- a/backend/drm/util.c +++ b/backend/drm/util.c @@ -134,6 +134,70 @@ 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) { + struct wlr_output *output = &conn->output; + if (len > 0) { + int ret; + ret = sscanf((char*)data, "%d:%d:%d:%d:%d:%d:%d:%d", + &output->tile_info.group_id, + &output->tile_info.tile_is_single_monitor, + &output->tile_info.num_h_tile, + &output->tile_info.num_v_tile, + &output->tile_info.tile_h_loc, + &output->tile_info.tile_v_loc, + &output->tile_info.tile_h_size, + &output->tile_info.tile_v_size); + if(ret != 8) + wlr_log(WLR_ERROR, "Unable to understand tile information for " + "output %s", output->name); + else + wlr_log(WLR_INFO, "Output %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", + output->name, + output->tile_info.group_id, + output->tile_info.tile_is_single_monitor, + output->tile_info.num_h_tile, + output->tile_info.num_v_tile, + output->tile_info.tile_h_loc, + output->tile_info.tile_v_loc, + output->tile_info.tile_h_size, + output->tile_info.tile_v_size); + } + else + wlr_log(WLR_DEBUG, "No tile information available for output %s", + output->name); +} + +const char *conn_get_name(uint32_t type_id) { + switch (type_id) { + case DRM_MODE_CONNECTOR_Unknown: return "Unknown"; + case DRM_MODE_CONNECTOR_VGA: return "VGA"; + case DRM_MODE_CONNECTOR_DVII: return "DVI-I"; + case DRM_MODE_CONNECTOR_DVID: return "DVI-D"; + case DRM_MODE_CONNECTOR_DVIA: return "DVI-A"; + case DRM_MODE_CONNECTOR_Composite: return "Composite"; + case DRM_MODE_CONNECTOR_SVIDEO: return "SVIDEO"; + case DRM_MODE_CONNECTOR_LVDS: return "LVDS"; + case DRM_MODE_CONNECTOR_Component: return "Component"; + case DRM_MODE_CONNECTOR_9PinDIN: return "DIN"; + case DRM_MODE_CONNECTOR_DisplayPort: return "DP"; + case DRM_MODE_CONNECTOR_HDMIA: return "HDMI-A"; + case DRM_MODE_CONNECTOR_HDMIB: return "HDMI-B"; + case DRM_MODE_CONNECTOR_TV: return "TV"; + case DRM_MODE_CONNECTOR_eDP: return "eDP"; + case DRM_MODE_CONNECTOR_VIRTUAL: return "Virtual"; + case DRM_MODE_CONNECTOR_DSI: return "DSI"; + case DRM_MODE_CONNECTOR_DPI: return "DPI"; + case DRM_MODE_CONNECTOR_WRITEBACK: return "Writeback"; +#ifdef DRM_MODE_CONNECTOR_SPI + case DRM_MODE_CONNECTOR_SPI: return "SPI"; +#endif +#ifdef DRM_MODE_CONNECTOR_USB + case DRM_MODE_CONNECTOR_USB: return "USB"; +#endif + default: return "Unknown"; output->model = strdup(model_str); if (serial_str[0] != '\0') { output->serial = strdup(serial_str); diff --git a/include/backend/drm/properties.h b/include/backend/drm/properties.h index b6ca14f19..432c183c4 100644 --- a/include/backend/drm/properties.h +++ b/include/backend/drm/properties.h @@ -20,6 +20,7 @@ union wlr_drm_connector_props { uint32_t subconnector; // not guaranteed to exist uint32_t non_desktop; uint32_t panel_orientation; // not guaranteed to exist + uint32_t tile; uint32_t content_type; // not guaranteed to exist uint32_t max_bpc; // not guaranteed to exist @@ -27,7 +28,7 @@ union wlr_drm_connector_props { uint32_t crtc_id; }; - uint32_t props[4]; + uint32_t props[8]; }; union wlr_drm_crtc_props { diff --git a/include/backend/drm/util.h b/include/backend/drm/util.h index 0d4c3d74c..f545fcf7c 100644 --- a/include/backend/drm/util.h +++ b/include/backend/drm/util.h @@ -12,6 +12,8 @@ int32_t calculate_refresh_rate(const drmModeModeInfo *mode); enum wlr_output_mode_aspect_ratio get_picture_aspect_ratio(const drmModeModeInfo *mode); // 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); +// Parses the TILE property +void parse_tile(struct wlr_drm_connector *conn, size_t len, const uint8_t *data); // Part of match_obj enum { diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 309c2731f..b8126053e 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -16,6 +16,7 @@ #include #include #include +#include enum wlr_output_mode_aspect_ratio { WLR_OUTPUT_MODE_ASPECT_RATIO_NONE, @@ -51,6 +52,17 @@ struct wlr_output_cursor { struct wl_listener surface_destroy; }; +struct wlr_output_tile_info { + uint32_t group_id; + uint32_t tile_is_single_monitor; + uint32_t num_h_tile; + uint32_t num_v_tile; + uint32_t tile_h_loc; + uint32_t tile_v_loc; + uint32_t tile_h_size; + uint32_t tile_v_size; +}; + enum wlr_output_adaptive_sync_status { WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED, WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED, @@ -66,6 +78,7 @@ enum wlr_output_state_field { WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED = 1 << 6, WLR_OUTPUT_STATE_GAMMA_LUT = 1 << 7, WLR_OUTPUT_STATE_RENDER_FORMAT = 1 << 8, + WLR_OUTPUT_STATE_SOURCE_BOX = 1 << 9, WLR_OUTPUT_STATE_SUBPIXEL = 1 << 9, }; @@ -87,6 +100,9 @@ struct wlr_output_state { float scale; enum wl_output_transform transform; bool adaptive_sync_enabled; + /* allow partial buffer scanout for tiling displays + * only valid if WLR_OUTPUT_STATE_SOURCE_BOX */ + struct wlr_box source_box; // source box for respective output uint32_t render_format; enum wl_output_subpixel subpixel; @@ -131,6 +147,7 @@ struct wlr_output { char *description; // may be NULL char *make, *model, *serial; // may be NULL int32_t phys_width, phys_height; // mm + struct wlr_output_tile_info tile_info; // Note: some backends may have zero modes struct wl_list modes; // wlr_output_mode::link @@ -416,6 +433,14 @@ uint32_t wlr_output_preferred_read_format(struct wlr_output *output); */ void wlr_output_set_damage(struct wlr_output *output, pixman_region32_t *damage); +/** + * This can be used in case the output buffer is larger than the buffer that + * is supposed to be presented on the actual screen attached to the DRM + * connector. Current use case are hi-res tiling displays which use multiple + * DRM connectors to make up the full monitor. + */ +void wlr_output_set_source_box(struct wlr_output *output, + struct wlr_box source_box); /** * Test whether the pending output state would be accepted by the backend. If * this function returns true, wlr_output_commit() can only fail due to a diff --git a/include/wlr/types/wlr_output_group.h b/include/wlr/types/wlr_output_group.h new file mode 100644 index 000000000..65e9221d1 --- /dev/null +++ b/include/wlr/types/wlr_output_group.h @@ -0,0 +1,32 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#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 + +struct wlr_output_group_child; + +struct wlr_output_group { + struct wlr_output base; + + // Private state + + struct wlr_output *main_output; + struct wl_list children; // wlr_output_group_child.link + + struct wl_listener main_output_destroy; +}; + +struct wlr_output_group *wlr_output_group_create(struct wlr_output *main_output); +struct wlr_output_group_child *wlr_output_group_add( + struct wlr_output_group *group, struct wlr_output *output); +void wlr_output_group_child_destroy(struct wlr_output_group_child *child); + +#endif diff --git a/types/meson.build b/types/meson.build index f7c24e123..e7bd68af2 100644 --- a/types/meson.build +++ b/types/meson.build @@ -54,6 +54,7 @@ wlr_files += files( 'wlr_linux_dmabuf_v1.c', 'wlr_matrix.c', 'wlr_output_damage.c', + 'wlr_output_group.c', 'wlr_output_layout.c', 'wlr_output_management_v1.c', 'wlr_output_power_management_v1.c', diff --git a/types/output/output.c b/types/output/output.c index 04361231a..dae60b7a2 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -873,6 +873,12 @@ void wlr_output_attach_buffer(struct wlr_output *output, output_state_attach_buffer(&output->pending, buffer); } +void wlr_output_set_source_box(struct wlr_output *output, + struct wlr_box source_box) { + output->pending.source_box = source_box; + output->pending.committed |= WLR_OUTPUT_STATE_SOURCE_BOX; +} + void wlr_output_send_frame(struct wlr_output *output) { output->frame_pending = false; if (output->enabled) { diff --git a/types/wlr_output_group.c b/types/wlr_output_group.c new file mode 100644 index 000000000..b249fb09f --- /dev/null +++ b/types/wlr_output_group.c @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include + +struct wlr_output_group_child { + struct wlr_output *output; + struct wl_list link; // wlr_output_group.children + + struct wl_listener output_destroy; +}; + +static const struct wlr_output_impl output_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 void group_destroy(struct wlr_output *output) { + struct wlr_output_group *group = group_from_output(output); + + wl_list_remove(&group->main_output_destroy.link); + + struct wlr_output_group_child *child, *child_tmp; + wl_list_for_each_safe(child, child_tmp, &group->children, link) { + wlr_output_group_child_destroy(child); + } + + free(group); +} + +static void output_apply(struct wlr_output *output, + struct wlr_output_state *state) { + if (state->committed & WLR_OUTPUT_STATE_BUFFER) { + wlr_output_attach_buffer(output, state->buffer); + } + // TODO: everything else +} + +static bool group_commit(struct wlr_output *output) { + struct wlr_output_group *group = group_from_output(output); + + output_apply(group->main_output, &output->pending); + + struct wlr_output_group_child *child; + wl_list_for_each(child, &group->children, link) { + output_apply(child->output, &output->pending); + } + + // TODO: perform a backend-wide commit if possible + if (!wlr_output_commit(group->main_output)) { + goto error; + } + + wl_list_for_each(child, &group->children, link) { + if (!wlr_output_commit(child->output)) { + goto error; + } + } + + // TODO: update our current state + + return true; + +error: + wlr_output_rollback(group->main_output); + + wl_list_for_each(child, &group->children, link) { + wlr_output_rollback(child->output); + } + + return false; +} + +static const struct wlr_drm_format_set *group_get_primary_formats( + struct wlr_output *output, uint32_t buffer_caps) { + struct wlr_output_group *group = group_from_output(output); + + // TODO: intersect primary formats from all children + return group->main_output->impl->get_primary_formats(group->main_output, buffer_caps); +} + +static const struct wlr_output_impl output_impl = { + .destroy = group_destroy, + .commit = group_commit, + .get_primary_formats = group_get_primary_formats, +}; + +static void group_handle_main_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_output_group *group = + wl_container_of(listener, group, main_output_destroy); + wlr_output_destroy(&group->base); +} + +struct wlr_output_group *wlr_output_group_create(struct wlr_output *main_output) { + struct wlr_output_group *group = calloc(1, sizeof(*group)); + if (group == NULL) { + return NULL; + } + + wlr_output_init(&group->base, main_output->backend, + &output_impl, main_output->display); + + wl_list_init(&group->children); + + group->main_output_destroy.notify = group_handle_main_output_destroy; + wl_signal_add(&main_output->events.destroy, &group->main_output_destroy); + + memcpy(&group->base.name, &main_output->name, sizeof(group->base.name)); + wlr_output_set_description(&group->base, main_output->description); + memcpy(&group->base.make, &main_output->make, sizeof(group->base.make)); + memcpy(&group->base.model, &main_output->model, sizeof(group->base.model)); + memcpy(&group->base.serial, &main_output->serial, sizeof(group->base.serial)); + group->base.phys_width = main_output->phys_width; + group->base.phys_height = main_output->phys_height; + group->base.modes = main_output->modes; + group->base.current_mode = main_output->current_mode; + group->base.width = main_output->width; + group->base.height = main_output->height; + group->base.refresh = main_output->refresh; + group->base.enabled = main_output->enabled; + group->base.scale = main_output->scale; + group->base.subpixel = main_output->subpixel; + group->base.transform = main_output->transform; + group->base.adaptive_sync_status = main_output->adaptive_sync_status; + + // TODO: listen to main output events and pass them through + + return group; +} + +static void child_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_output_group_child *child = + wl_container_of(listener, child, output_destroy); + wlr_output_group_child_destroy(child); +} + +struct wlr_output_group_child *wlr_output_group_add( + struct wlr_output_group *group, struct wlr_output *output) { + struct wlr_output_group_child *child; + wl_list_for_each(child, &group->children, link) { + if (child->output == output) { + return child; + } + } + + child = calloc(1, sizeof(*child)); + if (child == NULL) { + return NULL; + } + + child->output = output; + wl_list_insert(&group->children, &child->link); + + child->output_destroy.notify = child_handle_output_destroy; + wl_signal_add(&output->events.destroy, &child->output_destroy); + + return child; +} + +void wlr_output_group_child_destroy(struct wlr_output_group_child *child) { + wl_list_remove(&child->output_destroy.link); + wl_list_remove(&child->link); + free(child); +}