| From 6ff0106dc3fc3e9e442d634cb8e5dc0ef792416b Mon Sep 17 00:00:00 2001 |
| From: Jia Rong <jia.rong@mediatek.com> |
| Date: Fri, 20 Apr 2018 20:43:48 +0800 |
| Subject: [PATCH 6/9] weston: porting atomic drm from weston4.0 |
| |
| porting atomic drm from weston4.0 |
| Test: build pass |
| |
| Change-Id: I37829d8a4815117d4b8bac8d2f8d21f666f83104 |
| Signed-off-by: Jia Rong <jia.rong@mediatek.com> |
| CR-Id: AUTO00016576 |
| --- |
| Makefile.am | 2 + |
| libweston/compositor-drm.c | 3922 +++++++++++++++++++++++++++++++-------- |
| libweston/compositor-drm.h | 16 +- |
| libweston/compositor-fbdev.c | 5 +- |
| libweston/compositor-headless.c | 3 +- |
| libweston/compositor-rdp.c | 3 +- |
| libweston/compositor-wayland.c | 6 +- |
| libweston/compositor-x11.c | 6 +- |
| libweston/compositor.c | 177 +- |
| libweston/compositor.h | 59 +- |
| libweston/pixel-formats.c | 430 +++++ |
| libweston/pixel-formats.h | 194 ++ |
| shared/timespec-util.h | 184 ++ |
| 13 files changed, 4221 insertions(+), 786 deletions(-) |
| create mode 100644 libweston/pixel-formats.c |
| create mode 100644 libweston/pixel-formats.h |
| |
| diff --git a/Makefile.am b/Makefile.am |
| index e23b03b..238cd2f 100644 |
| --- a/Makefile.am |
| +++ b/Makefile.am |
| @@ -105,6 +105,8 @@ libweston_@LIBWESTON_MAJOR@_la_SOURCES = \ |
| libweston/timeline-object.h \ |
| libweston/linux-dmabuf.c \ |
| libweston/linux-dmabuf.h \ |
| + libweston/pixel-formats.c \ |
| + libweston/pixel-formats.h \ |
| shared/helpers.h \ |
| shared/matrix.c \ |
| shared/matrix.h \ |
| diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c |
| index 4a5cd67..bba22b3 100644 |
| --- a/libweston/compositor-drm.c |
| +++ b/libweston/compositor-drm.c |
| @@ -54,6 +54,7 @@ |
| #include "gl-renderer.h" |
| #include "weston-egl-ext.h" |
| #include "pixman-renderer.h" |
| +#include "pixel-formats.h" |
| #include "libbacklight.h" |
| #include "libinput-seat.h" |
| #include "launcher-util.h" |
| @@ -62,10 +63,16 @@ |
| #include "linux-dmabuf.h" |
| #include "linux-dmabuf-unstable-v1-server-protocol.h" |
| |
| +#define HAVE_DRM_ATOMIC |
| + |
| #ifndef DRM_CAP_TIMESTAMP_MONOTONIC |
| #define DRM_CAP_TIMESTAMP_MONOTONIC 0x6 |
| #endif |
| |
| +#ifndef DRM_CLIENT_CAP_UNIVERSAL_PLANES |
| +#define DRM_CLIENT_CAP_UNIVERSAL_PLANES 2 |
| +#endif |
| + |
| #ifndef DRM_CAP_CURSOR_WIDTH |
| #define DRM_CAP_CURSOR_WIDTH 0x8 |
| #endif |
| @@ -78,6 +85,139 @@ |
| #define GBM_BO_USE_CURSOR GBM_BO_USE_CURSOR_64X64 |
| #endif |
| |
| +/** |
| + * Represents the values of an enum-type KMS property |
| + */ |
| +struct drm_property_enum_info { |
| + const char *name; /**< name as string (static, not freed) */ |
| + bool valid; /**< true if value is supported; ignore if false */ |
| + uint64_t value; /**< raw value */ |
| +}; |
| + |
| +/** |
| + * Holds information on a DRM property, including its ID and the enum |
| + * values it holds. |
| + * |
| + * DRM properties are allocated dynamically, and maintained as DRM objects |
| + * within the normal object ID space; they thus do not have a stable ID |
| + * to refer to. This includes enum values, which must be referred to by |
| + * integer values, but these are not stable. |
| + * |
| + * drm_property_info allows a cache to be maintained where Weston can use |
| + * enum values internally to refer to properties, with the mapping to DRM |
| + * ID values being maintained internally. |
| + */ |
| +struct drm_property_info { |
| + const char *name; /**< name as string (static, not freed) */ |
| + uint32_t prop_id; /**< KMS property object ID */ |
| + unsigned int num_enum_values; /**< number of enum values */ |
| + struct drm_property_enum_info *enum_values; /**< array of enum values */ |
| +}; |
| + |
| +/** |
| + * List of properties attached to DRM planes |
| + */ |
| +enum wdrm_plane_property { |
| + WDRM_PLANE_TYPE = 0, |
| + WDRM_PLANE_SRC_X, |
| + WDRM_PLANE_SRC_Y, |
| + WDRM_PLANE_SRC_W, |
| + WDRM_PLANE_SRC_H, |
| + WDRM_PLANE_CRTC_X, |
| + WDRM_PLANE_CRTC_Y, |
| + WDRM_PLANE_CRTC_W, |
| + WDRM_PLANE_CRTC_H, |
| + WDRM_PLANE_FB_ID, |
| + WDRM_PLANE_CRTC_ID, |
| + WDRM_PLANE__COUNT |
| +}; |
| + |
| +/** |
| + * Possible values for the WDRM_PLANE_TYPE property. |
| + */ |
| +enum wdrm_plane_type { |
| + WDRM_PLANE_TYPE_PRIMARY = 0, |
| + WDRM_PLANE_TYPE_CURSOR, |
| + WDRM_PLANE_TYPE_OVERLAY, |
| + WDRM_PLANE_TYPE__COUNT |
| +}; |
| + |
| +static struct drm_property_enum_info plane_type_enums[] = { |
| + [WDRM_PLANE_TYPE_PRIMARY] = { |
| + .name = "Primary", |
| + }, |
| + [WDRM_PLANE_TYPE_OVERLAY] = { |
| + .name = "Overlay", |
| + }, |
| + [WDRM_PLANE_TYPE_CURSOR] = { |
| + .name = "Cursor", |
| + }, |
| +}; |
| + |
| +static const struct drm_property_info plane_props[] = { |
| + [WDRM_PLANE_TYPE] = { |
| + .name = "type", |
| + .enum_values = plane_type_enums, |
| + .num_enum_values = WDRM_PLANE_TYPE__COUNT, |
| + }, |
| + [WDRM_PLANE_SRC_X] = { .name = "SRC_X", }, |
| + [WDRM_PLANE_SRC_Y] = { .name = "SRC_Y", }, |
| + [WDRM_PLANE_SRC_W] = { .name = "SRC_W", }, |
| + [WDRM_PLANE_SRC_H] = { .name = "SRC_H", }, |
| + [WDRM_PLANE_CRTC_X] = { .name = "CRTC_X", }, |
| + [WDRM_PLANE_CRTC_Y] = { .name = "CRTC_Y", }, |
| + [WDRM_PLANE_CRTC_W] = { .name = "CRTC_W", }, |
| + [WDRM_PLANE_CRTC_H] = { .name = "CRTC_H", }, |
| + [WDRM_PLANE_FB_ID] = { .name = "FB_ID", }, |
| + [WDRM_PLANE_CRTC_ID] = { .name = "CRTC_ID", }, |
| +}; |
| + |
| +/** |
| + * List of properties attached to a DRM connector |
| + */ |
| +enum wdrm_connector_property { |
| + WDRM_CONNECTOR_EDID = 0, |
| + WDRM_CONNECTOR_DPMS, |
| + WDRM_CONNECTOR_CRTC_ID, |
| + WDRM_CONNECTOR__COUNT |
| +}; |
| + |
| +static const struct drm_property_info connector_props[] = { |
| + [WDRM_CONNECTOR_EDID] = { .name = "EDID" }, |
| + [WDRM_CONNECTOR_DPMS] = { .name = "DPMS" }, |
| + [WDRM_CONNECTOR_CRTC_ID] = { .name = "CRTC_ID", }, |
| +}; |
| + |
| +/** |
| + * List of properties attached to DRM CRTCs |
| + */ |
| +enum wdrm_crtc_property { |
| + WDRM_CRTC_MODE_ID = 0, |
| + WDRM_CRTC_ACTIVE, |
| + WDRM_CRTC__COUNT |
| +}; |
| + |
| +static const struct drm_property_info crtc_props[] = { |
| + [WDRM_CRTC_MODE_ID] = { .name = "MODE_ID", }, |
| + [WDRM_CRTC_ACTIVE] = { .name = "ACTIVE", }, |
| +}; |
| + |
| +/** |
| + * Mode for drm_output_state_duplicate. |
| + */ |
| +enum drm_output_state_duplicate_mode { |
| + DRM_OUTPUT_STATE_CLEAR_PLANES, /**< reset all planes to off */ |
| + DRM_OUTPUT_STATE_PRESERVE_PLANES, /**< preserve plane state */ |
| +}; |
| + |
| +/** |
| + * Mode for drm_pending_state_apply and co. |
| + */ |
| +enum drm_state_apply_mode { |
| + DRM_STATE_APPLY_SYNC, /**< state fully processed */ |
| + DRM_STATE_APPLY_ASYNC, /**< state pending event delivery */ |
| +}; |
| + |
| struct drm_backend { |
| struct weston_backend base; |
| struct weston_compositor *compositor; |
| @@ -103,14 +243,24 @@ struct drm_backend { |
| */ |
| int min_width, max_width; |
| int min_height, max_height; |
| - int no_addfb2; |
| |
| - struct wl_list sprite_list; |
| + struct wl_list plane_list; |
| int sprites_are_broken; |
| int sprites_hidden; |
| |
| + void *repaint_data; |
| + |
| + bool state_invalid; |
| + |
| + /* Connector and CRTC IDs not used by any enabled output. */ |
| + struct wl_array unused_connectors; |
| + struct wl_array unused_crtcs; |
| + |
| int cursors_are_broken; |
| |
| + bool universal_planes; |
| + bool atomic_modeset; |
| + |
| int use_pixman; |
| |
| struct udev_input input; |
| @@ -118,23 +268,39 @@ struct drm_backend { |
| int32_t cursor_width; |
| int32_t cursor_height; |
| |
| - uint32_t connector; |
| + uint32_t pageflip_timeout; |
| + |
| + bool shutting_down; |
| }; |
| |
| struct drm_mode { |
| struct weston_mode base; |
| drmModeModeInfo mode_info; |
| + uint32_t blob_id; |
| +}; |
| + |
| +enum drm_fb_type { |
| + BUFFER_INVALID = 0, /**< never used */ |
| + BUFFER_CLIENT, /**< directly sourced from client */ |
| + BUFFER_PIXMAN_DUMB, /**< internal Pixman rendering */ |
| + BUFFER_GBM_SURFACE, /**< internal EGL rendering */ |
| + BUFFER_CURSOR, /**< internal cursor buffer */ |
| }; |
| |
| struct drm_fb { |
| + enum drm_fb_type type; |
| + |
| + int refcnt; |
| + |
| uint32_t fb_id, stride, handle, size; |
| + const struct pixel_format_info *format; |
| int width, height; |
| int fd; |
| - int is_client_buffer; |
| struct weston_buffer_reference buffer_ref; |
| |
| /* Used by gbm fbs */ |
| struct gbm_bo *bo; |
| + struct gbm_surface *gbm_surface; |
| |
| /* Used by dumb fbs */ |
| void *map; |
| @@ -147,6 +313,95 @@ struct drm_edid { |
| char serial_number[13]; |
| }; |
| |
| +/** |
| + * Pending state holds one or more drm_output_state structures, collected from |
| + * performing repaint. This pending state is transient, and only lives between |
| + * beginning a repaint group and flushing the results: after flush, each |
| + * output state will complete and be retired separately. |
| + */ |
| +struct drm_pending_state { |
| + struct drm_backend *backend; |
| + struct wl_list output_list; |
| +}; |
| + |
| +/* |
| + * Output state holds the dynamic state for one Weston output, i.e. a KMS CRTC, |
| + * plus >= 1 each of encoder/connector/plane. Since everything but the planes |
| + * is currently statically assigned per-output, we mainly use this to track |
| + * plane state. |
| + * |
| + * pending_state is set when the output state is owned by a pending_state, |
| + * i.e. when it is being constructed and has not yet been applied. When the |
| + * output state has been applied, the owning pending_state is freed. |
| + */ |
| +struct drm_output_state { |
| + struct drm_pending_state *pending_state; |
| + struct drm_output *output; |
| + struct wl_list link; |
| + enum dpms_enum dpms; |
| + struct wl_list plane_list; |
| +}; |
| + |
| +/** |
| + * Plane state holds the dynamic state for a plane: where it is positioned, |
| + * and which buffer it is currently displaying. |
| + * |
| + * The plane state is owned by an output state, except when setting an initial |
| + * state. See drm_output_state for notes on state object lifetime. |
| + */ |
| +struct drm_plane_state { |
| + struct drm_plane *plane; |
| + struct drm_output *output; |
| + struct drm_output_state *output_state; |
| + |
| + struct drm_fb *fb; |
| + |
| + int32_t src_x, src_y; |
| + uint32_t src_w, src_h; |
| + int32_t dest_x, dest_y; |
| + uint32_t dest_w, dest_h; |
| + |
| + bool complete; |
| + |
| + struct wl_list link; /* drm_output_state::plane_list */ |
| +}; |
| + |
| +/** |
| + * A plane represents one buffer, positioned within a CRTC, and stacked |
| + * relative to other planes on the same CRTC. |
| + * |
| + * Each CRTC has a 'primary plane', which use used to display the classic |
| + * framebuffer contents, as accessed through the legacy drmModeSetCrtc |
| + * call (which combines setting the CRTC's actual physical mode, and the |
| + * properties of the primary plane). |
| + * |
| + * The cursor plane also has its own alternate legacy API. |
| + * |
| + * Other planes are used opportunistically to display content we do not |
| + * wish to blit into the primary plane. These non-primary/cursor planes |
| + * are referred to as 'sprites'. |
| + */ |
| +struct drm_plane { |
| + struct weston_plane base; |
| + |
| + struct drm_backend *backend; |
| + |
| + enum wdrm_plane_type type; |
| + |
| + uint32_t possible_crtcs; |
| + uint32_t plane_id; |
| + uint32_t count_formats; |
| + |
| + struct drm_property_info props[WDRM_PLANE__COUNT]; |
| + |
| + /* The last state submitted to the kernel for this plane. */ |
| + struct drm_plane_state *state_cur; |
| + |
| + struct wl_list link; |
| + |
| + uint32_t formats[]; |
| +}; |
| + |
| struct drm_output { |
| struct weston_output base; |
| drmModeConnector *connector; |
| @@ -154,26 +409,38 @@ struct drm_output { |
| uint32_t crtc_id; /* object ID to pass to DRM functions */ |
| int pipe; /* index of CRTC in resource array / bitmasks */ |
| uint32_t connector_id; |
| - drmModeCrtcPtr original_crtc; |
| struct drm_edid edid; |
| - drmModePropertyPtr dpms_prop; |
| - uint32_t gbm_format; |
| |
| - enum dpms_enum dpms; |
| + /* Holds the properties for the connector */ |
| + struct drm_property_info props_conn[WDRM_CONNECTOR__COUNT]; |
| + /* Holds the properties for the CRTC */ |
| + struct drm_property_info props_crtc[WDRM_CRTC__COUNT]; |
| + |
| + struct backlight *backlight; |
| |
| int vblank_pending; |
| int page_flip_pending; |
| + int atomic_complete_pending; |
| int destroy_pending; |
| int disable_pending; |
| + int dpms_off_pending; |
| |
| - struct gbm_surface *gbm_surface; |
| - struct gbm_bo *gbm_cursor_bo[2]; |
| - struct weston_plane cursor_plane; |
| - struct weston_plane fb_plane; |
| + struct drm_fb *gbm_cursor_fb[2]; |
| + struct drm_plane *cursor_plane; |
| struct weston_view *cursor_view; |
| int current_cursor; |
| - struct drm_fb *current, *next; |
| - struct backlight *backlight; |
| + |
| + struct gbm_surface *gbm_surface; |
| + uint32_t gbm_format; |
| + |
| + /* Plane being displayed directly on the CRTC */ |
| + struct drm_plane *scanout_plane; |
| + |
| + /* The last state submitted to the kernel for this CRTC. */ |
| + struct drm_output_state *state_cur; |
| + /* The previously-submitted state, where the hardware has not |
| + * yet acknowledged completion of state_cur. */ |
| + struct drm_output_state *state_last; |
| |
| struct drm_fb *dumb[2]; |
| pixman_image_t *image[2]; |
| @@ -182,36 +449,33 @@ struct drm_output { |
| |
| struct vaapi_recorder *recorder; |
| struct wl_listener recorder_frame_listener; |
| -}; |
| |
| -/* |
| - * An output has a primary display plane plus zero or more sprites for |
| - * blending display contents. |
| - */ |
| -struct drm_sprite { |
| - struct wl_list link; |
| + struct wl_event_source *pageflip_timer; |
| +}; |
| |
| - struct weston_plane plane; |
| +static struct gl_renderer_interface *gl_renderer; |
| |
| - struct drm_fb *current, *next; |
| - struct drm_output *output; |
| - struct drm_backend *backend; |
| +static const char default_seat[] = "seat0"; |
| |
| - uint32_t possible_crtcs; |
| - uint32_t plane_id; |
| - uint32_t count_formats; |
| +static void |
| +wl_array_remove_uint32(struct wl_array *array, uint32_t elm) |
| +{ |
| + uint32_t *pos, *end; |
| |
| - int32_t src_x, src_y; |
| - uint32_t src_w, src_h; |
| - uint32_t dest_x, dest_y; |
| - uint32_t dest_w, dest_h; |
| + end = (uint32_t *) ((char *) array->data + array->size); |
| |
| - uint32_t formats[]; |
| -}; |
| + wl_array_for_each(pos, array) { |
| + if (*pos != elm) |
| + continue; |
| |
| -static struct gl_renderer_interface *gl_renderer; |
| + array->size -= sizeof(*pos); |
| + if (pos + 1 == end) |
| + break; |
| |
| -static const char default_seat[] = "seat0"; |
| + memmove(pos, pos + 1, (char *) end - (char *) (pos + 1)); |
| + break; |
| + } |
| +} |
| |
| static inline struct drm_output * |
| to_drm_output(struct weston_output *base) |
| @@ -225,16 +489,286 @@ to_drm_backend(struct weston_compositor *base) |
| return container_of(base->backend, struct drm_backend, base); |
| } |
| |
| +static int |
| +pageflip_timeout(void *data) { |
| + /* |
| + * Our timer just went off, that means we're not receiving drm |
| + * page flip events anymore for that output. Let's gracefully exit |
| + * weston with a return value so devs can debug what's going on. |
| + */ |
| + struct drm_output *output = data; |
| + struct weston_compositor *compositor = output->base.compositor; |
| + |
| + weston_log("Pageflip timeout reached on output %s, your " |
| + "driver is probably buggy! Exiting.\n", |
| + output->base.name); |
| + weston_compositor_exit_with_code(compositor, EXIT_FAILURE); |
| + |
| + return 0; |
| +} |
| + |
| +/* Creates the pageflip timer. Note that it isn't armed by default */ |
| +static int |
| +drm_output_pageflip_timer_create(struct drm_output *output) |
| +{ |
| + struct wl_event_loop *loop = NULL; |
| + struct weston_compositor *ec = output->base.compositor; |
| + |
| + loop = wl_display_get_event_loop(ec->wl_display); |
| + assert(loop); |
| + output->pageflip_timer = wl_event_loop_add_timer(loop, |
| + pageflip_timeout, |
| + output); |
| + |
| + if (output->pageflip_timer == NULL) { |
| + weston_log("creating drm pageflip timer failed: %m\n"); |
| + return -1; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +static inline struct drm_mode * |
| +to_drm_mode(struct weston_mode *base) |
| +{ |
| + return container_of(base, struct drm_mode, base); |
| +} |
| + |
| +/** |
| + * Get the current value of a KMS property |
| + * |
| + * Given a drmModeObjectGetProperties return, as well as the drm_property_info |
| + * for the target property, return the current value of that property, |
| + * with an optional default. If the property is a KMS enum type, the return |
| + * value will be translated into the appropriate internal enum. |
| + * |
| + * If the property is not present, the default value will be returned. |
| + * |
| + * @param info Internal structure for property to look up |
| + * @param props Raw KMS properties for the target object |
| + * @param def Value to return if property is not found |
| + */ |
| +static uint64_t |
| +drm_property_get_value(struct drm_property_info *info, |
| + drmModeObjectPropertiesPtr props, |
| + uint64_t def) |
| +{ |
| + unsigned int i; |
| + |
| + if (info->prop_id == 0) |
| + return def; |
| + |
| + for (i = 0; i < props->count_props; i++) { |
| + unsigned int j; |
| + |
| + if (props->props[i] != info->prop_id) |
| + continue; |
| + |
| + /* Simple (non-enum) types can return the value directly */ |
| + if (info->num_enum_values == 0) |
| + return props->prop_values[i]; |
| + |
| + /* Map from raw value to enum value */ |
| + for (j = 0; j < info->num_enum_values; j++) { |
| + if (!info->enum_values[j].valid) |
| + continue; |
| + if (info->enum_values[j].value != props->prop_values[i]) |
| + continue; |
| + |
| + return j; |
| + } |
| + |
| + /* We don't have a mapping for this enum; return default. */ |
| + break; |
| + } |
| + |
| + return def; |
| +} |
| + |
| +/** |
| + * Cache DRM property values |
| + * |
| + * Update a per-object array of drm_property_info structures, given the |
| + * DRM properties of the object. |
| + * |
| + * Call this every time an object newly appears (note that only connectors |
| + * can be hotplugged), the first time it is seen, or when its status changes |
| + * in a way which invalidates the potential property values (currently, the |
| + * only case for this is connector hotplug). |
| + * |
| + * This updates the property IDs and enum values within the drm_property_info |
| + * array. |
| + * |
| + * DRM property enum values are dynamic at runtime; the user must query the |
| + * property to find out the desired runtime value for a requested string |
| + * name. Using the 'type' field on planes as an example, there is no single |
| + * hardcoded constant for primary plane types; instead, the property must be |
| + * queried at runtime to find the value associated with the string "Primary". |
| + * |
| + * This helper queries and caches the enum values, to allow us to use a set |
| + * of compile-time-constant enums portably across various implementations. |
| + * The values given in enum_names are searched for, and stored in the |
| + * same-indexed field of the map array. |
| + * |
| + * @param b DRM backend object |
| + * @param src DRM property info array to source from |
| + * @param info DRM property info array to copy into |
| + * @param num_infos Number of entries in the source array |
| + * @param props DRM object properties for the object |
| + */ |
| +static void |
| +drm_property_info_populate(struct drm_backend *b, |
| + const struct drm_property_info *src, |
| + struct drm_property_info *info, |
| + unsigned int num_infos, |
| + drmModeObjectProperties *props) |
| +{ |
| + drmModePropertyRes *prop; |
| + unsigned i, j; |
| + |
| + for (i = 0; i < num_infos; i++) { |
| + unsigned int j; |
| + |
| + info[i].name = src[i].name; |
| + info[i].prop_id = 0; |
| + info[i].num_enum_values = src[i].num_enum_values; |
| + |
| + if (src[i].num_enum_values == 0) |
| + continue; |
| + |
| + info[i].enum_values = |
| + malloc(src[i].num_enum_values * |
| + sizeof(*info[i].enum_values)); |
| + assert(info[i].enum_values); |
| + for (j = 0; j < info[i].num_enum_values; j++) { |
| + info[i].enum_values[j].name = src[i].enum_values[j].name; |
| + info[i].enum_values[j].valid = false; |
| + } |
| + } |
| + |
| + for (i = 0; i < props->count_props; i++) { |
| + unsigned int k; |
| + |
| + prop = drmModeGetProperty(b->drm.fd, props->props[i]); |
| + if (!prop) |
| + continue; |
| + |
| + for (j = 0; j < num_infos; j++) { |
| + if (!strcmp(prop->name, info[j].name)) |
| + break; |
| + } |
| + |
| + /* We don't know/care about this property. */ |
| + if (j == num_infos) { |
| +#ifdef DEBUG |
| + weston_log("DRM debug: unrecognized property %u '%s'\n", |
| + prop->prop_id, prop->name); |
| +#endif |
| + drmModeFreeProperty(prop); |
| + continue; |
| + } |
| + |
| + if (info[j].num_enum_values == 0 && |
| + (prop->flags & DRM_MODE_PROP_ENUM)) { |
| + weston_log("DRM: expected property %s to not be an" |
| + " enum, but it is; ignoring\n", prop->name); |
| + drmModeFreeProperty(prop); |
| + continue; |
| + } |
| + |
| + info[j].prop_id = props->props[i]; |
| + |
| + if (info[j].num_enum_values == 0) { |
| + drmModeFreeProperty(prop); |
| + continue; |
| + } |
| + |
| + if (!(prop->flags & DRM_MODE_PROP_ENUM)) { |
| + weston_log("DRM: expected property %s to be an enum," |
| + " but it is not; ignoring\n", prop->name); |
| + drmModeFreeProperty(prop); |
| + info[j].prop_id = 0; |
| + continue; |
| + } |
| + |
| + for (k = 0; k < info[j].num_enum_values; k++) { |
| + int l; |
| + |
| + for (l = 0; l < prop->count_enums; l++) { |
| + if (!strcmp(prop->enums[l].name, |
| + info[j].enum_values[k].name)) |
| + break; |
| + } |
| + |
| + if (l == prop->count_enums) |
| + continue; |
| + |
| + info[j].enum_values[k].valid = true; |
| + info[j].enum_values[k].value = prop->enums[l].value; |
| + } |
| + |
| + drmModeFreeProperty(prop); |
| + } |
| + |
| +#ifdef DEBUG |
| + for (i = 0; i < num_infos; i++) { |
| + if (info[i].prop_id == 0) |
| + weston_log("DRM warning: property '%s' missing\n", |
| + info[i].name); |
| + } |
| +#endif |
| +} |
| + |
| +/** |
| + * Free DRM property information |
| + * |
| + * Frees all memory associated with a DRM property info array and zeroes |
| + * it out, leaving it usable for a further drm_property_info_update() or |
| + * drm_property_info_free(). |
| + * |
| + * @param info DRM property info array |
| + * @param num_props Number of entries in array to free |
| + */ |
| +static void |
| +drm_property_info_free(struct drm_property_info *info, int num_props) |
| +{ |
| + int i; |
| + |
| + for (i = 0; i < num_props; i++) |
| + free(info[i].enum_values); |
| + |
| + memset(info, 0, sizeof(*info) * num_props); |
| +} |
| + |
| static void |
| -drm_output_set_cursor(struct drm_output *output); |
| +drm_output_set_cursor(struct drm_output_state *output_state); |
| |
| static void |
| drm_output_update_msc(struct drm_output *output, unsigned int seq); |
| |
| -static int |
| -drm_sprite_crtc_supported(struct drm_output *output, struct drm_sprite *sprite) |
| +static void |
| +drm_output_destroy(struct weston_output *output_base); |
| + |
| +/** |
| + * Returns true if the plane can be used on the given output for its current |
| + * repaint cycle. |
| + */ |
| +static bool |
| +drm_plane_is_available(struct drm_plane *plane, struct drm_output *output) |
| { |
| - return !!(sprite->possible_crtcs & (1 << output->pipe)); |
| + assert(plane->state_cur); |
| + |
| + /* The plane still has a request not yet completed by the kernel. */ |
| + if (!plane->state_cur->complete) |
| + return false; |
| + |
| + /* The plane is still active on another output. */ |
| + if (plane->state_cur->output && plane->state_cur->output != output) |
| + return false; |
| + |
| + /* Check whether the plane can be used with this CRTC; possible_crtcs |
| + * is a bitmask of CRTC indices (pipe), rather than CRTC object ID. */ |
| + return !!(plane->possible_crtcs & (1 << output->pipe)); |
| } |
| |
| static struct drm_output * |
| @@ -276,16 +810,39 @@ drm_output_find_by_connector(struct drm_backend *b, uint32_t connector_id) |
| } |
| |
| static void |
| -drm_fb_destroy_callback(struct gbm_bo *bo, void *data) |
| +drm_fb_destroy(struct drm_fb *fb) |
| { |
| - struct drm_fb *fb = data; |
| - |
| - if (fb->fb_id) |
| + if (fb->fb_id != 0) |
| drmModeRmFB(fb->fd, fb->fb_id); |
| - |
| weston_buffer_reference(&fb->buffer_ref, NULL); |
| + free(fb); |
| +} |
| + |
| +static void |
| +drm_fb_destroy_dumb(struct drm_fb *fb) |
| +{ |
| + struct drm_mode_destroy_dumb destroy_arg; |
| |
| - free(data); |
| + assert(fb->type == BUFFER_PIXMAN_DUMB); |
| + |
| + if (fb->map && fb->size > 0) |
| + munmap(fb->map, fb->size); |
| + |
| + memset(&destroy_arg, 0, sizeof(destroy_arg)); |
| + destroy_arg.handle = fb->handle; |
| + drmIoctl(fb->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg); |
| + |
| + drm_fb_destroy(fb); |
| +} |
| + |
| +static void |
| +drm_fb_destroy_gbm(struct gbm_bo *bo, void *data) |
| +{ |
| + struct drm_fb *fb = data; |
| + |
| + assert(fb->type == BUFFER_GBM_SURFACE || fb->type == BUFFER_CLIENT || |
| + fb->type == BUFFER_CURSOR); |
| + drm_fb_destroy(fb); |
| } |
| |
| static struct drm_fb * |
| @@ -294,30 +851,33 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, |
| { |
| struct drm_fb *fb; |
| int ret; |
| - uint32_t bpp, depth; |
| |
| struct drm_mode_create_dumb create_arg; |
| struct drm_mode_destroy_dumb destroy_arg; |
| struct drm_mode_map_dumb map_arg; |
| + uint32_t handles[4] = { 0 }, pitches[4] = { 0 }, offsets[4] = { 0 }; |
| |
| fb = zalloc(sizeof *fb); |
| if (!fb) |
| return NULL; |
| |
| - switch (format) { |
| - case GBM_FORMAT_XRGB8888: |
| - bpp = 32; |
| - depth = 24; |
| - break; |
| - case GBM_FORMAT_RGB565: |
| - bpp = depth = 16; |
| - break; |
| - default: |
| - return NULL; |
| + fb->refcnt = 1; |
| + |
| + fb->format = pixel_format_get_info(format); |
| + if (!fb->format) { |
| + weston_log("failed to look up format 0x%lx\n", |
| + (unsigned long) format); |
| + goto err_fb; |
| + } |
| + |
| + if (!fb->format->depth || !fb->format->bpp) { |
| + weston_log("format 0x%lx is not compatible with dumb buffers\n", |
| + (unsigned long) format); |
| + goto err_fb; |
| } |
| |
| memset(&create_arg, 0, sizeof create_arg); |
| - create_arg.bpp = bpp; |
| + create_arg.bpp = fb->format->bpp; |
| create_arg.width = width; |
| create_arg.height = height; |
| |
| @@ -325,6 +885,7 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, |
| if (ret) |
| goto err_fb; |
| |
| + fb->type = BUFFER_PIXMAN_DUMB; |
| fb->handle = create_arg.handle; |
| fb->stride = create_arg.pitch; |
| fb->size = create_arg.size; |
| @@ -332,26 +893,15 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, |
| fb->height = height; |
| fb->fd = b->drm.fd; |
| |
| - ret = -1; |
| - |
| - if (!b->no_addfb2) { |
| - uint32_t handles[4] = { 0 }, pitches[4] = { 0 }, offsets[4] = { 0 }; |
| - |
| - handles[0] = fb->handle; |
| - pitches[0] = fb->stride; |
| - offsets[0] = 0; |
| - |
| - ret = drmModeAddFB2(b->drm.fd, width, height, |
| - format, handles, pitches, offsets, |
| - &fb->fb_id, 0); |
| - if (ret) { |
| - weston_log("addfb2 failed: %m\n"); |
| - b->no_addfb2 = 1; |
| - } |
| - } |
| + handles[0] = fb->handle; |
| + pitches[0] = fb->stride; |
| + offsets[0] = 0; |
| |
| + ret = drmModeAddFB2(b->drm.fd, width, height, fb->format->format, |
| + handles, pitches, offsets, &fb->fb_id, 0); |
| if (ret) { |
| - ret = drmModeAddFB(b->drm.fd, width, height, depth, bpp, |
| + ret = drmModeAddFB(b->drm.fd, width, height, |
| + fb->format->depth, fb->format->bpp, |
| fb->stride, fb->handle, &fb->fb_id); |
| } |
| |
| @@ -382,52 +932,50 @@ err_fb: |
| return NULL; |
| } |
| |
| -static void |
| -drm_fb_destroy_dumb(struct drm_fb *fb) |
| +static struct drm_fb * |
| +drm_fb_ref(struct drm_fb *fb) |
| { |
| - struct drm_mode_destroy_dumb destroy_arg; |
| - |
| - if (!fb->map) |
| - return; |
| - |
| - if (fb->fb_id) |
| - drmModeRmFB(fb->fd, fb->fb_id); |
| - |
| - weston_buffer_reference(&fb->buffer_ref, NULL); |
| - |
| - munmap(fb->map, fb->size); |
| - |
| - memset(&destroy_arg, 0, sizeof(destroy_arg)); |
| - destroy_arg.handle = fb->handle; |
| - drmIoctl(fb->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg); |
| - |
| - free(fb); |
| + fb->refcnt++; |
| + return fb; |
| } |
| |
| static struct drm_fb * |
| -drm_fb_get_from_bo(struct gbm_bo *bo, |
| - struct drm_backend *backend, uint32_t format) |
| +drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, |
| + uint32_t format, enum drm_fb_type type) |
| { |
| struct drm_fb *fb = gbm_bo_get_user_data(bo); |
| uint32_t handles[4] = { 0 }, pitches[4] = { 0 }, offsets[4] = { 0 }; |
| int ret; |
| |
| - if (fb) |
| - return fb; |
| + if (fb) { |
| + assert(fb->type == type); |
| + return drm_fb_ref(fb); |
| + } |
| + |
| + assert(format != 0); |
| |
| fb = zalloc(sizeof *fb); |
| if (fb == NULL) |
| return NULL; |
| |
| + fb->type = type; |
| + fb->refcnt = 1; |
| fb->bo = bo; |
| |
| fb->width = gbm_bo_get_width(bo); |
| fb->height = gbm_bo_get_height(bo); |
| fb->stride = gbm_bo_get_stride(bo); |
| fb->handle = gbm_bo_get_handle(bo).u32; |
| + fb->format = pixel_format_get_info(format); |
| fb->size = fb->stride * fb->height; |
| fb->fd = backend->drm.fd; |
| |
| + if (!fb->format) { |
| + weston_log("couldn't look up format 0x%lx\n", |
| + (unsigned long) format); |
| + goto err_free; |
| + } |
| + |
| if (backend->min_width > fb->width || |
| fb->width > backend->max_width || |
| backend->min_height > fb->height || |
| @@ -436,33 +984,24 @@ drm_fb_get_from_bo(struct gbm_bo *bo, |
| goto err_free; |
| } |
| |
| - ret = -1; |
| - |
| - if (format && !backend->no_addfb2) { |
| - handles[0] = fb->handle; |
| - pitches[0] = fb->stride; |
| - offsets[0] = 0; |
| + handles[0] = fb->handle; |
| + pitches[0] = fb->stride; |
| + offsets[0] = 0; |
| |
| - ret = drmModeAddFB2(backend->drm.fd, fb->width, fb->height, |
| - format, handles, pitches, offsets, |
| - &fb->fb_id, 0); |
| - if (ret) { |
| - weston_log("addfb2 failed: %m\n"); |
| - backend->no_addfb2 = 1; |
| - backend->sprites_are_broken = 1; |
| - } |
| - } |
| - |
| - if (ret) |
| + ret = drmModeAddFB2(backend->drm.fd, fb->width, fb->height, |
| + fb->format->format, handles, pitches, offsets, |
| + &fb->fb_id, 0); |
| + if (ret && fb->format->depth && fb->format->bpp) |
| ret = drmModeAddFB(backend->drm.fd, fb->width, fb->height, |
| - 24, 32, fb->stride, fb->handle, &fb->fb_id); |
| + fb->format->depth, fb->format->bpp, |
| + fb->stride, fb->handle, &fb->fb_id); |
| |
| if (ret) { |
| weston_log("failed to create kms fb: %m\n"); |
| goto err_free; |
| } |
| |
| - gbm_bo_set_user_data(bo, fb, drm_fb_destroy_callback); |
| + gbm_bo_set_user_data(bo, fb, drm_fb_destroy_gbm); |
| |
| return fb; |
| |
| @@ -475,28 +1014,496 @@ static void |
| drm_fb_set_buffer(struct drm_fb *fb, struct weston_buffer *buffer) |
| { |
| assert(fb->buffer_ref.buffer == NULL); |
| + assert(fb->type == BUFFER_CLIENT); |
| + weston_buffer_reference(&fb->buffer_ref, buffer); |
| +} |
| + |
| +static void |
| +drm_fb_unref(struct drm_fb *fb) |
| +{ |
| + if (!fb) |
| + return; |
| + |
| + assert(fb->refcnt > 0); |
| + if (--fb->refcnt > 0) |
| + return; |
| + |
| + switch (fb->type) { |
| + case BUFFER_PIXMAN_DUMB: |
| + drm_fb_destroy_dumb(fb); |
| + break; |
| + case BUFFER_CURSOR: |
| + case BUFFER_CLIENT: |
| + gbm_bo_destroy(fb->bo); |
| + break; |
| + case BUFFER_GBM_SURFACE: |
| + gbm_surface_release_buffer(fb->gbm_surface, fb->bo); |
| + break; |
| + default: |
| + assert(NULL); |
| + break; |
| + } |
| +} |
| + |
| +/** |
| + * Allocate a new, empty, plane state. |
| + */ |
| +static struct drm_plane_state * |
| +drm_plane_state_alloc(struct drm_output_state *state_output, |
| + struct drm_plane *plane) |
| +{ |
| + struct drm_plane_state *state = zalloc(sizeof(*state)); |
| + |
| + assert(state); |
| + state->output_state = state_output; |
| + state->plane = plane; |
| + |
| + /* Here we only add the plane state to the desired link, and not |
| + * set the member. Having an output pointer set means that the |
| + * plane will be displayed on the output; this won't be the case |
| + * when we go to disable a plane. In this case, it must be part of |
| + * the commit (and thus the output state), but the member must be |
| + * NULL, as it will not be on any output when the state takes |
| + * effect. |
| + */ |
| + if (state_output) |
| + wl_list_insert(&state_output->plane_list, &state->link); |
| + else |
| + wl_list_init(&state->link); |
| + |
| + return state; |
| +} |
| + |
| +/** |
| + * Free an existing plane state. As a special case, the state will not |
| + * normally be freed if it is the current state; see drm_plane_set_state. |
| + */ |
| +static void |
| +drm_plane_state_free(struct drm_plane_state *state, bool force) |
| +{ |
| + if (!state) |
| + return; |
| + |
| + wl_list_remove(&state->link); |
| + wl_list_init(&state->link); |
| + state->output_state = NULL; |
| + |
| + if (force || state != state->plane->state_cur) { |
| + drm_fb_unref(state->fb); |
| + free(state); |
| + } |
| +} |
| + |
| +/** |
| + * Duplicate an existing plane state into a new plane state, storing it within |
| + * the given output state. If the output state already contains a plane state |
| + * for the drm_plane referenced by 'src', that plane state is freed first. |
| + */ |
| +static struct drm_plane_state * |
| +drm_plane_state_duplicate(struct drm_output_state *state_output, |
| + struct drm_plane_state *src) |
| +{ |
| + struct drm_plane_state *dst = malloc(sizeof(*dst)); |
| + struct drm_plane_state *old, *tmp; |
| + |
| + assert(src); |
| + assert(dst); |
| + *dst = *src; |
| + wl_list_init(&dst->link); |
| + |
| + wl_list_for_each_safe(old, tmp, &state_output->plane_list, link) { |
| + /* Duplicating a plane state into the same output state, so |
| + * it can replace itself with an identical copy of itself, |
| + * makes no sense. */ |
| + assert(old != src); |
| + if (old->plane == dst->plane) |
| + drm_plane_state_free(old, false); |
| + } |
| + |
| + wl_list_insert(&state_output->plane_list, &dst->link); |
| + if (src->fb) |
| + dst->fb = drm_fb_ref(src->fb); |
| + dst->output_state = state_output; |
| + dst->complete = false; |
| + |
| + return dst; |
| +} |
| + |
| +/** |
| + * Remove a plane state from an output state; if the plane was previously |
| + * enabled, then replace it with a disabling state. This ensures that the |
| + * output state was untouched from it was before the plane state was |
| + * modified by the caller of this function. |
| + * |
| + * This is required as drm_output_state_get_plane may either allocate a |
| + * new plane state, in which case this function will just perform a matching |
| + * drm_plane_state_free, or it may instead repurpose an existing disabling |
| + * state (if the plane was previously active), in which case this function |
| + * will reset it. |
| + */ |
| +static void |
| +drm_plane_state_put_back(struct drm_plane_state *state) |
| +{ |
| + struct drm_output_state *state_output; |
| + struct drm_plane *plane; |
| + |
| + if (!state) |
| + return; |
| + |
| + state_output = state->output_state; |
| + plane = state->plane; |
| + drm_plane_state_free(state, false); |
| + |
| + /* Plane was previously disabled; no need to keep this temporary |
| + * state around. */ |
| + if (!plane->state_cur->fb) |
| + return; |
| + |
| + (void) drm_plane_state_alloc(state_output, plane); |
| +} |
| + |
| +/** |
| + * Return a plane state from a drm_output_state. |
| + */ |
| +static struct drm_plane_state * |
| +drm_output_state_get_existing_plane(struct drm_output_state *state_output, |
| + struct drm_plane *plane) |
| +{ |
| + struct drm_plane_state *ps; |
| + |
| + wl_list_for_each(ps, &state_output->plane_list, link) { |
| + if (ps->plane == plane) |
| + return ps; |
| + } |
| + |
| + return NULL; |
| +} |
| + |
| +/** |
| + * Return a plane state from a drm_output_state, either existing or |
| + * freshly allocated. |
| + */ |
| +static struct drm_plane_state * |
| +drm_output_state_get_plane(struct drm_output_state *state_output, |
| + struct drm_plane *plane) |
| +{ |
| + struct drm_plane_state *ps; |
| + |
| + ps = drm_output_state_get_existing_plane(state_output, plane); |
| + if (ps) |
| + return ps; |
| + |
| + return drm_plane_state_alloc(state_output, plane); |
| +} |
| + |
| +/** |
| + * Allocate a new, empty drm_output_state. This should not generally be used |
| + * in the repaint cycle; see drm_output_state_duplicate. |
| + */ |
| +static struct drm_output_state * |
| +drm_output_state_alloc(struct drm_output *output, |
| + struct drm_pending_state *pending_state) |
| +{ |
| + struct drm_output_state *state = zalloc(sizeof(*state)); |
| + |
| + assert(state); |
| + state->output = output; |
| + state->dpms = WESTON_DPMS_OFF; |
| + state->pending_state = pending_state; |
| + if (pending_state) |
| + wl_list_insert(&pending_state->output_list, &state->link); |
| + else |
| + wl_list_init(&state->link); |
| + |
| + wl_list_init(&state->plane_list); |
| + |
| + return state; |
| +} |
| + |
| +/** |
| + * Duplicate an existing drm_output_state into a new one. This is generally |
| + * used during the repaint cycle, to capture the existing state of an output |
| + * and modify it to create a new state to be used. |
| + * |
| + * The mode determines whether the output will be reset to an a blank state, |
| + * or an exact mirror of the current state. |
| + */ |
| +static struct drm_output_state * |
| +drm_output_state_duplicate(struct drm_output_state *src, |
| + struct drm_pending_state *pending_state, |
| + enum drm_output_state_duplicate_mode plane_mode) |
| +{ |
| + struct drm_output_state *dst = malloc(sizeof(*dst)); |
| + struct drm_plane_state *ps; |
| + |
| + assert(dst); |
| + |
| + /* Copy the whole structure, then individually modify the |
| + * pending_state, as well as the list link into our pending |
| + * state. */ |
| + *dst = *src; |
| + |
| + dst->pending_state = pending_state; |
| + if (pending_state) |
| + wl_list_insert(&pending_state->output_list, &dst->link); |
| + else |
| + wl_list_init(&dst->link); |
| + |
| + wl_list_init(&dst->plane_list); |
| + |
| + wl_list_for_each(ps, &src->plane_list, link) { |
| + /* Don't carry planes which are now disabled; these should be |
| + * free for other outputs to reuse. */ |
| + if (!ps->output) |
| + continue; |
| + |
| + if (plane_mode == DRM_OUTPUT_STATE_CLEAR_PLANES) |
| + (void) drm_plane_state_alloc(dst, ps->plane); |
| + else |
| + (void) drm_plane_state_duplicate(dst, ps); |
| + } |
| + |
| + return dst; |
| +} |
| + |
| +/** |
| + * Free an unused drm_output_state. |
| + */ |
| +static void |
| +drm_output_state_free(struct drm_output_state *state) |
| +{ |
| + struct drm_plane_state *ps, *next; |
| + |
| + if (!state) |
| + return; |
| + |
| + wl_list_for_each_safe(ps, next, &state->plane_list, link) |
| + drm_plane_state_free(ps, false); |
| + |
| + wl_list_remove(&state->link); |
| + |
| + free(state); |
| +} |
| + |
| +/** |
| + * Get output state to disable output |
| + * |
| + * Returns a pointer to an output_state object which can be used to disable |
| + * an output (e.g. DPMS off). |
| + * |
| + * @param pending_state The pending state object owning this update |
| + * @param output The output to disable |
| + * @returns A drm_output_state to disable the output |
| + */ |
| +static struct drm_output_state * |
| +drm_output_get_disable_state(struct drm_pending_state *pending_state, |
| + struct drm_output *output) |
| +{ |
| + struct drm_output_state *output_state; |
| + |
| + output_state = drm_output_state_duplicate(output->state_cur, |
| + pending_state, |
| + DRM_OUTPUT_STATE_CLEAR_PLANES); |
| + output_state->dpms = WESTON_DPMS_OFF; |
| + |
| + return output_state; |
| +} |
| + |
| +/** |
| + * Allocate a new drm_pending_state |
| + * |
| + * Allocate a new, empty, 'pending state' structure to be used across a |
| + * repaint cycle or similar. |
| + * |
| + * @param backend DRM backend |
| + * @returns Newly-allocated pending state structure |
| + */ |
| +static struct drm_pending_state * |
| +drm_pending_state_alloc(struct drm_backend *backend) |
| +{ |
| + struct drm_pending_state *ret; |
| + |
| + ret = calloc(1, sizeof(*ret)); |
| + if (!ret) |
| + return NULL; |
| + |
| + ret->backend = backend; |
| + wl_list_init(&ret->output_list); |
| + |
| + return ret; |
| +} |
| + |
| +/** |
| + * Free a drm_pending_state structure |
| + * |
| + * Frees a pending_state structure, as well as any output_states connected |
| + * to this pending state. |
| + * |
| + * @param pending_state Pending state structure to free |
| + */ |
| +static void |
| +drm_pending_state_free(struct drm_pending_state *pending_state) |
| +{ |
| + struct drm_output_state *output_state, *tmp; |
| + |
| + if (!pending_state) |
| + return; |
| + |
| + wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, |
| + link) { |
| + drm_output_state_free(output_state); |
| + } |
| + |
| + free(pending_state); |
| +} |
| + |
| +/** |
| + * Find an output state in a pending state |
| + * |
| + * Given a pending_state structure, find the output_state for a particular |
| + * output. |
| + * |
| + * @param pending_state Pending state structure to search |
| + * @param output Output to find state for |
| + * @returns Output state if present, or NULL if not |
| + */ |
| +static struct drm_output_state * |
| +drm_pending_state_get_output(struct drm_pending_state *pending_state, |
| + struct drm_output *output) |
| +{ |
| + struct drm_output_state *output_state; |
| + |
| + wl_list_for_each(output_state, &pending_state->output_list, link) { |
| + if (output_state->output == output) |
| + return output_state; |
| + } |
| + |
| + return NULL; |
| +} |
| + |
| +static int drm_pending_state_apply_sync(struct drm_pending_state *state); |
| + |
| +/** |
| + * Mark a drm_output_state (the output's last state) as complete. This handles |
| + * any post-completion actions such as updating the repaint timer, disabling the |
| + * output, and finally freeing the state. |
| + */ |
| +static void |
| +drm_output_update_complete(struct drm_output *output, uint32_t flags, |
| + unsigned int sec, unsigned int usec) |
| +{ |
| + struct drm_backend *b = to_drm_backend(output->base.compositor); |
| + struct drm_plane_state *ps; |
| + struct timespec ts; |
| + |
| + /* Stop the pageflip timer instead of rearming it here */ |
| + if (output->pageflip_timer) |
| + wl_event_source_timer_update(output->pageflip_timer, 0); |
| + |
| + wl_list_for_each(ps, &output->state_cur->plane_list, link) |
| + ps->complete = true; |
| + |
| + drm_output_state_free(output->state_last); |
| + output->state_last = NULL; |
| + |
| + if (output->destroy_pending) { |
| + output->destroy_pending = 0; |
| + output->disable_pending = 0; |
| + output->dpms_off_pending = 0; |
| + drm_output_destroy(&output->base); |
| + return; |
| + } else if (output->disable_pending) { |
| + output->disable_pending = 0; |
| + output->dpms_off_pending = 0; |
| + weston_output_disable(&output->base); |
| + return; |
| + } else if (output->dpms_off_pending) { |
| + struct drm_pending_state *pending = drm_pending_state_alloc(b); |
| + output->dpms_off_pending = 0; |
| + drm_output_get_disable_state(pending, output); |
| + drm_pending_state_apply_sync(pending); |
| + return; |
| + } else if (output->state_cur->dpms == WESTON_DPMS_OFF && |
| + output->base.repaint_status != REPAINT_AWAITING_COMPLETION) { |
| + /* DPMS can happen to us either in the middle of a repaint |
| + * cycle (when we have painted fresh content, only to throw it |
| + * away for DPMS off), or at any other random point. If the |
| + * latter is true, then we cannot go through finish_frame, |
| + * because the repaint machinery does not expect this. */ |
| + return; |
| + } |
| + |
| + ts.tv_sec = sec; |
| + ts.tv_nsec = usec * 1000; |
| + weston_output_finish_frame(&output->base, &ts, flags); |
| + |
| + /* We can't call this from frame_notify, because the output's |
| + * repaint needed flag is cleared just after that */ |
| + if (output->recorder) |
| + weston_output_schedule_repaint(&output->base); |
| +} |
| + |
| +/** |
| + * Mark an output state as current on the output, i.e. it has been |
| + * submitted to the kernel. The mode argument determines whether this |
| + * update will be applied synchronously (e.g. when calling drmModeSetCrtc), |
| + * or asynchronously (in which case we wait for events to complete). |
| + */ |
| +static void |
| +drm_output_assign_state(struct drm_output_state *state, |
| + enum drm_state_apply_mode mode) |
| +{ |
| + struct drm_output *output = state->output; |
| + struct drm_backend *b = to_drm_backend(output->base.compositor); |
| + struct drm_plane_state *plane_state; |
| + |
| + assert(!output->state_last); |
| + |
| + if (mode == DRM_STATE_APPLY_ASYNC) |
| + output->state_last = output->state_cur; |
| + else |
| + drm_output_state_free(output->state_cur); |
| + |
| + wl_list_remove(&state->link); |
| + wl_list_init(&state->link); |
| + state->pending_state = NULL; |
| + |
| + output->state_cur = state; |
| + |
| + if (b->atomic_modeset && mode == DRM_STATE_APPLY_ASYNC) |
| + output->atomic_complete_pending = 1; |
| + |
| + /* Replace state_cur on each affected plane with the new state, being |
| + * careful to dispose of orphaned (but only orphaned) previous state. |
| + * If the previous state is not orphaned (still has an output_state |
| + * attached), it will be disposed of by freeing the output_state. */ |
| + wl_list_for_each(plane_state, &state->plane_list, link) { |
| + struct drm_plane *plane = plane_state->plane; |
| + |
| + if (plane->state_cur && !plane->state_cur->output_state) |
| + drm_plane_state_free(plane->state_cur, true); |
| + plane->state_cur = plane_state; |
| |
| - fb->is_client_buffer = 1; |
| + if (mode != DRM_STATE_APPLY_ASYNC) { |
| + plane_state->complete = true; |
| + continue; |
| + } |
| |
| - weston_buffer_reference(&fb->buffer_ref, buffer); |
| + if (b->atomic_modeset) |
| + continue; |
| + |
| + if (plane->type == WDRM_PLANE_TYPE_OVERLAY) |
| + output->vblank_pending++; |
| + else if (plane->type == WDRM_PLANE_TYPE_PRIMARY) |
| + output->page_flip_pending = 1; |
| + } |
| } |
| |
| -static void |
| -drm_output_release_fb(struct drm_output *output, struct drm_fb *fb) |
| +static int |
| +drm_view_transform_supported(struct weston_view *ev) |
| { |
| - if (!fb) |
| - return; |
| - |
| - if (fb->map && |
| - (fb != output->dumb[0] && fb != output->dumb[1])) { |
| - drm_fb_destroy_dumb(fb); |
| - } else if (fb->bo) { |
| - if (fb->is_client_buffer) |
| - gbm_bo_destroy(fb->bo); |
| - else |
| - gbm_surface_release_buffer(output->gbm_surface, |
| - fb->bo); |
| - } |
| + return !ev->transform.enabled || |
| + (ev->transform.matrix.type < WESTON_MATRIX_TRANSFORM_ROTATE); |
| } |
| |
| static uint32_t |
| @@ -530,36 +1537,61 @@ drm_output_check_scanout_format(struct drm_output *output, |
| } |
| |
| static struct weston_plane * |
| -drm_output_prepare_scanout_view(struct drm_output *output, |
| +drm_output_prepare_scanout_view(struct drm_output_state *output_state, |
| struct weston_view *ev) |
| { |
| + struct drm_output *output = output_state->output; |
| struct drm_backend *b = to_drm_backend(output->base.compositor); |
| + struct drm_plane *scanout_plane = output->scanout_plane; |
| + struct drm_plane_state *state; |
| struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; |
| struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; |
| struct gbm_bo *bo; |
| uint32_t format; |
| |
| + /* Don't import buffers which span multiple outputs. */ |
| + if (ev->output_mask != (1u << output->base.id)) |
| + return NULL; |
| + |
| /* We use GBM to import buffers. */ |
| if (b->gbm == NULL) |
| return NULL; |
| |
| if (buffer == NULL) |
| return NULL; |
| + if (wl_shm_buffer_get(buffer->resource)) |
| + return NULL; |
| |
| /* Make sure our view is exactly compatible with the output. */ |
| if (ev->geometry.x != output->base.x || |
| ev->geometry.y != output->base.y) |
| return NULL; |
| + if (buffer->width != output->base.current_mode->width || |
| + buffer->height != output->base.current_mode->height) |
| + return NULL; |
| + |
| if (ev->transform.enabled) |
| return NULL; |
| if (ev->geometry.scissor_enabled) |
| return NULL; |
| + if (viewport->buffer.transform != output->base.transform) |
| + return NULL; |
| + if (viewport->buffer.scale != output->base.current_scale) |
| + return NULL; |
| + if (!drm_view_transform_supported(ev)) |
| + return NULL; |
| |
| - if (buffer->width != output->base.current_mode->width || |
| - buffer->height != output->base.current_mode->height) |
| + if (ev->alpha != 1.0f) |
| return NULL; |
| - if (viewport->buffer.transform != output->base.transform) |
| + |
| + state = drm_output_state_get_plane(output_state, scanout_plane); |
| + if (state->fb) { |
| + /* If there is already a framebuffer on the scanout plane, |
| + * a client view has already been placed on the scanout |
| + * view. In that case, do not free or put back the state, |
| + * but just leave it in place and quietly exit. */ |
| return NULL; |
| + } |
| |
| bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER, |
| buffer->resource, GBM_BO_USE_SCANOUT); |
| @@ -570,26 +1602,42 @@ drm_output_prepare_scanout_view(struct drm_output *output, |
| |
| format = drm_output_check_scanout_format(output, ev->surface, bo); |
| if (format == 0) { |
| + drm_plane_state_put_back(state); |
| gbm_bo_destroy(bo); |
| return NULL; |
| } |
| |
| - output->next = drm_fb_get_from_bo(bo, b, format); |
| - if (!output->next) { |
| + state->fb = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT); |
| + if (!state->fb) { |
| + drm_plane_state_put_back(state); |
| gbm_bo_destroy(bo); |
| return NULL; |
| } |
| |
| - drm_fb_set_buffer(output->next, buffer); |
| + drm_fb_set_buffer(state->fb, buffer); |
| + |
| + state->output = output; |
| + |
| + state->src_x = 0; |
| + state->src_y = 0; |
| + state->src_w = state->fb->width << 16; |
| + state->src_h = state->fb->height << 16; |
| |
| - return &output->fb_plane; |
| + state->dest_x = 0; |
| + state->dest_y = 0; |
| + state->dest_w = output->base.current_mode->width; |
| + state->dest_h = output->base.current_mode->height; |
| + |
| + return &scanout_plane->base; |
| } |
| |
| -static void |
| -drm_output_render_gl(struct drm_output *output, pixman_region32_t *damage) |
| +static struct drm_fb * |
| +drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage) |
| { |
| + struct drm_output *output = state->output; |
| struct drm_backend *b = to_drm_backend(output->base.compositor); |
| struct gbm_bo *bo; |
| + struct drm_fb *ret; |
| |
| output->base.compositor->renderer->repaint_output(&output->base, |
| damage); |
| @@ -597,20 +1645,25 @@ drm_output_render_gl(struct drm_output *output, pixman_region32_t *damage) |
| bo = gbm_surface_lock_front_buffer(output->gbm_surface); |
| if (!bo) { |
| weston_log("failed to lock front buffer: %m\n"); |
| - return; |
| + return NULL; |
| } |
| |
| - output->next = drm_fb_get_from_bo(bo, b, output->gbm_format); |
| - if (!output->next) { |
| + ret = drm_fb_get_from_bo(bo, b, output->gbm_format, BUFFER_GBM_SURFACE); |
| + if (!ret) { |
| weston_log("failed to get drm_fb for bo\n"); |
| gbm_surface_release_buffer(output->gbm_surface, bo); |
| - return; |
| + return NULL; |
| } |
| + ret->gbm_surface = output->gbm_surface; |
| + |
| + return ret; |
| } |
| |
| -static void |
| -drm_output_render_pixman(struct drm_output *output, pixman_region32_t *damage) |
| +static struct drm_fb * |
| +drm_output_render_pixman(struct drm_output_state *state, |
| + pixman_region32_t *damage) |
| { |
| + struct drm_output *output = state->output; |
| struct weston_compositor *ec = output->base.compositor; |
| pixman_region32_t total_damage, previous_damage; |
| |
| @@ -624,7 +1677,6 @@ drm_output_render_pixman(struct drm_output *output, pixman_region32_t *damage) |
| |
| output->current_image ^= 1; |
| |
| - output->next = output->dumb[output->current_image]; |
| pixman_renderer_output_set_buffer(&output->base, |
| output->image[output->current_image]); |
| |
| @@ -632,18 +1684,60 @@ drm_output_render_pixman(struct drm_output *output, pixman_region32_t *damage) |
| |
| pixman_region32_fini(&total_damage); |
| pixman_region32_fini(&previous_damage); |
| + |
| + return drm_fb_ref(output->dumb[output->current_image]); |
| } |
| |
| static void |
| -drm_output_render(struct drm_output *output, pixman_region32_t *damage) |
| +drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) |
| { |
| + struct drm_output *output = state->output; |
| struct weston_compositor *c = output->base.compositor; |
| + struct drm_plane_state *scanout_state; |
| + struct drm_plane *scanout_plane = output->scanout_plane; |
| struct drm_backend *b = to_drm_backend(c); |
| + struct drm_fb *fb; |
| + |
| + /* If we already have a client buffer promoted to scanout, then we don't |
| + * want to render. */ |
| + scanout_state = drm_output_state_get_plane(state, |
| + output->scanout_plane); |
| + if (scanout_state->fb) |
| + return; |
| + |
| + if (!pixman_region32_not_empty(damage) && |
| + scanout_plane->state_cur->fb && |
| + (scanout_plane->state_cur->fb->type == BUFFER_GBM_SURFACE || |
| + scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB) && |
| + scanout_plane->state_cur->fb->width == |
| + output->base.current_mode->width && |
| + scanout_plane->state_cur->fb->height == |
| + output->base.current_mode->height) { |
| + fb = drm_fb_ref(scanout_plane->state_cur->fb); |
| + } else if (b->use_pixman) { |
| + fb = drm_output_render_pixman(state, damage); |
| + } else { |
| + fb = drm_output_render_gl(state, damage); |
| + } |
| + |
| + if (!fb) { |
| + drm_plane_state_put_back(scanout_state); |
| + return; |
| + } |
| + |
| + scanout_state->fb = fb; |
| + scanout_state->output = output; |
| + |
| + scanout_state->src_x = 0; |
| + scanout_state->src_y = 0; |
| + scanout_state->src_w = output->base.current_mode->width << 16; |
| + scanout_state->src_h = output->base.current_mode->height << 16; |
| + |
| + scanout_state->dest_x = 0; |
| + scanout_state->dest_y = 0; |
| + scanout_state->dest_w = scanout_state->src_w >> 16; |
| + scanout_state->dest_h = scanout_state->src_h >> 16; |
| |
| - if (b->use_pixman) |
| - drm_output_render_pixman(output, damage); |
| - else |
| - drm_output_render_gl(output, damage); |
| |
| pixman_region32_subtract(&c->primary_plane.damage, |
| &c->primary_plane.damage, damage); |
| @@ -661,8 +1755,6 @@ drm_output_set_gamma(struct weston_output *output_base, |
| /* check */ |
| if (output_base->gamma_size != size) |
| return; |
| - if (!output->original_crtc) |
| - return; |
| |
| rc = drmModeCrtcSetGamma(backend->drm.fd, |
| output->crtc_id, |
| @@ -697,102 +1789,612 @@ drm_waitvblank_pipe(struct drm_output *output) |
| } |
| |
| static int |
| -drm_output_repaint(struct weston_output *output_base, |
| - pixman_region32_t *damage) |
| +drm_output_apply_state_legacy(struct drm_output_state *state) |
| { |
| - struct drm_output *output = to_drm_output(output_base); |
| - struct drm_backend *backend = |
| - to_drm_backend(output->base.compositor); |
| - struct drm_sprite *s; |
| + struct drm_output *output = state->output; |
| + struct drm_backend *backend = to_drm_backend(output->base.compositor); |
| + struct drm_plane *scanout_plane = output->scanout_plane; |
| + struct drm_property_info *dpms_prop = |
| + &output->props_conn[WDRM_CONNECTOR_DPMS]; |
| + struct drm_plane_state *scanout_state; |
| + struct drm_plane_state *ps; |
| + struct drm_plane *p; |
| struct drm_mode *mode; |
| + struct timespec now; |
| int ret = 0; |
| |
| - if (output->disable_pending || output->destroy_pending) |
| - return -1; |
| + /* If disable_planes is set then assign_planes() wasn't |
| + * called for this render, so we could still have a stale |
| + * cursor plane set up. |
| + */ |
| + if (output->base.disable_planes) { |
| + output->cursor_view = NULL; |
| + if (output->cursor_plane) { |
| + output->cursor_plane->base.x = INT32_MIN; |
| + output->cursor_plane->base.y = INT32_MIN; |
| + } |
| + } |
| |
| - if (!output->next) |
| - drm_output_render(output, damage); |
| - if (!output->next) |
| - return -1; |
| + if (state->dpms != WESTON_DPMS_ON) { |
| + wl_list_for_each(ps, &state->plane_list, link) { |
| + p = ps->plane; |
| + assert(ps->fb == NULL); |
| + assert(ps->output == NULL); |
| + |
| + if (p->type != WDRM_PLANE_TYPE_OVERLAY) |
| + continue; |
| + |
| + ret = drmModeSetPlane(backend->drm.fd, p->plane_id, |
| + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); |
| + if (ret) |
| + weston_log("drmModeSetPlane failed disable: %m\n"); |
| + } |
| + |
| + if (output->cursor_plane) { |
| + ret = drmModeSetCursor(backend->drm.fd, output->crtc_id, |
| + 0, 0, 0); |
| + if (ret) |
| + weston_log("drmModeSetCursor failed disable: %m\n"); |
| + } |
| + |
| + ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, 0, 0, 0, |
| + &output->connector_id, 0, NULL); |
| + if (ret) |
| + weston_log("drmModeSetCrtc failed disabling: %m\n"); |
| + |
| + drm_output_assign_state(state, DRM_STATE_APPLY_SYNC); |
| + weston_compositor_read_presentation_clock(output->base.compositor, &now); |
| + drm_output_update_complete(output, |
| + WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION, |
| + now.tv_sec, now.tv_nsec / 1000); |
| + |
| + return 0; |
| + } |
| |
| - mode = container_of(output->base.current_mode, struct drm_mode, base); |
| - if (!output->current || |
| - output->current->stride != output->next->stride) { |
| + scanout_state = |
| + drm_output_state_get_existing_plane(state, scanout_plane); |
| + |
| + /* The legacy SetCrtc API doesn't allow us to do scaling, and the |
| + * legacy PageFlip API doesn't allow us to do clipping either. */ |
| + assert(scanout_state->src_x == 0); |
| + assert(scanout_state->src_y == 0); |
| + assert(scanout_state->src_w == |
| + (unsigned) (output->base.current_mode->width << 16)); |
| + assert(scanout_state->src_h == |
| + (unsigned) (output->base.current_mode->height << 16)); |
| + assert(scanout_state->dest_x == 0); |
| + assert(scanout_state->dest_y == 0); |
| + assert(scanout_state->dest_w == scanout_state->src_w >> 16); |
| + assert(scanout_state->dest_h == scanout_state->src_h >> 16); |
| + |
| + mode = to_drm_mode(output->base.current_mode); |
| + if (backend->state_invalid || !scanout_plane->state_cur->fb || |
| + scanout_plane->state_cur->fb->stride != scanout_state->fb->stride) { |
| ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id, |
| - output->next->fb_id, 0, 0, |
| + scanout_state->fb->fb_id, |
| + 0, 0, |
| &output->connector_id, 1, |
| &mode->mode_info); |
| if (ret) { |
| weston_log("set mode failed: %m\n"); |
| - goto err_pageflip; |
| + goto err; |
| } |
| - output_base->set_dpms(output_base, WESTON_DPMS_ON); |
| } |
| |
| if (drmModePageFlip(backend->drm.fd, output->crtc_id, |
| - output->next->fb_id, |
| + scanout_state->fb->fb_id, |
| DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { |
| weston_log("queueing pageflip failed: %m\n"); |
| - goto err_pageflip; |
| + goto err; |
| } |
| |
| - output->page_flip_pending = 1; |
| + assert(!output->page_flip_pending); |
| + |
| + if (output->pageflip_timer) |
| + wl_event_source_timer_update(output->pageflip_timer, |
| + backend->pageflip_timeout); |
| |
| - drm_output_set_cursor(output); |
| + drm_output_set_cursor(state); |
| |
| /* |
| * Now, update all the sprite surfaces |
| */ |
| - wl_list_for_each(s, &backend->sprite_list, link) { |
| + wl_list_for_each(ps, &state->plane_list, link) { |
| uint32_t flags = 0, fb_id = 0; |
| drmVBlank vbl = { |
| .request.type = DRM_VBLANK_RELATIVE | DRM_VBLANK_EVENT, |
| .request.sequence = 1, |
| }; |
| |
| - if ((!s->current && !s->next) || |
| - !drm_sprite_crtc_supported(output, s)) |
| - continue; |
| + p = ps->plane; |
| + if (p->type != WDRM_PLANE_TYPE_OVERLAY) |
| + continue; |
| + |
| + assert(p->state_cur->complete); |
| + assert(!!p->state_cur->output == !!p->state_cur->fb); |
| + assert(!p->state_cur->output || p->state_cur->output == output); |
| + assert(!ps->complete); |
| + assert(!ps->output || ps->output == output); |
| + assert(!!ps->output == !!ps->fb); |
| + |
| + if (ps->fb && !backend->sprites_hidden) |
| + fb_id = ps->fb->fb_id; |
| + |
| + ret = drmModeSetPlane(backend->drm.fd, p->plane_id, |
| + output->crtc_id, fb_id, flags, |
| + ps->dest_x, ps->dest_y, |
| + ps->dest_w, ps->dest_h, |
| + ps->src_x, ps->src_y, |
| + ps->src_w, ps->src_h); |
| + if (ret) |
| + weston_log("setplane failed: %d: %s\n", |
| + ret, strerror(errno)); |
| + |
| + vbl.request.type |= drm_waitvblank_pipe(output); |
| + |
| + /* |
| + * Queue a vblank signal so we know when the surface |
| + * becomes active on the display or has been replaced. |
| + */ |
| + vbl.request.signal = (unsigned long) ps; |
| + ret = drmWaitVBlank(backend->drm.fd, &vbl); |
| + if (ret) { |
| + weston_log("vblank event request failed: %d: %s\n", |
| + ret, strerror(errno)); |
| + } |
| + } |
| + |
| + if (dpms_prop->prop_id && state->dpms != output->state_cur->dpms) { |
| + ret = drmModeConnectorSetProperty(backend->drm.fd, |
| + output->connector_id, |
| + dpms_prop->prop_id, |
| + state->dpms); |
| + if (ret) { |
| + weston_log("DRM: DPMS: failed property set for %s\n", |
| + output->base.name); |
| + } |
| + } |
| + |
| + drm_output_assign_state(state, DRM_STATE_APPLY_ASYNC); |
| + |
| + return 0; |
| + |
| +err: |
| + output->cursor_view = NULL; |
| + drm_output_state_free(state); |
| + return -1; |
| +} |
| + |
| +#ifdef HAVE_DRM_ATOMIC |
| +static int |
| +crtc_add_prop(drmModeAtomicReq *req, struct drm_output *output, |
| + enum wdrm_crtc_property prop, uint64_t val) |
| +{ |
| + struct drm_property_info *info = &output->props_crtc[prop]; |
| + int ret; |
| + |
| + if (info->prop_id == 0) |
| + return -1; |
| + |
| + ret = drmModeAtomicAddProperty(req, output->crtc_id, info->prop_id, |
| + val); |
| + return (ret <= 0) ? -1 : 0; |
| +} |
| + |
| +static int |
| +connector_add_prop(drmModeAtomicReq *req, struct drm_output *output, |
| + enum wdrm_connector_property prop, uint64_t val) |
| +{ |
| + struct drm_property_info *info = &output->props_conn[prop]; |
| + int ret; |
| + |
| + if (info->prop_id == 0) |
| + return -1; |
| + |
| + ret = drmModeAtomicAddProperty(req, output->connector_id, |
| + info->prop_id, val); |
| + return (ret <= 0) ? -1 : 0; |
| +} |
| + |
| +static int |
| +plane_add_prop(drmModeAtomicReq *req, struct drm_plane *plane, |
| + enum wdrm_plane_property prop, uint64_t val) |
| +{ |
| + struct drm_property_info *info = &plane->props[prop]; |
| + int ret; |
| + |
| + if (info->prop_id == 0) |
| + return -1; |
| + |
| + ret = drmModeAtomicAddProperty(req, plane->plane_id, info->prop_id, |
| + val); |
| + return (ret <= 0) ? -1 : 0; |
| +} |
| + |
| +static int |
| +drm_mode_ensure_blob(struct drm_backend *backend, struct drm_mode *mode) |
| +{ |
| + int ret; |
| + |
| + if (mode->blob_id) |
| + return 0; |
| + |
| + ret = drmModeCreatePropertyBlob(backend->drm.fd, |
| + &mode->mode_info, |
| + sizeof(mode->mode_info), |
| + &mode->blob_id); |
| + if (ret != 0) |
| + weston_log("failed to create mode property blob: %m\n"); |
| + |
| + return ret; |
| +} |
| + |
| +static int |
| +drm_output_apply_state_atomic(struct drm_output_state *state, |
| + drmModeAtomicReq *req, |
| + uint32_t *flags) |
| +{ |
| + struct drm_output *output = state->output; |
| + struct drm_backend *backend = to_drm_backend(output->base.compositor); |
| + struct drm_plane_state *plane_state; |
| + struct drm_mode *current_mode = to_drm_mode(output->base.current_mode); |
| + int ret = 0; |
| + |
| + if (state->dpms != output->state_cur->dpms) |
| + *flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; |
| + |
| + if (state->dpms == WESTON_DPMS_ON) { |
| + ret = drm_mode_ensure_blob(backend, current_mode); |
| + if (ret != 0) |
| + return ret; |
| + |
| + ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID, |
| + current_mode->blob_id); |
| + ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 1); |
| + ret |= connector_add_prop(req, output, WDRM_CONNECTOR_CRTC_ID, |
| + output->crtc_id); |
| + } else { |
| + ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID, 0); |
| + ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 0); |
| + ret |= connector_add_prop(req, output, WDRM_CONNECTOR_CRTC_ID, |
| + 0); |
| + } |
| + |
| + if (ret != 0) { |
| + weston_log("couldn't set atomic CRTC/connector state\n"); |
| + return ret; |
| + } |
| + |
| + wl_list_for_each(plane_state, &state->plane_list, link) { |
| + struct drm_plane *plane = plane_state->plane; |
| + |
| + ret |= plane_add_prop(req, plane, WDRM_PLANE_FB_ID, |
| + plane_state->fb ? plane_state->fb->fb_id : 0); |
| + ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_ID, |
| + plane_state->fb ? output->crtc_id : 0); |
| + ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_X, |
| + plane_state->src_x); |
| + ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_Y, |
| + plane_state->src_y); |
| + ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_W, |
| + plane_state->src_w); |
| + ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_H, |
| + plane_state->src_h); |
| + ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_X, |
| + plane_state->dest_x); |
| + ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_Y, |
| + plane_state->dest_y); |
| + ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_W, |
| + plane_state->dest_w); |
| + ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_H, |
| + plane_state->dest_h); |
| + |
| + if (ret != 0) { |
| + weston_log("couldn't set plane state\n"); |
| + return ret; |
| + } |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +/** |
| + * Helper function used only by drm_pending_state_apply, with the same |
| + * guarantees and constraints as that function. |
| + */ |
| +static int |
| +drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, |
| + enum drm_state_apply_mode mode) |
| +{ |
| + struct drm_backend *b = pending_state->backend; |
| + struct drm_output_state *output_state, *tmp; |
| + struct drm_plane *plane; |
| + drmModeAtomicReq *req = drmModeAtomicAlloc(); |
| + uint32_t flags = 0; |
| + int ret = 0; |
| + |
| + if (!req) |
| + return -1; |
| + |
| + if (b->state_invalid) { |
| + uint32_t *unused; |
| + int err; |
| + |
| + /* If we need to reset all our state (e.g. because we've |
| + * just started, or just been VT-switched in), explicitly |
| + * disable all the CRTCs and connectors we aren't using. */ |
| + wl_array_for_each(unused, &b->unused_connectors) { |
| + struct drm_property_info infos[WDRM_CONNECTOR__COUNT]; |
| + struct drm_property_info *info; |
| + drmModeObjectProperties *props; |
| + |
| + memset(infos, 0, sizeof(infos)); |
| + |
| + props = drmModeObjectGetProperties(b->drm.fd, |
| + *unused, |
| + DRM_MODE_OBJECT_CONNECTOR); |
| + if (!props) { |
| + ret = -1; |
| + continue; |
| + } |
| + |
| + drm_property_info_populate(b, connector_props, infos, |
| + WDRM_CONNECTOR__COUNT, |
| + props); |
| + drmModeFreeObjectProperties(props); |
| + |
| + info = &infos[WDRM_CONNECTOR_CRTC_ID]; |
| + err = drmModeAtomicAddProperty(req, *unused, |
| + info->prop_id, 0); |
| + if (err <= 0) |
| + ret = -1; |
| + |
| + info = &infos[WDRM_CONNECTOR_DPMS]; |
| + if (info->prop_id > 0) |
| + err = drmModeAtomicAddProperty(req, *unused, |
| + info->prop_id, |
| + DRM_MODE_DPMS_OFF); |
| + if (err <= 0) |
| + ret = -1; |
| + |
| + drm_property_info_free(infos, WDRM_CONNECTOR__COUNT); |
| + } |
| + |
| + wl_array_for_each(unused, &b->unused_crtcs) { |
| + struct drm_property_info infos[WDRM_CRTC__COUNT]; |
| + struct drm_property_info *info; |
| + drmModeObjectProperties *props; |
| + uint64_t active; |
| + |
| + memset(infos, 0, sizeof(infos)); |
| + |
| + /* We can't emit a disable on a CRTC that's already |
| + * off, as the kernel will refuse to generate an event |
| + * for an off->off state and fail the commit. |
| + */ |
| + props = drmModeObjectGetProperties(b->drm.fd, |
| + *unused, |
| + DRM_MODE_OBJECT_CRTC); |
| + if (!props) { |
| + ret = -1; |
| + continue; |
| + } |
| + |
| + drm_property_info_populate(b, crtc_props, infos, |
| + WDRM_CRTC__COUNT, |
| + props); |
| + |
| + info = &infos[WDRM_CRTC_ACTIVE]; |
| + active = drm_property_get_value(info, props, 0); |
| + drmModeFreeObjectProperties(props); |
| + if (active == 0) { |
| + drm_property_info_free(infos, WDRM_CRTC__COUNT); |
| + continue; |
| + } |
| + |
| + err = drmModeAtomicAddProperty(req, *unused, |
| + info->prop_id, 0); |
| + if (err <= 0) |
| + ret = -1; |
| + |
| + info = &infos[WDRM_CRTC_MODE_ID]; |
| + err = drmModeAtomicAddProperty(req, *unused, |
| + info->prop_id, 0); |
| + if (err <= 0) |
| + ret = -1; |
| + |
| + drm_property_info_free(infos, WDRM_CRTC__COUNT); |
| + } |
| + |
| + /* Disable all the planes; planes which are being used will |
| + * override this state in the output-state application. */ |
| + wl_list_for_each(plane, &b->plane_list, link) { |
| + plane_add_prop(req, plane, WDRM_PLANE_CRTC_ID, 0); |
| + plane_add_prop(req, plane, WDRM_PLANE_FB_ID, 0); |
| + } |
| + |
| + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; |
| + } |
| + |
| + wl_list_for_each(output_state, &pending_state->output_list, link) { |
| + if (mode == DRM_STATE_APPLY_SYNC) |
| + assert(output_state->dpms == WESTON_DPMS_OFF); |
| + ret |= drm_output_apply_state_atomic(output_state, req, &flags); |
| + } |
| + |
| + if (ret != 0) { |
| + weston_log("atomic: couldn't compile atomic state\n"); |
| + goto out; |
| + } |
| + |
| + switch (mode) { |
| + case DRM_STATE_APPLY_SYNC: |
| + break; |
| + case DRM_STATE_APPLY_ASYNC: |
| + flags |= DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; |
| + break; |
| + } |
| + |
| + ret = drmModeAtomicCommit(b->drm.fd, req, flags, b); |
| + if (ret != 0) { |
| + weston_log("atomic: couldn't commit new state: %m\n"); |
| + goto out; |
| + } |
| + |
| + wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, |
| + link) |
| + drm_output_assign_state(output_state, mode); |
| + |
| + b->state_invalid = false; |
| + |
| + assert(wl_list_empty(&pending_state->output_list)); |
| + |
| +out: |
| + drmModeAtomicFree(req); |
| + drm_pending_state_free(pending_state); |
| + return ret; |
| +} |
| +#endif |
| + |
| +/** |
| + * Applies all of a pending_state asynchronously: the primary entry point for |
| + * applying KMS state to a device. Updates the state for all outputs in the |
| + * pending_state, as well as disabling any unclaimed outputs. |
| + * |
| + * Unconditionally takes ownership of pending_state, and clears state_invalid. |
| + */ |
| +static int |
| +drm_pending_state_apply(struct drm_pending_state *pending_state) |
| +{ |
| + struct drm_backend *b = pending_state->backend; |
| + struct drm_output_state *output_state, *tmp; |
| + uint32_t *unused; |
| + |
| +#ifdef HAVE_DRM_ATOMIC |
| + if (b->atomic_modeset) |
| + return drm_pending_state_apply_atomic(pending_state, |
| + DRM_STATE_APPLY_ASYNC); |
| +#endif |
| + |
| + if (b->state_invalid) { |
| + /* If we need to reset all our state (e.g. because we've |
| + * just started, or just been VT-switched in), explicitly |
| + * disable all the CRTCs we aren't using. This also disables |
| + * all connectors on these CRTCs, so we don't need to do that |
| + * separately with the pre-atomic API. */ |
| + wl_array_for_each(unused, &b->unused_crtcs) |
| + drmModeSetCrtc(b->drm.fd, *unused, 0, 0, 0, NULL, 0, |
| + NULL); |
| + } |
| + |
| + wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, |
| + link) { |
| + struct drm_output *output = output_state->output; |
| + int ret; |
| + |
| + ret = drm_output_apply_state_legacy(output_state); |
| + if (ret != 0) { |
| + weston_log("Couldn't apply state for output %s\n", |
| + output->base.name); |
| + } |
| + } |
| + |
| + b->state_invalid = false; |
| + |
| + assert(wl_list_empty(&pending_state->output_list)); |
| + |
| + drm_pending_state_free(pending_state); |
| + |
| + return 0; |
| +} |
| + |
| +/** |
| + * The synchronous version of drm_pending_state_apply. May only be used to |
| + * disable outputs. Does so synchronously: the request is guaranteed to have |
| + * completed on return, and the output will not be touched afterwards. |
| + * |
| + * Unconditionally takes ownership of pending_state, and clears state_invalid. |
| + */ |
| +static int |
| +drm_pending_state_apply_sync(struct drm_pending_state *pending_state) |
| +{ |
| + struct drm_backend *b = pending_state->backend; |
| + struct drm_output_state *output_state, *tmp; |
| + uint32_t *unused; |
| + |
| +#ifdef HAVE_DRM_ATOMIC |
| + if (b->atomic_modeset) |
| + return drm_pending_state_apply_atomic(pending_state, |
| + DRM_STATE_APPLY_SYNC); |
| +#endif |
| + |
| + if (b->state_invalid) { |
| + /* If we need to reset all our state (e.g. because we've |
| + * just started, or just been VT-switched in), explicitly |
| + * disable all the CRTCs we aren't using. This also disables |
| + * all connectors on these CRTCs, so we don't need to do that |
| + * separately with the pre-atomic API. */ |
| + wl_array_for_each(unused, &b->unused_crtcs) |
| + drmModeSetCrtc(b->drm.fd, *unused, 0, 0, 0, NULL, 0, |
| + NULL); |
| + } |
| + |
| + wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, |
| + link) { |
| + int ret; |
| + |
| + assert(output_state->dpms == WESTON_DPMS_OFF); |
| + ret = drm_output_apply_state_legacy(output_state); |
| + if (ret != 0) { |
| + weston_log("Couldn't apply state for output %s\n", |
| + output_state->output->base.name); |
| + } |
| + } |
| + |
| + b->state_invalid = false; |
| |
| - if (s->next && !backend->sprites_hidden) |
| - fb_id = s->next->fb_id; |
| + assert(wl_list_empty(&pending_state->output_list)); |
| |
| - ret = drmModeSetPlane(backend->drm.fd, s->plane_id, |
| - output->crtc_id, fb_id, flags, |
| - s->dest_x, s->dest_y, |
| - s->dest_w, s->dest_h, |
| - s->src_x, s->src_y, |
| - s->src_w, s->src_h); |
| - if (ret) |
| - weston_log("setplane failed: %d: %s\n", |
| - ret, strerror(errno)); |
| + drm_pending_state_free(pending_state); |
| |
| - vbl.request.type |= drm_waitvblank_pipe(output); |
| + return 0; |
| +} |
| |
| - /* |
| - * Queue a vblank signal so we know when the surface |
| - * becomes active on the display or has been replaced. |
| - */ |
| - vbl.request.signal = (unsigned long)s; |
| - ret = drmWaitVBlank(backend->drm.fd, &vbl); |
| - if (ret) { |
| - weston_log("vblank event request failed: %d: %s\n", |
| - ret, strerror(errno)); |
| - } |
| +static int |
| +drm_output_repaint(struct weston_output *output_base, |
| + pixman_region32_t *damage, |
| + void *repaint_data) |
| +{ |
| + struct drm_pending_state *pending_state = repaint_data; |
| + struct drm_output *output = to_drm_output(output_base); |
| + struct drm_output_state *state = NULL; |
| + struct drm_plane_state *scanout_state; |
| |
| - s->output = output; |
| - output->vblank_pending = 1; |
| - } |
| + if (output->disable_pending || output->destroy_pending) |
| + goto err; |
| |
| - return 0; |
| + assert(!output->state_last); |
| + |
| + /* If planes have been disabled in the core, we might not have |
| + * hit assign_planes at all, so might not have valid output state |
| + * here. */ |
| + state = drm_pending_state_get_output(pending_state, output); |
| + if (!state) |
| + state = drm_output_state_duplicate(output->state_cur, |
| + pending_state, |
| + DRM_OUTPUT_STATE_CLEAR_PLANES); |
| + state->dpms = WESTON_DPMS_ON; |
| + |
| + drm_output_render(state, damage); |
| + scanout_state = drm_output_state_get_plane(state, |
| + output->scanout_plane); |
| + if (!scanout_state || !scanout_state->fb) |
| + goto err; |
| |
| -err_pageflip: |
| - output->cursor_view = NULL; |
| - if (output->next) { |
| - drm_output_release_fb(output, output->next); |
| - output->next = NULL; |
| - } |
| + return 0; |
| |
| +err: |
| + drm_output_state_free(state); |
| return -1; |
| } |
| |
| @@ -800,9 +2402,10 @@ static void |
| drm_output_start_repaint_loop(struct weston_output *output_base) |
| { |
| struct drm_output *output = to_drm_output(output_base); |
| + struct drm_pending_state *pending_state; |
| + struct drm_plane *scanout_plane = output->scanout_plane; |
| struct drm_backend *backend = |
| to_drm_backend(output_base->compositor); |
| - uint32_t fb_id; |
| struct timespec ts, tnow; |
| struct timespec vbl2now; |
| int64_t refresh_nsec; |
| @@ -816,11 +2419,19 @@ drm_output_start_repaint_loop(struct weston_output *output_base) |
| if (output->disable_pending || output->destroy_pending) |
| return; |
| |
| - if (!output->current) { |
| + if (!output->scanout_plane->state_cur->fb) { |
| /* We can't page flip if there's no mode set */ |
| goto finish_frame; |
| } |
| |
| + /* Need to smash all state in from scratch; current timings might not |
| + * be what we want, page flip might not work, etc. |
| + */ |
| + if (backend->state_invalid) |
| + goto finish_frame; |
| + |
| + assert(scanout_plane->state_cur->output == output); |
| + |
| /* Try to get current msc and timestamp via instant query */ |
| vbl.request.type |= drm_waitvblank_pipe(output); |
| ret = drmWaitVBlank(backend->drm.fd, &vbl); |
| @@ -850,11 +2461,17 @@ drm_output_start_repaint_loop(struct weston_output *output_base) |
| /* Immediate query didn't provide valid timestamp. |
| * Use pageflip fallback. |
| */ |
| - fb_id = output->current->fb_id; |
| |
| - if (drmModePageFlip(backend->drm.fd, output->crtc_id, fb_id, |
| - DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { |
| - weston_log("queueing pageflip failed: %m\n"); |
| + assert(!output->page_flip_pending); |
| + assert(!output->state_last); |
| + |
| + pending_state = drm_pending_state_alloc(backend); |
| + drm_output_state_duplicate(output->state_cur, pending_state, |
| + DRM_OUTPUT_STATE_PRESERVE_PLANES); |
| + |
| + ret = drm_pending_state_apply(pending_state); |
| + if (ret != 0) { |
| + weston_log("applying repaint-start state failed: %m\n"); |
| goto finish_frame; |
| } |
| |
| @@ -862,8 +2479,7 @@ drm_output_start_repaint_loop(struct weston_output *output_base) |
| |
| finish_frame: |
| /* if we cannot page-flip, immediately finish frame */ |
| - weston_compositor_read_presentation_clock(output_base->compositor, &ts); |
| - weston_output_finish_frame(output_base, &ts, |
| + weston_output_finish_frame(output_base, NULL, |
| WP_PRESENTATION_FEEDBACK_INVALID); |
| } |
| |
| @@ -882,70 +2498,132 @@ static void |
| vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, |
| void *data) |
| { |
| - struct drm_sprite *s = (struct drm_sprite *)data; |
| - struct drm_output *output = s->output; |
| - struct timespec ts; |
| + struct drm_plane_state *ps = (struct drm_plane_state *) data; |
| + struct drm_output_state *os = ps->output_state; |
| + struct drm_output *output = os->output; |
| + struct drm_backend *b = to_drm_backend(output->base.compositor); |
| uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | |
| WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; |
| |
| + assert(!b->atomic_modeset); |
| + |
| drm_output_update_msc(output, frame); |
| - output->vblank_pending = 0; |
| + output->vblank_pending--; |
| + assert(output->vblank_pending >= 0); |
| |
| - drm_output_release_fb(output, s->current); |
| - s->current = s->next; |
| - s->next = NULL; |
| + assert(ps->fb); |
| |
| - if (!output->page_flip_pending) { |
| - ts.tv_sec = sec; |
| - ts.tv_nsec = usec * 1000; |
| - weston_output_finish_frame(&output->base, &ts, flags); |
| - } |
| -} |
| + if (output->page_flip_pending || output->vblank_pending) |
| + return; |
| |
| -static void |
| -drm_output_destroy(struct weston_output *base); |
| + drm_output_update_complete(output, flags, sec, usec); |
| +} |
| |
| static void |
| page_flip_handler(int fd, unsigned int frame, |
| unsigned int sec, unsigned int usec, void *data) |
| { |
| struct drm_output *output = data; |
| - struct timespec ts; |
| + struct drm_backend *b = to_drm_backend(output->base.compositor); |
| uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_VSYNC | |
| WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | |
| WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; |
| |
| drm_output_update_msc(output, frame); |
| |
| - /* We don't set page_flip_pending on start_repaint_loop, in that case |
| - * we just want to page flip to the current buffer to get an accurate |
| - * timestamp */ |
| - if (output->page_flip_pending) { |
| - drm_output_release_fb(output, output->current); |
| - output->current = output->next; |
| - output->next = NULL; |
| - } |
| - |
| + assert(!b->atomic_modeset); |
| + assert(output->page_flip_pending); |
| output->page_flip_pending = 0; |
| |
| - if (output->destroy_pending) |
| - drm_output_destroy(&output->base); |
| - else if (output->disable_pending) |
| - weston_output_disable(&output->base); |
| - else if (!output->vblank_pending) { |
| - ts.tv_sec = sec; |
| - ts.tv_nsec = usec * 1000; |
| - weston_output_finish_frame(&output->base, &ts, flags); |
| + if (output->vblank_pending) |
| + return; |
| |
| - /* We can't call this from frame_notify, because the output's |
| - * repaint needed flag is cleared just after that */ |
| - if (output->recorder) |
| - weston_output_schedule_repaint(&output->base); |
| - } |
| + drm_output_update_complete(output, flags, sec, usec); |
| +} |
| + |
| +/** |
| + * Begin a new repaint cycle |
| + * |
| + * Called by the core compositor at the beginning of a repaint cycle. Creates |
| + * a new pending_state structure to own any output state created by individual |
| + * output repaint functions until the repaint is flushed or cancelled. |
| + */ |
| +static void * |
| +drm_repaint_begin(struct weston_compositor *compositor) |
| +{ |
| + struct drm_backend *b = to_drm_backend(compositor); |
| + struct drm_pending_state *ret; |
| + |
| + ret = drm_pending_state_alloc(b); |
| + b->repaint_data = ret; |
| + |
| + return ret; |
| +} |
| + |
| +/** |
| + * Flush a repaint set |
| + * |
| + * Called by the core compositor when a repaint cycle has been completed |
| + * and should be flushed. Frees the pending state, transitioning ownership |
| + * of the output state from the pending state, to the update itself. When |
| + * the update completes (see drm_output_update_complete), the output |
| + * state will be freed. |
| + */ |
| +static void |
| +drm_repaint_flush(struct weston_compositor *compositor, void *repaint_data) |
| +{ |
| + struct drm_backend *b = to_drm_backend(compositor); |
| + struct drm_pending_state *pending_state = repaint_data; |
| + |
| + drm_pending_state_apply(pending_state); |
| + b->repaint_data = NULL; |
| +} |
| + |
| +/** |
| + * Cancel a repaint set |
| + * |
| + * Called by the core compositor when a repaint has finished, so the data |
| + * held across the repaint cycle should be discarded. |
| + */ |
| +static void |
| +drm_repaint_cancel(struct weston_compositor *compositor, void *repaint_data) |
| +{ |
| + struct drm_backend *b = to_drm_backend(compositor); |
| + struct drm_pending_state *pending_state = repaint_data; |
| + |
| + drm_pending_state_free(pending_state); |
| + b->repaint_data = NULL; |
| +} |
| + |
| +#ifdef HAVE_DRM_ATOMIC |
| +static void |
| +atomic_flip_handler(int fd, unsigned int frame, unsigned int sec, |
| + unsigned int usec, unsigned int crtc_id, void *data) |
| +{ |
| + struct drm_backend *b = data; |
| + struct drm_output *output = drm_output_find_by_crtc(b, crtc_id); |
| + uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_VSYNC | |
| + WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | |
| + WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; |
| + |
| + /* During the initial modeset, we can disable CRTCs which we don't |
| + * actually handle during normal operation; this will give us events |
| + * for unknown outputs. Ignore them. */ |
| + if (!output || !output->base.enabled) |
| + return; |
| + |
| + drm_output_update_msc(output, frame); |
| + |
| + assert(b->atomic_modeset); |
| + assert(output->atomic_complete_pending); |
| + output->atomic_complete_pending = 0; |
| + |
| + drm_output_update_complete(output, flags, sec, usec); |
| } |
| +#endif |
| |
| static uint32_t |
| -drm_output_check_sprite_format(struct drm_sprite *s, |
| +drm_output_check_plane_format(struct drm_plane *p, |
| struct weston_view *ev, struct gbm_bo *bo) |
| { |
| uint32_t i, format; |
| @@ -966,31 +2644,25 @@ drm_output_check_sprite_format(struct drm_sprite *s, |
| pixman_region32_fini(&r); |
| } |
| |
| - for (i = 0; i < s->count_formats; i++) |
| - if (s->formats[i] == format) |
| + for (i = 0; i < p->count_formats; i++) |
| + if (p->formats[i] == format) |
| return format; |
| |
| return 0; |
| } |
| |
| -static int |
| -drm_view_transform_supported(struct weston_view *ev) |
| -{ |
| - return !ev->transform.enabled || |
| - (ev->transform.matrix.type < WESTON_MATRIX_TRANSFORM_ROTATE); |
| -} |
| - |
| static struct weston_plane * |
| -drm_output_prepare_overlay_view(struct drm_output *output, |
| +drm_output_prepare_overlay_view(struct drm_output_state *output_state, |
| struct weston_view *ev) |
| { |
| + struct drm_output *output = output_state->output; |
| struct weston_compositor *ec = output->base.compositor; |
| struct drm_backend *b = to_drm_backend(ec); |
| struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; |
| struct wl_resource *buffer_resource; |
| - struct drm_sprite *s; |
| + struct drm_plane *p; |
| + struct drm_plane_state *state = NULL; |
| struct linux_dmabuf_buffer *dmabuf; |
| - int found = 0; |
| struct gbm_bo *bo; |
| pixman_region32_t dest_rect, src_rect; |
| pixman_box32_t *box, tbox; |
| @@ -1024,18 +2696,24 @@ drm_output_prepare_overlay_view(struct drm_output *output, |
| if (ev->alpha != 1.0f) |
| return NULL; |
| |
| - wl_list_for_each(s, &b->sprite_list, link) { |
| - if (!drm_sprite_crtc_supported(output, s)) |
| + wl_list_for_each(p, &b->plane_list, link) { |
| + if (p->type != WDRM_PLANE_TYPE_OVERLAY) |
| continue; |
| |
| - if (!s->next) { |
| - found = 1; |
| - break; |
| + if (!drm_plane_is_available(p, output)) |
| + continue; |
| + |
| + state = drm_output_state_get_plane(output_state, p); |
| + if (state->fb) { |
| + state = NULL; |
| + continue; |
| } |
| + |
| + break; |
| } |
| |
| /* No sprites available */ |
| - if (!found) |
| + if (!state) |
| return NULL; |
| |
| if ((dmabuf = linux_dmabuf_buffer_get(buffer_resource))) { |
| @@ -1072,32 +2750,30 @@ drm_output_prepare_overlay_view(struct drm_output *output, |
| bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_FD, &gbm_dmabuf, |
| GBM_BO_USE_SCANOUT); |
| #else |
| - return NULL; |
| + goto err; |
| #endif |
| } else { |
| bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER, |
| buffer_resource, GBM_BO_USE_SCANOUT); |
| } |
| if (!bo) |
| - return NULL; |
| + goto err; |
| |
| - format = drm_output_check_sprite_format(s, ev, bo); |
| - if (format == 0) { |
| - gbm_bo_destroy(bo); |
| - return NULL; |
| - } |
| + format = drm_output_check_plane_format(p, ev, bo); |
| + if (format == 0) |
| + goto err; |
| |
| - s->next = drm_fb_get_from_bo(bo, b, format); |
| - if (!s->next) { |
| - gbm_bo_destroy(bo); |
| - return NULL; |
| - } |
| + state->fb = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT); |
| + if (!state->fb) |
| + goto err; |
| + |
| + drm_fb_set_buffer(state->fb, ev->surface->buffer_ref.buffer); |
| |
| - drm_fb_set_buffer(s->next, ev->surface->buffer_ref.buffer); |
| + state->output = output; |
| |
| box = pixman_region32_extents(&ev->transform.boundingbox); |
| - s->plane.x = box->x1; |
| - s->plane.y = box->y1; |
| + p->base.x = box->x1; |
| + p->base.y = box->y1; |
| |
| /* |
| * Calculate the source & dest rects properly based on actual |
| @@ -1114,10 +2790,10 @@ drm_output_prepare_overlay_view(struct drm_output *output, |
| output->base.transform, |
| output->base.current_scale, |
| *box); |
| - s->dest_x = tbox.x1; |
| - s->dest_y = tbox.y1; |
| - s->dest_w = tbox.x2 - tbox.x1; |
| - s->dest_h = tbox.y2 - tbox.y1; |
| + state->dest_x = tbox.x1; |
| + state->dest_y = tbox.y1; |
| + state->dest_w = tbox.x2 - tbox.x1; |
| + state->dest_h = tbox.y2 - tbox.y1; |
| pixman_region32_fini(&dest_rect); |
| |
| pixman_region32_init(&src_rect); |
| @@ -1154,27 +2830,83 @@ drm_output_prepare_overlay_view(struct drm_output *output, |
| viewport->buffer.scale, |
| tbox); |
| |
| - s->src_x = tbox.x1 << 8; |
| - s->src_y = tbox.y1 << 8; |
| - s->src_w = (tbox.x2 - tbox.x1) << 8; |
| - s->src_h = (tbox.y2 - tbox.y1) << 8; |
| + state->src_x = tbox.x1 << 8; |
| + state->src_y = tbox.y1 << 8; |
| + state->src_w = (tbox.x2 - tbox.x1) << 8; |
| + state->src_h = (tbox.y2 - tbox.y1) << 8; |
| pixman_region32_fini(&src_rect); |
| |
| - return &s->plane; |
| + return &p->base; |
| + |
| +err: |
| + drm_plane_state_put_back(state); |
| + if (bo) |
| + gbm_bo_destroy(bo); |
| + return NULL; |
| +} |
| + |
| +/** |
| + * Update the image for the current cursor surface |
| + * |
| + * @param b DRM backend structure |
| + * @param bo GBM buffer object to write into |
| + * @param ev View to use for cursor image |
| + */ |
| +static void |
| +cursor_bo_update(struct drm_backend *b, struct gbm_bo *bo, |
| + struct weston_view *ev) |
| +{ |
| + struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; |
| + uint32_t buf[b->cursor_width * b->cursor_height]; |
| + int32_t stride; |
| + uint8_t *s; |
| + int i; |
| + |
| + assert(buffer && buffer->shm_buffer); |
| + assert(buffer->shm_buffer == wl_shm_buffer_get(buffer->resource)); |
| + assert(ev->surface->width <= b->cursor_width); |
| + assert(ev->surface->height <= b->cursor_height); |
| + |
| + memset(buf, 0, sizeof buf); |
| + stride = wl_shm_buffer_get_stride(buffer->shm_buffer); |
| + s = wl_shm_buffer_get_data(buffer->shm_buffer); |
| + |
| + wl_shm_buffer_begin_access(buffer->shm_buffer); |
| + for (i = 0; i < ev->surface->height; i++) |
| + memcpy(buf + i * b->cursor_width, |
| + s + i * stride, |
| + ev->surface->width * 4); |
| + wl_shm_buffer_end_access(buffer->shm_buffer); |
| + |
| + if (gbm_bo_write(bo, buf, sizeof buf) < 0) |
| + weston_log("failed update cursor: %m\n"); |
| } |
| |
| static struct weston_plane * |
| -drm_output_prepare_cursor_view(struct drm_output *output, |
| +drm_output_prepare_cursor_view(struct drm_output_state *output_state, |
| struct weston_view *ev) |
| { |
| + struct drm_output *output = output_state->output; |
| struct drm_backend *b = to_drm_backend(output->base.compositor); |
| + struct drm_plane *plane = output->cursor_plane; |
| + struct drm_plane_state *plane_state; |
| struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; |
| struct wl_shm_buffer *shmbuf; |
| + bool needs_update = false; |
| + float x, y; |
| |
| + if (!plane) |
| + return NULL; |
| + |
| +#if 0 |
| if (b->cursors_are_broken) |
| return NULL; |
| +#endif |
| + |
| + if (!plane->state_cur->complete) |
| + return NULL; |
| |
| - if (output->cursor_view) |
| + if (plane->state_cur->output && plane->state_cur->output != output) |
| return NULL; |
| |
| /* Don't import buffers which span multiple outputs. */ |
| @@ -1207,111 +2939,120 @@ drm_output_prepare_cursor_view(struct drm_output *output, |
| ev->surface->height > b->cursor_height) |
| return NULL; |
| |
| - output->cursor_view = ev; |
| - |
| - return &output->cursor_plane; |
| -} |
| - |
| -/** |
| - * Update the image for the current cursor surface |
| - * |
| - * @param b DRM backend structure |
| - * @param bo GBM buffer object to write into |
| - * @param ev View to use for cursor image |
| - */ |
| -static void |
| -cursor_bo_update(struct drm_backend *b, struct gbm_bo *bo, |
| - struct weston_view *ev) |
| -{ |
| - struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; |
| - uint32_t buf[b->cursor_width * b->cursor_height]; |
| - int32_t stride; |
| - uint8_t *s; |
| - int i; |
| - |
| - assert(buffer && buffer->shm_buffer); |
| - assert(buffer->shm_buffer == wl_shm_buffer_get(buffer->resource)); |
| - assert(ev->surface->width <= b->cursor_width); |
| - assert(ev->surface->height <= b->cursor_height); |
| + plane_state = |
| + drm_output_state_get_plane(output_state, output->cursor_plane); |
| |
| - memset(buf, 0, sizeof buf); |
| - stride = wl_shm_buffer_get_stride(buffer->shm_buffer); |
| - s = wl_shm_buffer_get_data(buffer->shm_buffer); |
| + if (plane_state && plane_state->fb) |
| + return NULL; |
| |
| - wl_shm_buffer_begin_access(buffer->shm_buffer); |
| - for (i = 0; i < ev->surface->height; i++) |
| - memcpy(buf + i * b->cursor_width, |
| - s + i * stride, |
| - ev->surface->width * 4); |
| - wl_shm_buffer_end_access(buffer->shm_buffer); |
| + /* Since we're setting plane state up front, we need to work out |
| + * whether or not we need to upload a new cursor. We can't use the |
| + * plane damage, since the planes haven't actually been calculated |
| + * yet: instead try to figure it out directly. KMS cursor planes are |
| + * pretty unique here, in that they lie partway between a Weston plane |
| + * (direct scanout) and a renderer. */ |
| + if (ev != output->cursor_view || |
| + pixman_region32_not_empty(&ev->surface->damage)) { |
| + output->current_cursor++; |
| + output->current_cursor = |
| + output->current_cursor % |
| + ARRAY_LENGTH(output->gbm_cursor_fb); |
| + needs_update = true; |
| + } |
| |
| - if (gbm_bo_write(bo, buf, sizeof buf) < 0) |
| - weston_log("failed update cursor: %m\n"); |
| + output->cursor_view = ev; |
| + weston_view_to_global_float(ev, 0, 0, &x, &y); |
| + plane->base.x = x; |
| + plane->base.y = y; |
| + |
| + plane_state->fb = |
| + drm_fb_ref(output->gbm_cursor_fb[output->current_cursor]); |
| + plane_state->output = output; |
| + plane_state->src_x = 0; |
| + plane_state->src_y = 0; |
| + plane_state->src_w = b->cursor_width << 16; |
| + plane_state->src_h = b->cursor_height << 16; |
| + plane_state->dest_x = (x - output->base.x) * output->base.current_scale; |
| + plane_state->dest_y = (y - output->base.y) * output->base.current_scale; |
| + plane_state->dest_w = b->cursor_width; |
| + plane_state->dest_h = b->cursor_height; |
| + |
| + if (needs_update) |
| + cursor_bo_update(b, plane_state->fb->bo, ev); |
| + |
| + return &plane->base; |
| } |
| |
| static void |
| -drm_output_set_cursor(struct drm_output *output) |
| +drm_output_set_cursor(struct drm_output_state *output_state) |
| { |
| - struct weston_view *ev = output->cursor_view; |
| - struct weston_buffer *buffer; |
| + struct drm_output *output = output_state->output; |
| struct drm_backend *b = to_drm_backend(output->base.compositor); |
| + struct drm_plane *plane = output->cursor_plane; |
| + struct drm_plane_state *state; |
| EGLint handle; |
| struct gbm_bo *bo; |
| - float x, y; |
| |
| - output->cursor_view = NULL; |
| - if (ev == NULL) { |
| + if (!plane) |
| + return; |
| + |
| + state = drm_output_state_get_existing_plane(output_state, plane); |
| + if (!state) |
| + return; |
| + |
| + if (!state->fb) { |
| + pixman_region32_fini(&plane->base.damage); |
| + pixman_region32_init(&plane->base.damage); |
| drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); |
| - output->cursor_plane.x = INT32_MIN; |
| - output->cursor_plane.y = INT32_MIN; |
| return; |
| } |
| |
| - buffer = ev->surface->buffer_ref.buffer; |
| - |
| - if (buffer && |
| - pixman_region32_not_empty(&output->cursor_plane.damage)) { |
| - pixman_region32_fini(&output->cursor_plane.damage); |
| - pixman_region32_init(&output->cursor_plane.damage); |
| - output->current_cursor ^= 1; |
| - bo = output->gbm_cursor_bo[output->current_cursor]; |
| + assert(state->fb == output->gbm_cursor_fb[output->current_cursor]); |
| + assert(!plane->state_cur->output || plane->state_cur->output == output); |
| |
| - cursor_bo_update(b, bo, ev); |
| + if (plane->state_cur->fb != state->fb) { |
| + bo = state->fb->bo; |
| handle = gbm_bo_get_handle(bo).s32; |
| if (drmModeSetCursor(b->drm.fd, output->crtc_id, handle, |
| - b->cursor_width, b->cursor_height)) { |
| + b->cursor_width, b->cursor_height)) { |
| weston_log("failed to set cursor: %m\n"); |
| - b->cursors_are_broken = 1; |
| + goto err; |
| } |
| } |
| |
| - weston_view_to_global_float(ev, 0, 0, &x, &y); |
| + pixman_region32_fini(&plane->base.damage); |
| + pixman_region32_init(&plane->base.damage); |
| |
| - /* From global to output space, output transform is guaranteed to be |
| - * NORMAL by drm_output_prepare_cursor_view(). |
| - */ |
| - x = (x - output->base.x) * output->base.current_scale; |
| - y = (y - output->base.y) * output->base.current_scale; |
| + if (drmModeMoveCursor(b->drm.fd, output->crtc_id, |
| + state->dest_x, state->dest_y)) { |
| + weston_log("failed to move cursor: %m\n"); |
| + goto err; |
| + } |
| |
| - if (output->cursor_plane.x != x || output->cursor_plane.y != y) { |
| - if (drmModeMoveCursor(b->drm.fd, output->crtc_id, x, y)) { |
| - weston_log("failed to move cursor: %m\n"); |
| - b->cursors_are_broken = 1; |
| - } |
| + return; |
| |
| - output->cursor_plane.x = x; |
| - output->cursor_plane.y = y; |
| - } |
| +err: |
| + b->cursors_are_broken = 1; |
| + drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); |
| } |
| |
| static void |
| -drm_assign_planes(struct weston_output *output_base) |
| +drm_assign_planes(struct weston_output *output_base, void *repaint_data) |
| { |
| struct drm_backend *b = to_drm_backend(output_base->compositor); |
| + struct drm_pending_state *pending_state = repaint_data; |
| struct drm_output *output = to_drm_output(output_base); |
| - struct weston_view *ev, *next; |
| - pixman_region32_t overlap, surface_overlap; |
| + struct drm_output_state *state; |
| + struct drm_plane_state *plane_state; |
| + struct weston_view *ev; |
| + pixman_region32_t surface_overlap, renderer_region; |
| struct weston_plane *primary, *next_plane; |
| + bool picked_scanout = false; |
| + |
| + assert(!output->state_last); |
| + state = drm_output_state_duplicate(output->state_cur, |
| + pending_state, |
| + DRM_OUTPUT_STATE_CLEAR_PLANES); |
| |
| /* |
| * Find a surface for each sprite in the output using some heuristics: |
| @@ -1326,10 +3067,10 @@ drm_assign_planes(struct weston_output *output_base) |
| * the client buffer can be used directly for the sprite surface |
| * as we do for flipping full screen surfaces. |
| */ |
| - pixman_region32_init(&overlap); |
| + pixman_region32_init(&renderer_region); |
| primary = &output_base->compositor->primary_plane; |
| |
| - wl_list_for_each_safe(ev, next, &output_base->compositor->view_list, link) { |
| + wl_list_for_each(ev, &output_base->compositor->view_list, link) { |
| struct weston_surface *es = ev->surface; |
| |
| /* Test whether this buffer can ever go into a plane: |
| @@ -1350,29 +3091,40 @@ drm_assign_planes(struct weston_output *output_base) |
| es->keep_buffer = false; |
| |
| pixman_region32_init(&surface_overlap); |
| - pixman_region32_intersect(&surface_overlap, &overlap, |
| + pixman_region32_intersect(&surface_overlap, &renderer_region, |
| &ev->transform.boundingbox); |
| |
| next_plane = NULL; |
| - if (pixman_region32_not_empty(&surface_overlap)) |
| + if (pixman_region32_not_empty(&surface_overlap) || picked_scanout) |
| next_plane = primary; |
| if (next_plane == NULL) |
| - next_plane = drm_output_prepare_cursor_view(output, ev); |
| - if (next_plane == NULL) |
| - next_plane = drm_output_prepare_scanout_view(output, ev); |
| + next_plane = drm_output_prepare_cursor_view(state, ev); |
| + |
| + /* If a higher-stacked view already got assigned to scanout, it's incorrect to |
| + * assign a subsequent (lower-stacked) view to scanout. |
| + */ |
| + if (next_plane == NULL) { |
| + next_plane = drm_output_prepare_scanout_view(state, ev); |
| + if (next_plane) |
| + picked_scanout = true; |
| + } |
| + |
| if (next_plane == NULL) |
| - next_plane = drm_output_prepare_overlay_view(output, ev); |
| + next_plane = drm_output_prepare_overlay_view(state, ev); |
| + |
| if (next_plane == NULL) |
| next_plane = primary; |
| |
| weston_view_move_to_plane(ev, next_plane); |
| |
| if (next_plane == primary) |
| - pixman_region32_union(&overlap, &overlap, |
| + pixman_region32_union(&renderer_region, |
| + &renderer_region, |
| &ev->transform.boundingbox); |
| |
| if (next_plane == primary || |
| - next_plane == &output->cursor_plane) { |
| + (output->cursor_plane && |
| + next_plane == &output->cursor_plane->base)) { |
| /* cursor plane involves a copy */ |
| ev->psf_flags = 0; |
| } else { |
| @@ -1384,7 +3136,20 @@ drm_assign_planes(struct weston_output *output_base) |
| |
| pixman_region32_fini(&surface_overlap); |
| } |
| - pixman_region32_fini(&overlap); |
| + pixman_region32_fini(&renderer_region); |
| + |
| + /* We rely on output->cursor_view being both an accurate reflection of |
| + * the cursor plane's state, but also being maintained across repaints |
| + * to avoid unnecessary damage uploads, per the comment in |
| + * drm_output_prepare_cursor_view. In the event that we go from having |
| + * a cursor view to not having a cursor view, we need to clear it. */ |
| + if (output->cursor_view) { |
| + plane_state = |
| + drm_output_state_get_existing_plane(state, |
| + output->cursor_plane); |
| + if (!plane_state || !plane_state->fb) |
| + output->cursor_view = NULL; |
| + } |
| } |
| |
| /** |
| @@ -1407,7 +3172,7 @@ choose_mode (struct drm_output *output, struct weston_mode *target_mode) |
| output->base.current_mode->height == target_mode->height && |
| (output->base.current_mode->refresh == target_mode->refresh || |
| target_mode->refresh == 0)) |
| - return (struct drm_mode *)output->base.current_mode; |
| + return to_drm_mode(output->base.current_mode); |
| |
| wl_list_for_each(mode, &output->base.mode_list, base.link) { |
| if (mode->mode_info.hdisplay == target_mode->width && |
| @@ -1435,26 +3200,13 @@ drm_output_fini_pixman(struct drm_output *output); |
| static int |
| drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mode) |
| { |
| - struct drm_output *output; |
| - struct drm_mode *drm_mode; |
| - struct drm_backend *b; |
| - |
| - if (output_base == NULL) { |
| - weston_log("output is NULL.\n"); |
| - return -1; |
| - } |
| - |
| - if (mode == NULL) { |
| - weston_log("mode is NULL.\n"); |
| - return -1; |
| - } |
| - |
| - b = to_drm_backend(output_base->compositor); |
| - output = to_drm_output(output_base); |
| - drm_mode = choose_mode (output, mode); |
| + struct drm_output *output = to_drm_output(output_base); |
| + struct drm_backend *b = to_drm_backend(output_base->compositor); |
| + struct drm_mode *drm_mode = choose_mode(output, mode); |
| |
| if (!drm_mode) { |
| - weston_log("%s, invalid resolution:%dx%d\n", __func__, mode->width, mode->height); |
| + weston_log("%s: invalid resolution %dx%d\n", |
| + output_base->name, mode->width, mode->height); |
| return -1; |
| } |
| |
| @@ -1467,10 +3219,13 @@ drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mo |
| output->base.current_mode->flags = |
| WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; |
| |
| - /* reset rendering stuff. */ |
| - drm_output_release_fb(output, output->current); |
| - drm_output_release_fb(output, output->next); |
| - output->current = output->next = NULL; |
| + /* XXX: This drops our current buffer too early, before we've started |
| + * displaying it. Ideally this should be much more atomic and |
| + * integrated with a full repaint cycle, rather than doing a |
| + * sledgehammer modeswitch first, and only later showing new |
| + * content. |
| + */ |
| + b->state_invalid = true; |
| |
| if (b->use_pixman) { |
| drm_output_fini_pixman(output); |
| @@ -1494,11 +3249,21 @@ drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mo |
| static int |
| on_drm_input(int fd, uint32_t mask, void *data) |
| { |
| +#ifdef HAVE_DRM_ATOMIC |
| + struct drm_backend *b = data; |
| +#endif |
| drmEventContext evctx; |
| |
| memset(&evctx, 0, sizeof evctx); |
| - evctx.version = DRM_EVENT_CONTEXT_VERSION; |
| - evctx.page_flip_handler = page_flip_handler; |
| +#ifndef HAVE_DRM_ATOMIC |
| + evctx.version = 2; |
| +#else |
| + evctx.version = 3; |
| + if (b->atomic_modeset) |
| + evctx.page_flip_handler2 = atomic_flip_handler; |
| + else |
| +#endif |
| + evctx.page_flip_handler = page_flip_handler; |
| evctx.vblank_handler = vblank_handler; |
| drmHandleEvent(fd, &evctx); |
| |
| @@ -1506,36 +3271,15 @@ on_drm_input(int fd, uint32_t mask, void *data) |
| } |
| |
| static int |
| -init_drm(struct drm_backend *b, struct udev_device *device) |
| +init_kms_caps(struct drm_backend *b) |
| { |
| - const char *filename, *sysnum; |
| uint64_t cap; |
| - int fd, ret; |
| + int ret; |
| clockid_t clk_id; |
| |
| - sysnum = udev_device_get_sysnum(device); |
| - if (sysnum) |
| - b->drm.id = atoi(sysnum); |
| - if (!sysnum || b->drm.id < 0) { |
| - weston_log("cannot get device sysnum\n"); |
| - return -1; |
| - } |
| - |
| - filename = udev_device_get_devnode(device); |
| - fd = weston_launcher_open(b->compositor->launcher, filename, O_RDWR); |
| - if (fd < 0) { |
| - /* Probably permissions error */ |
| - weston_log("couldn't open %s, skipping\n", |
| - udev_device_get_devnode(device)); |
| - return -1; |
| - } |
| - |
| - weston_log("using %s\n", filename); |
| - |
| - b->drm.fd = fd; |
| - b->drm.filename = strdup(filename); |
| + weston_log("using %s\n", b->drm.filename); |
| |
| - ret = drmGetCap(fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap); |
| + ret = drmGetCap(b->drm.fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap); |
| if (ret == 0 && cap == 1) |
| clk_id = CLOCK_MONOTONIC; |
| else |
| @@ -1547,18 +3291,40 @@ init_drm(struct drm_backend *b, struct udev_device *device) |
| return -1; |
| } |
| |
| - ret = drmGetCap(fd, DRM_CAP_CURSOR_WIDTH, &cap); |
| + ret = drmGetCap(b->drm.fd, DRM_CAP_CURSOR_WIDTH, &cap); |
| if (ret == 0) |
| b->cursor_width = cap; |
| else |
| b->cursor_width = 64; |
| |
| - ret = drmGetCap(fd, DRM_CAP_CURSOR_HEIGHT, &cap); |
| + ret = drmGetCap(b->drm.fd, DRM_CAP_CURSOR_HEIGHT, &cap); |
| if (ret == 0) |
| b->cursor_height = cap; |
| else |
| b->cursor_height = 64; |
| |
| + if (!getenv("WESTON_DISABLE_UNIVERSAL_PLANES")) { |
| + ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); |
| + b->universal_planes = (ret == 0); |
| + } |
| + weston_log("DRM: %s universal planes\n", |
| + b->universal_planes ? "supports" : "does not support"); |
| + |
| +#ifdef HAVE_DRM_ATOMIC |
| + if (b->universal_planes && !getenv("WESTON_DISABLE_ATOMIC")) { |
| + #if 0 |
| + ret = drmGetCap(b->drm.fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap); |
| + if (ret != 0) |
| + cap = 0; |
| + #endif |
| + cap = 1 ; |
| + ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_ATOMIC, 1); |
| + b->atomic_modeset = ((ret == 0) && (cap == 1)); |
| + } |
| +#endif |
| + weston_log("DRM: %s atomic modesetting\n", |
| + b->atomic_modeset ? "supports" : "does not support"); |
| + |
| return 0; |
| } |
| |
| @@ -1642,7 +3408,6 @@ init_egl(struct drm_backend *b) |
| |
| if (drm_backend_create_gl_renderer(b) < 0) { |
| gbm_device_destroy(b->gbm); |
| - b->gbm = NULL; |
| return -1; |
| } |
| |
| @@ -1656,6 +3421,293 @@ init_pixman(struct drm_backend *b) |
| } |
| |
| /** |
| + * Create a drm_plane for a hardware plane |
| + * |
| + * Creates one drm_plane structure for a hardware plane, and initialises its |
| + * properties and formats. |
| + * |
| + * In the absence of universal plane support, where KMS does not explicitly |
| + * expose the primary and cursor planes to userspace, this may also create |
| + * an 'internal' plane for internal management. |
| + * |
| + * This function does not add the plane to the list of usable planes in Weston |
| + * itself; the caller is responsible for this. |
| + * |
| + * Call drm_plane_destroy to clean up the plane. |
| + * |
| + * @sa drm_output_find_special_plane |
| + * @param b DRM compositor backend |
| + * @param kplane DRM plane to create, or NULL if creating internal plane |
| + * @param output Output to create internal plane for, or NULL |
| + * @param type Type to use when creating internal plane, or invalid |
| + * @param format Format to use for internal planes, or 0 |
| + */ |
| +static struct drm_plane * |
| +drm_plane_create(struct drm_backend *b, const drmModePlane *kplane, |
| + struct drm_output *output, enum wdrm_plane_type type, |
| + uint32_t format) |
| +{ |
| + struct drm_plane *plane; |
| + drmModeObjectProperties *props; |
| + int num_formats = (kplane) ? kplane->count_formats : 1; |
| + |
| + plane = zalloc(sizeof(*plane) + |
| + (sizeof(uint32_t) * num_formats)); |
| + if (!plane) { |
| + weston_log("%s: out of memory\n", __func__); |
| + return NULL; |
| + } |
| + |
| + plane->backend = b; |
| + plane->state_cur = drm_plane_state_alloc(NULL, plane); |
| + plane->state_cur->complete = true; |
| + |
| + if (kplane) { |
| + plane->possible_crtcs = kplane->possible_crtcs; |
| + plane->plane_id = kplane->plane_id; |
| + plane->count_formats = kplane->count_formats; |
| + memcpy(plane->formats, kplane->formats, |
| + kplane->count_formats * sizeof(kplane->formats[0])); |
| + |
| + props = drmModeObjectGetProperties(b->drm.fd, kplane->plane_id, |
| + DRM_MODE_OBJECT_PLANE); |
| + if (!props) { |
| + weston_log("couldn't get plane properties\n"); |
| + goto err; |
| + } |
| + drm_property_info_populate(b, plane_props, plane->props, |
| + WDRM_PLANE__COUNT, props); |
| + plane->type = |
| + drm_property_get_value(&plane->props[WDRM_PLANE_TYPE], |
| + props, |
| + WDRM_PLANE_TYPE__COUNT); |
| + drmModeFreeObjectProperties(props); |
| + } |
| + else { |
| + plane->possible_crtcs = (1 << output->pipe); |
| + plane->plane_id = 0; |
| + plane->count_formats = 1; |
| + plane->formats[0] = format; |
| + plane->type = type; |
| + } |
| + |
| + if (plane->type == WDRM_PLANE_TYPE__COUNT) |
| + goto err_props; |
| + |
| + /* With universal planes, everything is a DRM plane; without |
| + * universal planes, the only DRM planes are overlay planes. |
| + * Everything else is a fake plane. */ |
| + if (b->universal_planes) { |
| + assert(kplane); |
| + } else { |
| + if (kplane) |
| + assert(plane->type == WDRM_PLANE_TYPE_OVERLAY); |
| + else |
| + assert(plane->type != WDRM_PLANE_TYPE_OVERLAY && |
| + output); |
| + } |
| + |
| + weston_plane_init(&plane->base, b->compositor, 0, 0); |
| + wl_list_insert(&b->plane_list, &plane->link); |
| + |
| + return plane; |
| + |
| +err_props: |
| + drm_property_info_free(plane->props, WDRM_PLANE__COUNT); |
| +err: |
| + drm_plane_state_free(plane->state_cur, true); |
| + free(plane); |
| + return NULL; |
| +} |
| + |
| +/** |
| + * Find, or create, a special-purpose plane |
| + * |
| + * Primary and cursor planes are a special case, in that before universal |
| + * planes, they are driven by non-plane API calls. Without universal plane |
| + * support, the only way to configure a primary plane is via drmModeSetCrtc, |
| + * and the only way to configure a cursor plane is drmModeSetCursor2. |
| + * |
| + * Although they may actually be regular planes in the hardware, without |
| + * universal plane support, these planes are not actually exposed to |
| + * userspace in the regular plane list. |
| + * |
| + * However, for ease of internal tracking, we want to manage all planes |
| + * through the same drm_plane structures. Therefore, when we are running |
| + * without universal plane support, we create fake drm_plane structures |
| + * to track these planes. |
| + * |
| + * @param b DRM backend |
| + * @param output Output to use for plane |
| + * @param type Type of plane |
| + */ |
| +static struct drm_plane * |
| +drm_output_find_special_plane(struct drm_backend *b, struct drm_output *output, |
| + enum wdrm_plane_type type) |
| +{ |
| + struct drm_plane *plane; |
| + |
| + if (!b->universal_planes) { |
| + uint32_t format; |
| + |
| + switch (type) { |
| + case WDRM_PLANE_TYPE_CURSOR: |
| + format = GBM_FORMAT_ARGB8888; |
| + break; |
| + case WDRM_PLANE_TYPE_PRIMARY: |
| + /* We don't know what formats the primary plane supports |
| + * before universal planes, so we just assume that the |
| + * GBM format works; however, this isn't set until after |
| + * the output is created. */ |
| + format = 0; |
| + break; |
| + default: |
| + assert(!"invalid type in drm_output_find_special_plane"); |
| + break; |
| + } |
| + |
| + return drm_plane_create(b, NULL, output, type, format); |
| + } |
| + |
| + wl_list_for_each(plane, &b->plane_list, link) { |
| + struct drm_output *tmp; |
| + bool found_elsewhere = false; |
| + |
| + if (plane->type != type) |
| + continue; |
| + if (!drm_plane_is_available(plane, output)) |
| + continue; |
| + |
| + /* On some platforms, primary/cursor planes can roam |
| + * between different CRTCs, so make sure we don't claim the |
| + * same plane for two outputs. */ |
| + wl_list_for_each(tmp, &b->compositor->pending_output_list, |
| + base.link) { |
| + if (tmp->cursor_plane == plane || |
| + tmp->scanout_plane == plane) { |
| + found_elsewhere = true; |
| + break; |
| + } |
| + } |
| + wl_list_for_each(tmp, &b->compositor->output_list, |
| + base.link) { |
| + if (tmp->cursor_plane == plane || |
| + tmp->scanout_plane == plane) { |
| + found_elsewhere = true; |
| + break; |
| + } |
| + } |
| + |
| + if (found_elsewhere) |
| + continue; |
| + |
| + plane->possible_crtcs = (1 << output->pipe); |
| + return plane; |
| + } |
| + |
| + return NULL; |
| +} |
| + |
| +/** |
| + * Destroy one DRM plane |
| + * |
| + * Destroy a DRM plane, removing it from screen and releasing its retained |
| + * buffers in the process. The counterpart to drm_plane_create. |
| + * |
| + * @param plane Plane to deallocate (will be freed) |
| + */ |
| +static void |
| +drm_plane_destroy(struct drm_plane *plane) |
| +{ |
| + if (plane->type == WDRM_PLANE_TYPE_OVERLAY) |
| + drmModeSetPlane(plane->backend->drm.fd, plane->plane_id, |
| + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); |
| + drm_plane_state_free(plane->state_cur, true); |
| + drm_property_info_free(plane->props, WDRM_PLANE__COUNT); |
| + weston_plane_release(&plane->base); |
| + wl_list_remove(&plane->link); |
| + free(plane); |
| +} |
| + |
| +/** |
| + * Initialise sprites (overlay planes) |
| + * |
| + * Walk the list of provided DRM planes, and add overlay planes. |
| + * |
| + * Call destroy_sprites to free these planes. |
| + * |
| + * @param b DRM compositor backend |
| + */ |
| +static void |
| +create_sprites(struct drm_backend *b) |
| +{ |
| + drmModePlaneRes *kplane_res; |
| + drmModePlane *kplane; |
| + struct drm_plane *drm_plane; |
| + uint32_t i; |
| + kplane_res = drmModeGetPlaneResources(b->drm.fd); |
| + if (!kplane_res) { |
| + weston_log("failed to get plane resources: %s\n", |
| + strerror(errno)); |
| + return; |
| + } |
| + |
| + for (i = 0; i < kplane_res->count_planes; i++) { |
| + kplane = drmModeGetPlane(b->drm.fd, kplane_res->planes[i]); |
| + if (!kplane) |
| + continue; |
| + |
| + drm_plane = drm_plane_create(b, kplane, NULL, |
| + WDRM_PLANE_TYPE__COUNT, 0); |
| + drmModeFreePlane(kplane); |
| + if (!drm_plane) |
| + continue; |
| + |
| + if (drm_plane->type == WDRM_PLANE_TYPE_OVERLAY) |
| + weston_compositor_stack_plane(b->compositor, |
| + &drm_plane->base, |
| + &b->compositor->primary_plane); |
| + } |
| + |
| + drmModeFreePlaneResources(kplane_res); |
| +} |
| + |
| +/** |
| + * Clean up sprites (overlay planes) |
| + * |
| + * The counterpart to create_sprites. |
| + * |
| + * @param b DRM compositor backend |
| + */ |
| +static void |
| +destroy_sprites(struct drm_backend *b) |
| +{ |
| + struct drm_plane *plane, *next; |
| + |
| + wl_list_for_each_safe(plane, next, &b->plane_list, link) |
| + drm_plane_destroy(plane); |
| +} |
| + |
| +static uint32_t |
| +drm_refresh_rate_mHz(const drmModeModeInfo *info) |
| +{ |
| + uint64_t refresh; |
| + |
| + /* Calculate higher precision (mHz) refresh rate */ |
| + refresh = (info->clock * 1000000LL / info->htotal + |
| + info->vtotal / 2) / info->vtotal; |
| + |
| + if (info->flags & DRM_MODE_FLAG_INTERLACE) |
| + refresh *= 2; |
| + if (info->flags & DRM_MODE_FLAG_DBLSCAN) |
| + refresh /= 2; |
| + if (info->vscan > 1) |
| + refresh /= info->vscan; |
| + |
| + return refresh; |
| +} |
| + |
| +/** |
| * Add a mode to output's mode list |
| * |
| * Copy the supplied DRM mode into a Weston mode structure, and add it to the |
| @@ -1669,7 +3721,6 @@ static struct drm_mode * |
| drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info) |
| { |
| struct drm_mode *mode; |
| - uint64_t refresh; |
| |
| mode = malloc(sizeof *mode); |
| if (mode == NULL) |
| @@ -1679,19 +3730,9 @@ drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info) |
| mode->base.width = info->hdisplay; |
| mode->base.height = info->vdisplay; |
| |
| - /* Calculate higher precision (mHz) refresh rate */ |
| - refresh = (info->clock * 1000000LL / info->htotal + |
| - info->vtotal / 2) / info->vtotal; |
| - |
| - if (info->flags & DRM_MODE_FLAG_INTERLACE) |
| - refresh *= 2; |
| - if (info->flags & DRM_MODE_FLAG_DBLSCAN) |
| - refresh /= 2; |
| - if (info->vscan > 1) |
| - refresh /= info->vscan; |
| - |
| - mode->base.refresh = refresh; |
| + mode->base.refresh = drm_refresh_rate_mHz(info); |
| mode->mode_info = *info; |
| + mode->blob_id = 0; |
| |
| if (info->type & DRM_MODE_TYPE_PREFERRED) |
| mode->base.flags |= WL_OUTPUT_MODE_PREFERRED; |
| @@ -1701,6 +3742,32 @@ drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info) |
| return mode; |
| } |
| |
| +/** |
| + * Destroys a mode, and removes it from the list. |
| + */ |
| +static void |
| +drm_output_destroy_mode(struct drm_backend *backend, struct drm_mode *mode) |
| +{ |
| + if (mode->blob_id) |
| + drmModeDestroyPropertyBlob(backend->drm.fd, mode->blob_id); |
| + wl_list_remove(&mode->base.link); |
| + free(mode); |
| +} |
| + |
| +/** Destroy a list of drm_modes |
| + * |
| + * @param backend The backend for releasing mode property blobs. |
| + * @param mode_list The list linked by drm_mode::base.link. |
| + */ |
| +static void |
| +drm_mode_list_destroy(struct drm_backend *backend, struct wl_list *mode_list) |
| +{ |
| + struct drm_mode *mode, *next; |
| + |
| + wl_list_for_each_safe(mode, next, mode_list, base.link) |
| + drm_output_destroy_mode(backend, mode); |
| +} |
| + |
| static int |
| drm_subpixel_to_wayland(int drm_value) |
| { |
| @@ -1757,46 +3824,72 @@ drm_set_backlight(struct weston_output *output_base, uint32_t value) |
| backlight_set_brightness(output->backlight, new_brightness); |
| } |
| |
| -static drmModePropertyPtr |
| -drm_get_prop(int fd, drmModeConnectorPtr connector, const char *name) |
| -{ |
| - drmModePropertyPtr props; |
| - int i; |
| - |
| - for (i = 0; i < connector->count_props; i++) { |
| - props = drmModeGetProperty(fd, connector->props[i]); |
| - if (!props) |
| - continue; |
| - |
| - if (!strcmp(props->name, name)) |
| - return props; |
| - |
| - drmModeFreeProperty(props); |
| - } |
| - |
| - return NULL; |
| -} |
| - |
| +/** |
| + * Power output on or off |
| + * |
| + * The DPMS/power level of an output is used to switch it on or off. This |
| + * is DRM's hook for doing so, which can called either as part of repaint, |
| + * or independently of the repaint loop. |
| + * |
| + * If we are called as part of repaint, we simply set the relevant bit in |
| + * state and return. |
| + */ |
| static void |
| drm_set_dpms(struct weston_output *output_base, enum dpms_enum level) |
| { |
| struct drm_output *output = to_drm_output(output_base); |
| - struct weston_compositor *ec = output_base->compositor; |
| - struct drm_backend *b = to_drm_backend(ec); |
| + struct drm_backend *b = to_drm_backend(output_base->compositor); |
| + struct drm_pending_state *pending_state = b->repaint_data; |
| + struct drm_output_state *state; |
| int ret; |
| |
| - if (!output->dpms_prop) |
| + if (output->state_cur->dpms == level) |
| return; |
| |
| - ret = drmModeConnectorSetProperty(b->drm.fd, output->connector_id, |
| - output->dpms_prop->prop_id, level); |
| - if (ret) { |
| - weston_log("DRM: DPMS: failed property set for %s\n", |
| - output->base.name); |
| + /* If we're being called during the repaint loop, then this is |
| + * simple: discard any previously-generated state, and create a new |
| + * state where we disable everything. When we come to flush, this |
| + * will be applied. |
| + * |
| + * However, we need to be careful: we can be called whilst another |
| + * output is in its repaint cycle (pending_state exists), but our |
| + * output still has an incomplete state application outstanding. |
| + * In that case, we need to wait until that completes. */ |
| + if (pending_state && !output->state_last) { |
| + /* The repaint loop already sets DPMS on; we don't need to |
| + * explicitly set it on here, as it will already happen |
| + * whilst applying the repaint state. */ |
| + if (level == WESTON_DPMS_ON) |
| + return; |
| + |
| + state = drm_pending_state_get_output(pending_state, output); |
| + if (state) |
| + drm_output_state_free(state); |
| + state = drm_output_get_disable_state(pending_state, output); |
| return; |
| } |
| |
| - output->dpms = level; |
| + /* As we throw everything away when disabling, just send us back through |
| + * a repaint cycle. */ |
| + if (level == WESTON_DPMS_ON) { |
| + if (output->dpms_off_pending) |
| + output->dpms_off_pending = 0; |
| + weston_output_schedule_repaint(output_base); |
| + return; |
| + } |
| + |
| + /* If we've already got a request in the pipeline, then we need to |
| + * park our DPMS request until that request has quiesced. */ |
| + if (output->state_last) { |
| + output->dpms_off_pending = 1; |
| + return; |
| + } |
| + |
| + pending_state = drm_pending_state_alloc(b); |
| + drm_output_get_disable_state(pending_state, output); |
| + ret = drm_pending_state_apply_sync(pending_state); |
| + if (ret != 0) |
| + weston_log("drm_set_dpms: couldn't disable output?\n"); |
| } |
| |
| static const char * const connector_type_names[] = { |
| @@ -1821,11 +3914,20 @@ static const char * const connector_type_names[] = { |
| #endif |
| }; |
| |
| +/** Create a name given a DRM connector |
| + * |
| + * \param con The DRM connector whose type and id form the name. |
| + * \return A newly allocate string, or NULL on error. Must be free()'d |
| + * after use. |
| + * |
| + * The name does not identify the DRM display device. |
| + */ |
| static char * |
| make_connector_name(const drmModeConnector *con) |
| { |
| - char name[32]; |
| + char *name; |
| const char *type_name = NULL; |
| + int ret; |
| |
| if (con->connector_type < ARRAY_LENGTH(connector_type_names)) |
| type_name = connector_type_names[con->connector_type]; |
| @@ -1833,9 +3935,11 @@ make_connector_name(const drmModeConnector *con) |
| if (!type_name) |
| type_name = "UNNAMED"; |
| |
| - snprintf(name, sizeof name, "%s-%d", type_name, con->connector_type_id); |
| + ret = asprintf(&name, "%s-%d", type_name, con->connector_type_id); |
| + if (ret < 0) |
| + return NULL; |
| |
| - return strdup(name); |
| + return name; |
| } |
| |
| static int |
| @@ -1843,16 +3947,20 @@ find_crtc_for_connector(struct drm_backend *b, |
| drmModeRes *resources, drmModeConnector *connector) |
| { |
| drmModeEncoder *encoder; |
| - uint32_t possible_crtcs; |
| int i, j; |
| + int ret = -1; |
| |
| for (j = 0; j < connector->count_encoders; j++) { |
| + uint32_t possible_crtcs, encoder_id, crtc_id; |
| + |
| encoder = drmModeGetEncoder(b->drm.fd, connector->encoders[j]); |
| if (encoder == NULL) { |
| weston_log("Failed to get encoder.\n"); |
| - return -1; |
| + continue; |
| } |
| + encoder_id = encoder->encoder_id; |
| possible_crtcs = encoder->possible_crtcs; |
| + crtc_id = encoder->crtc_id; |
| drmModeFreeEncoder(encoder); |
| |
| for (i = 0; i < resources->count_crtcs; i++) { |
| @@ -1862,10 +3970,66 @@ find_crtc_for_connector(struct drm_backend *b, |
| if (drm_output_find_by_crtc(b, resources->crtcs[i])) |
| continue; |
| |
| - return i; |
| + /* Try to preserve the existing |
| + * CRTC -> encoder -> connector routing; it makes |
| + * initialisation faster, and also since we have a |
| + * very dumb picking algorithm, may preserve a better |
| + * choice. */ |
| + if (!connector->encoder_id || |
| + (encoder_id == connector->encoder_id && |
| + crtc_id == resources->crtcs[i])) |
| + return i; |
| + |
| + ret = i; |
| + } |
| + } |
| + |
| + return ret; |
| +} |
| + |
| +static void drm_output_fini_cursor_egl(struct drm_output *output) |
| +{ |
| + unsigned int i; |
| + |
| + for (i = 0; i < ARRAY_LENGTH(output->gbm_cursor_fb); i++) { |
| + drm_fb_unref(output->gbm_cursor_fb[i]); |
| + output->gbm_cursor_fb[i] = NULL; |
| + } |
| +} |
| + |
| +static int |
| +drm_output_init_cursor_egl(struct drm_output *output, struct drm_backend *b) |
| +{ |
| + unsigned int i; |
| + |
| + /* No point creating cursors if we don't have a plane for them. */ |
| + if (!output->cursor_plane) |
| + return 0; |
| + |
| + for (i = 0; i < ARRAY_LENGTH(output->gbm_cursor_fb); i++) { |
| + struct gbm_bo *bo; |
| + |
| + bo = gbm_bo_create(b->gbm, b->cursor_width, b->cursor_height, |
| + GBM_FORMAT_ARGB8888, |
| + GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE); |
| + if (!bo) |
| + goto err; |
| + |
| + output->gbm_cursor_fb[i] = |
| + drm_fb_get_from_bo(bo, b, GBM_FORMAT_ARGB8888, |
| + BUFFER_CURSOR); |
| + if (!output->gbm_cursor_fb[i]) { |
| + gbm_bo_destroy(bo); |
| + goto err; |
| } |
| } |
| |
| + return 0; |
| + |
| +err: |
| + weston_log("cursor buffers unavailable, using gl cursors\n"); |
| + b->cursors_are_broken = 1; |
| + drm_output_fini_cursor_egl(output); |
| return -1; |
| } |
| |
| @@ -1877,7 +4041,7 @@ drm_output_init_egl(struct drm_output *output, struct drm_backend *b) |
| output->gbm_format, |
| fallback_format_for(output->gbm_format), |
| }; |
| - int i, flags, n_formats = 1; |
| + int n_formats = 1; |
| |
| output->gbm_surface = gbm_surface_create(b->gbm, |
| output->base.current_mode->width, |
| @@ -1903,21 +4067,7 @@ drm_output_init_egl(struct drm_output *output, struct drm_backend *b) |
| return -1; |
| } |
| |
| - flags = GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE; |
| - |
| - for (i = 0; i < 2; i++) { |
| - if (output->gbm_cursor_bo[i]) |
| - continue; |
| - |
| - output->gbm_cursor_bo[i] = |
| - gbm_bo_create(b->gbm, b->cursor_width, b->cursor_height, |
| - GBM_FORMAT_ARGB8888, flags); |
| - } |
| - |
| - if (output->gbm_cursor_bo[0] == NULL || output->gbm_cursor_bo[1] == NULL) { |
| - weston_log("cursor buffers unavailable, using gl cursors\n"); |
| - b->cursors_are_broken = 1; |
| - } |
| + drm_output_init_cursor_egl(output, b); |
| |
| return 0; |
| } |
| @@ -1925,8 +4075,22 @@ drm_output_init_egl(struct drm_output *output, struct drm_backend *b) |
| static void |
| drm_output_fini_egl(struct drm_output *output) |
| { |
| + struct drm_backend *b = to_drm_backend(output->base.compositor); |
| + |
| + /* Destroying the GBM surface will destroy all our GBM buffers, |
| + * regardless of refcount. Ensure we destroy them here. */ |
| + if (!b->shutting_down && |
| + output->scanout_plane->state_cur->fb && |
| + output->scanout_plane->state_cur->fb->type == BUFFER_GBM_SURFACE) { |
| + drm_plane_state_free(output->scanout_plane->state_cur, true); |
| + output->scanout_plane->state_cur = |
| + drm_plane_state_alloc(NULL, output->scanout_plane); |
| + output->scanout_plane->state_cur->complete = true; |
| + } |
| + |
| gl_renderer->output_destroy(&output->base); |
| gbm_surface_destroy(output->gbm_surface); |
| + drm_output_fini_cursor_egl(output); |
| } |
| |
| static int |
| @@ -1975,7 +4139,7 @@ drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) |
| err: |
| for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { |
| if (output->dumb[i]) |
| - drm_fb_destroy_dumb(output->dumb[i]); |
| + drm_fb_unref(output->dumb[i]); |
| if (output->image[i]) |
| pixman_image_unref(output->image[i]); |
| |
| @@ -1989,14 +4153,26 @@ err: |
| static void |
| drm_output_fini_pixman(struct drm_output *output) |
| { |
| + struct drm_backend *b = to_drm_backend(output->base.compositor); |
| unsigned int i; |
| |
| + /* Destroying the Pixman surface will destroy all our buffers, |
| + * regardless of refcount. Ensure we destroy them here. */ |
| + if (!b->shutting_down && |
| + output->scanout_plane->state_cur->fb && |
| + output->scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB) { |
| + drm_plane_state_free(output->scanout_plane->state_cur, true); |
| + output->scanout_plane->state_cur = |
| + drm_plane_state_alloc(NULL, output->scanout_plane); |
| + output->scanout_plane->state_cur->complete = true; |
| + } |
| + |
| pixman_renderer_output_destroy(&output->base); |
| pixman_region32_fini(&output->previous_damage); |
| |
| for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { |
| - drm_fb_destroy_dumb(output->dumb[i]); |
| pixman_image_unref(output->image[i]); |
| + drm_fb_unref(output->dumb[i]); |
| output->dumb[i] = NULL; |
| output->image[i] = NULL; |
| } |
| @@ -2100,27 +4276,38 @@ edid_parse(struct drm_edid *edid, const uint8_t *data, size_t length) |
| return 0; |
| } |
| |
| +/** Parse monitor make, model and serial from EDID |
| + * |
| + * \param b The backend instance. |
| + * \param output The output whose \c drm_edid to fill in. |
| + * \param props The DRM connector properties to get the EDID from. |
| + * \param make[out] The monitor make (PNP ID). |
| + * \param model[out] The monitor model (name). |
| + * \param serial_number[out] The monitor serial number. |
| + * |
| + * Each of \c *make, \c *model and \c *serial_number are set only if the |
| + * information is found in the EDID. The pointers they are set to must not |
| + * be free()'d explicitly, instead they get implicitly freed when the |
| + * \c drm_output is destroyed. |
| + */ |
| static void |
| -find_and_parse_output_edid(struct drm_backend *b, |
| - struct drm_output *output, |
| - drmModeConnector *connector) |
| +find_and_parse_output_edid(struct drm_backend *b, struct drm_output *output, |
| + drmModeObjectPropertiesPtr props, |
| + const char **make, |
| + const char **model, |
| + const char **serial_number) |
| { |
| drmModePropertyBlobPtr edid_blob = NULL; |
| - drmModePropertyPtr property; |
| - int i; |
| + uint32_t blob_id; |
| int rc; |
| |
| - for (i = 0; i < connector->count_props && !edid_blob; i++) { |
| - property = drmModeGetProperty(b->drm.fd, connector->props[i]); |
| - if (!property) |
| - continue; |
| - if ((property->flags & DRM_MODE_PROP_BLOB) && |
| - !strcmp(property->name, "EDID")) { |
| - edid_blob = drmModeGetPropertyBlob(b->drm.fd, |
| - connector->prop_values[i]); |
| - } |
| - drmModeFreeProperty(property); |
| - } |
| + blob_id = |
| + drm_property_get_value(&output->props_conn[WDRM_CONNECTOR_EDID], |
| + props, 0); |
| + if (!blob_id) |
| + return; |
| + |
| + edid_blob = drmModeGetPropertyBlob(b->drm.fd, blob_id); |
| if (!edid_blob) |
| return; |
| |
| @@ -2133,17 +4320,15 @@ find_and_parse_output_edid(struct drm_backend *b, |
| output->edid.monitor_name, |
| output->edid.serial_number); |
| if (output->edid.pnp_id[0] != '\0') |
| - output->base.make = output->edid.pnp_id; |
| + *make = output->edid.pnp_id; |
| if (output->edid.monitor_name[0] != '\0') |
| - output->base.model = output->edid.monitor_name; |
| + *model = output->edid.monitor_name; |
| if (output->edid.serial_number[0] != '\0') |
| - output->base.serial_number = output->edid.serial_number; |
| + *serial_number = output->edid.serial_number; |
| } |
| drmModeFreePropertyBlob(edid_blob); |
| } |
| |
| - |
| - |
| static int |
| parse_modeline(const char *s, drmModeModeInfo *mode) |
| { |
| @@ -2151,6 +4336,8 @@ parse_modeline(const char *s, drmModeModeInfo *mode) |
| char vsync[16]; |
| float fclock; |
| |
| + memset(mode, 0, sizeof *mode); |
| + |
| mode->type = DRM_MODE_TYPE_USERDEF; |
| mode->hskew = 0; |
| mode->vscan = 0; |
| @@ -2355,29 +4542,15 @@ drm_output_set_mode(struct weston_output *base, |
| struct drm_output *output = to_drm_output(base); |
| struct drm_backend *b = to_drm_backend(base->compositor); |
| |
| - struct drm_mode *drm_mode, *next, *current; |
| - drmModeModeInfo crtc_mode; |
| - int i; |
| - |
| - output->base.make = "unknown"; |
| - output->base.model = "unknown"; |
| - output->base.serial_number = "unknown"; |
| - wl_list_init(&output->base.mode_list); |
| - |
| - output->original_crtc = drmModeGetCrtc(b->drm.fd, output->crtc_id); |
| + struct drm_mode *current; |
| + drmModeModeInfo crtc_mode; |
| |
| if (connector_get_current_mode(output->connector, b->drm.fd, &crtc_mode) < 0) |
| - goto err_free; |
| - |
| - for (i = 0; i < output->connector->count_modes; i++) { |
| - drm_mode = drm_output_add_mode(output, &output->connector->modes[i]); |
| - if (!drm_mode) |
| - goto err_free; |
| - } |
| + return -1; |
| |
| current = drm_output_choose_initial_mode(b, output, mode, modeline, &crtc_mode); |
| if (!current) |
| - goto err_free; |
| + return -1; |
| |
| output->base.current_mode = ¤t->base; |
| output->base.current_mode->flags |= WL_OUTPUT_MODE_CURRENT; |
| @@ -2386,22 +4559,7 @@ drm_output_set_mode(struct weston_output *base, |
| output->base.native_mode = output->base.current_mode; |
| output->base.native_scale = output->base.current_scale; |
| |
| - output->base.mm_width = output->connector->mmWidth; |
| - output->base.mm_height = output->connector->mmHeight; |
| - |
| return 0; |
| - |
| -err_free: |
| - drmModeFreeCrtc(output->original_crtc); |
| - output->original_crtc = NULL; |
| - |
| - wl_list_for_each_safe(drm_mode, next, &output->base.mode_list, |
| - base.link) { |
| - wl_list_remove(&drm_mode->base.link); |
| - free(drm_mode); |
| - } |
| - |
| - return -1; |
| } |
| |
| static void |
| @@ -2413,6 +4571,12 @@ drm_output_set_gbm_format(struct weston_output *base, |
| |
| if (parse_gbm_format(gbm_format, b->gbm_format, &output->gbm_format) == -1) |
| output->gbm_format = b->gbm_format; |
| + |
| + /* Without universal planes, we can't discover which formats are |
| + * supported by the primary plane; we just hope that the GBM format |
| + * works. */ |
| + if (!b->universal_planes) |
| + output->scanout_plane->formats[0] = output->gbm_format; |
| } |
| |
| static void |
| @@ -2427,22 +4591,145 @@ drm_output_set_seat(struct weston_output *base, |
| } |
| |
| static int |
| +drm_output_init_gamma_size(struct drm_output *output) |
| +{ |
| + struct drm_backend *backend = to_drm_backend(output->base.compositor); |
| + drmModeCrtc *crtc; |
| + |
| + assert(output->base.compositor); |
| + assert(output->crtc_id != 0); |
| + crtc = drmModeGetCrtc(backend->drm.fd, output->crtc_id); |
| + if (!crtc) |
| + return -1; |
| + |
| + output->base.gamma_size = crtc->gamma_size; |
| + |
| + drmModeFreeCrtc(crtc); |
| + |
| + return 0; |
| +} |
| + |
| +/** Allocate a CRTC for the output |
| + * |
| + * @param output The output with no allocated CRTC. |
| + * @param resources DRM KMS resources. |
| + * @param connector The DRM KMS connector data. |
| + * @return 0 on success, -1 on failure. |
| + * |
| + * Finds a free CRTC that can drive the given connector, reserves the CRTC |
| + * for the output, and loads the CRTC properties. |
| + * |
| + * Populates the cursor and scanout planes. |
| + * |
| + * On failure, the output remains without a CRTC. |
| + */ |
| +static int |
| +drm_output_init_crtc(struct drm_output *output, |
| + drmModeRes *resources, drmModeConnector *connector) |
| +{ |
| + struct drm_backend *b = to_drm_backend(output->base.compositor); |
| + drmModeObjectPropertiesPtr props; |
| + int i; |
| + |
| + assert(output->crtc_id == 0); |
| + |
| + i = find_crtc_for_connector(b, resources, connector); |
| + if (i < 0) { |
| + weston_log("No usable crtc/encoder pair for connector.\n"); |
| + return -1; |
| + } |
| + |
| + output->crtc_id = resources->crtcs[i]; |
| + output->pipe = i; |
| + |
| + props = drmModeObjectGetProperties(b->drm.fd, output->crtc_id, |
| + DRM_MODE_OBJECT_CRTC); |
| + if (!props) { |
| + weston_log("failed to get CRTC properties\n"); |
| + goto err_crtc; |
| + } |
| + drm_property_info_populate(b, crtc_props, output->props_crtc, |
| + WDRM_CRTC__COUNT, props); |
| + drmModeFreeObjectProperties(props); |
| + |
| + output->scanout_plane = |
| + drm_output_find_special_plane(b, output, |
| + WDRM_PLANE_TYPE_PRIMARY); |
| + if (!output->scanout_plane) { |
| + weston_log("Failed to find primary plane for output %s\n", |
| + output->base.name); |
| + goto err_crtc; |
| + } |
| + |
| + /* Failing to find a cursor plane is not fatal, as we'll fall back |
| + * to software cursor. */ |
| + output->cursor_plane = |
| + drm_output_find_special_plane(b, output, |
| + WDRM_PLANE_TYPE_CURSOR); |
| + |
| + return 0; |
| + |
| +err_crtc: |
| + output->crtc_id = 0; |
| + output->pipe = 0; |
| + |
| + return -1; |
| +} |
| + |
| +/** Free the CRTC from the output |
| + * |
| + * @param output The output whose CRTC to deallocate. |
| + * |
| + * The CRTC reserved for the given output becomes free to use again. |
| + */ |
| +static void |
| +drm_output_fini_crtc(struct drm_output *output) |
| +{ |
| + struct drm_backend *b = to_drm_backend(output->base.compositor); |
| + |
| + if (!b->universal_planes && !b->shutting_down) { |
| + /* With universal planes, the 'special' planes are allocated at |
| + * startup, freed at shutdown, and live on the plane list in |
| + * between. We want the planes to continue to exist and be freed |
| + * up for other outputs. |
| + * |
| + * Without universal planes, our special planes are |
| + * pseudo-planes allocated at output creation, freed at output |
| + * destruction, and not usable by other outputs. |
| + * |
| + * On the other hand, if the compositor is already shutting down, |
| + * the plane has already been destroyed. |
| + */ |
| + if (output->cursor_plane) |
| + drm_plane_destroy(output->cursor_plane); |
| + if (output->scanout_plane) |
| + drm_plane_destroy(output->scanout_plane); |
| + } |
| + |
| + drm_property_info_free(output->props_crtc, WDRM_CRTC__COUNT); |
| + output->crtc_id = 0; |
| + output->cursor_plane = NULL; |
| + output->scanout_plane = NULL; |
| +} |
| + |
| +static int |
| drm_output_enable(struct weston_output *base) |
| { |
| struct drm_output *output = to_drm_output(base); |
| struct drm_backend *b = to_drm_backend(base->compositor); |
| struct weston_mode *m; |
| |
| - output->dpms_prop = drm_get_prop(b->drm.fd, output->connector, "DPMS"); |
| + if (b->pageflip_timeout) |
| + drm_output_pageflip_timer_create(output); |
| |
| if (b->use_pixman) { |
| if (drm_output_init_pixman(output, b) < 0) { |
| weston_log("Failed to init output pixman state\n"); |
| - goto err_free; |
| + goto err; |
| } |
| } else if (drm_output_init_egl(output, b) < 0) { |
| weston_log("Failed to init output gl state\n"); |
| - goto err_free; |
| + goto err; |
| } |
| |
| if (output->backlight) { |
| @@ -2459,25 +4746,22 @@ drm_output_enable(struct weston_output *base) |
| output->base.assign_planes = drm_assign_planes; |
| output->base.set_dpms = drm_set_dpms; |
| output->base.switch_mode = drm_output_switch_mode; |
| - |
| - output->base.gamma_size = output->original_crtc->gamma_size; |
| output->base.set_gamma = drm_output_set_gamma; |
| |
| - output->base.subpixel = drm_subpixel_to_wayland(output->connector->subpixel); |
| - |
| - find_and_parse_output_edid(b, output, output->connector); |
| - if (output->connector->connector_type == DRM_MODE_CONNECTOR_LVDS || |
| - output->connector->connector_type == DRM_MODE_CONNECTOR_eDP) |
| - output->base.connection_internal = 1; |
| - |
| - weston_plane_init(&output->cursor_plane, b->compositor, |
| - INT32_MIN, INT32_MIN); |
| - weston_plane_init(&output->fb_plane, b->compositor, 0, 0); |
| + if (output->cursor_plane) |
| + weston_compositor_stack_plane(b->compositor, |
| + &output->cursor_plane->base, |
| + NULL); |
| + else |
| + b->cursors_are_broken = 1; |
| |
| - weston_compositor_stack_plane(b->compositor, &output->cursor_plane, NULL); |
| - weston_compositor_stack_plane(b->compositor, &output->fb_plane, |
| + weston_compositor_stack_plane(b->compositor, |
| + &output->scanout_plane->base, |
| &b->compositor->primary_plane); |
| |
| + wl_array_remove_uint32(&b->unused_connectors, output->connector_id); |
| + wl_array_remove_uint32(&b->unused_crtcs, output->crtc_id); |
| + |
| weston_log("Output %s, (connector %d, crtc %d)\n", |
| output->base.name, output->connector_id, output->crtc_id); |
| wl_list_for_each(m, &output->base.mode_list, link) |
| @@ -2492,9 +4776,7 @@ drm_output_enable(struct weston_output *base) |
| |
| return 0; |
| |
| -err_free: |
| - drmModeFreeProperty(output->dpms_prop); |
| - |
| +err: |
| return -1; |
| } |
| |
| @@ -2503,19 +4785,36 @@ drm_output_deinit(struct weston_output *base) |
| { |
| struct drm_output *output = to_drm_output(base); |
| struct drm_backend *b = to_drm_backend(base->compositor); |
| + uint32_t *unused; |
| |
| if (b->use_pixman) |
| drm_output_fini_pixman(output); |
| else |
| drm_output_fini_egl(output); |
| |
| - weston_plane_release(&output->fb_plane); |
| - weston_plane_release(&output->cursor_plane); |
| + /* Since our planes are no longer in use anywhere, remove their base |
| + * weston_plane's link from the plane stacking list, unless we're |
| + * shutting down, in which case the plane has already been |
| + * destroyed. */ |
| + if (!b->shutting_down) { |
| + wl_list_remove(&output->scanout_plane->base.link); |
| + wl_list_init(&output->scanout_plane->base.link); |
| + |
| + if (output->cursor_plane) { |
| + wl_list_remove(&output->cursor_plane->base.link); |
| + wl_list_init(&output->cursor_plane->base.link); |
| + /* Turn off hardware cursor */ |
| + drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); |
| + } |
| + } |
| |
| - drmModeFreeProperty(output->dpms_prop); |
| + unused = wl_array_add(&b->unused_connectors, sizeof(*unused)); |
| + *unused = output->connector_id; |
| + unused = wl_array_add(&b->unused_crtcs, sizeof(*unused)); |
| + *unused = output->crtc_id; |
| |
| - /* Turn off hardware cursor */ |
| - drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); |
| + /* Force programming unused connectors and crtcs. */ |
| + b->state_invalid = true; |
| } |
| |
| static void |
| @@ -2523,9 +4822,9 @@ drm_output_destroy(struct weston_output *base) |
| { |
| struct drm_output *output = to_drm_output(base); |
| struct drm_backend *b = to_drm_backend(base->compositor); |
| - drmModeCrtcPtr origcrtc = output->original_crtc; |
| |
| - if (output->page_flip_pending) { |
| + if (output->page_flip_pending || output->vblank_pending || |
| + output->atomic_complete_pending) { |
| output->destroy_pending = 1; |
| weston_log("destroy output while page flip pending\n"); |
| return; |
| @@ -2534,21 +4833,24 @@ drm_output_destroy(struct weston_output *base) |
| if (output->base.enabled) |
| drm_output_deinit(&output->base); |
| |
| - if (origcrtc) { |
| - /* Restore original CRTC state */ |
| - drmModeSetCrtc(b->drm.fd, origcrtc->crtc_id, origcrtc->buffer_id, |
| - origcrtc->x, origcrtc->y, |
| - &output->connector_id, 1, &origcrtc->mode); |
| - drmModeFreeCrtc(origcrtc); |
| - } |
| + drm_mode_list_destroy(b, &output->base.mode_list); |
| + |
| + if (output->pageflip_timer) |
| + wl_event_source_remove(output->pageflip_timer); |
| |
| weston_output_destroy(&output->base); |
| |
| + drm_output_fini_crtc(output); |
| + |
| + drm_property_info_free(output->props_conn, WDRM_CONNECTOR__COUNT); |
| drmModeFreeConnector(output->connector); |
| |
| if (output->backlight) |
| backlight_destroy(output->backlight); |
| |
| + assert(!output->state_last); |
| + drm_output_state_free(output->state_cur); |
| + |
| free(output); |
| } |
| |
| @@ -2556,26 +4858,69 @@ static int |
| drm_output_disable(struct weston_output *base) |
| { |
| struct drm_output *output = to_drm_output(base); |
| - struct drm_backend *b = to_drm_backend(base->compositor); |
| |
| - if (output->page_flip_pending) { |
| + if (output->page_flip_pending || output->vblank_pending || |
| + output->atomic_complete_pending) { |
| output->disable_pending = 1; |
| return -1; |
| } |
| |
| + weston_log("Disabling output %s\n", output->base.name); |
| + |
| if (output->base.enabled) |
| drm_output_deinit(&output->base); |
| |
| output->disable_pending = 0; |
| |
| - weston_log("Disabling output %s\n", output->base.name); |
| - drmModeSetCrtc(b->drm.fd, output->crtc_id, |
| - 0, 0, 0, 0, 0, NULL); |
| - |
| return 0; |
| } |
| |
| /** |
| + * Update the list of unused connectors and CRTCs |
| + * |
| + * This keeps the unused_connectors and unused_crtcs arrays up to date. |
| + * |
| + * @param b Weston backend structure |
| + * @param resources DRM resources for this device |
| + */ |
| +static void |
| +drm_backend_update_unused_outputs(struct drm_backend *b, drmModeRes *resources) |
| +{ |
| + int i; |
| + |
| + wl_array_release(&b->unused_connectors); |
| + wl_array_init(&b->unused_connectors); |
| + |
| + for (i = 0; i < resources->count_connectors; i++) { |
| + struct drm_output *output; |
| + uint32_t *connector_id; |
| + |
| + output = drm_output_find_by_connector(b, resources->connectors[i]); |
| + if (output && output->base.enabled) |
| + continue; |
| + |
| + connector_id = wl_array_add(&b->unused_connectors, |
| + sizeof(*connector_id)); |
| + *connector_id = resources->connectors[i]; |
| + } |
| + |
| + wl_array_release(&b->unused_crtcs); |
| + wl_array_init(&b->unused_crtcs); |
| + |
| + for (i = 0; i < resources->count_crtcs; i++) { |
| + struct drm_output *output; |
| + uint32_t *crtc_id; |
| + |
| + output = drm_output_find_by_crtc(b, resources->crtcs[i]); |
| + if (output && output->base.enabled) |
| + continue; |
| + |
| + crtc_id = wl_array_add(&b->unused_crtcs, sizeof(*crtc_id)); |
| + *crtc_id = resources->crtcs[i]; |
| + } |
| +} |
| + |
| +/** |
| * Create a Weston output structure |
| * |
| * Given a DRM connector, create a matching drm_output structure and add it |
| @@ -2595,108 +4940,87 @@ create_output_for_connector(struct drm_backend *b, |
| struct udev_device *drm_device) |
| { |
| struct drm_output *output; |
| + drmModeObjectPropertiesPtr props; |
| + struct drm_mode *drm_mode; |
| + char *name; |
| + const char *make = "unknown"; |
| + const char *model = "unknown"; |
| + const char *serial_number = "unknown"; |
| int i; |
| |
| - i = find_crtc_for_connector(b, resources, connector); |
| - if (i < 0) { |
| - weston_log("No usable crtc/encoder pair for connector.\n"); |
| - return -1; |
| - } |
| - |
| output = zalloc(sizeof *output); |
| if (output == NULL) |
| - return -1; |
| + goto err_init; |
| |
| output->connector = connector; |
| - output->crtc_id = resources->crtcs[i]; |
| - output->pipe = i; |
| output->connector_id = connector->connector_id; |
| |
| output->backlight = backlight_init(drm_device, |
| connector->connector_type); |
| |
| + output->base.name = make_connector_name(connector); |
| + weston_output_init(&output->base, b->compositor); |
| + wl_list_init(&output->base.mode_list); |
| + |
| output->base.enable = drm_output_enable; |
| output->base.destroy = drm_output_destroy; |
| output->base.disable = drm_output_disable; |
| - output->base.name = make_connector_name(connector); |
| |
| output->destroy_pending = 0; |
| output->disable_pending = 0; |
| - output->original_crtc = NULL; |
| |
| - weston_output_init(&output->base, b->compositor); |
| - weston_compositor_add_pending_output(&output->base, b->compositor); |
| + if (drm_output_init_crtc(output, resources, connector) < 0) |
| + goto err_output; |
| |
| - return 0; |
| -} |
| + props = drmModeObjectGetProperties(b->drm.fd, connector->connector_id, |
| + DRM_MODE_OBJECT_CONNECTOR); |
| + if (!props) { |
| + weston_log("failed to get connector properties\n"); |
| + goto err_output; |
| + } |
| + drm_property_info_populate(b, connector_props, output->props_conn, |
| + WDRM_CONNECTOR__COUNT, props); |
| + find_and_parse_output_edid(b, output, props, |
| + &make, &model, &serial_number); |
| + output->base.make = (char *)make; |
| + output->base.model = (char *)model; |
| + output->base.serial_number = (char *)serial_number; |
| + output->base.subpixel = drm_subpixel_to_wayland(output->connector->subpixel); |
| |
| -static void |
| -create_sprites(struct drm_backend *b) |
| -{ |
| - struct drm_sprite *sprite; |
| - drmModePlaneRes *plane_res; |
| - drmModePlane *plane; |
| - uint32_t i; |
| + drmModeFreeObjectProperties(props); |
| |
| - plane_res = drmModeGetPlaneResources(b->drm.fd); |
| - if (!plane_res) { |
| - weston_log("failed to get plane resources: %s\n", |
| - strerror(errno)); |
| - return; |
| - } |
| + if (output->connector->connector_type == DRM_MODE_CONNECTOR_LVDS || |
| + output->connector->connector_type == DRM_MODE_CONNECTOR_eDP) |
| + output->base.connection_internal = true; |
| |
| - for (i = 0; i < plane_res->count_planes; i++) { |
| - plane = drmModeGetPlane(b->drm.fd, plane_res->planes[i]); |
| - if (!plane) |
| - continue; |
| + if (drm_output_init_gamma_size(output) < 0) |
| + goto err_output; |
| |
| - sprite = zalloc(sizeof(*sprite) + ((sizeof(uint32_t)) * |
| - plane->count_formats)); |
| - if (!sprite) { |
| - weston_log("%s: out of memory\n", |
| - __func__); |
| - drmModeFreePlane(plane); |
| - continue; |
| - } |
| + output->state_cur = drm_output_state_alloc(output, NULL); |
| |
| - sprite->possible_crtcs = plane->possible_crtcs; |
| - sprite->plane_id = plane->plane_id; |
| - sprite->current = NULL; |
| - sprite->next = NULL; |
| - sprite->backend = b; |
| - sprite->count_formats = plane->count_formats; |
| - memcpy(sprite->formats, plane->formats, |
| - plane->count_formats * sizeof(plane->formats[0])); |
| - drmModeFreePlane(plane); |
| - weston_plane_init(&sprite->plane, b->compositor, 0, 0); |
| - weston_compositor_stack_plane(b->compositor, &sprite->plane, |
| - &b->compositor->primary_plane); |
| + output->base.mm_width = output->connector->mmWidth; |
| + output->base.mm_height = output->connector->mmHeight; |
| |
| - wl_list_insert(&b->sprite_list, &sprite->link); |
| + for (i = 0; i < output->connector->count_modes; i++) { |
| + drm_mode = drm_output_add_mode(output, &output->connector->modes[i]); |
| + if (!drm_mode) { |
| + weston_log("failed to add mode\n"); |
| + goto err_output; |
| + } |
| } |
| |
| - drmModeFreePlaneResources(plane_res); |
| -} |
| + weston_compositor_add_pending_output(&output->base, b->compositor); |
| |
| -static void |
| -destroy_sprites(struct drm_backend *backend) |
| -{ |
| - struct drm_sprite *sprite, *next; |
| - struct drm_output *output; |
| + return 0; |
| |
| - output = container_of(backend->compositor->output_list.next, |
| - struct drm_output, base.link); |
| +err_output: |
| + drm_output_destroy(&output->base); |
| + return -1; |
| + /* no fallthrough! */ |
| |
| - wl_list_for_each_safe(sprite, next, &backend->sprite_list, link) { |
| - drmModeSetPlane(backend->drm.fd, |
| - sprite->plane_id, |
| - output->crtc_id, 0, 0, |
| - 0, 0, 0, 0, 0, 0, 0, 0); |
| - drm_output_release_fb(output, sprite->current); |
| - drm_output_release_fb(output, sprite->next); |
| - weston_plane_release(&sprite->plane); |
| - free(sprite); |
| - } |
| +err_init: |
| + drmModeFreeConnector(connector); |
| + return -1; |
| } |
| |
| static int |
| @@ -2718,24 +5042,25 @@ create_outputs(struct drm_backend *b, struct udev_device *drm_device) |
| b->max_height = resources->max_height; |
| |
| for (i = 0; i < resources->count_connectors; i++) { |
| + int ret; |
| + |
| connector = drmModeGetConnector(b->drm.fd, |
| resources->connectors[i]); |
| if (connector == NULL) |
| continue; |
| |
| - if (connector->connection == DRM_MODE_CONNECTED && |
| - (b->connector == 0 || |
| - connector->connector_id == b->connector)) { |
| - if (create_output_for_connector(b, resources, |
| - connector, drm_device) < 0) { |
| - drmModeFreeConnector(connector); |
| - continue; |
| - } |
| + if (connector->connection == DRM_MODE_CONNECTED) { |
| + ret = create_output_for_connector(b, resources, |
| + connector, drm_device); |
| + if (ret < 0) |
| + weston_log("failed to create new connector\n"); |
| } else { |
| drmModeFreeConnector(connector); |
| } |
| } |
| |
| + drm_backend_update_unused_outputs(b, resources); |
| + |
| if (wl_list_empty(&b->compositor->output_list) && |
| wl_list_empty(&b->compositor->pending_output_list)) |
| weston_log("No currently active connector found.\n"); |
| @@ -2779,11 +5104,6 @@ update_outputs(struct drm_backend *b, struct udev_device *drm_device) |
| continue; |
| } |
| |
| - if (b->connector && (b->connector != connector_id)) { |
| - drmModeFreeConnector(connector); |
| - continue; |
| - } |
| - |
| connected[i] = connector_id; |
| |
| if (drm_output_find_by_connector(b, connector_id)) { |
| @@ -2832,6 +5152,8 @@ update_outputs(struct drm_backend *b, struct udev_device *drm_device) |
| drm_output_destroy(&output->base); |
| } |
| |
| + drm_backend_update_unused_outputs(b, resources); |
| + |
| free(connected); |
| drmModeFreeResources(resources); |
| } |
| @@ -2885,6 +5207,8 @@ drm_destroy(struct weston_compositor *ec) |
| wl_event_source_remove(b->udev_drm_source); |
| wl_event_source_remove(b->drm_source); |
| |
| + b->shutting_down = true; |
| + |
| destroy_sprites(b); |
| |
| weston_compositor_shutdown(ec); |
| @@ -2892,9 +5216,16 @@ drm_destroy(struct weston_compositor *ec) |
| if (b->gbm) |
| gbm_device_destroy(b->gbm); |
| |
| + udev_monitor_unref(b->udev_monitor); |
| + udev_unref(b->udev); |
| + |
| weston_launcher_destroy(ec->launcher); |
| |
| + wl_array_release(&b->unused_crtcs); |
| + wl_array_release(&b->unused_connectors); |
| + |
| close(b->drm.fd); |
| + free(b->drm.filename); |
| free(b); |
| } |
| |
| @@ -2903,13 +5234,14 @@ session_notify(struct wl_listener *listener, void *data) |
| { |
| struct weston_compositor *compositor = data; |
| struct drm_backend *b = to_drm_backend(compositor); |
| - struct drm_sprite *sprite; |
| + struct drm_plane *plane; |
| struct drm_output *output; |
| |
| if (compositor->session_active) { |
| weston_log("activating session\n"); |
| weston_compositor_wake(compositor); |
| weston_compositor_damage_all(compositor); |
| + b->state_invalid = true; |
| udev_input_enable(&b->input); |
| } else { |
| weston_log("deactivating session\n"); |
| @@ -2926,19 +5258,80 @@ session_notify(struct wl_listener *listener, void *data) |
| * pending frame callbacks. */ |
| |
| wl_list_for_each(output, &compositor->output_list, base.link) { |
| - output->base.repaint_needed = 0; |
| - drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0); |
| + output->base.repaint_needed = false; |
| + if (output->cursor_plane) |
| + drmModeSetCursor(b->drm.fd, output->crtc_id, |
| + 0, 0, 0); |
| } |
| |
| output = container_of(compositor->output_list.next, |
| struct drm_output, base.link); |
| |
| - wl_list_for_each(sprite, &b->sprite_list, link) |
| + wl_list_for_each(plane, &b->plane_list, link) { |
| + if (plane->type != WDRM_PLANE_TYPE_OVERLAY) |
| + continue; |
| + |
| drmModeSetPlane(b->drm.fd, |
| - sprite->plane_id, |
| + plane->plane_id, |
| output->crtc_id, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0); |
| - }; |
| + } |
| + } |
| +} |
| + |
| +/** |
| + * Determines whether or not a device is capable of modesetting. If successful, |
| + * sets b->drm.fd and b->drm.filename to the opened device. |
| + */ |
| +static bool |
| +drm_device_is_kms(struct drm_backend *b, struct udev_device *device) |
| +{ |
| + const char *filename = udev_device_get_devnode(device); |
| + const char *sysnum = udev_device_get_sysnum(device); |
| + drmModeRes *res; |
| + int id, fd; |
| + |
| + if (!filename) |
| + return false; |
| + |
| + fd = weston_launcher_open(b->compositor->launcher, filename, O_RDWR); |
| + if (fd < 0) |
| + return false; |
| + |
| + res = drmModeGetResources(fd); |
| + if (!res) |
| + goto out_fd; |
| + |
| + if (res->count_crtcs <= 0 || res->count_connectors <= 0 || |
| + res->count_encoders <= 0) |
| + goto out_res; |
| + |
| + if (sysnum) |
| + id = atoi(sysnum); |
| + if (!sysnum || id < 0) { |
| + weston_log("couldn't get sysnum for device %s\n", filename); |
| + goto out_res; |
| + } |
| + |
| + /* We can be called successfully on multiple devices; if we have, |
| + * clean up old entries. */ |
| + if (b->drm.fd >= 0) |
| + weston_launcher_close(b->compositor->launcher, b->drm.fd); |
| + free(b->drm.filename); |
| + |
| + b->drm.fd = fd; |
| + b->drm.id = id; |
| + b->drm.filename = strdup(filename); |
| + |
| + drmModeFreeResources(res); |
| + |
| + return true; |
| + |
| +out_res: |
| + drmModeFreeResources(res); |
| +out_fd: |
| + weston_launcher_close(b->compositor->launcher, fd); |
| + return false; |
| } |
| |
| /* |
| @@ -2947,6 +5340,9 @@ session_notify(struct wl_listener *listener, void *data) |
| * function loops over all devices and tries to find a PCI device with the |
| * boot_vga sysfs attribute set to 1. |
| * If no such device is found, the first DRM device reported by udev is used. |
| + * Devices are also vetted to make sure they are are capable of modesetting, |
| + * rather than pure render nodes (GPU with no display), or pure |
| + * memory-allocation devices (VGEM). |
| */ |
| static struct udev_device* |
| find_primary_gpu(struct drm_backend *b, const char *seat) |
| @@ -2963,6 +5359,8 @@ find_primary_gpu(struct drm_backend *b, const char *seat) |
| udev_enumerate_scan_devices(e); |
| drm_device = NULL; |
| udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { |
| + bool is_boot_vga = false; |
| + |
| path = udev_list_entry_get_name(entry); |
| device = udev_device_new_from_syspath(b->udev, path); |
| if (!device) |
| @@ -2979,27 +5377,77 @@ find_primary_gpu(struct drm_backend *b, const char *seat) |
| "pci", NULL); |
| if (pci) { |
| id = udev_device_get_sysattr_value(pci, "boot_vga"); |
| - if (id && !strcmp(id, "1")) { |
| - if (drm_device) |
| - udev_device_unref(drm_device); |
| - drm_device = device; |
| - break; |
| - } |
| + if (id && !strcmp(id, "1")) |
| + is_boot_vga = true; |
| } |
| |
| - if (!drm_device) |
| - drm_device = device; |
| - else |
| + /* If we already have a modesetting-capable device, and this |
| + * device isn't our boot-VGA device, we aren't going to use |
| + * it. */ |
| + if (!is_boot_vga && drm_device) { |
| + udev_device_unref(device); |
| + continue; |
| + } |
| + |
| + /* Make sure this device is actually capable of modesetting; |
| + * if this call succeeds, b->drm.{fd,filename} will be set, |
| + * and any old values freed. */ |
| + if (!drm_device_is_kms(b, device)) { |
| udev_device_unref(device); |
| + continue; |
| + } |
| + |
| + /* There can only be one boot_vga device, and we try to use it |
| + * at all costs. */ |
| + if (is_boot_vga) { |
| + if (drm_device) |
| + udev_device_unref(drm_device); |
| + drm_device = device; |
| + break; |
| + } |
| + |
| + /* Per the (!is_boot_vga && drm_device) test above, we only |
| + * trump existing saved devices with boot-VGA devices, so if |
| + * we end up here, this must be the first device we've seen. */ |
| + assert(!drm_device); |
| + drm_device = device; |
| } |
| |
| + /* If we're returning a device to use, we must have an open FD for |
| + * it. */ |
| + assert(!!drm_device == (b->drm.fd >= 0)); |
| + |
| udev_enumerate_unref(e); |
| return drm_device; |
| } |
| |
| +static struct udev_device * |
| +open_specific_drm_device(struct drm_backend *b, const char *name) |
| +{ |
| + struct udev_device *device; |
| + |
| + device = udev_device_new_from_subsystem_sysname(b->udev, "drm", name); |
| + if (!device) { |
| + weston_log("ERROR: could not open DRM device '%s'\n", name); |
| + return NULL; |
| + } |
| + |
| + if (!drm_device_is_kms(b, device)) { |
| + udev_device_unref(device); |
| + weston_log("ERROR: DRM device '%s' is not a KMS device.\n", name); |
| + return NULL; |
| + } |
| + |
| + /* If we're returning a device to use, we must have an open FD for |
| + * it. */ |
| + assert(b->drm.fd >= 0); |
| + |
| + return device; |
| +} |
| + |
| static void |
| -planes_binding(struct weston_keyboard *keyboard, uint32_t time, uint32_t key, |
| - void *data) |
| +planes_binding(struct weston_keyboard *keyboard, uint32_t time, |
| + uint32_t key, void *data) |
| { |
| struct drm_backend *b = data; |
| |
| @@ -3045,7 +5493,8 @@ recorder_frame_notify(struct wl_listener *listener, void *data) |
| if (!output->recorder) |
| return; |
| |
| - ret = drmPrimeHandleToFD(b->drm.fd, output->current->handle, |
| + ret = drmPrimeHandleToFD(b->drm.fd, |
| + output->scanout_plane->state_cur->fb->handle, |
| DRM_CLOEXEC, &fd); |
| if (ret) { |
| weston_log("[libva recorder] " |
| @@ -3054,7 +5503,7 @@ recorder_frame_notify(struct wl_listener *listener, void *data) |
| } |
| |
| ret = vaapi_recorder_frame(output->recorder, fd, |
| - output->current->stride); |
| + output->scanout_plane->state_cur->fb->stride); |
| if (ret < 0) { |
| weston_log("[libva recorder] aborted: %m\n"); |
| recorder_destroy(output); |
| @@ -3079,8 +5528,8 @@ create_recorder(struct drm_backend *b, int width, int height, |
| } |
| |
| static void |
| -recorder_binding(struct weston_keyboard *keyboard, uint32_t time, uint32_t key, |
| - void *data) |
| +recorder_binding(struct weston_keyboard *keyboard, uint32_t time, |
| + uint32_t key, void *data) |
| { |
| struct drm_backend *b = data; |
| struct drm_output *output; |
| @@ -3121,8 +5570,8 @@ recorder_binding(struct weston_keyboard *keyboard, uint32_t time, uint32_t key, |
| } |
| #else |
| static void |
| -recorder_binding(struct weston_keyboard *keyboard, uint32_t time, uint32_t key, |
| - void *data) |
| +recorder_binding(struct weston_keyboard *keyboard, uint32_t time, |
| + uint32_t key, void *data) |
| { |
| weston_log("Compiled without libva support\n"); |
| } |
| @@ -3173,8 +5622,8 @@ switch_to_gl_renderer(struct drm_backend *b) |
| } |
| |
| static void |
| -renderer_switch_binding(struct weston_keyboard *keyboard, uint32_t time, |
| - uint32_t key, void *data) |
| +renderer_switch_binding(struct weston_keyboard *keyboard, |
| + uint32_t time, uint32_t key, void *data) |
| { |
| struct drm_backend *b = |
| to_drm_backend(keyboard->seat->compositor); |
| @@ -3195,7 +5644,6 @@ drm_backend_create(struct weston_compositor *compositor, |
| struct drm_backend *b; |
| struct udev_device *drm_device; |
| struct wl_event_loop *loop; |
| - const char *path; |
| const char *seat_id = default_seat; |
| int ret; |
| |
| @@ -3205,6 +5653,11 @@ drm_backend_create(struct weston_compositor *compositor, |
| if (b == NULL) |
| return NULL; |
| |
| + b->state_invalid = true; |
| + b->drm.fd = -1; |
| + wl_array_init(&b->unused_crtcs); |
| + wl_array_init(&b->unused_connectors); |
| + |
| /* |
| * KMS support for hardware planes cannot properly synchronize |
| * without nuclear page flip. Without nuclear/atomic, hw plane |
| @@ -3218,6 +5671,9 @@ drm_backend_create(struct weston_compositor *compositor, |
| b->sprites_are_broken = 1; |
| b->compositor = compositor; |
| b->use_pixman = config->use_pixman; |
| + b->pageflip_timeout = config->pageflip_timeout; |
| + |
| + compositor->backend = &b->base; |
| |
| if (parse_gbm_format(config->gbm_format, GBM_FORMAT_XRGB8888, &b->gbm_format) < 0) |
| goto err_compositor; |
| @@ -3229,8 +5685,9 @@ drm_backend_create(struct weston_compositor *compositor, |
| compositor->launcher = weston_launcher_connect(compositor, config->tty, |
| seat_id, true); |
| if (compositor->launcher == NULL) { |
| - weston_log("fatal: drm backend should be run " |
| - "using weston-launch binary or as root\n"); |
| + weston_log("fatal: drm backend should be run using " |
| + "weston-launch binary, or your system should " |
| + "provide the logind D-Bus API.\n"); |
| goto err_compositor; |
| } |
| |
| @@ -3243,14 +5700,16 @@ drm_backend_create(struct weston_compositor *compositor, |
| b->session_listener.notify = session_notify; |
| wl_signal_add(&compositor->session_signal, &b->session_listener); |
| |
| - drm_device = find_primary_gpu(b, seat_id); |
| + if (config->specific_device) |
| + drm_device = open_specific_drm_device(b, config->specific_device); |
| + else |
| + drm_device = find_primary_gpu(b, seat_id); |
| if (drm_device == NULL) { |
| weston_log("no drm device found\n"); |
| goto err_udev; |
| } |
| - path = udev_device_get_syspath(drm_device); |
| |
| - if (init_drm(b, drm_device) < 0) { |
| + if (init_kms_caps(b) < 0) { |
| weston_log("failed to initialize kms\n"); |
| goto err_udev_dev; |
| } |
| @@ -3262,21 +5721,20 @@ drm_backend_create(struct weston_compositor *compositor, |
| } |
| } else { |
| if (init_egl(b) < 0) { |
| - weston_log("failed to initialize egl, use pixman\n"); |
| - if (init_pixman(b) < 0) { |
| - weston_log("failed to initialize pixman renderer\n"); |
| - goto err_udev_dev; |
| - } |
| - b->use_pixman = 1; |
| + weston_log("failed to initialize egl\n"); |
| + goto err_udev_dev; |
| } |
| } |
| |
| b->base.destroy = drm_destroy; |
| b->base.restore = drm_restore; |
| + b->base.repaint_begin = drm_repaint_begin; |
| + b->base.repaint_flush = drm_repaint_flush; |
| + b->base.repaint_cancel = drm_repaint_cancel; |
| |
| weston_setup_vt_switch_bindings(compositor); |
| |
| - wl_list_init(&b->sprite_list); |
| + wl_list_init(&b->plane_list); |
| create_sprites(b); |
| |
| if (udev_input_init(&b->input, |
| @@ -3286,10 +5744,8 @@ drm_backend_create(struct weston_compositor *compositor, |
| goto err_sprite; |
| } |
| |
| - b->connector = config->connector; |
| - |
| if (create_outputs(b, drm_device) < 0) { |
| - weston_log("failed to create output for %s\n", path); |
| + weston_log("failed to create output for %s\n", b->drm.filename); |
| goto err_udev_input; |
| } |
| |
| @@ -3298,8 +5754,6 @@ drm_backend_create(struct weston_compositor *compositor, |
| if (!b->cursors_are_broken) |
| compositor->capabilities |= WESTON_CAP_CURSOR_PLANE; |
| |
| - path = NULL; |
| - |
| loop = wl_display_get_event_loop(compositor->wl_display); |
| b->drm_source = |
| wl_event_loop_add_fd(loop, b->drm.fd, |
| @@ -3341,8 +5795,6 @@ drm_backend_create(struct weston_compositor *compositor, |
| "support failed.\n"); |
| } |
| |
| - compositor->backend = &b->base; |
| - |
| ret = weston_plugin_api_register(compositor, WESTON_DRM_OUTPUT_API_NAME, |
| &api, sizeof(api)); |
| |
| diff --git a/libweston/compositor-drm.h b/libweston/compositor-drm.h |
| index 2e2995a..838e937 100644 |
| --- a/libweston/compositor-drm.h |
| +++ b/libweston/compositor-drm.h |
| @@ -35,7 +35,7 @@ |
| extern "C" { |
| #endif |
| |
| -#define WESTON_DRM_BACKEND_CONFIG_VERSION 2 |
| +#define WESTON_DRM_BACKEND_CONFIG_VERSION 3 |
| |
| struct libinput_device; |
| |
| @@ -138,6 +138,20 @@ struct weston_drm_backend_config { |
| */ |
| void (*configure_device)(struct weston_compositor *compositor, |
| struct libinput_device *device); |
| + |
| + /** Maximum duration for a pageflip event to arrive, after which the |
| + * compositor will consider the DRM driver crashed and will try to exit |
| + * cleanly. |
| + * |
| + * It is exprimed in milliseconds, 0 means disabled. */ |
| + uint32_t pageflip_timeout; |
| + |
| + /** Specific DRM device to open |
| + * |
| + * A DRM device name, like "card0", to open. If NULL, use heuristics |
| + * based on seat names and boot_vga to find the right device. |
| + */ |
| + char *specific_device; |
| }; |
| |
| #ifdef __cplusplus |
| diff --git a/libweston/compositor-fbdev.c b/libweston/compositor-fbdev.c |
| index 44f0cf5..32d71e0 100644 |
| --- a/libweston/compositor-fbdev.c |
| +++ b/libweston/compositor-fbdev.c |
| @@ -118,7 +118,8 @@ fbdev_output_start_repaint_loop(struct weston_output *output) |
| } |
| |
| static int |
| -fbdev_output_repaint(struct weston_output *base, pixman_region32_t *damage) |
| +fbdev_output_repaint(struct weston_output *base, pixman_region32_t *damage, |
| + void *repaint_data) |
| { |
| struct fbdev_output *output = to_fbdev_output(base); |
| struct weston_compositor *ec = output->base.compositor; |
| @@ -693,7 +694,7 @@ session_notify(struct wl_listener *listener, void *data) |
| |
| wl_list_for_each(output, |
| &compositor->output_list, link) { |
| - output->repaint_needed = 0; |
| + output->repaint_needed = false; |
| } |
| } |
| } |
| diff --git a/libweston/compositor-headless.c b/libweston/compositor-headless.c |
| index a1aec6d..9e42e7f 100644 |
| --- a/libweston/compositor-headless.c |
| +++ b/libweston/compositor-headless.c |
| @@ -92,7 +92,8 @@ finish_frame_handler(void *data) |
| |
| static int |
| headless_output_repaint(struct weston_output *output_base, |
| - pixman_region32_t *damage) |
| + pixman_region32_t *damage, |
| + void *repaint_data) |
| { |
| struct headless_output *output = to_headless_output(output_base); |
| struct weston_compositor *ec = output->base.compositor; |
| diff --git a/libweston/compositor-rdp.c b/libweston/compositor-rdp.c |
| index d9668e8..091472b 100644 |
| --- a/libweston/compositor-rdp.c |
| +++ b/libweston/compositor-rdp.c |
| @@ -355,7 +355,8 @@ rdp_output_start_repaint_loop(struct weston_output *output) |
| } |
| |
| static int |
| -rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage) |
| +rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage, |
| + void *repaint_data) |
| { |
| struct rdp_output *output = container_of(output_base, struct rdp_output, base); |
| struct weston_compositor *ec = output->base.compositor; |
| diff --git a/libweston/compositor-wayland.c b/libweston/compositor-wayland.c |
| index 9d35ef7..ebdbd13 100644 |
| --- a/libweston/compositor-wayland.c |
| +++ b/libweston/compositor-wayland.c |
| @@ -488,7 +488,8 @@ wayland_output_start_repaint_loop(struct weston_output *output_base) |
| #ifdef ENABLE_EGL |
| static int |
| wayland_output_repaint_gl(struct weston_output *output_base, |
| - pixman_region32_t *damage) |
| + pixman_region32_t *damage, |
| + void *repaint_data) |
| { |
| struct wayland_output *output = to_wayland_output(output_base); |
| struct weston_compositor *ec = output->base.compositor; |
| @@ -595,7 +596,8 @@ wayland_shm_buffer_attach(struct wayland_shm_buffer *sb) |
| |
| static int |
| wayland_output_repaint_pixman(struct weston_output *output_base, |
| - pixman_region32_t *damage) |
| + pixman_region32_t *damage, |
| + void *repaint_data) |
| { |
| struct wayland_output *output = to_wayland_output(output_base); |
| struct wayland_backend *b = |
| diff --git a/libweston/compositor-x11.c b/libweston/compositor-x11.c |
| index f9cb461..02cdf3e 100644 |
| --- a/libweston/compositor-x11.c |
| +++ b/libweston/compositor-x11.c |
| @@ -389,7 +389,8 @@ x11_output_start_repaint_loop(struct weston_output *output) |
| |
| static int |
| x11_output_repaint_gl(struct weston_output *output_base, |
| - pixman_region32_t *damage) |
| + pixman_region32_t *damage, |
| + void *repaint_data) |
| { |
| struct x11_output *output = to_x11_output(output_base); |
| struct weston_compositor *ec = output->base.compositor; |
| @@ -457,7 +458,8 @@ set_clip_for_output(struct weston_output *output_base, pixman_region32_t *region |
| |
| static int |
| x11_output_repaint_shm(struct weston_output *output_base, |
| - pixman_region32_t *damage) |
| + pixman_region32_t *damage, |
| + void *repaint_data) |
| { |
| struct x11_output *output = to_x11_output(output_base); |
| struct weston_compositor *ec = output->base.compositor; |
| diff --git a/libweston/compositor.c b/libweston/compositor.c |
| index 9ded23f..fb647da 100644 |
| --- a/libweston/compositor.c |
| +++ b/libweston/compositor.c |
| @@ -2254,7 +2254,7 @@ weston_output_take_feedback_list(struct weston_output *output, |
| } |
| |
| static int |
| -weston_output_repaint(struct weston_output *output) |
| +weston_output_repaint(struct weston_output *output, void *repaint_data) |
| { |
| struct weston_compositor *ec = output->compositor; |
| struct weston_view *ev; |
| @@ -2273,7 +2273,7 @@ weston_output_repaint(struct weston_output *output) |
| weston_compositor_build_view_list(ec); |
| |
| if (output->assign_planes && !output->disable_planes) { |
| - output->assign_planes(output); |
| + output->assign_planes(output, repaint_data); |
| } else { |
| wl_list_for_each(ev, &ec->view_list, link) { |
| weston_view_move_to_plane(ev, &ec->primary_plane); |
| @@ -2306,11 +2306,13 @@ weston_output_repaint(struct weston_output *output) |
| if (output->dirty) |
| weston_output_update_matrix(output); |
| |
| - r = output->repaint(output, &output_damage); |
| + r = output->repaint(output, &output_damage, repaint_data); |
| |
| pixman_region32_fini(&output_damage); |
| |
| - output->repaint_needed = 0; |
| + output->repaint_needed = false; |
| + if (r == 0) |
| + output->repaint_status = REPAINT_AWAITING_COMPLETION; |
| |
| weston_compositor_repick(ec); |
| |
| @@ -2332,16 +2334,25 @@ weston_output_repaint(struct weston_output *output) |
| static void |
| weston_output_schedule_repaint_reset(struct weston_output *output) |
| { |
| - output->repaint_scheduled = 0; |
| + output->repaint_status = REPAINT_NOT_SCHEDULED; |
| TL_POINT("core_repaint_exit_loop", TLP_OUTPUT(output), TLP_END); |
| } |
| |
| static int |
| -output_repaint_timer_handler(void *data) |
| +weston_output_maybe_repaint(struct weston_output *output, struct timespec *now, |
| + void *repaint_data) |
| { |
| - struct weston_output *output = data; |
| struct weston_compositor *compositor = output->compositor; |
| - int ret; |
| + int ret = 0; |
| + int64_t msec_to_repaint; |
| + |
| + /* We're not ready yet; come back to make a decision later. */ |
| + if (output->repaint_status != REPAINT_SCHEDULED) |
| + return ret; |
| + |
| + msec_to_repaint = timespec_sub_to_msec(&output->next_repaint, now); |
| + if (msec_to_repaint > 1) |
| + return ret; |
| |
| /* If we're sleeping, drop the repaint machinery entirely; we will |
| * explicitly repaint all outputs when we come back. */ |
| @@ -2356,15 +2367,92 @@ output_repaint_timer_handler(void *data) |
| |
| /* If repaint fails, we aren't going to get weston_output_finish_frame |
| * to trigger a new repaint, so drop it from repaint and hope |
| - * something schedules a successful repaint later. */ |
| - ret = weston_output_repaint(output); |
| + * something schedules a successful repaint later. As repainting may |
| + * take some time, re-read our clock as a courtesy to the next |
| + * output. */ |
| + ret = weston_output_repaint(output, repaint_data); |
| + weston_compositor_read_presentation_clock(compositor, now); |
| if (ret != 0) |
| goto err; |
| |
| - return 0; |
| + return ret; |
| |
| err: |
| weston_output_schedule_repaint_reset(output); |
| + return ret; |
| +} |
| + |
| +static void |
| +output_repaint_timer_arm(struct weston_compositor *compositor) |
| +{ |
| + struct weston_output *output; |
| + bool any_should_repaint = false; |
| + struct timespec now; |
| + int64_t msec_to_next; |
| + |
| + weston_compositor_read_presentation_clock(compositor, &now); |
| + |
| + wl_list_for_each(output, &compositor->output_list, link) { |
| + int64_t msec_to_this; |
| + |
| + if (output->repaint_status != REPAINT_SCHEDULED) |
| + continue; |
| + |
| + msec_to_this = timespec_sub_to_msec(&output->next_repaint, |
| + &now); |
| + if (!any_should_repaint || msec_to_this < msec_to_next) |
| + msec_to_next = msec_to_this; |
| + |
| + any_should_repaint = true; |
| + } |
| + |
| + if (!any_should_repaint) |
| + return; |
| + |
| + /* Even if we should repaint immediately, add the minimum 1 ms delay. |
| + * This is a workaround to allow coalescing multiple output repaints |
| + * particularly from weston_output_finish_frame() |
| + * into the same call, which would not happen if we called |
| + * output_repaint_timer_handler() directly. |
| + */ |
| + if (msec_to_next < 1) |
| + msec_to_next = 1; |
| + |
| + wl_event_source_timer_update(compositor->repaint_timer, msec_to_next); |
| +} |
| + |
| +static int |
| +output_repaint_timer_handler(void *data) |
| +{ |
| + struct weston_compositor *compositor = data; |
| + struct weston_output *output; |
| + struct timespec now; |
| + void *repaint_data = NULL; |
| + int ret; |
| + |
| + weston_compositor_read_presentation_clock(compositor, &now); |
| + |
| + if (compositor->backend->repaint_begin) |
| + repaint_data = compositor->backend->repaint_begin(compositor); |
| + |
| + wl_list_for_each(output, &compositor->output_list, link) { |
| + ret = weston_output_maybe_repaint(output, &now, repaint_data); |
| + if (ret) |
| + break; |
| + } |
| + |
| + if (ret == 0) { |
| + if (compositor->backend->repaint_flush) |
| + compositor->backend->repaint_flush(compositor, |
| + repaint_data); |
| + } else { |
| + if (compositor->backend->repaint_cancel) |
| + compositor->backend->repaint_cancel(compositor, |
| + repaint_data); |
| + } |
| + |
| + output_repaint_timer_arm(compositor); |
| + |
| return 0; |
| } |
| |
| @@ -2376,47 +2464,59 @@ weston_output_finish_frame(struct weston_output *output, |
| struct weston_compositor *compositor = output->compositor; |
| int32_t refresh_nsec; |
| struct timespec now; |
| - struct timespec gone; |
| - int msec; |
| + int64_t msec_rel; |
| |
| TL_POINT("core_repaint_finished", TLP_OUTPUT(output), |
| TLP_VBLANK(stamp), TLP_END); |
| |
| + assert(output->repaint_status == REPAINT_AWAITING_COMPLETION); |
| + assert(stamp || (presented_flags & WP_PRESENTATION_FEEDBACK_INVALID)); |
| + |
| + weston_compositor_read_presentation_clock(compositor, &now); |
| + |
| + /* If we haven't been supplied any timestamp at all, we don't have a |
| + * timebase to work against, so any delay just wastes time. Push a |
| + * repaint as soon as possible so we can get on with it. */ |
| + if (!stamp) { |
| + output->next_repaint = now; |
| + goto out; |
| + } |
| + |
| refresh_nsec = millihz_to_nsec(output->current_mode->refresh); |
| weston_presentation_feedback_present_list(&output->feedback_list, |
| output, refresh_nsec, stamp, |
| output->msc, |
| presented_flags); |
| |
| - output->frame_time = stamp->tv_sec * 1000 + stamp->tv_nsec / 1000000; |
| + output->frame_time = timespec_to_msec(stamp); |
| |
| - weston_compositor_read_presentation_clock(compositor, &now); |
| - timespec_sub(&gone, &now, stamp); |
| - msec = (refresh_nsec - timespec_to_nsec(&gone)) / 1000000; /* floor */ |
| - msec -= compositor->repaint_msec; |
| + timespec_add_nsec(&output->next_repaint, stamp, refresh_nsec); |
| + timespec_add_msec(&output->next_repaint, &output->next_repaint, |
| + -compositor->repaint_msec); |
| + msec_rel = timespec_sub_to_msec(&output->next_repaint, &now); |
| |
| - if (msec < -1000 || msec > 1000) { |
| + if (msec_rel < -1000 || msec_rel > 1000) { |
| static bool warned; |
| |
| if (!warned) |
| weston_log("Warning: computed repaint delay is " |
| - "insane: %d msec\n", msec); |
| + "insane: %lld msec\n", (long long) msec_rel); |
| warned = true; |
| |
| - msec = 0; |
| + output->next_repaint = now; |
| } |
| |
| /* Called from restart_repaint_loop and restart happens already after |
| * the deadline given by repaint_msec? In that case we delay until |
| * the deadline of the next frame, to give clients a more predictable |
| * timing of the repaint cycle to lock on. */ |
| - if (presented_flags == WP_PRESENTATION_FEEDBACK_INVALID && msec < 0) |
| - msec += refresh_nsec / 1000000; |
| + if (presented_flags == WP_PRESENTATION_FEEDBACK_INVALID && msec_rel < 0) |
| + timespec_add_nsec(&output->next_repaint, &output->next_repaint, |
| + refresh_nsec); |
| |
| - if (msec < 1) |
| - output_repaint_timer_handler(output); |
| - else |
| - wl_event_source_timer_update(output->repaint_timer, msec); |
| +out: |
| + output->repaint_status = REPAINT_SCHEDULED; |
| + output_repaint_timer_arm(compositor); |
| } |
| |
| static void |
| @@ -2424,6 +2524,8 @@ idle_repaint(void *data) |
| { |
| struct weston_output *output = data; |
| |
| + assert(output->repaint_status == REPAINT_BEGIN_FROM_IDLE); |
| + output->repaint_status = REPAINT_AWAITING_COMPLETION; |
| output->start_repaint_loop(output); |
| } |
| |
| @@ -2538,12 +2640,17 @@ weston_output_schedule_repaint(struct weston_output *output) |
| TL_POINT("core_repaint_req", TLP_OUTPUT(output), TLP_END); |
| |
| loop = wl_display_get_event_loop(compositor->wl_display); |
| - output->repaint_needed = 1; |
| - if (output->repaint_scheduled) |
| + output->repaint_needed = true; |
| + |
| + /* If we already have a repaint scheduled for our idle handler, |
| + * no need to set it again. If the repaint has been called but |
| + * not finished, then weston_output_finish_frame() will notice |
| + * that a repaint is needed and schedule one. */ |
| + if (output->repaint_status != REPAINT_NOT_SCHEDULED) |
| return; |
| |
| + output->repaint_status = REPAINT_BEGIN_FROM_IDLE; |
| wl_event_loop_add_idle(loop, idle_repaint, output); |
| - output->repaint_scheduled = 1; |
| TL_POINT("core_repaint_enter_loop", TLP_OUTPUT(output), TLP_END); |
| } |
| |
| @@ -4400,8 +4507,6 @@ weston_output_transform_coordinate(struct weston_output *output, |
| static void |
| weston_output_enable_undo(struct weston_output *output) |
| { |
| - wl_event_source_remove(output->repaint_timer); |
| - |
| wl_global_destroy(output->global); |
| |
| pixman_region32_fini(&output->region); |
| @@ -4583,7 +4688,6 @@ weston_output_enable(struct weston_output *output) |
| { |
| struct weston_compositor *c = output->compositor; |
| struct weston_output *iterator; |
| - struct wl_event_loop *loop; |
| int x = 0, y = 0; |
| |
| assert(output->enable); |
| @@ -4624,10 +4728,6 @@ weston_output_enable(struct weston_output *output) |
| wl_list_init(&output->feedback_list); |
| wl_list_init(&output->link); |
| |
| - loop = wl_display_get_event_loop(c->wl_display); |
| - output->repaint_timer = wl_event_loop_add_timer(loop, |
| - output_repaint_timer_handler, output); |
| - |
| /* Invert the output id pool and look for the lowest numbered |
| * switch (the least significant bit). Take that bit's position |
| * as our ID, and mark it used in the compositor's output_id_pool. |
| @@ -5129,6 +5229,9 @@ weston_compositor_create(struct wl_display *display, void *user_data) |
| |
| loop = wl_display_get_event_loop(ec->wl_display); |
| ec->idle_source = wl_event_loop_add_timer(loop, idle_handler, ec); |
| + ec->repaint_timer = |
| + wl_event_loop_add_timer(loop, output_repaint_timer_handler, |
| + ec); |
| |
| weston_layer_init(&ec->fade_layer, ec); |
| weston_layer_init(&ec->cursor_layer, ec); |
| diff --git a/libweston/compositor.h b/libweston/compositor.h |
| index 08e728a..6070c77 100644 |
| --- a/libweston/compositor.h |
| +++ b/libweston/compositor.h |
| @@ -170,9 +170,23 @@ struct weston_output { |
| pixman_region32_t region; |
| |
| pixman_region32_t previous_damage; |
| - int repaint_needed; |
| - int repaint_scheduled; |
| - struct wl_event_source *repaint_timer; |
| + |
| + /** True if damage has occurred since the last repaint for this output; |
| + * if set, a repaint will eventually occur. */ |
| + bool repaint_needed; |
| + |
| + /** State of the repaint loop */ |
| + enum { |
| + REPAINT_NOT_SCHEDULED = 0, /**< idle; no repaint will occur */ |
| + REPAINT_BEGIN_FROM_IDLE, /**< start_repaint_loop scheduled */ |
| + REPAINT_SCHEDULED, /**< repaint is scheduled to occur */ |
| + REPAINT_AWAITING_COMPLETION, /**< last repaint not yet finished */ |
| + } repaint_status; |
| + |
| + /** If repaint_status is REPAINT_SCHEDULED, contains the time the |
| + * next repaint should be run */ |
| + struct timespec next_repaint; |
| + |
| struct weston_output_zoom zoom; |
| int dirty; |
| struct wl_signal frame_signal; |
| @@ -198,9 +212,10 @@ struct weston_output { |
| |
| void (*start_repaint_loop)(struct weston_output *output); |
| int (*repaint)(struct weston_output *output, |
| - pixman_region32_t *damage); |
| + pixman_region32_t *damage, |
| + void *repaint_data); |
| void (*destroy)(struct weston_output *output); |
| - void (*assign_planes)(struct weston_output *output); |
| + void (*assign_planes)(struct weston_output *output, void *repaint_data); |
| int (*switch_mode)(struct weston_output *output, struct weston_mode *mode); |
| |
| /* backlight values are on 0-255 range, where higher is brighter */ |
| @@ -790,6 +805,39 @@ struct weston_backend_config { |
| struct weston_backend { |
| void (*destroy)(struct weston_compositor *compositor); |
| void (*restore)(struct weston_compositor *compositor); |
| + |
| + /** Begin a repaint sequence |
| + * |
| + * Provides the backend with explicit markers around repaint |
| + * sequences, which may allow the backend to aggregate state |
| + * application. This call will be bracketed by the repaint_flush (on |
| + * success), or repaint_cancel (when any output in the grouping fails |
| + * repaint). |
| + * |
| + * Returns an opaque pointer, which the backend may use as private |
| + * data referring to the repaint cycle. |
| + */ |
| + void * (*repaint_begin)(struct weston_compositor *compositor); |
| + |
| + /** Cancel a repaint sequence |
| + * |
| + * Cancels a repaint sequence, when an error has occurred during |
| + * one output's repaint; see repaint_begin. |
| + * |
| + * @param repaint_data Data returned by repaint_begin |
| + */ |
| + void (*repaint_cancel)(struct weston_compositor *compositor, |
| + void *repaint_data); |
| + |
| + /** Conclude a repaint sequence |
| + * |
| + * Called on successful completion of a repaint sequence; see |
| + * repaint_begin. |
| + * |
| + * @param repaint_data Data returned by repaint_begin |
| + */ |
| + void (*repaint_flush)(struct weston_compositor *compositor, |
| + void *repaint_data); |
| }; |
| |
| struct weston_desktop_xwayland; |
| @@ -845,6 +893,7 @@ struct weston_compositor { |
| struct wl_event_source *idle_source; |
| uint32_t idle_inhibit; |
| int idle_time; /* timeout, s */ |
| + struct wl_event_source *repaint_timer; |
| |
| const struct weston_pointer_grab_interface *default_pointer_grab; |
| |
| diff --git a/libweston/pixel-formats.c b/libweston/pixel-formats.c |
| new file mode 100644 |
| index 0000000..df84a9f |
| --- /dev/null |
| +++ b/libweston/pixel-formats.c |
| @@ -0,0 +1,430 @@ |
| +/* |
| + * Copyright © 2016 Collabora, Ltd. |
| + * |
| + * Permission is hereby granted, free of charge, to any person obtaining a |
| + * copy of this software and associated documentation files (the "Software"), |
| + * to deal in the Software without restriction, including without limitation |
| + * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| + * and/or sell copies of the Software, and to permit persons to whom the |
| + * Software is furnished to do so, subject to the following conditions: |
| + * |
| + * The above copyright notice and this permission notice (including the next |
| + * paragraph) shall be included in all copies or substantial portions of the |
| + * Software. |
| + * |
| + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
| + * DEALINGS IN THE SOFTWARE. |
| + * |
| + * Author: Daniel Stone <daniels@collabora.com> |
| + */ |
| + |
| +#include "config.h" |
| + |
| +#include <endian.h> |
| +#include <inttypes.h> |
| +#include <stdbool.h> |
| +#include <unistd.h> |
| +#include <drm_fourcc.h> |
| + |
| +#include "helpers.h" |
| +#include "wayland-util.h" |
| +#include "pixel-formats.h" |
| + |
| +#if ENABLE_EGL |
| +#include <EGL/egl.h> |
| +#include <EGL/eglext.h> |
| +#include <GLES2/gl2.h> |
| +#include <GLES2/gl2ext.h> |
| +#define GL_FORMAT(fmt) .gl_format = (fmt) |
| +#define GL_TYPE(type) .gl_type = (type) |
| +#define SAMPLER_TYPE(type) .sampler_type = (type) |
| +#else |
| +#define GL_FORMAT(fmt) .gl_format = 0 |
| +#define GL_TYPE(type) .gl_type = 0 |
| +#define SAMPLER_TYPE(type) .sampler_type = 0 |
| +#endif |
| + |
| +#include "weston-egl-ext.h" |
| + |
| +/** |
| + * Table of DRM formats supported by Weston; RGB, ARGB and YUV formats are |
| + * supported. Indexed/greyscale formats, and formats not containing complete |
| + * colour channels, are not supported. |
| + */ |
| +static const struct pixel_format_info pixel_format_table[] = { |
| + { |
| + .format = DRM_FORMAT_XRGB4444, |
| + }, |
| + { |
| + .format = DRM_FORMAT_ARGB4444, |
| + .opaque_substitute = DRM_FORMAT_XRGB4444, |
| + }, |
| + { |
| + .format = DRM_FORMAT_XBGR4444, |
| + }, |
| + { |
| + .format = DRM_FORMAT_ABGR4444, |
| + .opaque_substitute = DRM_FORMAT_XBGR4444, |
| + }, |
| + { |
| + .format = DRM_FORMAT_RGBX4444, |
| +# if __BYTE_ORDER == __LITTLE_ENDIAN |
| + GL_FORMAT(GL_RGBA), |
| + GL_TYPE(GL_UNSIGNED_SHORT_4_4_4_4), |
| +#endif |
| + }, |
| + { |
| + .format = DRM_FORMAT_RGBA4444, |
| + .opaque_substitute = DRM_FORMAT_RGBX4444, |
| +# if __BYTE_ORDER == __LITTLE_ENDIAN |
| + GL_FORMAT(GL_RGBA), |
| + GL_TYPE(GL_UNSIGNED_SHORT_4_4_4_4), |
| +#endif |
| + }, |
| + { |
| + .format = DRM_FORMAT_BGRX4444, |
| + }, |
| + { |
| + .format = DRM_FORMAT_BGRA4444, |
| + .opaque_substitute = DRM_FORMAT_BGRX4444, |
| + }, |
| + { |
| + .format = DRM_FORMAT_XRGB1555, |
| + .depth = 15, |
| + .bpp = 16, |
| + }, |
| + { |
| + .format = DRM_FORMAT_ARGB1555, |
| + .opaque_substitute = DRM_FORMAT_XRGB1555, |
| + }, |
| + { |
| + .format = DRM_FORMAT_XBGR1555, |
| + }, |
| + { |
| + .format = DRM_FORMAT_ABGR1555, |
| + .opaque_substitute = DRM_FORMAT_XBGR1555, |
| + }, |
| + { |
| + .format = DRM_FORMAT_RGBX5551, |
| +# if __BYTE_ORDER == __LITTLE_ENDIAN |
| + GL_FORMAT(GL_RGBA), |
| + GL_TYPE(GL_UNSIGNED_SHORT_5_5_5_1), |
| +#endif |
| + }, |
| + { |
| + .format = DRM_FORMAT_RGBA5551, |
| + .opaque_substitute = DRM_FORMAT_RGBX5551, |
| +# if __BYTE_ORDER == __LITTLE_ENDIAN |
| + GL_FORMAT(GL_RGBA), |
| + GL_TYPE(GL_UNSIGNED_SHORT_5_5_5_1), |
| +#endif |
| + }, |
| + { |
| + .format = DRM_FORMAT_BGRX5551, |
| + }, |
| + { |
| + .format = DRM_FORMAT_BGRA5551, |
| + .opaque_substitute = DRM_FORMAT_BGRX5551, |
| + }, |
| + { |
| + .format = DRM_FORMAT_RGB565, |
| + .depth = 16, |
| + .bpp = 16, |
| +# if __BYTE_ORDER == __LITTLE_ENDIAN |
| + GL_FORMAT(GL_RGB), |
| + GL_TYPE(GL_UNSIGNED_SHORT_5_6_5), |
| +#endif |
| + }, |
| + { |
| + .format = DRM_FORMAT_BGR565, |
| + }, |
| + { |
| + .format = DRM_FORMAT_RGB888, |
| + }, |
| + { |
| + .format = DRM_FORMAT_BGR888, |
| + GL_FORMAT(GL_RGB), |
| + GL_TYPE(GL_UNSIGNED_BYTE), |
| + }, |
| + { |
| + .format = DRM_FORMAT_XRGB8888, |
| + .depth = 24, |
| + .bpp = 32, |
| + GL_FORMAT(GL_BGRA_EXT), |
| + GL_TYPE(GL_UNSIGNED_BYTE), |
| + }, |
| + { |
| + .format = DRM_FORMAT_ARGB8888, |
| + .opaque_substitute = DRM_FORMAT_XRGB8888, |
| + .depth = 32, |
| + .bpp = 32, |
| + GL_FORMAT(GL_BGRA_EXT), |
| + GL_TYPE(GL_UNSIGNED_BYTE), |
| + }, |
| + { |
| + .format = DRM_FORMAT_XBGR8888, |
| + GL_FORMAT(GL_RGBA), |
| + GL_TYPE(GL_UNSIGNED_BYTE), |
| + }, |
| + { |
| + .format = DRM_FORMAT_ABGR8888, |
| + .opaque_substitute = DRM_FORMAT_XBGR8888, |
| + GL_FORMAT(GL_RGBA), |
| + GL_TYPE(GL_UNSIGNED_BYTE), |
| + }, |
| + { |
| + .format = DRM_FORMAT_RGBX8888, |
| + }, |
| + { |
| + .format = DRM_FORMAT_RGBA8888, |
| + .opaque_substitute = DRM_FORMAT_RGBX8888, |
| + }, |
| + { |
| + .format = DRM_FORMAT_BGRX8888, |
| + }, |
| + { |
| + .format = DRM_FORMAT_BGRA8888, |
| + .opaque_substitute = DRM_FORMAT_BGRX8888, |
| + }, |
| + { |
| + .format = DRM_FORMAT_XRGB2101010, |
| + .depth = 30, |
| + .bpp = 32, |
| + }, |
| + { |
| + .format = DRM_FORMAT_ARGB2101010, |
| + .opaque_substitute = DRM_FORMAT_XRGB2101010, |
| + }, |
| + { |
| + .format = DRM_FORMAT_XBGR2101010, |
| +# if __BYTE_ORDER == __LITTLE_ENDIAN |
| + GL_FORMAT(GL_RGBA), |
| + GL_TYPE(GL_UNSIGNED_INT_2_10_10_10_REV_EXT), |
| +#endif |
| + }, |
| + { |
| + .format = DRM_FORMAT_ABGR2101010, |
| + .opaque_substitute = DRM_FORMAT_XBGR2101010, |
| +# if __BYTE_ORDER == __LITTLE_ENDIAN |
| + GL_FORMAT(GL_RGBA), |
| + GL_TYPE(GL_UNSIGNED_INT_2_10_10_10_REV_EXT), |
| +#endif |
| + }, |
| + { |
| + .format = DRM_FORMAT_RGBX1010102, |
| + }, |
| + { |
| + .format = DRM_FORMAT_RGBA1010102, |
| + .opaque_substitute = DRM_FORMAT_RGBX1010102, |
| + }, |
| + { |
| + .format = DRM_FORMAT_BGRX1010102, |
| + }, |
| + { |
| + .format = DRM_FORMAT_BGRA1010102, |
| + .opaque_substitute = DRM_FORMAT_BGRX1010102, |
| + }, |
| + { |
| + .format = DRM_FORMAT_YUYV, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), |
| + .num_planes = 1, |
| + .hsub = 2, |
| + }, |
| + { |
| + .format = DRM_FORMAT_YVYU, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), |
| + .num_planes = 1, |
| + .chroma_order = ORDER_VU, |
| + .hsub = 2, |
| + }, |
| + { |
| + .format = DRM_FORMAT_UYVY, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), |
| + .num_planes = 1, |
| + .luma_chroma_order = ORDER_CHROMA_LUMA, |
| + .hsub = 2, |
| + }, |
| + { |
| + .format = DRM_FORMAT_VYUY, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), |
| + .num_planes = 1, |
| + .luma_chroma_order = ORDER_CHROMA_LUMA, |
| + .chroma_order = ORDER_VU, |
| + .hsub = 2, |
| + }, |
| + { |
| + .format = DRM_FORMAT_NV12, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), |
| + .num_planes = 2, |
| + .hsub = 2, |
| + .vsub = 2, |
| + }, |
| + { |
| + .format = DRM_FORMAT_NV21, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), |
| + .num_planes = 2, |
| + .chroma_order = ORDER_VU, |
| + .hsub = 2, |
| + .vsub = 2, |
| + }, |
| + { |
| + .format = DRM_FORMAT_NV16, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), |
| + .num_planes = 2, |
| + .hsub = 2, |
| + .vsub = 1, |
| + }, |
| + { |
| + .format = DRM_FORMAT_NV61, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), |
| + .num_planes = 2, |
| + .chroma_order = ORDER_VU, |
| + .hsub = 2, |
| + .vsub = 1, |
| + }, |
| + { |
| + .format = DRM_FORMAT_NV24, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), |
| + .num_planes = 2, |
| + }, |
| + { |
| + .format = DRM_FORMAT_NV42, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), |
| + .num_planes = 2, |
| + .chroma_order = ORDER_VU, |
| + }, |
| + { |
| + .format = DRM_FORMAT_YUV410, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), |
| + .num_planes = 3, |
| + .hsub = 4, |
| + .vsub = 4, |
| + }, |
| + { |
| + .format = DRM_FORMAT_YVU410, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), |
| + .num_planes = 3, |
| + .chroma_order = ORDER_VU, |
| + .hsub = 4, |
| + .vsub = 4, |
| + }, |
| + { |
| + .format = DRM_FORMAT_YUV411, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), |
| + .num_planes = 3, |
| + .hsub = 4, |
| + .vsub = 1, |
| + }, |
| + { |
| + .format = DRM_FORMAT_YVU411, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), |
| + .num_planes = 3, |
| + .chroma_order = ORDER_VU, |
| + .hsub = 4, |
| + .vsub = 1, |
| + }, |
| + { |
| + .format = DRM_FORMAT_YUV420, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), |
| + .num_planes = 3, |
| + .hsub = 2, |
| + .vsub = 2, |
| + }, |
| + { |
| + .format = DRM_FORMAT_YVU420, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), |
| + .num_planes = 3, |
| + .chroma_order = ORDER_VU, |
| + .hsub = 2, |
| + .vsub = 2, |
| + }, |
| + { |
| + .format = DRM_FORMAT_YUV422, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), |
| + .num_planes = 3, |
| + .hsub = 2, |
| + .vsub = 1, |
| + }, |
| + { |
| + .format = DRM_FORMAT_YVU422, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), |
| + .num_planes = 3, |
| + .chroma_order = ORDER_VU, |
| + .hsub = 2, |
| + .vsub = 1, |
| + }, |
| + { |
| + .format = DRM_FORMAT_YUV444, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), |
| + .num_planes = 3, |
| + }, |
| + { |
| + .format = DRM_FORMAT_YVU444, |
| + SAMPLER_TYPE(EGL_TEXTURE_Y_U_V_WL), |
| + .num_planes = 3, |
| + .chroma_order = ORDER_VU, |
| + }, |
| +}; |
| + |
| +WL_EXPORT const struct pixel_format_info * |
| +pixel_format_get_info(uint32_t format) |
| +{ |
| + unsigned int i; |
| + |
| + for (i = 0; i < ARRAY_LENGTH(pixel_format_table); i++) { |
| + if (pixel_format_table[i].format == format) |
| + return &pixel_format_table[i]; |
| + } |
| + |
| + return NULL; |
| +} |
| + |
| +WL_EXPORT unsigned int |
| +pixel_format_get_plane_count(const struct pixel_format_info *info) |
| +{ |
| + return info->num_planes ? info->num_planes : 1; |
| +} |
| + |
| +WL_EXPORT bool |
| +pixel_format_is_opaque(const struct pixel_format_info *info) |
| +{ |
| + return !info->opaque_substitute; |
| +} |
| + |
| +WL_EXPORT const struct pixel_format_info * |
| +pixel_format_get_opaque_substitute(const struct pixel_format_info *info) |
| +{ |
| + if (!info->opaque_substitute) |
| + return info; |
| + else |
| + return pixel_format_get_info(info->opaque_substitute); |
| +} |
| + |
| +WL_EXPORT unsigned int |
| +pixel_format_width_for_plane(const struct pixel_format_info *info, |
| + unsigned int plane, |
| + unsigned int width) |
| +{ |
| + /* We don't support any formats where the first plane is subsampled. */ |
| + if (plane == 0 || !info->hsub) |
| + return width; |
| + |
| + return width / info->hsub; |
| +} |
| + |
| +WL_EXPORT unsigned int |
| +pixel_format_height_for_plane(const struct pixel_format_info *info, |
| + unsigned int plane, |
| + unsigned int height) |
| +{ |
| + /* We don't support any formats where the first plane is subsampled. */ |
| + if (plane == 0 || !info->vsub) |
| + return height; |
| + |
| + return height / info->vsub; |
| +} |
| diff --git a/libweston/pixel-formats.h b/libweston/pixel-formats.h |
| new file mode 100644 |
| index 0000000..b16aae3 |
| --- /dev/null |
| +++ b/libweston/pixel-formats.h |
| @@ -0,0 +1,194 @@ |
| +/* |
| + * Copyright © 2016 Collabora, Ltd. |
| + * |
| + * Permission is hereby granted, free of charge, to any person obtaining a |
| + * copy of this software and associated documentation files (the "Software"), |
| + * to deal in the Software without restriction, including without limitation |
| + * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| + * and/or sell copies of the Software, and to permit persons to whom the |
| + * Software is furnished to do so, subject to the following conditions: |
| + * |
| + * The above copyright notice and this permission notice (including the next |
| + * paragraph) shall be included in all copies or substantial portions of the |
| + * Software. |
| + * |
| + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
| + * DEALINGS IN THE SOFTWARE. |
| + * |
| + * Author: Daniel Stone <daniels@collabora.com> |
| + */ |
| + |
| +#include <inttypes.h> |
| +#include <stdbool.h> |
| + |
| +/** |
| + * Contains information about pixel formats, mapping format codes from |
| + * wl_shm and drm_fourcc.h (which are deliberately identical, but for the |
| + * special cases of WL_SHM_ARGB8888 and WL_SHM_XRGB8888) into various |
| + * sets of information. Helper functions are provided for dealing with these |
| + * raw structures. |
| + */ |
| +struct pixel_format_info { |
| + /** DRM/wl_shm format code */ |
| + uint32_t format; |
| + |
| + /** If non-zero, number of planes in base (non-modified) format. */ |
| + int num_planes; |
| + |
| + /** If format contains alpha channel, opaque equivalent of format, |
| + * i.e. alpha channel replaced with X. */ |
| + uint32_t opaque_substitute; |
| + |
| + /** How the format should be sampled, expressed in terms of tokens |
| + * from the EGL_WL_bind_wayland_display extension. If not set, |
| + * assumed to be either RGB or RGBA, depending on whether or not |
| + * the format contains an alpha channel. The samplers may still |
| + * return alpha even for opaque formats; users must manually set |
| + * the alpha channel to 1.0 (or ignore it) if the format is |
| + * opaque. */ |
| + uint32_t sampler_type; |
| + |
| + /** GL format, if data can be natively/directly uploaded. Note that |
| + * whilst DRM formats are little-endian unless explicitly specified, |
| + * (i.e. DRM_FORMAT_ARGB8888 is stored BGRA as sequential bytes in |
| + * memory), GL uses the sequential byte order, so that format maps to |
| + * GL_BGRA_EXT plus GL_UNSIGNED_BYTE. To add to the confusion, the |
| + * explicitly-sized types (e.g. GL_UNSIGNED_SHORT_5_5_5_1) read in |
| + * machine-endian order, so for these types, the correspondence |
| + * depends on endianness. */ |
| + int gl_format; |
| + |
| + /** GL data type, if data can be natively/directly uploaded. */ |
| + int gl_type; |
| + |
| + /** If set, this format can be used with the legacy drmModeAddFB() |
| + * function (not AddFB2), using this and the bpp member. */ |
| + int depth; |
| + |
| + /** See 'depth' member above. */ |
| + int bpp; |
| + |
| + /** Horizontal subsampling; if non-zero, divide the width by this |
| + * member to obtain the number of columns in the source buffer for |
| + * secondary planes only. Stride is not affected by horizontal |
| + * subsampling. */ |
| + int hsub; |
| + |
| + /** Vertical subsampling; if non-zero, divide the height by this |
| + * member to obtain the number of rows in the source buffer for |
| + * secondary planes only. */ |
| + int vsub; |
| + |
| + /* Ordering of chroma components. */ |
| + enum { |
| + ORDER_UV = 0, |
| + ORDER_VU, |
| + } chroma_order; |
| + |
| + /* If packed YUV (num_planes == 1), ordering of luma/chroma |
| + * components. */ |
| + enum { |
| + ORDER_LUMA_CHROMA = 0, |
| + ORDER_CHROMA_LUMA, |
| + } luma_chroma_order; |
| +}; |
| + |
| +/** |
| + * Get pixel format information for a DRM format code |
| + * |
| + * Given a DRM format code, return a pixel format info structure describing |
| + * the properties of that format. |
| + * |
| + * @param format DRM format code to get info for |
| + * @returns A pixel format structure (must not be freed), or NULL if the |
| + * format could not be found |
| + */ |
| +const struct pixel_format_info *pixel_format_get_info(uint32_t format); |
| + |
| +/** |
| + * Get number of planes used by a pixel format |
| + * |
| + * Given a pixel format info structure, return the number of planes |
| + * required for a buffer. Note that this is not necessarily identical to |
| + * the number of samplers required to be bound, as two views into a single |
| + * plane are sometimes required. |
| + * |
| + * @param format Pixel format info structure |
| + * @returns Number of planes required for the format |
| + */ |
| +unsigned int |
| +pixel_format_get_plane_count(const struct pixel_format_info *format); |
| + |
| +/** |
| + * Determine if a pixel format is opaque or contains alpha |
| + * |
| + * Returns whether or not the pixel format is opaque, or contains a |
| + * significant alpha channel. Note that the suggested EGL sampler type may |
| + * still sample undefined data into the alpha channel; users must consider |
| + * alpha as 1.0 if the format is opaque, and not rely on the sampler to |
| + * return this when sampling from the alpha channel. |
| + * |
| + * @param format Pixel format info structure |
| + * @returns True if the format is opaque, or false if it has significant alpha |
| + */ |
| +bool pixel_format_is_opaque(const struct pixel_format_info *format); |
| + |
| +/** |
| + * Get compatible opaque equivalent for a format |
| + * |
| + * Given a pixel format info structure, return a format which is wholly |
| + * compatible with the input format, but opaque, ignoring the alpha channel. |
| + * If an alpha format is provided, but the content is known to all be opaque, |
| + * then this can be used as a substitute to avoid blending. |
| + * |
| + * If the input format is opaque, this function will return the input format. |
| + * |
| + * @param format Pixel format info structure |
| + * @returns A pixel format info structure for the compatible opaque substitute |
| + */ |
| +const struct pixel_format_info * |
| +pixel_format_get_opaque_substitute(const struct pixel_format_info *format); |
| + |
| +/** |
| + * Return the effective sampling width for a given plane |
| + * |
| + * When horizontal subsampling is effective, a sampler bound to a secondary |
| + * plane must bind the sampler with a smaller effective width. This function |
| + * returns the effective width to use for the sampler, i.e. dividing by hsub. |
| + * |
| + * If horizontal subsampling is not in effect, this will be equal to the |
| + * width. |
| + * |
| + * @param format Pixel format info structure |
| + * @param plane Zero-indexed plane number |
| + * @param width Width of the buffer |
| + * @returns Effective width for sampling |
| + */ |
| +unsigned int |
| +pixel_format_width_for_plane(const struct pixel_format_info *format, |
| + unsigned int plane, |
| + unsigned int width); |
| + |
| +/** |
| + * Return the effective sampling height for a given plane |
| + * |
| + * When vertical subsampling is in effect, a sampler bound to a secondary |
| + * plane must bind the sampler with a smaller effective height. This function |
| + * returns the effective height to use for the sampler, i.e. dividing by vsub. |
| + * |
| + * If vertical subsampling is not in effect, this will be equal to the height. |
| + * |
| + * @param format Pixel format info structure |
| + * @param plane Zero-indexed plane number |
| + * @param height Height of the buffer |
| + * @returns Effective width for sampling |
| + */ |
| +unsigned int |
| +pixel_format_height_for_plane(const struct pixel_format_info *format, |
| + unsigned int plane, |
| + unsigned int height); |
| diff --git a/shared/timespec-util.h b/shared/timespec-util.h |
| index edd4ec1..ca0156a 100644 |
| --- a/shared/timespec-util.h |
| +++ b/shared/timespec-util.h |
| @@ -28,6 +28,8 @@ |
| |
| #include <stdint.h> |
| #include <assert.h> |
| +#include <time.h> |
| +#include <stdbool.h> |
| |
| #define NSEC_PER_SEC 1000000000 |
| |
| @@ -49,6 +51,39 @@ timespec_sub(struct timespec *r, |
| } |
| } |
| |
| +/* Add a nanosecond value to a timespec |
| + * |
| + * \param r[out] result: a + b |
| + * \param a[in] base operand as timespec |
| + * \param b[in] operand in nanoseconds |
| + */ |
| +static inline void |
| +timespec_add_nsec(struct timespec *r, const struct timespec *a, int64_t b) |
| +{ |
| + r->tv_sec = a->tv_sec + (b / NSEC_PER_SEC); |
| + r->tv_nsec = a->tv_nsec + (b % NSEC_PER_SEC); |
| + |
| + if (r->tv_nsec >= NSEC_PER_SEC) { |
| + r->tv_sec++; |
| + r->tv_nsec -= NSEC_PER_SEC; |
| + } else if (r->tv_nsec < 0) { |
| + r->tv_sec--; |
| + r->tv_nsec += NSEC_PER_SEC; |
| + } |
| +} |
| + |
| +/* Add a millisecond value to a timespec |
| + * |
| + * \param r[out] result: a + b |
| + * \param a[in] base operand as timespec |
| + * \param b[in] operand in milliseconds |
| + */ |
| +static inline void |
| +timespec_add_msec(struct timespec *r, const struct timespec *a, int64_t b) |
| +{ |
| + return timespec_add_nsec(r, a, b * 1000000); |
| +} |
| + |
| /* Convert timespec to nanoseconds |
| * |
| * \param a timespec |
| @@ -60,6 +95,155 @@ timespec_to_nsec(const struct timespec *a) |
| return (int64_t)a->tv_sec * NSEC_PER_SEC + a->tv_nsec; |
| } |
| |
| +/* Subtract timespecs and return result in nanoseconds |
| + * |
| + * \param a[in] operand |
| + * \param b[in] operand |
| + * \return to_nanoseconds(a - b) |
| + */ |
| +static inline int64_t |
| +timespec_sub_to_nsec(const struct timespec *a, const struct timespec *b) |
| +{ |
| + struct timespec r; |
| + timespec_sub(&r, a, b); |
| + return timespec_to_nsec(&r); |
| +} |
| + |
| +/* Convert timespec to milliseconds |
| + * |
| + * \param a timespec |
| + * \return milliseconds |
| + * |
| + * Rounding to integer milliseconds happens always down (floor()). |
| + */ |
| +static inline int64_t |
| +timespec_to_msec(const struct timespec *a) |
| +{ |
| + return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000; |
| +} |
| + |
| +/* Subtract timespecs and return result in milliseconds |
| + * |
| + * \param a[in] operand |
| + * \param b[in] operand |
| + * \return to_milliseconds(a - b) |
| + */ |
| +static inline int64_t |
| +timespec_sub_to_msec(const struct timespec *a, const struct timespec *b) |
| +{ |
| + return timespec_sub_to_nsec(a, b) / 1000000; |
| +} |
| + |
| +/* Convert timespec to microseconds |
| + * |
| + * \param a timespec |
| + * \return microseconds |
| + * |
| + * Rounding to integer microseconds happens always down (floor()). |
| + */ |
| +static inline int64_t |
| +timespec_to_usec(const struct timespec *a) |
| +{ |
| + return (int64_t)a->tv_sec * 1000000 + a->tv_nsec / 1000; |
| +} |
| + |
| +/* Convert timespec to protocol data |
| + * |
| + * \param a timespec |
| + * \param tv_sec_hi[out] the high bytes of the seconds part |
| + * \param tv_sec_lo[out] the low bytes of the seconds part |
| + * \param tv_nsec[out] the nanoseconds part |
| + * |
| + * The input timespec must be normalized (the nanoseconds part should |
| + * be less than 1 second) and non-negative. |
| + */ |
| +static inline void |
| +timespec_to_proto(const struct timespec *a, uint32_t *tv_sec_hi, |
| + uint32_t *tv_sec_lo, uint32_t *tv_nsec) |
| +{ |
| + assert(a->tv_sec >= 0); |
| + assert(a->tv_nsec >= 0 && a->tv_nsec < NSEC_PER_SEC); |
| + |
| + uint64_t sec64 = a->tv_sec; |
| + |
| + *tv_sec_hi = sec64 >> 32; |
| + *tv_sec_lo = sec64 & 0xffffffff; |
| + *tv_nsec = a->tv_nsec; |
| +} |
| + |
| +/* Convert nanoseconds to timespec |
| + * |
| + * \param a timespec |
| + * \param b nanoseconds |
| + */ |
| +static inline void |
| +timespec_from_nsec(struct timespec *a, int64_t b) |
| +{ |
| + a->tv_sec = b / NSEC_PER_SEC; |
| + a->tv_nsec = b % NSEC_PER_SEC; |
| +} |
| + |
| +/* Convert microseconds to timespec |
| + * |
| + * \param a timespec |
| + * \param b microseconds |
| + */ |
| +static inline void |
| +timespec_from_usec(struct timespec *a, int64_t b) |
| +{ |
| + timespec_from_nsec(a, b * 1000); |
| +} |
| + |
| +/* Convert milliseconds to timespec |
| + * |
| + * \param a timespec |
| + * \param b milliseconds |
| + */ |
| +static inline void |
| +timespec_from_msec(struct timespec *a, int64_t b) |
| +{ |
| + timespec_from_nsec(a, b * 1000000); |
| +} |
| + |
| +/* Convert protocol data to timespec |
| + * |
| + * \param a[out] timespec |
| + * \param tv_sec_hi the high bytes of seconds part |
| + * \param tv_sec_lo the low bytes of seconds part |
| + * \param tv_nsec the nanoseconds part |
| + */ |
| +static inline void |
| +timespec_from_proto(struct timespec *a, uint32_t tv_sec_hi, |
| + uint32_t tv_sec_lo, uint32_t tv_nsec) |
| +{ |
| + a->tv_sec = ((uint64_t)tv_sec_hi << 32) + tv_sec_lo; |
| + a->tv_nsec = tv_nsec; |
| +} |
| + |
| +/* Check if a timespec is zero |
| + * |
| + * \param a timespec |
| + * \return whether the timespec is zero |
| + */ |
| +static inline bool |
| +timespec_is_zero(const struct timespec *a) |
| +{ |
| + return a->tv_sec == 0 && a->tv_nsec == 0; |
| +} |
| + |
| +/* Check if two timespecs are equal |
| + * |
| + * \param a[in] timespec to check |
| + * \param b[in] timespec to check |
| + * \return whether timespecs a and b are equal |
| + */ |
| +static inline bool |
| +timespec_eq(const struct timespec *a, const struct timespec *b) |
| +{ |
| + return a->tv_sec == b->tv_sec && |
| + a->tv_nsec == b->tv_nsec; |
| +} |
| + |
| /* Convert milli-Hertz to nanoseconds |
| * |
| * \param mhz frequency in mHz, not zero |
| -- |
| 1.9.1 |
| |