This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch wl/browser-all
in repository enlightenment.
View the commit online.
commit 1156a561206aa19b87f850f98b980f3551913274
Author: Cedric BAIL <[email protected]>
AuthorDate: Fri Aug 14 17:27:35 2026 -0600
e_comp_wl - add wp_viewporter
A viewport crops a surface's buffer to a source rectangle and scales the
result to a destination size, so the surface stops being the size of the
buffer behind it. Browsers use it to hand video straight to the compositor
at its own resolution instead of scaling it themselves first.
The two halves are independent and both double buffered, so the requests
only build up pending state and the commit is where it takes effect. That
placement is forced rather than stylistic: both of the protocol's errors
are specified as being raised "when the surface state is applied", and
neither can be answered any earlier. out_of_buffer needs the buffer, which
the same commit may be attaching; bad_size depends on whether a destination
is set, which the same commit may be setting. Their order matters too - a
source can be fractional and outside the buffer at once, and out_of_buffer
is the error for that case, so bounds are checked first.
wl_surface.set_buffer_scale stops being an empty stub and is remembered.
Rendering still does not scale by it - that is E-17, and wl_output goes on
claiming scale 1 so nothing asks for a scaled buffer - but a source
rectangle is given in coordinates that already have the scale applied, so
a crop cannot be turned back into buffer pixels without it.
Three things outside the protocol had to give way, all of them the same
assumption in different places:
- _e_comp_intercept_resize() clamps every resize to the pixmap, which is
right until a viewport makes the surface a size its buffer is not, and
then it silently undoes the viewport. E_Client.content_size carries the
size the surface actually presents so the clamp has something true to
clamp to. Without this the client geometry was correct and the canvas
object stayed at the buffer size.
- every resize path hung off state->new_attach, and a viewport needs no
new buffer, so a commit that only moved the viewport resized nothing.
- wl_surface.damage is in surface coordinates and damage_buffer is in
buffer ones. They were the same list behind a comment saying that had
to change once viewport landed. They are now two, converted at the
commit that knows the transform, and both are carried across the
subsurface cache rather than one being dropped there.
The source rectangle reaches the screen through
evas_object_image_buffer_crop_set(); the destination needs nothing, as the
object is image_filled and already scales to whatever size it is given.
wlcs 1.8.1: 731 -> 754 passed, failure set unchanged. All 23 wp_viewporter
tests pass. Those check the resulting size only - wlcs says sampling
"would require sampling-from-rendering support in WLCS" - so the crop was
checked separately against a four-quadrant buffer, asking for one quadrant
scaled to the full window and confirming the window comes out one flat
colour, for two different quadrants.
---
src/bin/e_client.h | 11 ++
src/bin/e_comp_object.c | 54 +++++++
src/bin/e_comp_object.h | 1 +
src/bin/e_comp_wl.c | 330 +++++++++++++++++++++++++++++++++++++++--
src/bin/e_comp_wl.h | 48 +++++-
src/bin/e_comp_wl_extensions.c | 173 +++++++++++++++++++++
src/bin/generated/meson.build | 1 +
src/tests/wlcs/e_wlcs.c | 1 +
8 files changed, 604 insertions(+), 15 deletions(-)
diff --git a/src/bin/e_client.h b/src/bin/e_client.h
index 2fe1189ab..3323df141 100644
--- a/src/bin/e_client.h
+++ b/src/bin/e_client.h
@@ -236,6 +236,17 @@ struct E_Client
int x, y, w, h; //frame+client geom before move or resize callback
} pre_cb;
Eina_Rectangle client; //client geom
+
+ /* The size the client presents its content at, when that is not the size
+ * of the buffer behind it. Everything from the resize path down assumes
+ * the two are the same and clamps geometry to the buffer; a wl_surface
+ * with a wp_viewport is exactly the case where they are not, because the
+ * whole point of a viewport is to crop and scale the buffer to a size the
+ * client chose. 0x0 means "no override", which is every other client. */
+ struct {
+ int w, h;
+ } content_size;
+
Evas_Object *frame; //comp object
Evas_Object *frame_object; //frame border comp object
Evas_Object *agent; //resize agent;
diff --git a/src/bin/e_comp_object.c b/src/bin/e_comp_object.c
index 4eec26beb..d4d7074da 100644
--- a/src/bin/e_comp_object.c
+++ b/src/bin/e_comp_object.c
@@ -72,6 +72,14 @@ typedef struct _E_Comp_Object
int bx, by, bxx, byy, w, h;
} border;
+ /* The part of the buffer that is actually shown, in buffer pixels. A
+ * wl_surface with a wp_viewport source rectangle shows a sub-rectangle of
+ * its buffer and nothing else; w == 0 means the whole buffer, which is
+ * every other client. */
+ struct {
+ int x, y, w, h;
+ } content_crop;
+
Eina_Stringshare *frame_theme;
Eina_Stringshare *frame_name;
Eina_Stringshare *visibility_effect; //effect when toggling visibility
@@ -1234,6 +1242,15 @@ _e_comp_intercept_resize(void *data, Evas_Object *obj, int w, int h)
}
prev_w = cw->w, prev_h = cw->h;
e_comp_object_frame_wh_adjust(obj, 0, 0, &fw, &fh);
+
+ /* Clamping below is to the buffer, on the assumption that a client cannot
+ * be a size its buffer is not. A wp_viewport breaks that assumption on
+ * purpose: the buffer is cropped and scaled to a size the client picked,
+ * and clamping to the buffer would simply undo it. Clamp to what the
+ * surface actually presents, which without a viewport is the buffer. */
+ if ((cw->ec->content_size.w > 0) && (cw->ec->content_size.h > 0))
+ pw = cw->ec->content_size.w, ph = cw->ec->content_size.h;
+
/* check shading and clamp to pixmap size for regular clients */
if ((!cw->ec->shading) && (!cw->ec->shaded) && (!cw->ec->input_only) && (!cw->ec->override) &&
(((w - fw != pw) || (h - fh != ph))))
@@ -4161,6 +4178,41 @@ e_comp_object_blank(Evas_Object *obj, Eina_Bool set)
e_comp_object_damage(obj, 0, 0, cw->w, cw->h);
}
+/* Show only the cropped part of the buffer, if something asked for one.
+ *
+ * Applied on every dirty rather than once when the crop is set, because
+ * mirrors come and go and each one needs it too. evas clamps the rectangle to
+ * the buffer and treats a zero extent as "no crop", so the unset case needs
+ * no special handling here, and re-setting the same rectangle is free. */
+static void
+_e_comp_object_content_crop_apply(E_Comp_Object *cw, Evas_Object *o)
+{
+ evas_object_image_buffer_crop_set(o, cw->content_crop.x, cw->content_crop.y,
+ cw->content_crop.w, cw->content_crop.h);
+}
+
+/* The sub-rectangle of the buffer to show, in buffer pixels. A zero or
+ * negative extent goes back to showing all of it. */
+E_API void
+e_comp_object_content_crop_set(Evas_Object *obj, int x, int y, int w, int h)
+{
+ Eina_List *l;
+ Evas_Object *o;
+
+ API_ENTRY;
+ if ((cw->content_crop.x == x) && (cw->content_crop.y == y) &&
+ (cw->content_crop.w == w) && (cw->content_crop.h == h)) return;
+
+ cw->content_crop.x = x;
+ cw->content_crop.y = y;
+ cw->content_crop.w = w;
+ cw->content_crop.h = h;
+
+ _e_comp_object_content_crop_apply(cw, cw->obj);
+ EINA_LIST_FOREACH(cw->obj_mirror, l, o)
+ _e_comp_object_content_crop_apply(cw, o);
+}
+
/* mark an object as dirty and setup damages */
E_API void
e_comp_object_dirty(Evas_Object *obj)
@@ -4182,6 +4234,7 @@ e_comp_object_dirty(Evas_Object *obj)
if (!dirty)
evas_object_image_data_set(cw->obj, e_pixmap_image_data_get(cw->ec->pixmap));
evas_object_image_size_set(cw->obj, w, h);
+ _e_comp_object_content_crop_apply(cw, cw->obj);
RENDER_DEBUG("SIZE [%p]: %dx%d", cw->ec, w, h);
if (cw->pending_updates)
@@ -4195,6 +4248,7 @@ e_comp_object_dirty(Evas_Object *obj)
if (!dirty)
evas_object_image_data_set(o, NULL);
evas_object_image_size_set(o, w, h);
+ _e_comp_object_content_crop_apply(cw, o);
evas_object_image_pixels_dirty_set(o, dirty);
evas_object_image_alpha_set(o, alpha);
visible |= evas_object_visible_get(o);
diff --git a/src/bin/e_comp_object.h b/src/bin/e_comp_object.h
index ac582bfde..9ce6510b6 100644
--- a/src/bin/e_comp_object.h
+++ b/src/bin/e_comp_object.h
@@ -89,6 +89,7 @@ E_API void e_comp_object_native_surface_override(Evas_Object *obj, Evas_Native_S
E_API Evas_Object *e_comp_object_agent_add(Evas_Object *obj);
E_API void e_comp_object_blank(Evas_Object *obj, Eina_Bool set);
E_API void e_comp_object_dirty(Evas_Object *obj);
+E_API void e_comp_object_content_crop_set(Evas_Object *obj, int x, int y, int w, int h);
E_API Eina_Bool e_comp_object_render(Evas_Object *obj);
E_API Eina_Bool e_comp_object_effect_allowed_get(Evas_Object *obj);
E_API Eina_Bool e_comp_object_effect_set(Evas_Object *obj, const char *effect);
diff --git a/src/bin/e_comp_wl.c b/src/bin/e_comp_wl.c
index 3d0b9a7b1..790a977f2 100644
--- a/src/bin/e_comp_wl.c
+++ b/src/bin/e_comp_wl.c
@@ -7,6 +7,10 @@
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
+/* wp_viewport's error codes: the two that can only be raised on commit are
+ * raised from here, not from the requests that set them up. */
+#include "viewporter-server-protocol.h"
+
#define COMPOSITOR_VERSION 4
E_API int E_EVENT_WAYLAND_GLOBAL_ADD = -1;
@@ -1432,10 +1436,142 @@ _e_comp_wl_cb_mouse_move(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Event_Mou
return ECORE_CALLBACK_RENEW;
}
+/* wp_viewport's two commit-time errors.
+ *
+ * Neither can be answered when the request arrives. out_of_buffer needs the
+ * buffer, which the same commit may be attaching; bad_size depends on whether
+ * a destination is set, which the same commit may be setting. The protocol
+ * says as much - both are raised "when the surface state is applied" - so
+ * both are checked here, against the state about to take effect rather than
+ * the state in effect now.
+ *
+ * Order matters, and is not arbitrary: a source rectangle can be fractional
+ * and out of bounds at the same time, and the protocol tests expect
+ * out_of_buffer for that case, so bounds are checked first.
+ *
+ * Returns false once an error has been posted, which kills the client - the
+ * caller must abandon the commit.
+ */
+static Eina_Bool
+_e_comp_wl_viewport_state_check(E_Client *ec, E_Comp_Wl_Surface_State *state)
+{
+ E_Comp_Wl_Viewport_State *v = &state->viewport;
+ struct wl_resource *res = ec->comp_data->viewport_resource;
+ int bw = 0, bh = 0, scale;
+
+ if ((!res) || (!v->src.set)) return EINA_TRUE;
+
+ scale = (state->buffer_scale > 0) ? state->buffer_scale : 1;
+
+ /* The buffer this commit leaves in place: a newly attached one if there is
+ * one, otherwise whatever is already there. "A NULL wl_buffer does not
+ * raise the out_of_buffer error" - there is no content to be outside of. */
+ if (state->new_attach)
+ {
+ if (!state->buffer) return EINA_TRUE;
+ bw = state->buffer->w;
+ bh = state->buffer->h;
+ }
+ else if (!e_pixmap_size_get(ec->pixmap, &bw, &bh)) return EINA_TRUE;
+ if ((bw < 1) || (bh < 1)) return EINA_TRUE;
+
+ /* Source coordinates are surface-local, so the buffer has to be brought
+ * into the same space before the two can be compared. */
+ if ((wl_fixed_to_double(v->src.x) + wl_fixed_to_double(v->src.w) >
+ (double)bw / scale) ||
+ (wl_fixed_to_double(v->src.y) + wl_fixed_to_double(v->src.h) >
+ (double)bh / scale))
+ {
+ wl_resource_post_error(res, WP_VIEWPORT_ERROR_OUT_OF_BUFFER,
+ "source rectangle %.2fx%.2f+%.2f+%.2f "
+ "extends outside the %dx%d buffer",
+ wl_fixed_to_double(v->src.w),
+ wl_fixed_to_double(v->src.h),
+ wl_fixed_to_double(v->src.x),
+ wl_fixed_to_double(v->src.y),
+ bw / scale, bh / scale);
+ return EINA_FALSE;
+ }
+
+ /* With a destination the source is scaled to it and may be any size. It is
+ * only when the source alone decides the surface size that it has to land
+ * on whole pixels, there being nothing to round it against. wl_fixed_t is
+ * 24.8, so a fractional part is the low 8 bits. */
+ if (v->dst.set) return EINA_TRUE;
+ if ((v->src.w & 0xff) || (v->src.h & 0xff))
+ {
+ wl_resource_post_error(res, WP_VIEWPORT_ERROR_BAD_SIZE,
+ "source size %.2fx%.2f is not integral and no "
+ "destination is set",
+ wl_fixed_to_double(v->src.w),
+ wl_fixed_to_double(v->src.h));
+ return EINA_FALSE;
+ }
+
+ return EINA_TRUE;
+}
+
+/* Surface-local damage into buffer pixels, undoing what the viewport did.
+ *
+ * The surface shows the source rectangle stretched over the whole surface, so
+ * a damaged surface rectangle maps back to the matching fraction of the
+ * source. Rounding goes outwards on purpose: repainting a pixel that did not
+ * change costs a pixel, missing one that did leaves the screen wrong.
+ *
+ * state->bw/bh is the surface size this commit is applying, so this must run
+ * after _e_comp_wl_surface_state_size_update().
+ */
+static void
+_e_comp_wl_surface_damage_to_buffer(E_Client *ec, E_Comp_Wl_Surface_State *state, Eina_Rectangle *r)
+{
+ E_Comp_Wl_Viewport_State *v = &ec->comp_data->viewport;
+ int scale = (ec->comp_data->buffer_scale > 0) ? ec->comp_data->buffer_scale : 1;
+ double sx, sy, sw, sh;
+ double x1, y1, x2, y2;
+
+ if ((state->bw < 1) || (state->bh < 1)) return;
+
+ /* The source rectangle in buffer pixels; without one, the whole buffer. */
+ if (v->src.set)
+ {
+ sx = wl_fixed_to_double(v->src.x) * scale;
+ sy = wl_fixed_to_double(v->src.y) * scale;
+ sw = wl_fixed_to_double(v->src.w) * scale;
+ sh = wl_fixed_to_double(v->src.h) * scale;
+ }
+ else
+ {
+ int bw = 0, bh = 0;
+
+ if (!e_pixmap_size_get(ec->pixmap, &bw, &bh)) return;
+ sx = sy = 0;
+ sw = bw;
+ sh = bh;
+ }
+ if ((sw <= 0) || (sh <= 0)) return;
+
+ /* Nothing to undo in the common case, and no rounding error to introduce
+ * by pretending otherwise. */
+ if ((!v->src.set) && (scale == 1) &&
+ (state->bw == (int)sw) && (state->bh == (int)sh))
+ return;
+
+ x1 = sx + (r->x * sw / state->bw);
+ y1 = sy + (r->y * sh / state->bh);
+ x2 = sx + ((r->x + r->w) * sw / state->bw);
+ y2 = sy + ((r->y + r->h) * sh / state->bh);
+
+ r->x = (int)floor(x1);
+ r->y = (int)floor(y1);
+ r->w = (int)ceil(x2) - r->x;
+ r->h = (int)ceil(y2) - r->y;
+}
+
static void
_e_comp_wl_surface_state_size_update(E_Client *ec, E_Comp_Wl_Surface_State *state)
{
Eina_Rectangle *window;
+ int raw_w, raw_h, scale;
/* double scale = 0.0; */
/* scale = e_comp_wl->output.scale; */
@@ -1454,7 +1590,72 @@ _e_comp_wl_surface_state_size_update(E_Client *ec, E_Comp_Wl_Surface_State *stat
/* break; */
/* } */
- if (!e_pixmap_size_get(ec->pixmap, &state->bw, &state->bh)) return;
+ if (!e_pixmap_size_get(ec->pixmap, &raw_w, &raw_h)) return;
+
+ /* Buffer pixels become surface-local coordinates in a fixed order: the
+ * buffer scale comes off first, and a viewport's source and destination
+ * are both expressed in what is left. */
+ scale = (ec->comp_data->buffer_scale > 0) ? ec->comp_data->buffer_scale : 1;
+ state->bw = raw_w / scale;
+ state->bh = raw_h / scale;
+
+ /* A viewport decides the surface size instead of the buffer. This is the
+ * one place that has to know, because bw/bh is what everything downstream
+ * - ec->client.w/h, the frame geometry, input - is derived from.
+ *
+ * Destination wins outright. With only a source, the surface takes the
+ * source's size, which is why the source has to be a whole number of
+ * pixels in that case: there is nothing to round it against. */
+ if (ec->comp_data->viewport.dst.set)
+ {
+ state->bw = ec->comp_data->viewport.dst.w;
+ state->bh = ec->comp_data->viewport.dst.h;
+ }
+ else if (ec->comp_data->viewport.src.set)
+ {
+ state->bw = wl_fixed_to_int(ec->comp_data->viewport.src.w);
+ state->bh = wl_fixed_to_int(ec->comp_data->viewport.src.h);
+ }
+
+ /* "the size is always at least 1x1 in surface local coordinates" */
+ if (state->bw < 1) state->bw = 1;
+ if (state->bh < 1) state->bh = 1;
+
+ /* Everything from the resize path down clamps geometry to the buffer,
+ * which is right until a viewport or a buffer scale makes the surface a
+ * size the buffer is not. Record the difference where the generic code
+ * can see it; equal sizes mean no override, which is the common case. */
+ if ((state->bw != raw_w) || (state->bh != raw_h))
+ ec->content_size.w = state->bw, ec->content_size.h = state->bh;
+ else
+ ec->content_size.w = ec->content_size.h = 0;
+
+ /* The source rectangle is what the surface actually shows. Sizing the
+ * client above only decides how big the result is drawn - without this the
+ * whole buffer would be squeezed into it instead of the part the client
+ * asked for. The crop is in buffer pixels, so the buffer scale that came
+ * off the coordinates above has to go back on.
+ *
+ * A source may be fractional while the crop cannot be, so take the whole
+ * pixels the rectangle touches: showing a sliver too much beats sampling
+ * short of the edge and stretching the remainder over the gap. */
+ if (ec->comp_data->viewport.src.set)
+ {
+ double cx, cy;
+ int ix, iy;
+
+ cx = wl_fixed_to_double(ec->comp_data->viewport.src.x) * scale;
+ cy = wl_fixed_to_double(ec->comp_data->viewport.src.y) * scale;
+ ix = (int)floor(cx);
+ iy = (int)floor(cy);
+
+ e_comp_object_content_crop_set(ec->frame, ix, iy,
+ (int)ceil(cx + wl_fixed_to_double(ec->comp_data->viewport.src.w) * scale) - ix,
+ (int)ceil(cy + wl_fixed_to_double(ec->comp_data->viewport.src.h) * scale) - iy);
+ }
+ else
+ e_comp_object_content_crop_set(ec->frame, 0, 0, 0, 0);
+
if (e_client_has_xwindow(ec) || e_comp_object_frame_exists(ec->frame)) return;
window = &ec->comp_data->shell.window;
if (window->x || window->y || window->w || window->h)
@@ -1484,6 +1685,7 @@ _e_comp_wl_surface_state_init(E_Comp_Wl_Surface_State *state)
state->buffer_destroy_listener.notify =
_e_comp_wl_surface_state_cb_buffer_destroy;
state->sx = state->sy = 0;
+ state->buffer_scale = 1;
state->input = NULL;
@@ -1508,6 +1710,9 @@ _e_comp_wl_surface_state_finish(E_Comp_Wl_Surface_State *state)
EINA_LIST_FREE(state->damages, dmg)
eina_rectangle_free(dmg);
+ EINA_LIST_FREE(state->buffer_damages, dmg)
+ eina_rectangle_free(dmg);
+
if (state->opaque) eina_tiler_free(state->opaque);
state->opaque = NULL;
@@ -1580,6 +1785,7 @@ static void
_e_comp_wl_surface_state_commit(E_Client *ec, E_Comp_Wl_Surface_State *state)
{
Eina_Bool first = EINA_FALSE;
+ Eina_Bool vp_latched = EINA_FALSE, vp_resize = EINA_FALSE;
Eina_Rectangle *dmg;
int x = 0, y = 0, w, h;
@@ -1597,7 +1803,24 @@ _e_comp_wl_surface_state_commit(E_Client *ec, E_Comp_Wl_Surface_State *state)
return;
}
+ /* Before anything is applied, and before in_commit is set: this posts a
+ * protocol error and kills the client, and there is no half-applied state
+ * worth leaving behind. */
+ if (!_e_comp_wl_viewport_state_check(ec, state)) return;
+
ec->comp_data->in_commit = 1;
+
+ /* Latch the viewport before anything reads a size. set_source and
+ * set_destination are double buffered like the rest of the surface state,
+ * so this commit is where the requests since the last one take effect. */
+ if (state->viewport_changed)
+ {
+ ec->comp_data->viewport = state->viewport;
+ state->viewport_changed = 0;
+ vp_latched = EINA_TRUE;
+ }
+ ec->comp_data->buffer_scale = state->buffer_scale;
+
if (ec->ignored && ec->comp_data->shell.surface)
{
EC_CHANGED(ec);
@@ -1676,6 +1899,14 @@ _e_comp_wl_surface_state_commit(E_Client *ec, E_Comp_Wl_Surface_State *state)
}
_e_comp_wl_surface_state_size_update(ec, state);
+ /* A viewport needs no buffer to change the surface size, and every resize
+ * below hangs off new_attach - so a commit that only moves the viewport
+ * would resize nothing at all. Give it its own reason to resize, without
+ * dragging in the placement and focus work that goes with a new buffer. */
+ vp_resize = vp_latched && (!state->new_attach) &&
+ e_pixmap_usable_get(ec->pixmap) &&
+ ((state->bw != ec->client.w) || (state->bh != ec->client.h));
+
if (state->new_attach)
{
if (ec->changes.pos || ec->internal_elm_win)
@@ -1721,6 +1952,16 @@ _e_comp_wl_surface_state_commit(E_Client *ec, E_Comp_Wl_Surface_State *state)
ec->comp_data->need_center = 0;
}
}
+ else if (vp_resize)
+ {
+ ec->client.w = state->bw;
+ ec->client.h = state->bh;
+ if (!ec->changes.size)
+ e_comp_object_frame_wh_adjust(ec->frame, ec->client.w, ec->client.h,
+ &ec->w, &ec->h);
+ x = ec->client.x, y = ec->client.y;
+ w = ec->client.w, h = ec->client.h;
+ }
else
w = state->bw, h = state->bh;
if (!e_pixmap_usable_get(ec->pixmap))
@@ -1863,6 +2104,14 @@ _e_comp_wl_surface_state_commit(E_Client *ec, E_Comp_Wl_Surface_State *state)
ec->want_focus |= ec->icccm.accepts_focus && (!ec->override);
}
}
+ else if (vp_resize)
+ {
+ if ((ec->comp_data->shell.surface) && (ec->comp_data->shell.configure))
+ ec->comp_data->shell.configure(ec->comp_data->shell.surface,
+ x, y, state->bw, state->bh);
+ else
+ e_client_util_move_resize_without_frame(ec, x, y, w, h);
+ }
else if (ec->comp_data->need_xdg_configure && ec->comp_data->shell.surface && !ec->iconic)
_e_comp_wl_configure_send(ec, 0, EINA_FALSE);
@@ -1881,11 +2130,19 @@ _e_comp_wl_surface_state_commit(E_Client *ec, E_Comp_Wl_Surface_State *state)
/* put state damages into surface */
if ((!e_comp->nocomp) && (ec->frame))
{
- EINA_LIST_FREE(state->damages, dmg)
+ EINA_LIST_FREE(state->buffer_damages, dmg)
{
e_comp_object_damage(ec->frame, dmg->x, dmg->y, dmg->w, dmg->h);
eina_rectangle_free(dmg);
}
+ EINA_LIST_FREE(state->damages, dmg)
+ {
+ Eina_Rectangle r = *dmg;
+
+ _e_comp_wl_surface_damage_to_buffer(ec, state, &r);
+ e_comp_object_damage(ec->frame, r.x, r.y, r.w, r.h);
+ eina_rectangle_free(dmg);
+ }
}
/* put state opaque into surface */
@@ -2116,19 +2373,27 @@ _e_comp_wl_surface_cb_damage_buffer(struct wl_client *client EINA_UNUSED, struct
if (!(dmg = eina_rectangle_new(x, y, w, h))) return;
- ec->comp_data->pending.damages =
- eina_list_append(ec->comp_data->pending.damages, dmg);
+ ec->comp_data->pending.buffer_damages =
+ eina_list_append(ec->comp_data->pending.buffer_damages, dmg);
}
-/*
- * Currently damage and damage_buffer are the same because we don't support
- * buffer_scale, transform, or viewport. Once we support those we'll have
- * to make surface_cb_damage handle damage in surface co-ordinates.
- */
+/* Damage in surface-local coordinates. Kept in its own list rather than
+ * converted here: the transform it has to go through is the one this commit
+ * is about to apply, and a set_source in the same batch has not been latched
+ * yet. The commit converts it once it knows. */
static void
-_e_comp_wl_surface_cb_damage(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, int32_t w, int32_t h)
+_e_comp_wl_surface_cb_damage(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, int32_t x, int32_t y, int32_t w, int32_t h)
{
- _e_comp_wl_surface_cb_damage_buffer(client, resource, x, y, w, h);
+ E_Client *ec;
+ Eina_Rectangle *dmg = NULL;
+
+ if (!(ec = wl_resource_get_user_data(resource))) return;
+ if (e_object_is_del(E_OBJECT(ec))) return;
+
+ if (!(dmg = eina_rectangle_new(x, y, w, h))) return;
+
+ ec->comp_data->pending.damages =
+ eina_list_append(ec->comp_data->pending.damages, dmg);
}
static void
@@ -2283,10 +2548,28 @@ _e_comp_wl_surface_cb_buffer_transform_set(struct wl_client *client EINA_UNUSED,
/* DBG("Surface Buffer Transform: %d", wl_resource_get_id(resource)); */
}
+/* We still do not scale rendering by the buffer scale - that is E-17, and
+ * until it lands wl_output keeps claiming scale 1 so that no client asks for
+ * a scaled buffer in the first place. What changed is that the value is now
+ * remembered rather than dropped, because wp_viewport's source rectangle is
+ * given in coordinates that already have the buffer scale applied, and a
+ * crop cannot be turned back into buffer pixels without it. */
static void
-_e_comp_wl_surface_cb_buffer_scale_set(struct wl_client *client EINA_UNUSED, struct wl_resource *resource EINA_UNUSED, int32_t scale EINA_UNUSED)
+_e_comp_wl_surface_cb_buffer_scale_set(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, int32_t scale)
{
- /* DBG("Surface Buffer Scale: %d", wl_resource_get_id(resource)); */
+ E_Client *ec;
+
+ if (!(ec = wl_resource_get_user_data(resource))) return;
+ if (e_object_is_del(E_OBJECT(ec))) return;
+
+ if (scale < 1)
+ {
+ wl_resource_post_error(resource, WL_SURFACE_ERROR_INVALID_SCALE,
+ "buffer scale must be at least 1, got %d", scale);
+ return;
+ }
+
+ ec->comp_data->pending.buffer_scale = scale;
}
static const struct wl_surface_interface _e_surface_interface =
@@ -2586,10 +2869,14 @@ _e_comp_wl_subsurface_commit_to_cache(E_Client *ec)
DBG("Subsurface Commit to Cache");
- /* move pending damage to cached */
+ /* move pending damage to cached, both kinds - a sync-mode subsurface that
+ * used wl_surface.damage would otherwise have it dropped here */
sdata->cached.damages = eina_list_merge(sdata->cached.damages,
cdata->pending.damages);
cdata->pending.damages = NULL;
+ sdata->cached.buffer_damages = eina_list_merge(sdata->cached.buffer_damages,
+ cdata->pending.buffer_damages);
+ cdata->pending.buffer_damages = NULL;
if (cdata->pending.new_attach)
{
@@ -2620,6 +2907,20 @@ _e_comp_wl_subsurface_commit_to_cache(E_Client *ec)
sdata->cached.frames = eina_list_merge(sdata->cached.frames,
cdata->pending.frames);
cdata->pending.frames = NULL;
+
+ /* A synchronised subsurface applies its cached state, not its pending
+ * state, so the crop and scale have to travel with it or they would sit in
+ * pending until the subsurface went desynchronised. Only carry the
+ * viewport across if there is a request waiting, otherwise an unrelated
+ * commit would clear a change that has not been applied yet. */
+ if (cdata->pending.viewport_changed)
+ {
+ sdata->cached.viewport = cdata->pending.viewport;
+ sdata->cached.viewport_changed = 1;
+ cdata->pending.viewport_changed = 0;
+ }
+ sdata->cached.buffer_scale = cdata->pending.buffer_scale;
+
sdata->cached.has_data = EINA_TRUE;
}
@@ -3020,6 +3321,7 @@ _e_comp_wl_client_cb_new(void *data EINA_UNUSED, E_Client *ec)
wl_signal_init(&ec->comp_data->destroy_signal);
_e_comp_wl_surface_state_init(&ec->comp_data->pending);
+ ec->comp_data->buffer_scale = 1;
/* set initial client properties */
ec->argb = EINA_TRUE;
diff --git a/src/bin/e_comp_wl.h b/src/bin/e_comp_wl.h
index 7fd18dd07..b4f6285ed 100644
--- a/src/bin/e_comp_wl.h
+++ b/src/bin/e_comp_wl.h
@@ -83,16 +83,50 @@ struct _E_Comp_Wl_Buffer
Eina_Bool destroyed;
};
+/* wp_viewport's two halves. Both are double buffered - a set_source or
+ * set_destination does nothing until the next wl_surface.commit - and both
+ * are independently unset by passing all -1, so "no source" has to be
+ * distinguishable from "a source of 0,0 0x0" rather than folded into it.
+ *
+ * The source is 24.8 fixed point on the wire and kept that way here. The
+ * destination is whole surface-local pixels by definition. */
+typedef struct _E_Comp_Wl_Viewport_State
+{
+ struct
+ {
+ wl_fixed_t x, y, w, h;
+ Eina_Bool set E_BITFIELD;
+ } src;
+ struct
+ {
+ int w, h;
+ Eina_Bool set E_BITFIELD;
+ } dst;
+} E_Comp_Wl_Viewport_State;
+
struct _E_Comp_Wl_Surface_State
{
int sx, sy;
int bw, bh;
E_Comp_Wl_Buffer *buffer;
struct wl_listener buffer_destroy_listener;
- Eina_List *damages, *frames;
+ /* wl_surface.damage_buffer is in buffer pixels and wl_surface.damage is in
+ * surface-local ones. They are the same thing until a buffer scale or a
+ * wp_viewport comes between the two, so the two kinds are kept apart until
+ * the commit that knows the transform can bring them into one space. */
+ Eina_List *damages, *buffer_damages, *frames;
Eina_Tiler *input, *opaque;
+ E_Comp_Wl_Viewport_State viewport;
+ /* wl_surface.set_buffer_scale. Double buffered like everything else here,
+ * and never below 1. We do not scale rendering by it - that is E-17 - but
+ * a viewport's source rectangle is in coordinates that already have it
+ * applied, so cropping cannot be worked out without knowing it. */
+ int buffer_scale;
Eina_Bool new_attach E_BITFIELD;
Eina_Bool has_data E_BITFIELD;
+ /* Whether viewport above is a request waiting for a commit, as opposed to
+ * the copy that is already in effect. */
+ Eina_Bool viewport_changed E_BITFIELD;
};
struct _E_Comp_Wl_Subsurf_Data
@@ -143,6 +177,10 @@ typedef struct E_Comp_Wl_Extension_Data
{
struct wl_global *global;
} xdg_activation_v1;
+ struct
+ {
+ struct wl_global *global;
+ } wp_viewporter;
/* end xdg-foreign */
struct
{
@@ -340,6 +378,14 @@ struct _E_Comp_Wl_Client_Data
struct wl_resource *surface;
struct wl_signal destroy_signal;
+ /* The one wp_viewport this surface is allowed, and the viewport state
+ * currently in effect - pending lives in comp_data->pending.viewport and
+ * is latched from there on commit. */
+ struct wl_resource *viewport_resource;
+ E_Comp_Wl_Viewport_State viewport;
+ /* wl_surface.set_buffer_scale currently in effect. Never below 1. */
+ int buffer_scale;
+
struct
{
/* shell surface resource */
diff --git a/src/bin/e_comp_wl_extensions.c b/src/bin/e_comp_wl_extensions.c
index 64275fb49..58ae69e2f 100644
--- a/src/bin/e_comp_wl_extensions.c
+++ b/src/bin/e_comp_wl_extensions.c
@@ -8,6 +8,7 @@
#include "pointer-constraints-unstable-v1-server-protocol.h"
#include "action_route-server-protocol.h"
#include "xdg-activation-v1-server-protocol.h"
+#include "viewporter-server-protocol.h"
/* mutter uses 32, seems reasonable */
#define HANDLE_LEN 32
@@ -1179,6 +1180,176 @@ static const struct xdg_activation_v1_interface _e_xdg_activation_v1_interface =
.activate = _e_xdg_activation_v1_cb_activate,
};
+/* wp_viewporter.
+ *
+ * A viewport crops its surface's buffer and scales the result to a size the
+ * client names, so a surface stops being the size of its buffer. Both halves
+ * are double buffered: a request only takes effect on the next
+ * wl_surface.commit, so everything here writes ec->comp_data->pending.viewport
+ * and lets the commit path latch it.
+ *
+ * The viewport outlives its surface. Every request after the surface is gone
+ * is a no_surface error, but destroying the viewport itself stays legal -
+ * a client that tears down in the wrong order has not done anything wrong.
+ */
+static E_Client *
+_e_viewport_client_get(struct wl_resource *resource)
+{
+ E_Client *ec = wl_resource_get_user_data(resource);
+
+ if (!ec) return NULL;
+ if (e_object_is_del(E_OBJECT(ec))) return NULL;
+ if (!ec->comp_data) return NULL;
+ return ec;
+}
+
+static void
+_e_wp_viewport_cb_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
+{
+ wl_resource_destroy(resource);
+}
+
+static void
+_e_wp_viewport_res_destroy(struct wl_resource *resource)
+{
+ E_Client *ec = _e_viewport_client_get(resource);
+
+ if (!ec) return;
+ ec->comp_data->viewport_resource = NULL;
+ /* Destroying the viewport drops both halves, on the next commit like any
+ * other viewport state change. */
+ memset(&ec->comp_data->pending.viewport, 0,
+ sizeof(ec->comp_data->pending.viewport));
+ ec->comp_data->pending.viewport_changed = 1;
+}
+
+static void
+_e_wp_viewport_cb_set_source(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, wl_fixed_t x, wl_fixed_t y, wl_fixed_t w, wl_fixed_t h)
+{
+ E_Client *ec = _e_viewport_client_get(resource);
+ E_Comp_Wl_Viewport_State *v;
+
+ if (!ec)
+ {
+ wl_resource_post_error(resource, WP_VIEWPORT_ERROR_NO_SURFACE,
+ "the wl_surface is gone");
+ return;
+ }
+
+ v = &ec->comp_data->pending.viewport;
+
+ /* All -1 unsets, and is the only way a negative value is legal. */
+ if ((x == wl_fixed_from_int(-1)) && (y == wl_fixed_from_int(-1)) &&
+ (w == wl_fixed_from_int(-1)) && (h == wl_fixed_from_int(-1)))
+ {
+ v->src.set = 0;
+ ec->comp_data->pending.viewport_changed = 1;
+ return;
+ }
+
+ if ((x < 0) || (y < 0) || (w <= 0) || (h <= 0))
+ {
+ wl_resource_post_error(resource, WP_VIEWPORT_ERROR_BAD_VALUE,
+ "source rectangle %fx%f%+f%+f is not positive",
+ wl_fixed_to_double(w), wl_fixed_to_double(h),
+ wl_fixed_to_double(x), wl_fixed_to_double(y));
+ return;
+ }
+
+ v->src.x = x;
+ v->src.y = y;
+ v->src.w = w;
+ v->src.h = h;
+ v->src.set = 1;
+ ec->comp_data->pending.viewport_changed = 1;
+}
+
+static void
+_e_wp_viewport_cb_set_destination(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, int32_t w, int32_t h)
+{
+ E_Client *ec = _e_viewport_client_get(resource);
+ E_Comp_Wl_Viewport_State *v;
+
+ if (!ec)
+ {
+ wl_resource_post_error(resource, WP_VIEWPORT_ERROR_NO_SURFACE,
+ "the wl_surface is gone");
+ return;
+ }
+
+ v = &ec->comp_data->pending.viewport;
+
+ if ((w == -1) && (h == -1))
+ {
+ v->dst.set = 0;
+ ec->comp_data->pending.viewport_changed = 1;
+ return;
+ }
+
+ if ((w <= 0) || (h <= 0))
+ {
+ wl_resource_post_error(resource, WP_VIEWPORT_ERROR_BAD_VALUE,
+ "destination size %dx%d is not positive", w, h);
+ return;
+ }
+
+ v->dst.w = w;
+ v->dst.h = h;
+ v->dst.set = 1;
+ ec->comp_data->pending.viewport_changed = 1;
+}
+
+static const struct wp_viewport_interface _e_wp_viewport_interface =
+{
+ .destroy = _e_wp_viewport_cb_destroy,
+ .set_source = _e_wp_viewport_cb_set_source,
+ .set_destination = _e_wp_viewport_cb_set_destination,
+};
+
+static void
+_e_wp_viewporter_cb_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
+{
+ wl_resource_destroy(resource);
+}
+
+static void
+_e_wp_viewporter_cb_get_viewport(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface)
+{
+ struct wl_resource *res;
+ E_Client *ec;
+
+ ec = wl_resource_get_user_data(surface);
+ if ((!ec) || e_object_is_del(E_OBJECT(ec)) || (!ec->comp_data))
+ {
+ wl_resource_post_error(resource, WP_VIEWPORTER_ERROR_VIEWPORT_EXISTS,
+ "wl_surface is gone");
+ return;
+ }
+ if (ec->comp_data->viewport_resource)
+ {
+ wl_resource_post_error(resource, WP_VIEWPORTER_ERROR_VIEWPORT_EXISTS,
+ "wl_surface already has a wp_viewport");
+ return;
+ }
+
+ res = wl_resource_create(client, &wp_viewport_interface,
+ wl_resource_get_version(resource), id);
+ if (!res)
+ {
+ wl_client_post_no_memory(client);
+ return;
+ }
+ wl_resource_set_implementation(res, &_e_wp_viewport_interface, ec,
+ _e_wp_viewport_res_destroy);
+ ec->comp_data->viewport_resource = res;
+}
+
+static const struct wp_viewporter_interface _e_wp_viewporter_interface =
+{
+ .destroy = _e_wp_viewporter_cb_destroy,
+ .get_viewport = _e_wp_viewporter_cb_get_viewport,
+};
+
#define GLOBAL_BIND_CB(NAME, IFACE, ...) \
static void \
_e_comp_wl_##NAME##_cb_bind(struct wl_client *client, void *data EINA_UNUSED, uint32_t version, uint32_t id) \
@@ -1197,6 +1368,7 @@ _e_comp_wl_##NAME##_cb_bind(struct wl_client *client, void *data EINA_UNUSED, ui
}
GLOBAL_BIND_CB(session_recovery, zwp_e_session_recovery_interface)
+GLOBAL_BIND_CB(wp_viewporter, wp_viewporter_interface)
GLOBAL_BIND_CB(zxdg_exporter_v1, zxdg_exporter_v1_interface)
GLOBAL_BIND_CB(zxdg_importer_v1, zxdg_importer_v1_interface)
GLOBAL_BIND_CB(zwp_relative_pointer_manager_v1, zwp_relative_pointer_manager_v1_interface)
@@ -1301,6 +1473,7 @@ e_comp_wl_extensions_init(void)
e_comp_wl->extensions->zwp_pointer_constraints_v1.constraints = eina_hash_pointer_new(NULL);
GLOBAL_CREATE_OR_RETURN(action_route, action_route_interface, 1);
GLOBAL_CREATE_OR_RETURN(xdg_activation_v1, xdg_activation_v1_interface, 1);
+ GLOBAL_CREATE_OR_RETURN(wp_viewporter, wp_viewporter_interface, 1);
ecore_event_handler_add(ECORE_WL2_EVENT_SYNC_DONE, _dmabuf_add, NULL);
diff --git a/src/bin/generated/meson.build b/src/bin/generated/meson.build
index c9beef897..cdb120ae3 100644
--- a/src/bin/generated/meson.build
+++ b/src/bin/generated/meson.build
@@ -7,6 +7,7 @@ protos = [
'@0@/unstable/relative-pointer/relative-pointer-unstable-v1.xml'.format(dir_wayland_protocols),
'@0@/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'.format(dir_wayland_protocols),
'@0@/staging/xdg-activation/xdg-activation-v1.xml'.format(dir_wayland_protocols),
+ '@0@/stable/viewporter/viewporter.xml'.format(dir_wayland_protocols),
]
proto_c = []
diff --git a/src/tests/wlcs/e_wlcs.c b/src/tests/wlcs/e_wlcs.c
index 38a7ee412..7f9d55b77 100644
--- a/src/tests/wlcs/e_wlcs.c
+++ b/src/tests/wlcs/e_wlcs.c
@@ -702,6 +702,7 @@ static const WlcsExtensionDescriptor _extensions[] =
{ "wl_output", 2 },
{ "wl_data_device_manager", 3 },
{ "xdg_activation_v1", 1 },
+ { "wp_viewporter", 1 },
{ "xdg_wm_base", 6 },
{ "zxdg_shell_v6", 1 },
{ "wl_shell", 1 },
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.