summaryrefslogtreecommitdiff
path: root/clients/subsurfaces.c
diff options
context:
space:
mode:
Diffstat (limited to 'clients/subsurfaces.c')
-rw-r--r--clients/subsurfaces.c801
1 files changed, 801 insertions, 0 deletions
diff --git a/clients/subsurfaces.c b/clients/subsurfaces.c
new file mode 100644
index 00000000..101ff17e
--- /dev/null
+++ b/clients/subsurfaces.c
@@ -0,0 +1,801 @@
+/*
+ * 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;
+static int32_t option_triangle_mode;
+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 subsurface_mode
+int_to_mode(int32_t i)
+{
+ switch (i) {
+ case 0:
+ return SUBSURFACE_DESYNCHRONIZED;
+ case 1:
+ return SUBSURFACE_SYNCHRONIZED;
+ 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 (0)\n"
+" -t, --triangle-mode=MODE\tthe commit mode for the GL sub-surface (0)\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"
+" 0\tfor desynchronized, i.e. free-running\n"
+" 1\tfor synchronized\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 synchronized,\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);
+
+ eglMakeCurrent(tri->egl->dpy, tri->egl_surface,
+ tri->egl_surface, tri->egl->ctx);
+
+ 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 = xmalloc(sizeof *tri);
+ memset(tri, 0, 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);
+
+ /* debug: paint whole surface magenta; no magenta should show */
+ 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 = xmalloc(sizeof *app);
+ memset(app, 0, sizeof *app);
+
+ 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;
+ }
+
+ if (!display_has_subcompositor(display)) {
+ fprintf(stderr, "compositor does not support "
+ "the subcompositor extension\n");
+ return -1;
+ }
+
+ app = demoapp_create(display);
+
+ display_run(display);
+
+ demoapp_destroy(app);
+ display_destroy(display);
+
+ return 0;
+}