[RFC Weston 09/10] clients: add subsurfaces demo

Pekka Paalanen ppaalanen at gmail.com
Fri Feb 22 07:07:53 PST 2013


Add a demo program with:
- a main surface (green)
- a Cairo-image sub-surface (red)
- a raw GLESv2 widget (triangle)

Sub-surface input region is set empty to avoid problems in toytoolkit.

If Cairo links to libGL, then we will end up with also libGLESv2 linked
to subsurfaces program, and both libs getting really used, which leads
to disaster.

Do not build subsurfaces demo, if Cairo links to libGL and cairo-egl is
usable.

The GL rendering loop is not tied to the toytoolkit or the widget, but
runs directly from its own frame callback. Therefore it runs
independent of the rest of the application. This also relies on one of
two things:
- eglSwapInterval(0) is implemented, and therefore eglSwapBuffers never
  blocks indefinitely, or
- toytoolkit has a workaround, that guarantees that eglSwapBuffers will
  return soon, when we force a repaint on resize.
Otherwise the demo will deadlock.

The code is separated into three sections:

1. The library component, using only EGL, GLESv2, and libwayland-client
   APIs, and not aware of any toolkit details of the parent application.
   This runs independently until the parent application tells otherwise.

2. The glue code: a toytoolkit application widget, who has its own
   rendering machinery.

3. The application written in toytoolkit.

This patch also adds new toytoolkit interfaces:
- widget_get_wl_surface()
- widget_get_last_time()
- widget_input_region_add()

Toytoolkit applications have not had a possibility to change the input
region. The frame widget (decorations) set the input region on its own
when used, otherwise the default input region of everything has been
used. If a window does not have a frame widget, it can now use
widget_input_region_add() to set a custom input region.

These are not a window methods, because a widget may lie on a different
wl_surface (sub-surface) than the window.
---
 clients/.gitignore    |   1 +
 clients/Makefile.am   |   8 +
 clients/subsurfaces.c | 791 ++++++++++++++++++++++++++++++++++++++++++++++++++
 clients/window.c      |  30 ++
 clients/window.h      |   9 +
 configure.ac          |   3 +
 6 files changed, 842 insertions(+)
 create mode 100644 clients/subsurfaces.c

diff --git a/clients/.gitignore b/clients/.gitignore
index 16088e8..f925ce6 100644
--- a/clients/.gitignore
+++ b/clients/.gitignore
@@ -22,6 +22,7 @@ simple-touch
 smoke
 subsurface-client-protocol.h
 subsurface-protocol.c
+subsurfaces
 tablet-shell-client-protocol.h
 tablet-shell-protocol.c
 text-client-protocol.h
diff --git a/clients/Makefile.am b/clients/Makefile.am
index 5f83acd..d360174 100644
--- a/clients/Makefile.am
+++ b/clients/Makefile.am
@@ -64,6 +64,7 @@ clients_programs =				\
 	clickdot				\
 	transformed				\
 	calibrator				\
+	$(subsurfaces)				\
 	$(full_gl_client_programs)
 
 desktop_shell = weston-desktop-shell
@@ -131,6 +132,13 @@ transformed_LDADD = libtoytoolkit.la
 calibrator_SOURCES = calibrator.c ../shared/matrix.c ../shared/matrix.h
 calibrator_LDADD = libtoytoolkit.la
 
+if BUILD_SUBSURFACES_CLIENT
+subsurfaces = subsurfaces
+subsurfaces_SOURCES = subsurfaces.c
+subsurfaces_CPPFLAGS = $(AM_CPPFLAGS) $(SIMPLE_EGL_CLIENT_CFLAGS)
+subsurfaces_LDADD = libtoytoolkit.la $(SIMPLE_EGL_CLIENT_LIBS) -lm
+endif
+
 if HAVE_PANGO
 pango_programs = editor
 editor_SOURCES = 				\
