[PATCH v14 11/41] compositor-drm: Move repaint state application to flush

Pekka Paalanen ppaalanen at gmail.com
Wed Jan 17 13:58:45 UTC 2018


On Wed, 20 Dec 2017 12:26:28 +0000
Daniel Stone <daniels at collabora.com> 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 | 366 ++++++++++++++++++++++++++++++++++++---------
>  1 file changed, 292 insertions(+), 74 deletions(-)


Hi Daniel,

this is still a tricky one, sorry.

I looked at the details here hard and long, and then came to a
revelation: is it at all possible to ever call drm_set_dpms() between
repaint_begin and repaint_flush/cancel?

I don't think it is and that would make many of my comments below just
moot.

> diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c
> index 87c2603c7..2bb13f3a2 100644
> --- a/libweston/compositor-drm.c
> +++ b/libweston/compositor-drm.c
> @@ -272,6 +272,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;
>  };
>  
> @@ -348,13 +349,13 @@ struct drm_output {
>  	/* Holds the properties for the connector */
>  	struct drm_property_info props_conn[WDRM_CONNECTOR__COUNT];
>  
> -	enum dpms_enum dpms;
>  	struct backlight *backlight;
>  
>  	int vblank_pending;
>  	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;
> @@ -1149,6 +1150,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);
> @@ -1225,6 +1227,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
>   *
> @@ -1297,6 +1323,9 @@ drm_pending_state_get_output(struct drm_pending_state *pending_state,
>  	return NULL;
>  }
>  
> +static int drm_pending_state_apply(struct drm_pending_state *state);
> +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
> @@ -1306,6 +1335,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;
>  
> @@ -1325,6 +1355,21 @@ 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);

See the question about drm_pending_state_apply vs. apply_sync() far
down below.


> +		output->dpms_off_pending = 0;
> +		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. */

When turning an output off, there should be no pageflip event, so how
can this ever be reached?

drm_output_apply_state() on the DPMS-off path does not schedule a
pageflip event.

>  		return;
>  	}
>  
> @@ -1387,14 +1432,13 @@ drm_output_assign_state(struct drm_output_state *state,
>  			output->page_flip_pending = 1;
>  	}
>  
> -	if (output->dpms == WESTON_DPMS_ON) {
> +	if (state->dpms == WESTON_DPMS_ON) {
>  		wl_array_remove_uint32(&b->unused_connectors,
>  				       output->connector_id);
>  		wl_array_remove_uint32(&b->unused_crtcs, output->crtc_id);
>  	}
>  }
>  
> -
>  static int
>  drm_view_transform_supported(struct weston_view *ev)
>  {
> @@ -1687,37 +1731,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.
> @@ -1728,10 +1755,45 @@ 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_STATE_APPLY_SYNC);
> +		weston_compositor_read_presentation_clock(output->base.compositor, &now);
> +		weston_output_finish_frame(&output->base,
> +					   &now,
> +					   WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION);

See the weston_output_finish_frame() comment further down below.

finish_frame here is necessary if and only if drm_set_dpms(off) was
called between repaint_begin and repaint_flush... but that's not
possible, is it?

> +
> +		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. */
> @@ -1758,7 +1820,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,
> @@ -1824,13 +1885,140 @@ 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_STATE_APPLY_ASYNC);
> +
>  	return 0;
>  
>  err:
>  	output->cursor_view = NULL;
> -
>  	drm_output_state_free(state);
> +	return -1;
> +}


> @@ -3058,9 +3229,6 @@ drm_output_find_special_plane(struct drm_backend *b, struct drm_output *output,
>  static void
>  drm_plane_destroy(struct drm_plane *plane)
>  {
> -	if (plane->plane_id > 0)
> -		drmModeSetPlane(plane->backend->drm.fd, plane->plane_id,
> -				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);

Did you have a rationale for this change? I still found it surprising,
and for v12 I wrote:

: This was a slightly surprising piece in this patch. Is the rationale
: here that Weston should not reset CRTC state on exit? Shouldn't that
: wait for the "compositor-drm: Don't restore original CRTC mode" patch?
: 
: Feels like this hunk doesn't belong in this patch.
: 
: I suppose this might leave an overlay up, but since we have overlays
: disabled by default, it shouldn't cause issues.


>  	drm_plane_state_free(plane->state_cur, true);
>  	drm_property_info_free(plane->props, WDRM_PLANE__COUNT);
>  	weston_plane_release(&plane->base);
> @@ -3230,28 +3398,76 @@ 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 (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);
> +	/* 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) {

Umm... now that I look at it, I cannot see how this could ever be
called with pending_state not NULL. output_repaint_timer_handler() and
anything it calls do not really leave any opportunity to call
weston_compositor_dpms() or weston_compositor_sleep()?!


> +		/* 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);

We set the disable state here, then:
drm_repaint_flush() -> drm_pending_state_apply() ->
drm_output_apply_state() ->
hit the "state->dpms != WESTON_DPMS_ON" path which will call
weston_output_finish_frame() unconditionally.

Would that not lead to a repaint state machine assertion violation,
which Michael Tretter was complaining about?


> +		state->dpms = level;

get_disable_state() already sets state->dpms.

>  		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 (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_get_disable_state(pending_state, output);
> +	ret = drm_pending_state_apply(pending_state);

Should this not be calling drm_pending_state_apply_sync() instead?

It's a little confusing as it does not matter whether one calls apply
or apply_sync, it's the DPMS state that determines whether it will be
sync or not.

Otherwise this code hints to me that it's an async apply.

> +	if (ret != 0)
> +		weston_log("drm_set_dpms: couldn't disable output?\n");
>  }
>  
>  static const char * const connector_type_names[] = {
> @@ -4123,24 +4339,26 @@ 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;
> +	int ret;
>  
>  	if (output->page_flip_pending || output->vblank_pending) {
>  		output->disable_pending = 1;
>  		return -1;
>  	}
>  
> +	weston_log("Disabling output %s\n", output->base.name);
> +	pending_state = drm_pending_state_alloc(b);
> +	drm_output_get_disable_state(pending_state, output);
> +	ret = drm_pending_state_apply_sync(pending_state);

This is using apply_sync.


> +	if (ret)
> +		weston_log("Couldn't disable 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);
> -
> -	drm_output_state_free(output->state_cur);
> -	output->state_cur = drm_output_state_alloc(output, NULL);
> -
>  	return 0;
>  }
>  

Thanks,
pq
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 833 bytes
Desc: OpenPGP digital signature
URL: <https://lists.freedesktop.org/archives/wayland-devel/attachments/20180117/1c04a916/attachment-0001.sig>


More information about the wayland-devel mailing list