mutter/clutter/clutter-stage-glx.c

899 lines
22 KiB
C
Raw Normal View History

/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Authored By Matthew Allum <mallum@openedhand.com>
*
* Copyright (C) 2006 OpenedHand
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:clutter-stage
* @short_description: Top level visual element to which actors are placed.
*
* #ClutterStage is a top level 'window' on which child actors are placed
* and manipulated.
*/
#include "config.h"
#include "clutter-stage.h"
#include "clutter-main.h"
#include "clutter-feature.h"
#include "clutter-color.h"
#include "clutter-util.h"
#include "clutter-marshal.h"
#include "clutter-enum-types.h"
#include "clutter-private.h"
#include "clutter-debug.h"
#include "clutter-stage-glx.h"
#include "clutter-backend-glx.h"
#include <GL/glx.h>
#include <GL/gl.h>
#include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
struct _ClutterStageBackend
{
XVisualInfo *xvisinfo;
Window xwin;
Pixmap xpixmap;
gint xwin_width, xwin_height; /* FIXME target_width / height */
GLXPixmap glxpixmap;
GLXContext gl_context;
gboolean is_foreign_xwin;
};
typedef struct
{
GSource source;
Display *display;
GPollFD event_poll_fd;
}
ClutterXEventSource;
typedef void (*ClutterXEventFunc) (XEvent *xev, gpointer user_data);
static gboolean
x_event_prepare (GSource *source,
gint *timeout)
{
Display *display = ((ClutterXEventSource*)source)->display;
*timeout = -1;
return XPending (display);
}
static gboolean
x_event_check (GSource *source)
{
ClutterXEventSource *display_source = (ClutterXEventSource*)source;
gboolean retval;
if (display_source->event_poll_fd.revents & G_IO_IN)
retval = XPending (display_source->display);
else
retval = FALSE;
return retval;
}
static gboolean
x_event_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
Display *display = ((ClutterXEventSource*)source)->display;
ClutterXEventFunc event_func = (ClutterXEventFunc) callback;
XEvent xev;
if (XPending (display))
{
XNextEvent (display, &xev);
if (event_func)
(*event_func) (&xev, user_data);
}
return TRUE;
}
static const GSourceFuncs x_event_funcs = {
x_event_prepare,
x_event_check,
x_event_dispatch,
NULL
};
static void
translate_key_event (ClutterKeyEvent *event,
XEvent *xevent)
{
event->type = xevent->xany.type == KeyPress ? CLUTTER_KEY_PRESS
: CLUTTER_KEY_RELEASE;
event->time = xevent->xkey.time;
event->modifier_state = xevent->xkey.state; /* FIXME: handle modifiers */
event->hardware_keycode = xevent->xkey.keycode;
event->keyval = XKeycodeToKeysym(xevent->xkey.display,
xevent->xkey.keycode,
0 ); /* FIXME: index with modifiers */
}
static void
translate_button_event (ClutterButtonEvent *event,
XEvent *xevent)
{
/* FIXME: catch double click */
CLUTTER_NOTE (EVENT, " button event at %ix%i",
xevent->xbutton.x,
xevent->xbutton.y);
event->type = xevent->xany.type == ButtonPress ? CLUTTER_BUTTON_PRESS
: CLUTTER_BUTTON_RELEASE;
event->time = xevent->xbutton.time;
event->x = xevent->xbutton.x;
event->y = xevent->xbutton.y;
event->modifier_state = xevent->xbutton.state; /* includes button masks */
event->button = xevent->xbutton.button;
}
static void
translate_motion_event (ClutterMotionEvent *event,
XEvent *xevent)
{
event->type = CLUTTER_MOTION;
event->time = xevent->xbutton.time;
event->x = xevent->xmotion.x;
event->y = xevent->xmotion.y;
event->modifier_state = xevent->xmotion.state;
}
static void
clutter_dispatch_x_event (XEvent *xevent,
gpointer data)
{
ClutterMainContext *ctx = CLUTTER_CONTEXT ();
ClutterEvent event;
ClutterStage *stage = ctx->stage;
gboolean emit_input_event = FALSE;
switch (xevent->type)
{
case Expose:
{
XEvent foo_xev;
/* Cheap compress */
while (XCheckTypedWindowEvent(clutter_glx_display(),
xevent->xexpose.window,
Expose,
&foo_xev));
/* FIXME: need to make stage an 'actor' so can que
* a paint direct from there rather than hack here...
*/
clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
}
break;
case KeyPress:
translate_key_event ((ClutterKeyEvent *) &event, xevent);
g_signal_emit_by_name (stage, "key-press-event", &event);
emit_input_event = TRUE;
break;
case KeyRelease:
translate_key_event ((ClutterKeyEvent *) &event, xevent);
g_signal_emit_by_name (stage, "key-release-event", &event);
emit_input_event = TRUE;
break;
case ButtonPress:
translate_button_event ((ClutterButtonEvent *) &event, xevent);
g_signal_emit_by_name (stage, "button-press-event", &event);
emit_input_event = TRUE;
break;
case ButtonRelease:
translate_button_event ((ClutterButtonEvent *) &event, xevent);
g_signal_emit_by_name (stage, "button-release-event", &event);
emit_input_event = TRUE;
break;
case MotionNotify:
translate_motion_event ((ClutterMotionEvent *) &event, xevent);
g_signal_emit_by_name (stage, "motion-event", &event);
emit_input_event = TRUE;
break;
}
if (emit_input_event)
g_signal_emit_by_name (stage, "input-event", &event);
}
static void
events_init()
{
ClutterMainContext *clutter_context;
GMainContext *gmain_context;
int connection_number;
GSource *source;
ClutterXEventSource *display_source;
clutter_context = clutter_context_get_default ();
gmain_context = g_main_context_default ();
g_main_context_ref (gmain_context);
connection_number = ConnectionNumber (clutter_glx_display());
source = g_source_new ((GSourceFuncs *)&x_event_funcs,
sizeof (ClutterXEventSource));
display_source = (ClutterXEventSource *)source;
display_source->event_poll_fd.fd = connection_number;
display_source->event_poll_fd.events = G_IO_IN;
display_source->display = clutter_glx_display();
g_source_add_poll (source, &display_source->event_poll_fd);
g_source_set_can_recurse (source, TRUE);
g_source_set_callback (source,
(GSourceFunc) clutter_dispatch_x_event,
NULL /* no userdata */, NULL);
g_source_attach (source, gmain_context);
g_source_unref (source);
}
static void
sync_fullscreen (ClutterStage *stage)
{
Atom atom_WINDOW_STATE, atom_WINDOW_STATE_FULLSCREEN;
gboolean want_fullscreen;
atom_WINDOW_STATE
= XInternAtom(clutter_glx_display(), "_NET_WM_STATE", False);
atom_WINDOW_STATE_FULLSCREEN
= XInternAtom(clutter_glx_display(), "_NET_WM_STATE_FULLSCREEN",False);
g_object_get (stage, "fullscreen", &want_fullscreen, NULL);
if (want_fullscreen)
{
clutter_actor_set_size (CLUTTER_ACTOR(stage),
DisplayWidth(clutter_glx_display(),
clutter_glx_screen()),
DisplayHeight(clutter_glx_display(),
clutter_glx_screen()));
if (stage->backend->xwin != None)
XChangeProperty(clutter_glx_display(), stage->backend->xwin,
atom_WINDOW_STATE, XA_ATOM, 32,
PropModeReplace,
(unsigned char *)&atom_WINDOW_STATE_FULLSCREEN, 1);
}
else
{
if (stage->backend->xwin != None)
XDeleteProperty(clutter_glx_display(),
stage->backend->xwin, atom_WINDOW_STATE);
}
}
static void
sync_cursor (ClutterStage *stage)
{
gboolean hide_cursor;
if (stage->backend->xwin == None)
return;
g_object_get (stage, "hide-cursor", &hide_cursor, NULL);
/* FIXME: Use XFixesHideCursor */
if (hide_cursor)
{
XColor col;
Pixmap pix;
Cursor curs;
pix = XCreatePixmap (clutter_glx_display(),
stage->backend->xwin, 1, 1, 1);
memset (&col, 0, sizeof (col));
curs = XCreatePixmapCursor (clutter_glx_display(),
pix, pix, &col, &col, 1, 1);
XFreePixmap (clutter_glx_display(), pix);
XDefineCursor(clutter_glx_display(), stage->backend->xwin, curs);
}
else
{
XUndefineCursor(clutter_glx_display(), stage->backend->xwin);
}
}
/* FIXME -> CGL */
static void
frustum (GLfloat left,
GLfloat right,
GLfloat bottom,
GLfloat top,
GLfloat nearval,
GLfloat farval)
{
GLfloat x, y, a, b, c, d;
GLfloat m[16];
x = (2.0 * nearval) / (right - left);
y = (2.0 * nearval) / (top - bottom);
a = (right + left) / (right - left);
b = (top + bottom) / (top - bottom);
c = -(farval + nearval) / ( farval - nearval);
d = -(2.0 * farval * nearval) / (farval - nearval);
#define M(row,col) m[col*4+row]
M(0,0) = x; M(0,1) = 0.0F; M(0,2) = a; M(0,3) = 0.0F;
M(1,0) = 0.0F; M(1,1) = y; M(1,2) = b; M(1,3) = 0.0F;
M(2,0) = 0.0F; M(2,1) = 0.0F; M(2,2) = c; M(2,3) = d;
M(3,0) = 0.0F; M(3,1) = 0.0F; M(3,2) = -1.0F; M(3,3) = 0.0F;
#undef M
glMultMatrixf (m);
}
static void
perspective (GLfloat fovy,
GLfloat aspect,
GLfloat zNear,
GLfloat zFar)
{
GLfloat xmin, xmax, ymin, ymax;
ymax = zNear * tan (fovy * M_PI / 360.0);
ymin = -ymax;
xmin = ymin * aspect;
xmax = ymax * aspect;
frustum (xmin, xmax, ymin, ymax, zNear, zFar);
}
static void
sync_viewport (ClutterStage *stage)
{
glViewport (0, 0, stage->backend->xwin_width, stage->backend->xwin_height);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
perspective (60.0f, 1.0f, 0.1f, 100.0f);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
/* Then for 2D like transform */
/* camera distance from screen, 0.5 * tan (FOV) */
#define DEFAULT_Z_CAMERA 0.866025404f
glTranslatef (-0.5f, -0.5f, -DEFAULT_Z_CAMERA);
glScalef (1.0f / stage->backend->xwin_width,
-1.0f / stage->backend->xwin_height,
1.0f / stage->backend->xwin_width);
glTranslatef (0.0f, -stage->backend->xwin_height, 0.0f);
}
static void
clutter_stage_glx_show (ClutterActor *self)
{
if (clutter_stage_glx_window (CLUTTER_STAGE(self)))
XMapWindow (clutter_glx_display(),
clutter_stage_glx_window (CLUTTER_STAGE(self)));
}
static void
clutter_stage_glx_hide (ClutterActor *self)
{
if (clutter_stage_glx_window (CLUTTER_STAGE(self)))
XUnmapWindow (clutter_glx_display(),
clutter_stage_glx_window (CLUTTER_STAGE(self)));
}
static void
clutter_stage_glx_unrealize (ClutterActor *actor)
{
ClutterStage *stage;
ClutterStagePrivate *priv;
ClutterStageBackend *backend;
gboolean want_offscreen;
stage = CLUTTER_STAGE(actor);
priv = stage->priv;
backend = stage->backend;
CLUTTER_MARK();
g_object_get (stage, "offscreen", &want_offscreen, NULL);
if (want_offscreen)
{
if (backend->glxpixmap)
{
glXDestroyGLXPixmap (clutter_glx_display(), backend->glxpixmap);
backend->glxpixmap = None;
}
if (backend->xpixmap)
{
XFreePixmap (clutter_glx_display(), backend->xpixmap);
backend->xpixmap = None;
}
}
else
{
if (!backend->is_foreign_xwin && backend->xwin != None)
{
XDestroyWindow (clutter_glx_display(), backend->xwin);
backend->xwin = None;
}
else
backend->xwin = None;
}
glXMakeCurrent(clutter_glx_display(), None, NULL);
if (backend->gl_context != None)
{
glXDestroyContext (clutter_glx_display(), backend->gl_context);
backend->gl_context = None;
}
}
static void
clutter_stage_glx_realize (ClutterActor *actor)
{
ClutterStage *stage;
ClutterStagePrivate *priv;
ClutterStageBackend *backend;
gboolean want_offscreen;
stage = CLUTTER_STAGE(actor);
priv = stage->priv;
backend = stage->backend;
CLUTTER_MARK();
g_object_get (stage, "offscreen", &want_offscreen, NULL);
if (want_offscreen)
{
int gl_attributes[] = {
GLX_RGBA,
GLX_RED_SIZE, 1,
GLX_GREEN_SIZE, 1,
GLX_BLUE_SIZE, 1,
0
};
if (backend->xvisinfo)
XFree(backend->xvisinfo);
backend->xvisinfo = glXChooseVisual (clutter_glx_display(),
clutter_glx_screen(),
gl_attributes);
if (!backend->xvisinfo)
{
g_critical ("Unable to find suitable GL visual.");
CLUTTER_ACTOR_UNSET_FLAGS (actor, CLUTTER_ACTOR_REALIZED);
return;
}
if (backend->gl_context)
glXDestroyContext (clutter_glx_display(), backend->gl_context);
backend->xpixmap = XCreatePixmap (clutter_glx_display(),
clutter_glx_root_window(),
backend->xwin_width,
backend->xwin_height,
backend->xvisinfo->depth);
backend->glxpixmap = glXCreateGLXPixmap(clutter_glx_display(),
backend->xvisinfo,
backend->xpixmap);
sync_fullscreen (stage);
/* indirect */
backend->gl_context = glXCreateContext (clutter_glx_display(),
backend->xvisinfo,
0,
False);
glXMakeCurrent(clutter_glx_display(),
backend->glxpixmap, backend->gl_context);
#if 0
/* Debug code for monitoring a off screen pixmap via window */
{
Colormap cmap;
XSetWindowAttributes swa;
cmap = XCreateColormap(clutter_glx_display(),
clutter_glx_root_window(),
backend->xvisinfo->visual, AllocNone);
/* create a window */
swa.colormap = cmap;
foo_win = XCreateWindow(clutter_glx_display(),
clutter_glx_root_window(),
0, 0,
backend->xwin_width, backend->xwin_height,
0,
backend->xvisinfo->depth,
InputOutput,
backend->xvisinfo->visual,
CWColormap, &swa);
XMapWindow(clutter_glx_display(), foo_win);
}
#endif
}
else
{
int gl_attributes[] =
{
GLX_RGBA,
GLX_DOUBLEBUFFER,
GLX_RED_SIZE, 1,
GLX_GREEN_SIZE, 1,
GLX_BLUE_SIZE, 1,
GLX_STENCIL_SIZE, 1,
0
};
if (backend->xvisinfo)
XFree(backend->xvisinfo);
if (backend->xvisinfo == None)
backend->xvisinfo = glXChooseVisual (clutter_glx_display(),
clutter_glx_screen(),
gl_attributes);
if (!backend->xvisinfo)
{
g_critical ("Unable to find suitable GL visual.");
CLUTTER_ACTOR_UNSET_FLAGS (actor, CLUTTER_ACTOR_REALIZED);
return;
}
if (backend->xwin == None)
backend->xwin = XCreateSimpleWindow(clutter_glx_display(),
clutter_glx_root_window(),
0, 0,
backend->xwin_width, backend->xwin_height,
0, 0,
WhitePixel(clutter_glx_display(),
clutter_glx_screen()));
XSelectInput(clutter_glx_display(),
backend->xwin,
StructureNotifyMask
|ExposureMask
/* FIXME: we may want to eplicity enable MotionMask */
|PointerMotionMask
|KeyPressMask
|KeyReleaseMask
|ButtonPressMask
|ButtonReleaseMask
|PropertyChangeMask);
sync_fullscreen (stage);
sync_cursor (stage);
if (backend->gl_context)
glXDestroyContext (clutter_glx_display(), backend->gl_context);
backend->gl_context = glXCreateContext (clutter_glx_display(),
backend->xvisinfo,
0,
True);
if (backend->gl_context == None)
{
g_critical ("Unable to create suitable GL context.");
CLUTTER_ACTOR_UNSET_FLAGS (actor, CLUTTER_ACTOR_REALIZED);
return;
}
glXMakeCurrent(clutter_glx_display(), backend->xwin, backend->gl_context);
}
CLUTTER_NOTE (GL,
"\n"
"===========================================\n"
"GL_VENDOR: %s\n"
"GL_RENDERER: %s\n"
"GL_VERSION: %s\n"
"GL_EXTENSIONS: %s\n"
"Is direct: %s\n"
"===========================================\n",
glGetString (GL_VENDOR),
glGetString (GL_RENDERER),
glGetString (GL_VERSION),
glGetString (GL_EXTENSIONS),
glXIsDirect(clutter_glx_display(), backend->gl_context) ? "yes" : "no"
);
sync_viewport (stage);
}
static void
clutter_stage_glx_paint (ClutterActor *self)
{
ClutterStage *stage = CLUTTER_STAGE(self);
ClutterColor stage_color;
static GTimer *timer = NULL;
static guint timer_n_frames = 0;
static ClutterActorClass *parent_class = NULL;
CLUTTER_NOTE (PAINT, " Redraw enter");
if (parent_class == NULL)
parent_class = g_type_class_peek_parent (CLUTTER_STAGE_GET_CLASS(stage));
if (clutter_want_fps ())
{
if (!timer)
timer = g_timer_new ();
}
clutter_stage_get_color (stage, &stage_color);
glClearColor(((float) stage_color.red / 0xff * 1.0),
((float) stage_color.green / 0xff * 1.0),
((float) stage_color.blue / 0xff * 1.0),
0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
parent_class->paint (self);
if (clutter_stage_glx_window (stage))
{
clutter_feature_wait_for_vblank ();
glXSwapBuffers(clutter_glx_display(), clutter_stage_glx_window (stage));
}
else
{
glXWaitGL();
CLUTTER_GLERR();
}
if (clutter_want_fps ())
{
timer_n_frames++;
if (g_timer_elapsed (timer, NULL) >= 1.0)
{
g_print ("*** FPS: %i ***\n", timer_n_frames);
timer_n_frames = 0;
g_timer_start (timer);
}
}
CLUTTER_NOTE (PAINT, " Redraw leave");
}
static void
clutter_stage_glx_allocate_coords (ClutterActor *self,
ClutterActorBox *box)
{
/* Do nothing, just stop group_allocate getting called */
/* TODO: sync up with any configure events from WM ?? */
return;
}
static void
clutter_stage_glx_request_coords (ClutterActor *self,
ClutterActorBox *box)
{
ClutterStage *stage;
ClutterStageBackend *backend;
gint new_width, new_height;
stage = CLUTTER_STAGE (self);
backend = stage->backend;
/* FIXME: some how have X configure_notfiys call this ?
*/
new_width = ABS(box->x2 - box->x1);
new_height = ABS(box->y2 - box->y1);
if (new_width != backend->xwin_width || new_height != backend->xwin_height)
{
backend->xwin_width = new_width;
backend->xwin_height = new_height;
if (backend->xwin != None)
XResizeWindow (clutter_glx_display(),
backend->xwin,
backend->xwin_width,
backend->xwin_height);
if (backend->xpixmap != None)
{
/* Need to recreate to resize */
clutter_actor_unrealize(self);
clutter_actor_realize(self);
}
sync_viewport (stage);
}
if (backend->xwin != None) /* Do we want to bother ? */
XMoveWindow (clutter_glx_display(),
backend->xwin,
box->x1,
box->y1);
}
static void
clutter_stage_glx_dispose (GObject *object)
{
#if 0
ClutterStage *self = CLUTTER_STAGE (object);
if (self->backend->xwin)
clutter_actor_unrealize (CLUTTER_ACTOR (self));
G_OBJECT_CLASS (clutter_stage_parent_class)->dispose (object);
#endif
}
static void
clutter_stage_glx_finalize (GObject *object)
{
#if 0
G_OBJECT_CLASS (clutter_stage_parent_class)->finalize (object);
#endif
}
void
clutter_stage_backend_init_vtable (ClutterStageVTable *vtable)
{
vtable->show = clutter_stage_glx_show;
vtable->hide = clutter_stage_glx_hide;
vtable->realize = clutter_stage_glx_realize;
vtable->unrealize = clutter_stage_glx_unrealize;
vtable->paint = clutter_stage_glx_paint;
vtable->request_coords = clutter_stage_glx_request_coords;
vtable->allocate_coords = clutter_stage_glx_allocate_coords;
vtable->sync_fullscreen = sync_fullscreen;
vtable->sync_cursor = sync_cursor;
vtable->sync_viewport = sync_viewport;
}
ClutterStageBackend*
clutter_stage_backend_init (ClutterStage *stage)
{
ClutterStageBackend *backend;
backend = g_new0(ClutterStageBackend, 1);
backend->xwin_width = 100;
backend->xwin_height = 100;
/* Maybe better somewhere else */
events_init ();
return backend;
}
/**
* clutter_stage_glx_get_xwindow
* @stage: A #ClutterStage
*
* Get the stage's underlying x window ID.
*
* Return Value: Stage X Window XID
*
* Since: 0.3
**/
Window
clutter_stage_glx_window (ClutterStage *stage)
{
return stage->backend->xwin;
}
/**
* clutter_stage_set_xwindow_foreign
* @stage: A #ClutterStage
* @xid: A preexisting X Window ID
*
* Target the #ClutterStage to use an existing external X Window.
*
* Return Value: TRUE if foreign window valid, FALSE otherwise
*
* Since: 0.3
**/
gboolean
clutter_stage_glx_set_window_foreign (ClutterStage *stage,
Window xid)
{
/* For screensavers via XSCREENSAVER_WINDOW env var.
* Also for toolkit binding.
*/
gint x,y;
guint width, height, border, depth;
Window root_return;
Status status;
ClutterGeometry geom;
clutter_glx_trap_x_errors();
status = XGetGeometry (clutter_glx_display(),
xid,
&root_return,
&x,
&y,
&width,
&height,
&border,
&depth);
if (clutter_glx_untrap_x_errors() || !status
|| width == 0 || height == 0 || depth != stage->backend->xvisinfo->depth)
return FALSE;
clutter_actor_unrealize (CLUTTER_ACTOR(stage));
stage->backend->xwin = xid;
geom.x = x;
geom.y = y;
geom.width = stage->backend->xwin_width = width;
geom.height = stage->backend->xwin_height = height;
clutter_actor_set_geometry (CLUTTER_ACTOR(stage), &geom);
clutter_actor_realize (CLUTTER_ACTOR(stage));
return TRUE;
}
/**
* clutter_stage_glx_get_xvisual
* @stage: A #ClutterStage
*
* Get the stage's XVisualInfo.
*
* Return Value: The stage's XVisualInfo
*
* Since: 0.3
**/
const XVisualInfo*
clutter_stage_glx_get_visual (ClutterStage *stage)
{
return stage->backend->xvisinfo;
}