diff --git a/clients/subsurfaces.c b/clients/subsurfaces.c
new file mode 100644
index 0000000..eec5a3e
--- /dev/null
+++ b/clients/subsurfaces.c
@@ -0,0 +1,791 @@
+/*
+ * Copyright © 2010 Intel Corporation
+ * Copyright © 2011 Benjamin Franzke
+ * Copyright © 2012-2013 Collabora, Ltd.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cairo.h>
+#include <math.h>
+#include <assert.h>
+
+#include <linux/input.h>
+#include <wayland-client.h>
+
+#include <wayland-egl.h>
+#include <GLES2/gl2.h>
+#include <EGL/egl.h>
+
+#include "window.h"
+
+#if 0
+#define DBG(fmt, ...) \
+	fprintf(stderr, "%d:%s " fmt, __LINE__, __func__, ##__VA_ARGS__)
+#else
+#define DBG(...) do {} while (0)
+#endif
+
+static int32_t option_red_mode = 2;
+static int32_t option_triangle_mode = 2;
+static int32_t option_no_triangle;
+static int32_t option_help;
+
+static const struct weston_option options[] = {
+	{ WESTON_OPTION_INTEGER, "red-mode", 'r', &option_red_mode },
+	{ WESTON_OPTION_INTEGER, "triangle-mode", 't', &option_triangle_mode },
+	{ WESTON_OPTION_BOOLEAN, "no-triangle", 'n', &option_no_triangle },
+	{ WESTON_OPTION_BOOLEAN, "help", 'h', &option_help },
+};
+
+static enum wl_subsurface_commit_mode
+int_to_mode(int32_t i)
+{
+	switch (i) {
+	case 1:
+		return WL_SUBSURFACE_COMMIT_MODE_PARENT_CACHED;
+	case 2:
+		return WL_SUBSURFACE_COMMIT_MODE_INDEPENDENT;
+	default:
+		fprintf(stderr, "error: %d is not a valid commit mode.\n", i);
+		exit(1);
+	}
+}
+
+static const char help_text[] =
+"Usage: %s [options]\n"
+"\n"
+"  -r, --red-mode=MODE\t\tthe commit mode for the red sub-surface (2)\n"
+"  -t, --triangle-mode=MODE\tthe commit mode for the GL sub-surface (2)\n"
+"  -n, --no-triangle\t\tDo not create the GL sub-surface.\n"
+"\n"
+"The MODE is the wl_subsurface commit mode used by default for the\n"
+"given sub-surface. Valid values are the integers:\n"
+"   1\tfor parent-cached\n"
+"   2\tfor independent, i.e. free-running\n"
+"\n"
+"This program demonstrates sub-surfaces with the toytoolkit.\n"
+"The main surface contains the decorations, a green canvas, and a\n"
+"green spinner. One sub-surface is red with a red spinner. These\n"
+"are rendered with Cairo. The other sub-surface contains a spinning\n"
+"triangle rendered in EGL/GLESv2, without Cairo, i.e. it is a raw GL\n"
+"widget.\n"
+"\n"
+"The GL widget animates on its own. The spinners follow wall clock\n"
+"time and update only when their surface is repainted, so you see\n"
+"which surfaces get redrawn. The red sub-surface animates on its own,\n"
+"but can be toggled with the spacebar.\n"
+"\n"
+"Even though the sub-surfaces attempt to animate on their own, they\n"
+"are subject to the commit mode. If commit mode is parent-cached,\n"
+"they will need a commit on the main surface to actually display.\n"
+"You can trigger a main surface repaint, without a resize, by\n"
+"hovering the pointer over the title bar buttons.\n"
+"\n"
+"Resizing will temporarily toggle the commit mode of all sub-surfaces\n"
+"to guarantee synchronized rendering on size changes. It also forces\n"
+"a repaint of all surfaces.\n"
+"\n"
+"Using -t1 -r1 is especially useful for trying to catch inconsistent\n"
+"rendering and deadlocks, since free-running sub-surfaces would\n"
+"immediately hide the problem.\n"
+"\n"
+"Key controls:\n"
+"  space - toggle red sub-surface animation loop\n"
+"  up	 - step window size shorter\n"
+"  down  - step window size taller\n"
+"\n";
+
+struct egl_state {
+	EGLDisplay dpy;
+	EGLContext ctx;
+	EGLConfig conf;
+};
+
+struct triangle_gl_state {
+	GLuint rotation_uniform;
+	GLuint pos;
+	GLuint col;
+};
+
+struct triangle {
+	struct egl_state *egl;
+
+	struct wl_surface *wl_surface;
+	struct wl_egl_window *egl_window;
+	EGLSurface egl_surface;
+	int width;
+	int height;
+
+	struct triangle_gl_state gl;
+
+	struct widget *widget;
+	uint32_t time;
+	struct wl_callback *frame_cb;
+};
+
+/******** Pure EGL/GLESv2/libwayland-client component: ***************/
+
+static const char *vert_shader_text =
+	"uniform mat4 rotation;\n"
+	"attribute vec4 pos;\n"
+	"attribute vec4 color;\n"
+	"varying vec4 v_color;\n"
+	"void main() {\n"
+	"  gl_Position = rotation * pos;\n"
+	"  v_color = color;\n"
+	"}\n";
+
+static const char *frag_shader_text =
+	"precision mediump float;\n"
+	"varying vec4 v_color;\n"
+	"void main() {\n"
+	"  gl_FragColor = v_color;\n"
+	"}\n";
+
+static void
+egl_print_config_info(struct egl_state *egl)
+{
+	EGLint r, g, b, a;
+
+	printf("Chosen EGL config details:\n");
+
+	printf("\tRGBA bits");
+	if (eglGetConfigAttrib(egl->dpy, egl->conf, EGL_RED_SIZE, &r) &&
+	    eglGetConfigAttrib(egl->dpy, egl->conf, EGL_GREEN_SIZE, &g) &&
+	    eglGetConfigAttrib(egl->dpy, egl->conf, EGL_BLUE_SIZE, &b) &&
+	    eglGetConfigAttrib(egl->dpy, egl->conf, EGL_ALPHA_SIZE, &a))
+		printf(": %d %d %d %d\n", r, g, b, a);
+	else
+		printf(" unknown\n");
+
+	printf("\tswap interval range");
+	if (eglGetConfigAttrib(egl->dpy, egl->conf, EGL_MIN_SWAP_INTERVAL, &a) &&
+	    eglGetConfigAttrib(egl->dpy, egl->conf, EGL_MAX_SWAP_INTERVAL, &b))
+		printf(": %d - %d\n", a, b);
+	else
+		printf(" unknown\n");
+}
+
+static struct egl_state *
+egl_state_create(struct wl_display *display)
+{
+	struct egl_state *egl;
+
+	static const EGLint context_attribs[] = {
+		EGL_CONTEXT_CLIENT_VERSION, 2,
+		EGL_NONE
+	};
+
+	EGLint config_attribs[] = {
+		EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+		EGL_RED_SIZE, 1,
+		EGL_GREEN_SIZE, 1,
+		EGL_BLUE_SIZE, 1,
+		EGL_ALPHA_SIZE, 1,
+		EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+		EGL_NONE
+	};
+
+	EGLint major, minor, n;
+	EGLBoolean ret;
+
+	egl = calloc(1, sizeof *egl);
+	assert(egl);
+
+	egl->dpy = eglGetDisplay(display);
+	assert(egl->dpy);
+
+	ret = eglInitialize(egl->dpy, &major, &minor);
+	assert(ret == EGL_TRUE);
+	ret = eglBindAPI(EGL_OPENGL_ES_API);
+	assert(ret == EGL_TRUE);
+
+	ret = eglChooseConfig(egl->dpy, config_attribs, &egl->conf, 1, &n);
+	assert(ret && n == 1);
+
+	egl->ctx = eglCreateContext(egl->dpy, egl->conf,
+				    EGL_NO_CONTEXT, context_attribs);
+	assert(egl->ctx);
+	egl_print_config_info(egl);
+
+	return egl;
+}
+
+static void
+egl_state_destroy(struct egl_state *egl)
+{
+	/* Required, otherwise segfault in egl_dri2.c: dri2_make_current()
+	 * on eglReleaseThread(). */
+	eglMakeCurrent(egl->dpy, EGL_NO_SURFACE, EGL_NO_SURFACE,
+		       EGL_NO_CONTEXT);
+
+	eglTerminate(egl->dpy);
+	eglReleaseThread();
+	free(egl);
+}
+
+static void
+egl_make_swapbuffers_nonblock(struct egl_state *egl)
+{
+	EGLint a = EGL_MIN_SWAP_INTERVAL;
+	EGLint b = EGL_MAX_SWAP_INTERVAL;
+
+	if (!eglGetConfigAttrib(egl->dpy, egl->conf, a, &a) ||
+	    !eglGetConfigAttrib(egl->dpy, egl->conf, b, &b)) {
+		fprintf(stderr, "warning: swap interval range unknown\n");
+	} else
+	if (a > 0) {
+		fprintf(stderr, "warning: minimum swap interval is %d, "
+			"while 0 is required to not deadlock on resize.\n", a);
+	}
+
+	/*
+	 * We rely on the Wayland compositor to sync to vblank anyway.
+	 * We just need to be able to call eglSwapBuffers() without the
+	 * risk of waiting for a frame callback in it.
+	 */
+	if (!eglSwapInterval(egl->dpy, 0)) {
+		fprintf(stderr, "error: eglSwapInterval() failed.\n");
+	}
+}
+
+static GLuint
+create_shader(const char *source, GLenum shader_type)
+{
+	GLuint shader;
+	GLint status;
+
+	shader = glCreateShader(shader_type);
+	assert(shader != 0);
+
+	glShaderSource(shader, 1, (const char **) &source, NULL);
+	glCompileShader(shader);
+
+	glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
+	if (!status) {
+		char log[1000];
+		GLsizei len;
+		glGetShaderInfoLog(shader, 1000, &len, log);
+		fprintf(stderr, "Error: compiling %s: %*s\n",
+			shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment",
+			len, log);
+		exit(1);
+	}
+
+	return shader;
+}
+
+static void
+triangle_init_gl(struct triangle_gl_state *trigl)
+{
+	GLuint frag, vert;
+	GLuint program;
+	GLint status;
+
+	frag = create_shader(frag_shader_text, GL_FRAGMENT_SHADER);
+	vert = create_shader(vert_shader_text, GL_VERTEX_SHADER);
+
+	program = glCreateProgram();
+	glAttachShader(program, frag);
+	glAttachShader(program, vert);
+	glLinkProgram(program);
+
+	glGetProgramiv(program, GL_LINK_STATUS, &status);
+	if (!status) {
+		char log[1000];
+		GLsizei len;
+		glGetProgramInfoLog(program, 1000, &len, log);
+		fprintf(stderr, "Error: linking:\n%*s\n", len, log);
+		exit(1);
+	}
+
+	glUseProgram(program);
+
+	trigl->pos = 0;
+	trigl->col = 1;
+
+	glBindAttribLocation(program, trigl->pos, "pos");
+	glBindAttribLocation(program, trigl->col, "color");
+	glLinkProgram(program);
+
+	trigl->rotation_uniform = glGetUniformLocation(program, "rotation");
+}
+
+static void
+triangle_draw(const struct triangle_gl_state *trigl, uint32_t time)
+{
+	static const GLfloat verts[3][2] = {
+		{ -0.5, -0.5 },
+		{  0.5, -0.5 },
+		{  0,    0.5 }
+	};
+	static const GLfloat colors[3][3] = {
+		{ 1, 0, 0 },
+		{ 0, 1, 0 },
+		{ 0, 0, 1 }
+	};
+	GLfloat angle;
+	GLfloat rotation[4][4] = {
+		{ 1, 0, 0, 0 },
+		{ 0, 1, 0, 0 },
+		{ 0, 0, 1, 0 },
+		{ 0, 0, 0, 1 }
+	};
+	static const int32_t speed_div = 5;
+
+	angle = (time / speed_div) % 360 * M_PI / 180.0;
+	rotation[0][0] =  cos(angle);
+	rotation[0][2] =  sin(angle);
+	rotation[2][0] = -sin(angle);
+	rotation[2][2] =  cos(angle);
+
+	glUniformMatrix4fv(trigl->rotation_uniform, 1, GL_FALSE,
+			   (GLfloat *) rotation);
+
+	glClearColor(0.0, 0.0, 0.0, 0.5);
+	glClear(GL_COLOR_BUFFER_BIT);
+
+	glVertexAttribPointer(trigl->pos, 2, GL_FLOAT, GL_FALSE, 0, verts);
+	glVertexAttribPointer(trigl->col, 3, GL_FLOAT, GL_FALSE, 0, colors);
+	glEnableVertexAttribArray(trigl->pos);
+	glEnableVertexAttribArray(trigl->col);
+
+	glDrawArrays(GL_TRIANGLES, 0, 3);
+
+	glDisableVertexAttribArray(trigl->pos);
+	glDisableVertexAttribArray(trigl->col);
+}
+
+static void
+triangle_frame_callback(void *data, struct wl_callback *callback,
+			uint32_t time);
+
+static const struct wl_callback_listener triangle_frame_listener = {
+	triangle_frame_callback
+};
+
+static void
+triangle_frame_callback(void *data, struct wl_callback *callback,
+			uint32_t time)
+{
+	struct triangle *tri = data;
+
+	DBG("%stime %u\n", callback ? "" : "artificial ", time);
+	assert(callback == tri->frame_cb);
+	tri->time = time;
+
+	if (callback)
+		wl_callback_destroy(callback);
+
+	glViewport(0, 0, tri->width, tri->height);
+
+	triangle_draw(&tri->gl, tri->time);
+
+	tri->frame_cb = wl_surface_frame(tri->wl_surface);
+	wl_callback_add_listener(tri->frame_cb, &triangle_frame_listener, tri);
+
+	eglSwapBuffers(tri->egl->dpy, tri->egl_surface);
+}
+
+static void
+triangle_create_egl_surface(struct triangle *tri, int width, int height)
+{
+	EGLBoolean ret;
+
+	tri->wl_surface = widget_get_wl_surface(tri->widget);
+	tri->egl_window = wl_egl_window_create(tri->wl_surface, width, height);
+	tri->egl_surface = eglCreateWindowSurface(tri->egl->dpy,
+						  tri->egl->conf,
+						  tri->egl_window, NULL);
+
+	ret = eglMakeCurrent(tri->egl->dpy, tri->egl_surface,
+			     tri->egl_surface, tri->egl->ctx);
+	assert(ret == EGL_TRUE);
+
+	egl_make_swapbuffers_nonblock(tri->egl);
+	triangle_init_gl(&tri->gl);
+}
+
+/********* The widget code interfacing the toolkit agnostic code: **********/
+
+static void
+triangle_resize_handler(struct widget *widget,
+			int32_t width, int32_t height, void *data)
+{
+	struct triangle *tri = data;
+
+	DBG("to %dx%d\n", width, height);
+	tri->width = width;
+	tri->height = height;
+
+	if (tri->egl_surface) {
+		wl_egl_window_resize(tri->egl_window, width, height, 0, 0);
+	} else {
+		triangle_create_egl_surface(tri, width, height);
+		triangle_frame_callback(tri, NULL, 0);
+	}
+}
+
+static void
+triangle_redraw_handler(struct widget *widget, void *data)
+{
+	struct triangle *tri = data;
+	int w, h;
+
+	wl_egl_window_get_attached_size(tri->egl_window, &w, &h);
+
+	DBG("previous %dx%d, new %dx%d\n", w, h, tri->width, tri->height);
+
+	/* If size is not changing, do not redraw ahead of time.
+	 * That would risk blocking in eglSwapbuffers().
+	 */
+	if (w == tri->width && h == tri->height)
+		return;
+
+	if (tri->frame_cb) {
+		wl_callback_destroy(tri->frame_cb);
+		tri->frame_cb = NULL;
+	}
+	triangle_frame_callback(tri, NULL, tri->time);
+}
+
+static void
+set_empty_input_region(struct widget *widget, struct display *display)
+{
+	struct wl_compositor *compositor;
+	struct wl_surface *surface;
+	struct wl_region *region;
+
+	compositor = display_get_compositor(display);
+	surface = widget_get_wl_surface(widget);
+	region = wl_compositor_create_region(compositor);
+	wl_surface_set_input_region(surface, region);
+	wl_region_destroy(region);
+}
+
+static struct triangle *
+triangle_create(struct window *window, struct egl_state *egl)
+{
+	struct triangle *tri;
+
+	tri = calloc(1, sizeof *tri);
+
+	tri->egl = egl;
+	tri->widget = window_add_subsurface(window, tri,
+		int_to_mode(option_triangle_mode));
+	widget_set_resize_handler(tri->widget, triangle_resize_handler);
+	widget_set_redraw_handler(tri->widget, triangle_redraw_handler);
+
+	set_empty_input_region(tri->widget, window_get_display(window));
+
+	return tri;
+}
+
+static void
+triangle_destroy(struct triangle *tri)
+{
+	if (tri->egl_surface)
+		eglDestroySurface(tri->egl->dpy, tri->egl_surface);
+
+	if (tri->egl_window)
+		wl_egl_window_destroy(tri->egl_window);
+
+	widget_destroy(tri->widget);
+	free(tri);
+}
+
+/************** The toytoolkit application code: *********************/
+
+struct demoapp {
+	struct display *display;
+	struct window *window;
+	struct widget *widget;
+	struct widget *subsurface;
+
+	struct egl_state *egl;
+	struct triangle *triangle;
+
+	int animate;
+};
+
+static void
+draw_spinner(cairo_t *cr, const struct rectangle *rect, uint32_t time)
+{
+	double cx, cy, r, angle;
+	unsigned t;
+
+	cx = rect->x + rect->width / 2;
+	cy = rect->y + rect->height / 2;
+	r = (rect->width < rect->height ? rect->width : rect->height) * 0.3;
+	t = time % 2000;
+	angle = t * (M_PI / 500.0);
+
+	cairo_set_line_width(cr, 4.0);
+
+	if (t < 1000)
+		cairo_arc(cr, cx, cy, r, 0.0, angle);
+	else
+		cairo_arc(cr, cx, cy, r, angle, 0.0);
+
+	cairo_stroke(cr);
+}
+
+static void
+sub_redraw_handler(struct widget *widget, void *data)
+{
+	struct demoapp *app = data;
+	cairo_t *cr;
+	struct rectangle allocation;
+	uint32_t time;
+
+	widget_get_allocation(app->subsurface, &allocation);
+
+	cr = widget_cairo_create(widget);
+	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+
+	cairo_set_source_rgba(cr, 0.9, 0.0, 0.9, 1.0);
+	cairo_paint(cr);
+
+	cairo_rectangle(cr,
+			allocation.x,
+			allocation.y,
+			allocation.width,
+			allocation.height);
+	cairo_clip(cr);
+
+	cairo_set_source_rgba(cr, 0.8, 0, 0, 0.8);
+	cairo_paint(cr);
+
+	time = widget_get_last_time(widget);
+	cairo_set_source_rgba(cr, 1.0, 0.5, 0.5, 1.0);
+	draw_spinner(cr, &allocation, time);
+
+	cairo_destroy(cr);
+
+	if (app->animate)
+		widget_schedule_redraw(app->subsurface);
+	DBG("%dx%d @ %d,%d, last time %u\n",
+	    allocation.width, allocation.height,
+	    allocation.x, allocation.y, time);
+}
+
+static void
+sub_resize_handler(struct widget *widget,
+		   int32_t width, int32_t height, void *data)
+{
+	DBG("%dx%d\n", width, height);
+	widget_input_region_add(widget, NULL);
+}
+
+static void
+redraw_handler(struct widget *widget, void *data)
+{
+	struct demoapp *app = data;
+	cairo_t *cr;
+	struct rectangle allocation;
+	uint32_t time;
+
+	widget_get_allocation(app->widget, &allocation);
+
+	cr = widget_cairo_create(widget);
+	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+	cairo_rectangle(cr,
+			allocation.x,
+			allocation.y,
+			allocation.width,
+			allocation.height);
+	cairo_set_source_rgba(cr, 0, 0.8, 0, 0.8);
+	cairo_fill(cr);
+
+	time = widget_get_last_time(widget);
+	cairo_set_source_rgba(cr, 0.5, 1.0, 0.5, 1.0);
+	draw_spinner(cr, &allocation, time);
+
+	cairo_destroy(cr);
+
+	DBG("%dx%d @ %d,%d, last time %u\n",
+	    allocation.width, allocation.height,
+	    allocation.x, allocation.y, time);
+}
+
+static void
+resize_handler(struct widget *widget,
+	       int32_t width, int32_t height, void *data)
+{
+	struct demoapp *app = data;
+	struct rectangle area;
+	int side, h;
+
+	widget_get_allocation(widget, &area);
+
+	side = area.width < area.height ? area.width / 2 : area.height / 2;
+	h = area.height - side;
+
+	widget_set_allocation(app->subsurface,
+			      area.x + area.width - side,
+			      area.y,
+			      side, h);
+
+	if (app->triangle) {
+		widget_set_allocation(app->triangle->widget,
+				      area.x + area.width - side,
+				      area.y + h,
+				      side, side);
+	}
+
+	DBG("green %dx%d, red %dx%d, GL %dx%d\n",
+	    area.width, area.height, side, h, side, side);
+}
+
+static void
+keyboard_focus_handler(struct window *window,
+		       struct input *device, void *data)
+{
+	struct demoapp *app = data;
+
+	window_schedule_redraw(app->window);
+}
+
+static void
+key_handler(struct window *window, struct input *input, uint32_t time,
+	    uint32_t key, uint32_t sym,
+	    enum wl_keyboard_key_state state, void *data)
+{
+	struct demoapp *app = data;
+	struct rectangle winrect;
+
+	if (state == WL_KEYBOARD_KEY_STATE_RELEASED)
+		return;
+
+	switch (sym) {
+	case XKB_KEY_space:
+		app->animate = !app->animate;
+		window_schedule_redraw(window);
+		break;
+	case XKB_KEY_Up:
+		window_get_allocation(window, &winrect);
+		winrect.height -= 100;
+		if (winrect.height < 150)
+			winrect.height = 150;
+		window_schedule_resize(window, winrect.width, winrect.height);
+		break;
+	case XKB_KEY_Down:
+		window_get_allocation(window, &winrect);
+		winrect.height += 100;
+		if (winrect.height > 600)
+			winrect.height = 600;
+		window_schedule_resize(window, winrect.width, winrect.height);
+		break;
+	case XKB_KEY_Escape:
+		display_exit(app->display);
+		break;
+	}
+}
+
+static struct demoapp *
+demoapp_create(struct display *display)
+{
+	struct demoapp *app;
+
+	app = calloc(1, sizeof *app);
+	if (!app)
+		return NULL;
+
+	app->egl = egl_state_create(display_get_display(display));
+
+	app->display = display;
+	display_set_user_data(app->display, app);
+
+	app->window = window_create(app->display);
+	app->widget = frame_create(app->window, app);
+	window_set_title(app->window, "Wayland Sub-surface Demo");
+
+	window_set_key_handler(app->window, key_handler);
+	window_set_user_data(app->window, app);
+	window_set_keyboard_focus_handler(app->window, keyboard_focus_handler);
+
+	widget_set_redraw_handler(app->widget, redraw_handler);
+	widget_set_resize_handler(app->widget, resize_handler);
+
+	app->subsurface = window_add_subsurface(app->window, app,
+		int_to_mode(option_red_mode));
+	widget_set_redraw_handler(app->subsurface, sub_redraw_handler);
+	widget_set_resize_handler(app->subsurface, sub_resize_handler);
+
+	if (app->egl && !option_no_triangle)
+		app->triangle = triangle_create(app->window, app->egl);
+
+	/* minimum size */
+	widget_schedule_resize(app->widget, 100, 100);
+
+	/* initial size */
+	widget_schedule_resize(app->widget, 400, 300);
+
+	app->animate = 1;
+
+	return app;
+}
+
+static void
+demoapp_destroy(struct demoapp *app)
+{
+	if (app->triangle)
+		triangle_destroy(app->triangle);
+
+	if (app->egl)
+		egl_state_destroy(app->egl);
+
+	widget_destroy(app->subsurface);
+	widget_destroy(app->widget);
+	window_destroy(app->window);
+	free(app);
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct display *display;
+	struct demoapp *app;
+
+	parse_options(options, ARRAY_LENGTH(options), &argc, argv);
+	if (option_help) {
+		printf(help_text, argv[0]);
+		return 0;
+	}
+
+	display = display_create(&argc, argv);
+	if (display == NULL) {
+		fprintf(stderr, "failed to create display: %m\n");
+		return -1;
+	}
+
+	app = demoapp_create(display);
+
+	display_run(display);
+
+	demoapp_destroy(app);
+	display_destroy(display);
+
+	return 0;
+}
diff --git a/clients/window.c b/clients/window.c
index 4730c89..ca6fb69 100644
--- a/clients/window.c
+++ b/clients/window.c
@@ -197,6 +197,7 @@ struct surface {
 	struct widget *widget;
 	int redraw_needed;
 	struct wl_callback *frame_cb;
+	uint32_t last_time;
 
 	struct rectangle allocation;
 	struct rectangle server_allocation;
@@ -1543,6 +1544,33 @@ widget_cairo_create(struct widget *widget)
 	return cr;
 }
 
