diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c index 6ac9626d..e123fe50 100644 --- a/libweston/compositor-drm.c +++ b/libweston/compositor-drm.c @@ -139,6 +139,22 @@ struct drm_property_info { struct drm_property_enum_info *enum_values; /**< array of enum values */ }; +/** + * 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; @@ -235,6 +251,23 @@ struct drm_edid { */ 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; }; /** @@ -328,6 +361,12 @@ struct drm_output { * repaint is flushed. */ struct drm_fb *fb_pending; + /* 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]; int current_image; @@ -602,6 +641,9 @@ drm_output_set_cursor(struct drm_output *output); static void drm_output_update_msc(struct drm_output *output, unsigned int seq); +static void +drm_output_destroy(struct weston_output *output_base); + static int drm_plane_crtc_supported(struct drm_output *output, struct drm_plane *plane) { @@ -902,11 +944,69 @@ drm_fb_unref(struct drm_fb *fb) } } -static int -drm_view_transform_supported(struct weston_view *ev) +/** + * 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) { - return !ev->transform.enabled || - (ev->transform.matrix.type < WESTON_MATRIX_TRANSFORM_ROTATE); + struct drm_output_state *state = zalloc(sizeof(*state)); + + assert(state); + state->output = output; + state->pending_state = pending_state; + if (pending_state) + wl_list_insert(&pending_state->output_list, &state->link); + else + wl_list_init(&state->link); + + 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)); + + 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); + + return dst; +} + +/** + * Free an unused drm_output_state. + */ +static void +drm_output_state_free(struct drm_output_state *state) +{ + if (!state) + return; + + wl_list_remove(&state->link); + free(state); } /** @@ -928,6 +1028,7 @@ drm_pending_state_alloc(struct drm_backend *backend) return NULL; ret->backend = backend; + wl_list_init(&ret->output_list); return ret; } @@ -935,19 +1036,122 @@ drm_pending_state_alloc(struct drm_backend *backend) /** * Free a drm_pending_state structure * - * Frees a 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; +} + +/** + * 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 timespec ts; + + /* Stop the pageflip timer instead of rearming it here */ + if (output->pageflip_timer) + wl_event_source_timer_update(output->pageflip_timer, 0); + + drm_output_state_free(output->state_last); + output->state_last = NULL; + + if (output->destroy_pending) { + drm_output_destroy(&output->base); + return; + } else if (output->disable_pending) { + weston_output_disable(&output->base); + output->disable_pending = 0; + 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; + + 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; +} + + +static int +drm_view_transform_supported(struct weston_view *ev) +{ + return !ev->transform.enabled || + (ev->transform.matrix.type < WESTON_MATRIX_TRANSFORM_ROTATE); +} + static uint32_t drm_output_check_scanout_format(struct drm_output *output, struct weston_surface *es, struct gbm_bo *bo) @@ -979,9 +1183,10 @@ 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 weston_buffer *buffer = ev->surface->buffer_ref.buffer; struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; @@ -1048,8 +1253,9 @@ drm_output_prepare_scanout_view(struct drm_output *output, } static struct drm_fb * -drm_output_render_gl(struct drm_output *output, pixman_region32_t *damage) +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; @@ -1075,8 +1281,10 @@ drm_output_render_gl(struct drm_output *output, pixman_region32_t *damage) } static struct drm_fb * -drm_output_render_pixman(struct drm_output *output, pixman_region32_t *damage) +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; @@ -1102,8 +1310,9 @@ drm_output_render_pixman(struct drm_output *output, pixman_region32_t *damage) } 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_backend *b = to_drm_backend(c); struct drm_fb *fb; @@ -1114,9 +1323,9 @@ drm_output_render(struct drm_output *output, pixman_region32_t *damage) return; if (b->use_pixman) - fb = drm_output_render_pixman(output, damage); + fb = drm_output_render_pixman(state, damage); else - fb = drm_output_render_gl(output, damage); + fb = drm_output_render_gl(state, damage); if (!fb) return; @@ -1178,6 +1387,8 @@ 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_state *state = NULL; struct drm_output *output = to_drm_output(output_base); struct drm_backend *backend = to_drm_backend(output->base.compositor); @@ -1186,7 +1397,18 @@ drm_output_repaint(struct weston_output *output_base, int ret = 0; if (output->disable_pending || output->destroy_pending) - return -1; + goto err; + + 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); assert(!output->fb_last); @@ -1200,9 +1422,9 @@ drm_output_repaint(struct weston_output *output_base, output->cursor_plane.y = INT32_MIN; } - drm_output_render(output, damage); + drm_output_render(state, damage); if (!output->fb_pending) - return -1; + goto err; mode = container_of(output->base.current_mode, struct drm_mode, base); if (output->state_invalid || !output->fb_current || @@ -1213,7 +1435,7 @@ drm_output_repaint(struct weston_output *output_base, &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); @@ -1224,7 +1446,7 @@ drm_output_repaint(struct weston_output *output_base, output->fb_pending->fb_id, DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { weston_log("queueing pageflip failed: %m\n"); - goto err_pageflip; + goto err; } output->fb_last = output->fb_current; @@ -1292,12 +1514,13 @@ drm_output_repaint(struct weston_output *output_base, return 0; -err_pageflip: +err: output->cursor_view = NULL; if (output->fb_pending) { drm_fb_unref(output->fb_pending); output->fb_pending = NULL; } + drm_output_state_free(state); return -1; } @@ -1306,6 +1529,8 @@ 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 = NULL; + struct drm_output_state *state; struct drm_backend *backend = to_drm_backend(output_base->compositor); uint32_t fb_id; @@ -1366,6 +1591,11 @@ drm_output_start_repaint_loop(struct weston_output *output_base) assert(!output->page_flip_pending); assert(!output->fb_last); + assert(!output->state_last); + + pending_state = drm_pending_state_alloc(backend); + state = drm_output_state_duplicate(output->state_cur, pending_state, + DRM_OUTPUT_STATE_PRESERVE_PLANES); if (drmModePageFlip(backend->drm.fd, output->crtc_id, fb_id, DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { @@ -1380,9 +1610,14 @@ drm_output_start_repaint_loop(struct weston_output *output_base) output->fb_last = drm_fb_ref(output->fb_current); output->page_flip_pending = 1; + drm_output_assign_state(state, DRM_STATE_APPLY_ASYNC); + drm_pending_state_free(pending_state); + return; finish_frame: + drm_pending_state_free(pending_state); + /* if we cannot page-flip, immediately finish frame */ weston_output_finish_frame(output_base, NULL, WP_PRESENTATION_FEEDBACK_INVALID); @@ -1405,7 +1640,6 @@ vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, { struct drm_plane *s = (struct drm_plane *)data; struct drm_output *output = s->output; - struct timespec ts; uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; @@ -1417,26 +1651,17 @@ vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, drm_fb_unref(s->fb_last); s->fb_last = NULL; - if (!output->page_flip_pending && !output->vblank_pending) { - /* Stop the pageflip timer instead of rearming it here */ - if (output->pageflip_timer) - wl_event_source_timer_update(output->pageflip_timer, 0); + if (output->page_flip_pending || output->vblank_pending) + return; - ts.tv_sec = sec; - ts.tv_nsec = usec * 1000; - weston_output_finish_frame(&output->base, &ts, flags); - } + drm_output_update_complete(output, flags, sec, usec); } -static void -drm_output_destroy(struct weston_output *base); - 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; uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_VSYNC | WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; @@ -1449,30 +1674,18 @@ page_flip_handler(int fd, unsigned int frame, drm_fb_unref(output->fb_last); output->fb_last = NULL; - if (output->destroy_pending) - drm_output_destroy(&output->base); - else if (output->disable_pending) - weston_output_disable(&output->base); - else if (!output->vblank_pending) { - /* Stop the pageflip timer instead of rearming it here */ - if (output->pageflip_timer) - wl_event_source_timer_update(output->pageflip_timer, 0); - - 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); - } + if (output->vblank_pending) + return; + + 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. + * 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) @@ -1490,13 +1703,22 @@ drm_repaint_begin(struct weston_compositor *compositor) * Flush a repaint set * * Called by the core compositor when a repaint cycle has been completed - * and should be flushed. + * 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; + struct drm_output_state *output_state, *tmp; + + wl_list_for_each_safe(output_state, tmp, &pending_state->output_list, + link) { + drm_output_assign_state(output_state, DRM_STATE_APPLY_ASYNC); + } drm_pending_state_free(pending_state); b->repaint_data = NULL; @@ -1548,9 +1770,10 @@ drm_output_check_plane_format(struct drm_plane *p, } 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; @@ -1734,9 +1957,10 @@ drm_output_prepare_overlay_view(struct drm_output *output, } 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 weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; struct wl_shm_buffer *shmbuf; @@ -1867,12 +2091,19 @@ static void 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 drm_output_state *state; struct weston_view *ev, *next; pixman_region32_t overlap, surface_overlap; 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: * 1) size @@ -1921,19 +2152,19 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) 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); + 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(output, ev); + 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; @@ -3260,7 +3491,7 @@ drm_output_destroy(struct weston_output *base) struct drm_mode *drm_mode, *next; drmModeCrtcPtr origcrtc = output->original_crtc; - if (output->page_flip_pending) { + if (output->page_flip_pending || output->vblank_pending) { output->destroy_pending = 1; weston_log("destroy output while page flip pending\n"); return; @@ -3295,6 +3526,9 @@ drm_output_destroy(struct weston_output *base) if (output->backlight) backlight_destroy(output->backlight); + assert(!output->state_last); + drm_output_state_free(output->state_cur); + free(output); } @@ -3304,7 +3538,7 @@ 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->disable_pending = 1; return -1; } @@ -3312,12 +3546,19 @@ drm_output_disable(struct weston_output *base) if (output->base.enabled) drm_output_deinit(&output->base); + assert(!output->fb_last); + assert(!output->fb_current); + assert(!output->fb_pending); + 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); + drm_output_state_free(output->state_cur); + output->state_cur = drm_output_state_alloc(output, NULL); + return 0; } @@ -3404,6 +3645,8 @@ create_output_for_connector(struct drm_backend *b, output->connector->connector_type == DRM_MODE_CONNECTOR_eDP) output->base.connection_internal = true; + output->state_cur = drm_output_state_alloc(output, NULL); + output->base.mm_width = output->connector->mmWidth; output->base.mm_height = output->connector->mmHeight;