You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							792 lines
						
					
					
						
							20 KiB
						
					
					
				
			
		
		
	
	
							792 lines
						
					
					
						
							20 KiB
						
					
					
				| /*
 | |
|  * 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);
 | |
| 
 | |
| 	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);
 | |
| 
 | |
| 	/* 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 = 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;
 | |
| }
 | |
| 
 |