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.
weston/clients/subsurfaces.c

803 lines
20 KiB

clients: add subsurfaces demo 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 window methods, because a widget may lie on a different wl_surface (sub-surface) than the window. Changes in v3: - replace set_commit_mode with set_sync and set_desync Signed-off-by: Pekka Paalanen <ppaalanen@gmail.com>
12 years ago
/*
* 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);
clients: add subsurfaces demo 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 window methods, because a widget may lie on a different wl_surface (sub-surface) than the window. Changes in v3: - replace set_commit_mode with set_sync and set_desync Signed-off-by: Pekka Paalanen <ppaalanen@gmail.com>
12 years ago
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);
clients: add subsurfaces demo 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 window methods, because a widget may lie on a different wl_surface (sub-surface) than the window. Changes in v3: - replace set_commit_mode with set_sync and set_desync Signed-off-by: Pekka Paalanen <ppaalanen@gmail.com>
12 years ago
tri->egl = egl;
tri->widget = window_add_subsurface(window, tri,
int_to_mode(option_triangle_mode));
widget_set_use_cairo(tri->widget, 0);
clients: add subsurfaces demo 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 window methods, because a widget may lie on a different wl_surface (sub-surface) than the window. Changes in v3: - replace set_commit_mode with set_sync and set_desync Signed-off-by: Pekka Paalanen <ppaalanen@gmail.com>
12 years ago
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);
clients: add subsurfaces demo 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 window methods, because a widget may lie on a different wl_surface (sub-surface) than the window. Changes in v3: - replace set_commit_mode with set_sync and set_desync Signed-off-by: Pekka Paalanen <ppaalanen@gmail.com>
12 years ago
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 = window_frame_create(app->window, app);
clients: add subsurfaces demo 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 window methods, because a widget may lie on a different wl_surface (sub-surface) than the window. Changes in v3: - replace set_commit_mode with set_sync and set_desync Signed-off-by: Pekka Paalanen <ppaalanen@gmail.com>
12 years ago
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;
}
clients: add subsurfaces demo 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 window methods, because a widget may lie on a different wl_surface (sub-surface) than the window. Changes in v3: - replace set_commit_mode with set_sync and set_desync Signed-off-by: Pekka Paalanen <ppaalanen@gmail.com>
12 years ago
app = demoapp_create(display);
display_run(display);
demoapp_destroy(app);
display_destroy(display);
return 0;
}