From 67e92d94523cd679b45a5394ae17afbd850ce75e Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Fri, 13 May 2011 16:54:11 +0100 Subject: Adds wayland-surface actor for wayland compositors This adds a --enable-wayland-compositor configure option which will add support for a ClutterWaylandSurface actor which can be used to aid in writing Wayland compositors using Clutter by providing a ClutterActor to represent Wayland client surfaces. Notably this configure option isn't tied into the flavour option since conceptually any of the flavours could be used to write a Wayland compositor. --- clutter/Makefile.am | 8 + clutter/cogl/clutter-backend-cogl.c | 37 ++ clutter/cogl/clutter-stage-cogl.c | 2 +- clutter/wayland/clutter-wayland-compositor.h | 43 ++ clutter/wayland/clutter-wayland-surface.c | 560 +++++++++++++++++++++++++++ clutter/wayland/clutter-wayland-surface.h | 94 +++++ configure.ac | 25 ++ doc/reference/clutter/clutter-sections.txt | 15 + tests/interactive/Makefile.am | 4 + tests/interactive/test-wayland-surface.c | 447 +++++++++++++++++++++ 10 files changed, 1234 insertions(+), 1 deletion(-) create mode 100644 clutter/wayland/clutter-wayland-compositor.h create mode 100644 clutter/wayland/clutter-wayland-surface.c create mode 100644 clutter/wayland/clutter-wayland-surface.h create mode 100644 tests/interactive/test-wayland-surface.c diff --git a/clutter/Makefile.am b/clutter/Makefile.am index 943f7be5a..014b8af23 100644 --- a/clutter/Makefile.am +++ b/clutter/Makefile.am @@ -509,6 +509,14 @@ backend_source_c += \ $(srcdir)/wayland/clutter-device-manager-wayland.c endif # SUPPORT_WAYLAND +if SUPPORT_WAYLAND_COMPOSITOR +backend_source_h += \ + $(srcdir)/wayland/clutter-wayland-compositor.h \ + $(srcdir)/wayland/clutter-wayland-surface.h +backend_source_c += \ + $(srcdir)/wayland/clutter-wayland-surface.c +endif + if SUPPORT_EGL backend_source_h += $(cogl_source_h) $(egl_source_h) backend_source_c += $(cogl_source_c) diff --git a/clutter/cogl/clutter-backend-cogl.c b/clutter/cogl/clutter-backend-cogl.c index fdfbf38f2..9df1098b5 100644 --- a/clutter/cogl/clutter-backend-cogl.c +++ b/clutter/cogl/clutter-backend-cogl.c @@ -58,6 +58,9 @@ #include "wayland/clutter-device-manager-wayland.h" #include "wayland/clutter-event-wayland.h" #endif +#ifdef HAVE_CLUTTER_WAYLAND_COMPOSITOR +#include +#endif static ClutterBackendCogl *backend_singleton = NULL; @@ -69,6 +72,10 @@ static gdl_plane_id_t gdl_plane = GDL_PLANE_ID_UPP_C; static guint gdl_n_buffers = CLUTTER_CEX100_TRIPLE_BUFFERING; #endif +#ifdef HAVE_CLUTTER_WAYLAND_COMPOSITOR +static struct wl_display *wayland_compositor_display; +#endif + #ifdef COGL_HAS_X11_SUPPORT G_DEFINE_TYPE (ClutterBackendCogl, _clutter_backend_cogl, CLUTTER_TYPE_BACKEND_X11); #else @@ -452,6 +459,11 @@ clutter_backend_cogl_create_context (ClutterBackend *backend, backend->cogl_display = cogl_display_new (backend->cogl_renderer, onscreen_template); +#ifdef HAVE_CLUTTER_WAYLAND_COMPOSITOR + cogl_wayland_display_set_compositor_display (backend->cogl_display, + wayland_compositor_display); +#endif + #ifdef COGL_HAS_EGL_PLATFORM_GDL_SUPPORT cogl_gdl_display_set_plane (backend->cogl_display, gdl_plane); #endif @@ -646,3 +658,28 @@ clutter_cex100_set_buffering_mode (ClutterCex100BufferingMode mode) gdl_n_buffers = mode; } #endif + +#ifdef HAVE_CLUTTER_WAYLAND_COMPOSITOR +/** + * clutter_wayland_set_compositor_display: + * @display: A compositor side struct wl_display pointer + * + * This informs Clutter of your compositor side Wayland display + * object. This must be called before calling clutter_init(). + * + * Since: 1.8 + * Stability: unstable + */ +void +clutter_wayland_set_compositor_display (struct wl_display *display) +{ + if (_clutter_context_is_initialized ()) + { + g_warning ("%s() can only be used before calling clutter_init()", + G_STRFUNC); + return; + } + + wayland_compositor_display = display; +} +#endif diff --git a/clutter/cogl/clutter-stage-cogl.c b/clutter/cogl/clutter-stage-cogl.c index be42827cb..055dc94f4 100644 --- a/clutter/cogl/clutter-stage-cogl.c +++ b/clutter/cogl/clutter-stage-cogl.c @@ -158,7 +158,7 @@ clutter_stage_cogl_realize (ClutterStageWindow *stage_window) #ifdef COGL_HAS_EGL_PLATFORM_WAYLAND_SUPPORT wl_surface = cogl_wayland_onscreen_get_surface (stage_cogl->onscreen); - wl_input_device_set_user_data (wl_surface, stage_cogl); + wl_surface_set_user_data (wl_surface, stage_cogl); #endif if (cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_SWAP_BUFFERS_EVENT)) diff --git a/clutter/wayland/clutter-wayland-compositor.h b/clutter/wayland/clutter-wayland-compositor.h new file mode 100644 index 000000000..f7fa96e76 --- /dev/null +++ b/clutter/wayland/clutter-wayland-compositor.h @@ -0,0 +1,43 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2011 Intel Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Robert Bragg + */ + +/** + * SECTION:clutter-wayland-compositor + * @short_description: Wayland compositor specific APIs + * + * Clutter provides some Wayland specific APIs to aid in writing + * Clutter based compositors. + * + * The Clutter Wayland compositor API is available since Clutter 1.8 + */ + +#include +#include + +G_BEGIN_DECLS + +void +clutter_wayland_set_compositor_display (struct wl_display *display); + +G_END_DECLS diff --git a/clutter/wayland/clutter-wayland-surface.c b/clutter/wayland/clutter-wayland-surface.c new file mode 100644 index 000000000..da9a02999 --- /dev/null +++ b/clutter/wayland/clutter-wayland-surface.c @@ -0,0 +1,560 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2011 Intel Corporation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + * Authors: + * Robert Bragg + */ + +/** + * SECTION:clutter-wayland-surface + * @Title: ClutterWaylandSurface + * @short_description: An actor which displays the content of a client surface + * + * #ClutterWaylandSurface is an actor for displaying the contents of a client + * surface. It is intended to support developers implementing Clutter based + * wayland compositors. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "clutter-wayland-surface.h" + +#include "clutter-actor-private.h" +#include "clutter-marshal.h" +#include "clutter-paint-volume-private.h" +#include "clutter-private.h" +#include "clutter-backend-private.h" + +#include + +#include + +enum +{ + PROP_SURFACE = 1, + PROP_WIDTH, + PROP_HEIGHT +}; + +#if 0 +enum +{ + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0, }; +#endif + +struct _ClutterWaylandSurfacePrivate +{ + struct wl_surface *surface; + CoglTexture2D *buffer; + int width, height; + CoglPipeline *pipeline; + GArray *damage; +}; + +G_DEFINE_TYPE (ClutterWaylandSurface, + clutter_wayland_surface, + CLUTTER_TYPE_ACTOR); + +static gboolean +clutter_wayland_surface_get_paint_volume (ClutterActor *self, + ClutterPaintVolume *volume) +{ + return clutter_paint_volume_set_from_allocation (volume, self); +} + +static void +clutter_wayland_surface_queue_damage_redraw (ClutterWaylandSurface *texture, + gint x, + gint y, + gint width, + gint height) +{ + ClutterWaylandSurfacePrivate *priv = texture->priv; + ClutterActor *self = CLUTTER_ACTOR (texture); + ClutterActorBox allocation; + float scale_x; + float scale_y; + ClutterVertex origin; + ClutterPaintVolume clip; + + /* NB: clutter_actor_queue_clipped_redraw expects a box in the actor's + * coordinate space so we need to convert from surface coordinates to + * actor coordinates... + */ + + /* Calling clutter_actor_get_allocation_box() is enormously expensive + * if the actor has an out-of-date allocation, since it triggers + * a full redraw. clutter_actor_queue_clipped_redraw() would redraw + * the whole stage anyways in that case, so just go ahead and do + * it here. + */ + if (!clutter_actor_has_allocation (self)) + { + clutter_actor_queue_redraw (self); + return; + } + + if (priv->width == 0 || priv->height == 0) + return; + + clutter_actor_get_allocation_box (self, &allocation); + + scale_x = (allocation.x2 - allocation.x1) / priv->width; + scale_y = (allocation.y2 - allocation.y1) / priv->height; + + _clutter_paint_volume_init_static (&clip, self); + + origin.x = x * scale_x; + origin.y = y * scale_y; + origin.z = 0; + clutter_paint_volume_set_origin (&clip, &origin); + clutter_paint_volume_set_width (&clip, width * scale_x); + clutter_paint_volume_set_height (&clip, height * scale_y); + + _clutter_actor_queue_redraw_with_clip (self, 0, &clip); + clutter_paint_volume_free (&clip); +} + +static void +free_pipeline (ClutterWaylandSurface *self) +{ + ClutterWaylandSurfacePrivate *priv = self->priv; + + if (priv->pipeline) + { + cogl_object_unref (priv->pipeline); + priv->pipeline = NULL; + } +} + +static void +opacity_change_cb (ClutterWaylandSurface *self) +{ + free_pipeline (self); +} + +static void +clutter_wayland_surface_init (ClutterWaylandSurface *self) +{ + ClutterWaylandSurfacePrivate *priv = + G_TYPE_INSTANCE_GET_PRIVATE (self, + CLUTTER_WAYLAND_TYPE_SURFACE, + ClutterWaylandSurfacePrivate); + + priv->surface = NULL; + priv->width = 0; + priv->height = 0; + priv->damage = g_array_new (FALSE, FALSE, sizeof (int)); + + self->priv = priv; + + g_signal_connect (self, "notify::opacity", G_CALLBACK (opacity_change_cb), NULL); +} + +static void +clutter_wayland_surface_dispose (GObject *object) +{ + ClutterWaylandSurface *self = CLUTTER_WAYLAND_SURFACE (object); + ClutterWaylandSurfacePrivate *priv = self->priv; + + if (priv->damage) + { + g_array_free (priv->damage, TRUE); + priv->damage = NULL; + } + + G_OBJECT_CLASS (clutter_wayland_surface_parent_class)->dispose (object); +} + +static void +set_size (ClutterWaylandSurface *self, + int width, + int height) +{ + ClutterWaylandSurfacePrivate *priv = self->priv; + + if (priv->width != width) + { + priv->width = width; + g_object_notify (G_OBJECT (self), "width"); + } + if (priv->height != height) + { + priv->height = height; + g_object_notify (G_OBJECT (self), "height"); + } +} + +static void +clutter_wayland_surface_set_surface (ClutterWaylandSurface *self, + struct wl_surface *surface) +{ + ClutterWaylandSurfacePrivate *priv; + + g_return_if_fail (CLUTTER_WAYLAND_IS_SURFACE (self)); + + priv = self->priv; + + g_return_if_fail (priv->surface == NULL); + priv->surface = surface; + + /* XXX: should we freeze/thaw notifications? */ + + g_object_notify (G_OBJECT (self), "surface"); + + /* We have to wait until the next attach event to find out the surface + * geometry... */ + set_size (self, 0, 0); +} + +static void +clutter_wayland_surface_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ClutterWaylandSurface *self = CLUTTER_WAYLAND_SURFACE (object); + + switch (prop_id) + { + case PROP_SURFACE: + clutter_wayland_surface_set_surface (self, g_value_get_pointer (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clutter_wayland_surface_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ClutterWaylandSurface *self = CLUTTER_WAYLAND_SURFACE (object); + ClutterWaylandSurfacePrivate *priv = self->priv; + + switch (prop_id) + { + case PROP_SURFACE: + g_value_set_pointer (value, priv->surface); + break; + case PROP_WIDTH: + g_value_set_uint (value, priv->width); + break; + case PROP_HEIGHT: + g_value_set_uint (value, priv->height); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clutter_wayland_surface_paint (ClutterActor *self) +{ + ClutterWaylandSurfacePrivate *priv; + ClutterActorBox box; + + g_return_if_fail (CLUTTER_WAYLAND_IS_SURFACE (self)); + + priv = CLUTTER_WAYLAND_SURFACE (self)->priv; + + if (G_UNLIKELY (priv->pipeline == NULL)) + { + guint8 paint_opacity = clutter_actor_get_paint_opacity (self); + + priv->pipeline = cogl_pipeline_new (); + cogl_pipeline_set_color4ub (priv->pipeline, + paint_opacity, + paint_opacity, + paint_opacity, + paint_opacity); + cogl_pipeline_set_layer_texture (priv->pipeline, 0, priv->buffer); + } + + cogl_set_source (priv->pipeline); + clutter_actor_get_allocation_box (self, &box); + cogl_rectangle (0, 0, box.x2 - box.x1, box.y2 - box.y1); +} + +static void +clutter_wayland_surface_pick (ClutterActor *self, + const ClutterColor *color) +{ + ClutterActorBox box; + + cogl_set_source_color4ub (color->red, color->green, color->blue, + color->alpha); + clutter_actor_get_allocation_box (self, &box); + cogl_rectangle (0, 0, box.x2 - box.x1, box.y2 - box.y1); +} + +static void +clutter_wayland_surface_get_preferred_width (ClutterActor *self, + gfloat for_height, + gfloat *min_width_p, + gfloat *natural_width_p) +{ + ClutterWaylandSurfacePrivate *priv; + + g_return_if_fail (CLUTTER_WAYLAND_IS_SURFACE (self)); + + priv = CLUTTER_WAYLAND_SURFACE (self)->priv; + + if (min_width_p) + *min_width_p = 0; + + if (natural_width_p) + *natural_width_p = priv->width; +} + +static void +clutter_wayland_surface_get_preferred_height (ClutterActor *self, + gfloat for_width, + gfloat *min_height_p, + gfloat *natural_height_p) +{ + ClutterWaylandSurfacePrivate *priv; + + g_return_if_fail (CLUTTER_WAYLAND_IS_SURFACE (self)); + + priv = CLUTTER_WAYLAND_SURFACE (self)->priv; + + if (min_height_p) + *min_height_p = 0; + + if (natural_height_p) + *natural_height_p = priv->height; +} + +static gboolean +clutter_wayland_surface_has_overlaps (ClutterActor *self) +{ + /* Rectangles never need an offscreen redirect because there are + never any overlapping primitives */ + return FALSE; +} + +static void +clutter_wayland_surface_class_init (ClutterWaylandSurfaceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + GParamSpec *pspec; + + g_type_class_add_private (klass, sizeof (ClutterWaylandSurfacePrivate)); + + actor_class->get_paint_volume = clutter_wayland_surface_get_paint_volume; + actor_class->paint = clutter_wayland_surface_paint; + actor_class->pick = clutter_wayland_surface_pick; + actor_class->get_preferred_width = + clutter_wayland_surface_get_preferred_width; + actor_class->get_preferred_height = + clutter_wayland_surface_get_preferred_height; + actor_class->has_overlaps = clutter_wayland_surface_has_overlaps; + + object_class->dispose = clutter_wayland_surface_dispose; + object_class->set_property = clutter_wayland_surface_set_property; + object_class->get_property = clutter_wayland_surface_get_property; + + pspec = g_param_spec_pointer ("surface", + P_("Surface"), + P_("The underlying wayland surface"), + CLUTTER_PARAM_READWRITE| + G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_property (object_class, PROP_SURFACE, pspec); + + pspec = g_param_spec_uint ("width", + P_("Surface width"), + P_("The width of the underlying wayland surface"), + 0, G_MAXUINT, + 0, + G_PARAM_READABLE); + + g_object_class_install_property (object_class, PROP_WIDTH, pspec); + + pspec = g_param_spec_uint ("height", + P_("Surface height"), + P_("The height of the underlying wayland surface"), + 0, G_MAXUINT, + 0, + G_PARAM_READABLE); + + g_object_class_install_property (object_class, PROP_HEIGHT, pspec); +} + +/** + * clutter_wayland_surface_new: + * @surface: the Wayland surface this actor should represent + * + * Creates a new #ClutterWaylandSurface for @surface + * + * Return value: A new #ClutterWaylandSurface representing @surface + * + * Since: 1.8 + * Stability: unstable + */ +ClutterActor * +clutter_wayland_surface_new (struct wl_surface *surface) +{ + ClutterActor *actor; + + actor = g_object_new (CLUTTER_WAYLAND_TYPE_SURFACE, + "surface", surface, + NULL); + + return actor; +} + +static void +free_surface_buffers (ClutterWaylandSurface *self) +{ + ClutterWaylandSurfacePrivate *priv = self->priv; + + if (priv->buffer) + { + cogl_object_unref (priv->buffer); + priv->buffer = NULL; + free_pipeline (self); + } +} + +/** + * clutter_wayland_surface_attach_buffer: + * @self: A #ClutterWaylandSurface actor + * @buffer: A compositor side struct wl_buffer pointer + * @error: A #GError + * + * This associates a client's buffer with the #ClutterWaylandSurface + * actor @self. This will automatically result in @self being re-drawn + * with the new buffer contents. + * + * Since: 1.8 + * Stability: unstable + */ +gboolean +clutter_wayland_surface_attach_buffer (ClutterWaylandSurface *self, + struct wl_buffer *buffer, + GError **error) +{ + ClutterWaylandSurfacePrivate *priv; + ClutterBackend *backend = clutter_get_default_backend (); + CoglContext *context = clutter_backend_get_cogl_context (backend); + + g_return_val_if_fail (CLUTTER_WAYLAND_IS_SURFACE (self), TRUE); + + priv = self->priv; + + free_surface_buffers (self); + + set_size (self, buffer->width, buffer->height); + + priv->buffer = + cogl_wayland_texture_2d_new_from_buffer (context, buffer, error); + + clutter_actor_queue_redraw (CLUTTER_ACTOR (self)); + + if (!priv->buffer) + return FALSE; + + return TRUE; +} + +static CoglPixelFormat +get_buffer_format (struct wl_buffer *wayland_buffer) +{ + struct wl_compositor *compositor = wayland_buffer->compositor; + struct wl_visual *visual = wayland_buffer->visual; +#if G_BYTE_ORDER == G_BIG_ENDIAN + if (visual == &compositor->premultiplied_argb_visual) + return COGL_PIXEL_FORMAT_ARGB_8888_PRE; + else if (visual == &compositor->argb_visual) + return COGL_PIXEL_FORMAT_ARGB_8888; + else if (visual == &compositor->rgb_visual) + return COGL_PIXEL_FORMAT_RGB_888; +#elif G_BYTE_ORDER == G_LITTLE_ENDIAN + if (visual == &compositor->premultiplied_argb_visual) + return COGL_PIXEL_FORMAT_BGRA_8888_PRE; + else if (visual == &compositor->argb_visual) + return COGL_PIXEL_FORMAT_BGRA_8888; + else if (visual == &compositor->rgb_visual) + return COGL_PIXEL_FORMAT_BGR_888; +#endif + else + g_return_val_if_reached (COGL_PIXEL_FORMAT_ANY); +} + +/** + * clutter_wayland_surface_damage_buffer: + * @self: A #ClutterWaylandSurface actor + * @buffer: A compositor side struct wl_buffer pointer + * @x: The x coordinate of the damaged rectangle + * @y: The y coordinate of the damaged rectangle + * @width: The width of the damaged rectangle + * @height: The height of the damaged rectangle + * + * This marks a region of the given @buffer has having been changed by + * the client. This will automatically result in the corresponding damaged + * region of the actor @self being redrawn. + * + * If multiple regions are changed then this should be called multiple + * times with different damage rectangles. + * + * Since: 1.8 + * Stability: unstable + */ +void +clutter_wayland_surface_damage_buffer (ClutterWaylandSurface *self, + struct wl_buffer *buffer, + gint32 x, + gint32 y, + gint32 width, + gint32 height) +{ + ClutterWaylandSurfacePrivate *priv; + + g_return_if_fail (CLUTTER_WAYLAND_IS_SURFACE (self)); + + priv = self->priv; + + if (priv->buffer && wl_buffer_is_shm (buffer)) + { + cogl_texture_set_region (priv->buffer, + x, y, + x, y, + width, height, + width, height, + get_buffer_format (buffer), + wl_shm_buffer_get_stride (buffer), + wl_shm_buffer_get_data (buffer)); + } + + clutter_wayland_surface_queue_damage_redraw (self, x, y, width, height); +} diff --git a/clutter/wayland/clutter-wayland-surface.h b/clutter/wayland/clutter-wayland-surface.h new file mode 100644 index 000000000..187ca80dd --- /dev/null +++ b/clutter/wayland/clutter-wayland-surface.h @@ -0,0 +1,94 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2011 Intel Corporation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + * Authors: + * Robert Bragg + * + */ + +#ifndef __CLUTTER_WAYLAND_SURFACE_H__ +#define __CLUTTER_WAYLAND_SURFACE_H__ + +#include +#include +#include + +#include + +G_BEGIN_DECLS + +#define CLUTTER_WAYLAND_TYPE_SURFACE (clutter_wayland_surface_get_type ()) +#define CLUTTER_WAYLAND_SURFACE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_WAYLAND_TYPE_SURFACE, ClutterWaylandSurface)) +#define CLUTTER_WAYLAND_SURFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_WAYLAND_TYPE_SURFACE, ClutterWaylandSurfaceClass)) +#define CLUTTER_WAYLAND_IS_SURFACE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_WAYLAND_TYPE_SURFACE)) +#define CLUTTER_WAYLAND_IS_SURFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_WAYLAND_TYPE_SURFACE)) +#define CLUTTER_WAYLAND_SURFACE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_WAYLAND_TYPE_SURFACE, ClutterWaylandSurfaceClass)) + +typedef struct _ClutterWaylandSurface ClutterWaylandSurface; +typedef struct _ClutterWaylandSurfaceClass ClutterWaylandSurfaceClass; +typedef struct _ClutterWaylandSurfacePrivate ClutterWaylandSurfacePrivate; + +/** + * ClutterWaylandSurface: + * + * The #ClutterWaylandSurface structure contains only private data + * + * Since: 1.8 + * Stability: unstable + */ +struct _ClutterWaylandSurface +{ + /*< private >*/ + ClutterActor parent; + + ClutterWaylandSurfacePrivate *priv; +}; + +/** + * ClutterWaylandSurfaceClass: + * + * The #ClutterWaylandSurfaceClass structure contains only private data + * + * Since: 0.8 + * Stability: unstable + */ +struct _ClutterWaylandSurfaceClass +{ + /*< private >*/ + ClutterActorClass parent_class; +}; + +GType clutter_wayland_surface_get_type (void) G_GNUC_CONST; + +ClutterActor *clutter_wayland_surface_new (struct wl_surface *surface); +gboolean clutter_wayland_surface_attach_buffer (ClutterWaylandSurface *self, + struct wl_buffer *buffer, + GError **error); +void clutter_wayland_surface_damage_buffer (ClutterWaylandSurface *self, + struct wl_buffer *buffer, + gint32 x, + gint32 y, + gint32 width, + gint32 height); + +G_END_DECLS + +#endif diff --git a/configure.ac b/configure.ac index 1206e72b3..14c79a1ba 100644 --- a/configure.ac +++ b/configure.ac @@ -395,6 +395,29 @@ AS_IF([test "x$SUPPORT_X11" = "x1"], []) ]) +dnl Note this is separate from the flavour since it doesn't correspond to a backend +dnl and conceptually you could support the wayland compositor features with multiple +dnl backends. +AC_ARG_ENABLE([wayland-compositor], + [AS_HELP_STRING([--enable-wayland-compositor], [Enable Wayland compositor features])], + [], + [AS_IF([test "x$SUPPORT_EGL" = "x1"], + [enable_wayland_compositor=yes], + [enable_wayland_compositor=no]) + ]) + +AS_IF([test "x$enable_wayland_compositor" = "xyes"], + [ + AS_IF([test "x$SUPPORT_EGL" != "x1"], + [AC_MSG_ERROR([Wayland compositor features currently only available for EGL flavours])]) + + PKG_CHECK_EXISTS([wayland-server], + [BACKEND_PC_FILES="$BACKEND_PC_FILES wayland-server"], []) + SUPPORT_WAYLAND_COMPOSITOR=1 + AC_DEFINE([HAVE_CLUTTER_WAYLAND_COMPOSITOR], [1], [Have wayland compositor support]) + ]) +AM_CONDITIONAL(SUPPORT_WAYLAND_COMPOSITOR, [test "x$SUPPORT_WAYLAND_COMPOSITOR" = "x1"]) + AS_IF([test "x$SUPPORT_GLX" = "x1"], [ AC_DEFINE([HAVE_CLUTTER_GLX], [1], [Have the GLX backend]) @@ -1039,6 +1062,8 @@ echo " Enable XKB: ${have_xkb}" echo " Enable X11 tests: ${x11_tests}" fi +echo " Wayland compositor features: ${SUPPORT_WAYLAND_COMPOSITOR}" + echo "" # General warning about experimental features diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt index 29d7c3b67..8ab05be30 100644 --- a/doc/reference/clutter/clutter-sections.txt +++ b/doc/reference/clutter/clutter-sections.txt @@ -1336,6 +1336,21 @@ ClutterGLXTexturePixmapPrivate clutter_glx_texture_pixmap_get_type +
+clutter-wayland-compositor +Wayland compositor specific support +clutter_wayland_set_compositor_display +
+ +
+clutter-wayland-surface +ClutterWaylandSurface +ClutterWaylandSurfaceClass +clutter_wayland_surface_new +clutter_wayland_surface_attach_buffer +clutter_wayland_surface_damage_buffer +
+
clutter-win32 Win32 Specific Support diff --git a/tests/interactive/Makefile.am b/tests/interactive/Makefile.am index cf324ec0e..9ed449834 100644 --- a/tests/interactive/Makefile.am +++ b/tests/interactive/Makefile.am @@ -65,6 +65,10 @@ if X11_TESTS UNIT_TESTS += test-pixmap.c test-devices.c endif +if SUPPORT_WAYLAND_COMPOSITOR +UNIT_TESTS += test-wayland-surface.c +endif + if OS_WIN32 SHEXT = else diff --git a/tests/interactive/test-wayland-surface.c b/tests/interactive/test-wayland-surface.c new file mode 100644 index 000000000..de5311b76 --- /dev/null +++ b/tests/interactive/test-wayland-surface.c @@ -0,0 +1,447 @@ +#define COGL_ENABLE_EXPERIMENTAL_2_0_API +#include +#include +#include + +#include +#include +#include + +#include + +typedef struct _TWSCompositor TWSCompositor; + +typedef struct +{ + struct wl_buffer *wayland_buffer; + GList *surfaces_attached_to; +} TWSBuffer; + +typedef struct +{ + TWSCompositor *compositor; + struct wl_surface wayland_surface; + int x; + int y; + TWSBuffer *buffer; + ClutterActor *actor; +} TWSSurface; + +typedef struct +{ + struct wl_object wayland_output; + int x; + int y; + int width; + int height; + /* XXX: with sliced stages we'd reference a CoglFramebuffer here. */ +} TWSOutput; + +typedef struct +{ + GSource source; + GPollFD pfd; + struct wl_event_loop *loop; +} WaylandEventSource; + +struct _TWSCompositor +{ + struct wl_display *wayland_display; + struct wl_compositor wayland_compositor; + struct wl_shm *wayland_shm; + struct wl_event_loop *wayland_loop; + ClutterActor *stage; + GList *outputs; + GSource *wayland_event_source; + GList *surfaces; +}; + +static guint32 +get_time (void) +{ + struct timeval tv; + gettimeofday (&tv, NULL); + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} + +static gboolean +wayland_event_source_prepare (GSource *base, int *timeout) +{ + *timeout = -1; + return FALSE; +} + +static gboolean +wayland_event_source_check (GSource *base) +{ + WaylandEventSource *source = (WaylandEventSource *)base; + return source->pfd.revents; +} + +static gboolean +wayland_event_source_dispatch (GSource *base, + GSourceFunc callback, + void *data) +{ + WaylandEventSource *source = (WaylandEventSource *)base; + wl_event_loop_dispatch (source->loop, 0); + return TRUE; +} + +static GSourceFuncs wayland_event_source_funcs = +{ + wayland_event_source_prepare, + wayland_event_source_check, + wayland_event_source_dispatch, + NULL +}; + +GSource * +wayland_event_source_new (struct wl_event_loop *loop) +{ + WaylandEventSource *source; + + source = (WaylandEventSource *) g_source_new (&wayland_event_source_funcs, + sizeof (WaylandEventSource)); + source->loop = loop; + source->pfd.fd = wl_event_loop_get_fd (loop); + source->pfd.events = G_IO_IN | G_IO_ERR; + g_source_add_poll (&source->source, &source->pfd); + + return &source->source; +} + +static TWSBuffer * +tws_buffer_new (struct wl_buffer *wayland_buffer) +{ + TWSBuffer *buffer = g_slice_new (TWSBuffer); + + buffer->wayland_buffer = wayland_buffer; + buffer->surfaces_attached_to = NULL; + + return buffer; +} + +static void +tws_buffer_free (TWSBuffer *buffer) +{ + GList *l; + + buffer->wayland_buffer->user_data = NULL; + + for (l = buffer->surfaces_attached_to; l; l = l->next) + { + TWSSurface *surface = l->data; + surface->buffer = NULL; + } + + g_list_free (buffer->surfaces_attached_to); + g_slice_free (TWSBuffer, buffer); +} + +static void +shm_buffer_created (struct wl_buffer *wayland_buffer) +{ + wayland_buffer->user_data = tws_buffer_new (wayland_buffer); +} + +static void +shm_buffer_damaged (struct wl_buffer *wayland_buffer, + gint32 x, + gint32 y, + gint32 width, + gint32 height) +{ + TWSBuffer *buffer = wayland_buffer->user_data; + GList *l; + + for (l = buffer->surfaces_attached_to; l; l = l->next) + { + TWSSurface *surface = l->data; + ClutterWaylandSurface *surface_actor = + CLUTTER_WAYLAND_SURFACE (surface->actor); + clutter_wayland_surface_damage_buffer (surface_actor, + wayland_buffer, + x, y, width, height); + } +} + +static void +shm_buffer_destroyed (struct wl_buffer *wayland_buffer) +{ + if (wayland_buffer->user_data) + tws_buffer_free ((TWSBuffer *)wayland_buffer->user_data); +} + +const static struct wl_shm_callbacks shm_callbacks = { + shm_buffer_created, + shm_buffer_damaged, + shm_buffer_destroyed +}; + +static void +tws_surface_destroy (struct wl_client *wayland_client, + struct wl_surface *wayland_surface) +{ + wl_resource_destroy (&wayland_surface->resource, wayland_client, get_time ()); +} + +static void +tws_surface_detach_buffer (TWSSurface *surface) +{ + TWSBuffer *buffer = surface->buffer; + + if (buffer) + { + buffer->surfaces_attached_to = + g_list_remove (buffer->surfaces_attached_to, surface); + if (buffer->surfaces_attached_to == NULL) + tws_buffer_free (buffer); + surface->buffer = NULL; + } +} + +static void +tws_surface_attach_buffer (struct wl_client *wayland_client, + struct wl_surface *wayland_surface, + struct wl_buffer *wayland_buffer, + gint32 dx, gint32 dy) +{ + TWSBuffer *buffer = wayland_buffer->user_data; + TWSSurface *surface = + container_of (wayland_surface, TWSSurface, wayland_surface); + TWSCompositor *compositor = surface->compositor; + ClutterWaylandSurface *surface_actor; + + tws_surface_detach_buffer (surface); + + /* XXX: we will have been notified of shm buffers already via the + * callbacks, but this will be the first we know of drm buffers */ + if (!buffer) + { + buffer = tws_buffer_new (wayland_buffer); + wayland_buffer->user_data = buffer; + } + + /* wayland-drm.c: drm_create_buffer doesn't fill this in for us...*/ + if (!wayland_buffer->compositor) + wayland_buffer->compositor = &compositor->wayland_compositor; + + g_return_if_fail (g_list_find (buffer->surfaces_attached_to, surface) == NULL); + + buffer->surfaces_attached_to = g_list_prepend (buffer->surfaces_attached_to, + surface); + + if (!surface->actor) + { + surface->actor = clutter_wayland_surface_new (wayland_surface); + clutter_container_add_actor (CLUTTER_CONTAINER (compositor->stage), + surface->actor); + } + + surface_actor = CLUTTER_WAYLAND_SURFACE (surface->actor); + if (!clutter_wayland_surface_attach_buffer (surface_actor, wayland_buffer, + NULL)) + g_warning ("Failed to attach buffer to ClutterWaylandSurface"); + + surface->buffer = buffer; +} + +static void +tws_surface_map_toplevel (struct wl_client *client, + struct wl_surface *surface) +{ +} + +static void +tws_surface_map_transient (struct wl_client *client, + struct wl_surface *surface, + struct wl_surface *parent, + gint32 dx, + gint32 dy, + guint32 flags) +{ +} + +static void +tws_surface_map_fullscreen (struct wl_client *client, + struct wl_surface *surface) +{ +} + +static void +tws_surface_damage (struct wl_client *client, + struct wl_surface *surface, + gint32 x, + gint32 y, + gint32 width, + gint32 height) +{ +} + +const struct wl_surface_interface tws_surface_interface = { + tws_surface_destroy, + tws_surface_attach_buffer, + tws_surface_map_toplevel, + tws_surface_map_transient, + tws_surface_map_fullscreen, + tws_surface_damage +}; + +static void +tws_surface_free (TWSSurface *surface) +{ + TWSCompositor *compositor = surface->compositor; + compositor->surfaces = g_list_remove (compositor->surfaces, surface); + tws_surface_detach_buffer (surface); + + clutter_actor_destroy (surface->actor); + + g_slice_free (TWSSurface, surface); +} + +static void +tws_surface_resource_destroy_cb (struct wl_resource *wayland_resource, + struct wl_client *wayland_client) +{ + TWSSurface *surface = + container_of (wayland_resource, TWSSurface, wayland_surface.resource); + tws_surface_free (surface); +} + +static void +tws_compositor_create_surface (struct wl_client *wayland_client, + struct wl_compositor *wayland_compositor, + guint32 wayland_id) +{ + TWSCompositor *compositor = + container_of (wayland_compositor, TWSCompositor, wayland_compositor); + TWSSurface *surface = g_slice_new0 (TWSSurface); + surface->compositor = compositor; + + surface->wayland_surface.resource.destroy = + tws_surface_resource_destroy_cb; + + surface->wayland_surface.resource.object.id = wayland_id; + surface->wayland_surface.resource.object.interface = &wl_surface_interface; + surface->wayland_surface.resource.object.implementation = + (void (**)(void)) &tws_surface_interface; + surface->wayland_surface.client = wayland_client; + + wl_client_add_resource (wayland_client, &surface->wayland_surface.resource); + + compositor->surfaces = g_list_prepend (compositor->surfaces, surface); +} + +const static struct wl_compositor_interface tws_compositor_interface = { + tws_compositor_create_surface, +}; + +static void +tws_output_post_geometry (struct wl_client *wayland_client, + struct wl_object *wayland_output, + guint32 version) +{ + TWSOutput *output = + container_of (wayland_output, TWSOutput, wayland_output); + + wl_client_post_event (wayland_client, + wayland_output, + WL_OUTPUT_GEOMETRY, + output->x, output->y, + output->width, output->height); +} + +static void +paint_finished_cb (ClutterActor *self, void *user_data) +{ + TWSCompositor *compositor = user_data; + GList *l; + + for (l = compositor->surfaces; l; l = l->next) + { + TWSSurface *surface = l->data; + wl_display_post_frame (compositor->wayland_display, + &surface->wayland_surface, get_time ()); + } +} + +static void +tws_compositor_create_output (TWSCompositor *compositor, + int x, + int y, + int width, + int height) +{ + TWSOutput *output = g_slice_new0 (TWSOutput); + + output->wayland_output.interface = &wl_output_interface; + + wl_display_add_object (compositor->wayland_display, &output->wayland_output); + wl_display_add_global (compositor->wayland_display, &output->wayland_output, + tws_output_post_geometry); + + output->x = x; + output->y = y; + output->width = width; + output->height = height; + + /* XXX: eventually we will support sliced stages and an output should + * correspond to a slice/CoglFramebuffer, but for now we only support + * one output so we make sure it always matches the size of the stage + */ + clutter_actor_set_size (compositor->stage, width, height); + + compositor->outputs = g_list_prepend (compositor->outputs, output); +} + +G_MODULE_EXPORT int +test_wayland_surface_main (int argc, char **argv) +{ + TWSCompositor compositor; + GMainLoop *loop; + + memset (&compositor, 0, sizeof (compositor)); + + compositor.wayland_display = wl_display_create (); + if (compositor.wayland_display == NULL) + g_error ("failed to create wayland display"); + + if (wl_compositor_init (&compositor.wayland_compositor, + &tws_compositor_interface, + compositor.wayland_display) < 0) + g_error ("Failed to init wayland compositor"); + + compositor.wayland_shm = wl_shm_init (compositor.wayland_display, + &shm_callbacks); + if (!compositor.wayland_shm) + g_error ("Failed to allocate setup wayland shm callbacks"); + + loop = g_main_loop_new (NULL, FALSE); + compositor.wayland_loop = + wl_display_get_event_loop (compositor.wayland_display); + compositor.wayland_event_source = + wayland_event_source_new (compositor.wayland_loop); + g_source_attach (compositor.wayland_event_source, NULL); + + clutter_wayland_set_compositor_display (compositor.wayland_display); + + if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS) + return 1; + + compositor.stage = clutter_stage_get_default (); + clutter_stage_set_user_resizable (CLUTTER_STAGE (compositor.stage), FALSE); + g_signal_connect_after (compositor.stage, "paint", + G_CALLBACK (paint_finished_cb), &compositor); + + tws_compositor_create_output (&compositor, 0, 0, 800, 600); + + clutter_actor_show (compositor.stage); + + if (wl_display_add_socket (compositor.wayland_display, "wayland-0")) + g_error ("Failed to create socket"); + + g_main_loop_run (loop); + + return 0; +} -- cgit v1.2.1