+struct wl_surface *
+widget_get_wl_surface(struct widget *widget)
+{
+	return widget->surface->surface;
+}
+
+uint32_t
+widget_get_last_time(struct widget *widget)
+{
+	return widget->surface->last_time;
+}
+
+void
+widget_input_region_add(struct widget *widget, const struct rectangle *rect)
+{
+	struct wl_compositor *comp = widget->window->display->compositor;
+	struct surface *surface = widget->surface;
+
+	if (!surface->input_region)
+		surface->input_region = wl_compositor_create_region(comp);
+
+	if (rect) {
+		wl_region_add(surface->input_region,
+			      rect->x, rect->y, rect->width, rect->height);
+	}
+}
+
 void
 widget_set_resize_handler(struct widget *widget,
 			  widget_resize_handler_t handler)
@@ -3457,6 +3485,8 @@ frame_callback(void *data, struct wl_callback *callback, uint32_t time)
 	wl_callback_destroy(callback);
 	surface->frame_cb = NULL;
 
+	surface->last_time = time;
+
 	if (surface->redraw_needed || surface->window->redraw_needed)
 		window_schedule_redraw_task(surface->window);
 }
diff --git a/clients/window.h b/clients/window.h
index 30466c0..ccba1cf 100644
--- a/clients/window.h
+++ b/clients/window.h
@@ -390,6 +390,15 @@ widget_get_user_data(struct widget *widget);
 cairo_t *
 widget_cairo_create(struct widget *widget);
 
