[PATCH weston v12 09/40] compositor-drm: Move repaint state application to flush

Michael Tretter m.tretter at pengutronix.de
Fri Nov 10 13:53:56 UTC 2017


Hi Daniel,

On Tue, 26 Sep 2017 18:15:42 +0100, Daniel Stone wrote:
> Split repaint into two stages, as implied by the grouped-repaint
> interface: drm_output_repaint generates the repaint state only, and
> drm_repaint_flush applies it.
> 
> This also moves DPMS into output state. Previously, the usual way to
> DPMS off was that repaint would be called and apply its state,
> followed by set_dpms being called afterwards to push the DPMS state
> separately. As this happens before the repaint_flush hook, with no
> change to DPMS we would set DPMS off, then immediately re-enable the
> output by posting the repaint. Not ideal.
> 
> Moving DPMS application at the same time complicates this patch, but I
> couldn't find a way to split it; if we keep set_dpms before
> begin_flush then we break DPMS off, or if we try to move DPMS to
> output state before using the repaint flush, we get stuck as the
> repaint hook generates an asynchronous state update, followed
> immediately by set_dpms generating a synchronous state update.
> 
> Signed-off-by: Daniel Stone <daniels at collabora.com>
> ---
>  libweston/compositor-drm.c | 313
> ++++++++++++++++++++++++++++++++++----------- 1 file changed, 240
> insertions(+), 73 deletions(-)
> 
> diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c
> index 530b0fd5..6c1c6881 100644
> --- a/libweston/compositor-drm.c
> +++ b/libweston/compositor-drm.c
> @@ -267,6 +267,7 @@ 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;
>  };
>  
> @@ -350,6 +351,7 @@ struct drm_output {
>  	int page_flip_pending;
>  	int destroy_pending;
>  	int disable_pending;
> +	int dpms_off_pending;
>  
>  	struct drm_fb *gbm_cursor_fb[2];
>  	struct drm_plane *cursor_plane;
> @@ -1124,6 +1126,7 @@ drm_output_state_alloc(struct drm_output
> *output, 
>  	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); @@ -1200,6 +1203,30 @@ drm_output_state_free(struct
> drm_output_state *state) 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
>   *
> @@ -1272,6 +1299,8 @@ drm_pending_state_get_output(struct
> drm_pending_state *pending_state, return NULL;
>  }
>  
> +static int drm_pending_state_apply(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 @@ -1281,6 +1310,7 @@ 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;
>  
> @@ -1300,6 +1330,13 @@ drm_output_update_complete(struct drm_output
> *output, uint32_t flags, } else if (output->disable_pending) {
>  		weston_output_disable(&output->base);
>  		output->disable_pending = 0;
> +		output->dpms_off_pending = 0;
> +		return;
> +	} else if (output->dpms_off_pending) {
> +		struct drm_pending_state *pending =
> drm_pending_state_alloc(b);
> +		drm_output_get_disable_state(pending, output);
> +		drm_pending_state_apply(pending);
> +		output->dpms_off_pending = 0;
>  		return;
>  	}
>  
> @@ -1360,7 +1397,6 @@ drm_output_assign_state(struct drm_output_state
> *state, }
>  }
>  
> -
>  static int
>  drm_view_transform_supported(struct weston_view *ev)
>  {
> @@ -1653,37 +1689,20 @@ drm_waitvblank_pipe(struct drm_output *output)
>  }
>  
>  static int
> -drm_output_repaint(struct weston_output *output_base,
> -		   pixman_region32_t *damage,
> -		   void *repaint_data)
> +drm_output_apply_state(struct drm_output_state *state)
>  {
> -	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);
> +	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)
> -		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); -
> -
>  	/* 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.
> @@ -1694,10 +1713,46 @@ drm_output_repaint(struct weston_output
> *output_base, output->cursor_plane->base.y = INT32_MIN;
>  	}
>  
> -	drm_output_render(state, damage);
> -	scanout_state = drm_output_state_get_plane(state,
> scanout_plane);
> -	if (!scanout_state || !scanout_state->fb)
> -		goto err;
> +	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_OUTPUT_STATE_UPDATE_SYNCHRONOUS);
> +
> weston_compositor_read_presentation_clock(output->base.compositor,
> &now);
> +		weston_output_finish_frame(&output->base,
> +					   &now,
> +
> WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION); +
> +		return 0;
> +	}
> +
> +	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. */ @@ -1724,7 +1779,6 @@ drm_output_repaint(struct
> weston_output *output_base, weston_log("set mode failed: %m\n");
>  			goto err;
>  		}
> -		output_base->set_dpms(output_base, WESTON_DPMS_ON);
>          }
>  
>  	if (drmModePageFlip(backend->drm.fd, output->crtc_id,
> @@ -1790,13 +1844,101 @@ drm_output_repaint(struct weston_output
> *output_base, }
>  	}
>  
> +	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_OUTPUT_STATE_UPDATE_ASYNCHRONOUS); +
>  	return 0;
>  
>  err:
>  	output->cursor_view = NULL;
> -
>  	drm_output_state_free(state);
> +	return -1;
> +}
> +
> +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;
> +
> +	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(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;
> +}
> +
> +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;
> +	struct drm_plane_state *scanout_state;
> +
> +	if (output->disable_pending || output->destroy_pending)
> +		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);
> +	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;
> +
> +	return 0;
> +
> +err:
> +	drm_output_state_free(state);
>  	return -1;
>  }
>  
> @@ -1922,6 +2064,9 @@ drm_output_update_msc(struct drm_output
> *output, unsigned int seq) output->base.msc = (msc_hi << 32) + seq;
>  }
>  
> +static void
> +drm_output_destroy(struct weston_output *base);
> +
>  static void
>  vblank_handler(int fd, unsigned int frame, unsigned int sec,
> unsigned int usec, void *data)
> @@ -1944,9 +2089,6 @@ vblank_handler(int fd, unsigned int frame,
> unsigned int sec, unsigned int usec,
> 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)
> @@ -2000,29 +2142,8 @@ 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;
> -	uint32_t *unused;
>  
> -	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) {
> -		drm_output_assign_state(output_state,
> -
> DRM_OUTPUT_STATE_UPDATE_ASYNCHRONOUS);
> -	}
> -
> -	b->state_invalid = false;
> -
> -	drm_pending_state_free(pending_state);
> +	drm_pending_state_apply(pending_state);
>  	b->repaint_data = NULL;
>  }
>  
> @@ -3026,8 +3147,6 @@ drm_output_find_special_plane(struct
> drm_backend *b, struct drm_output *output, static void
>  drm_plane_destroy(struct drm_plane *plane)
>  {
> -	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);
> @@ -3197,28 +3316,72 @@ drm_set_backlight(struct weston_output
> *output_base, uint32_t value)
> backlight_set_brightness(output->backlight, new_brightness); }
>  
> +/**
> + * 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_property_info *prop =
> -		&output->props_conn[WDRM_CONNECTOR_DPMS];
> +	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 (!prop->prop_id)
> +	/* 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. */
> +	if (pending_state) {
> +		/* 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);
> +		state->dpms = level;
> +		return;
> +	}
> +
> +	if (output->state_cur->dpms == level)
>  		return;
>  
> -	ret = drmModeConnectorSetProperty(b->drm.fd,
> output->connector_id,
> -					  prop->prop_id, level);
> -	if (ret) {
> -		weston_log("DRM: DPMS: failed property set for %s\n",
> -			   output->base.name);
> +	/* 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;
>  	}
>  
> -	output->dpms = level;
> +	if (output->state_cur->dpms == WESTON_DPMS_OFF)
> +		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_state_duplicate(output->state_cur, pending_state,
> +				   DRM_OUTPUT_STATE_CLEAR_PLANES);
> +	ret = drm_pending_state_apply(pending_state);

The page flip from this call violates the assertion repaint_state ==
REPAINT_AWAITING_COMPLETION in weston_output_finish_frame(). The
FADE_OUT animation seems to mask this case, but I can trigger it by
directly calling weston_compositor_sleep().

As a workaround, I check for the repaint_status in
drm_output_update_complete() and skip the call to
weston_output_frame_finish(), but I don't really like, because it
exposes the compositor status to compositor-drm.

Michael

> +	if (ret != 0)
> +		weston_log("drm_set_dpms: couldn't disable
> output?\n"); }
>  
>  static const char * const connector_type_names[] = {
> @@ -4025,7 +4188,9 @@ drm_output_disable(struct weston_output *base)
>  {
>  	struct drm_output *output = to_drm_output(base);
>  	struct drm_backend *b = to_drm_backend(base->compositor);
> +	struct drm_pending_state *pending_state;
>  	uint32_t *unused;
> +	int ret;
>  
>  	if (output->page_flip_pending || output->vblank_pending) {
>  		output->disable_pending = 1;
> @@ -4038,11 +4203,13 @@ drm_output_disable(struct weston_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);
> -
> -	drm_output_state_free(output->state_cur);
> -	output->state_cur = drm_output_state_alloc(output, NULL);
> +	pending_state = drm_pending_state_alloc(b);
> +	drm_output_get_disable_state(pending_state, output);
> +	ret = drm_pending_state_apply(pending_state);
> +	if (ret) {
> +		weston_log("Couldn't disable output output %s\n",
> +			   output->base.name);
> +	}
>  
>  	unused = wl_array_add(&b->unused_connectors,
> sizeof(*unused)); *unused = output->connector_id;


More information about the wayland-devel mailing list