+struct wl_surface *
+widget_get_wl_surface(struct widget *widget);
+
+uint32_t
+widget_get_last_time(struct widget *widget);
+
+void
+widget_input_region_add(struct widget *widget, const struct rectangle *rect);
+
 void
 widget_set_redraw_handler(struct widget *widget,
 			  widget_redraw_handler_t handler);
diff --git a/configure.ac b/configure.ac
index 74eb892..a7a0430 100644
--- a/configure.ac
+++ b/configure.ac
@@ -261,6 +261,9 @@ AM_CONDITIONAL(HAVE_PANGO, test "x$have_pango" = "xyes")
 AM_CONDITIONAL(BUILD_FULL_GL_CLIENTS,
 	       test x$cairo_modules = "xcairo-gl" -a "x$have_cairo_egl" = "xyes" -a "x$enable_egl" = "xyes")
 
+AM_CONDITIONAL(BUILD_SUBSURFACES_CLIENT,
+	       [test '(' "x$have_cairo_egl" != "xyes" -o "x$cairo_modules" = "xcairo-glesv2" ')' -a "x$enable_simple_egl_clients" = "xyes"])
+
 AM_CONDITIONAL(ENABLE_DESKTOP_SHELL, true)
 
 AC_ARG_ENABLE(tablet-shell,
-- 
1.7.12.4



More information about the wayland-devel mailing list