mutter/cogl/cogl/cogl-xlib-renderer.c

647 lines
19 KiB
C

/*
* Cogl
*
* A Low Level GPU Graphics and Utilities API
*
* Copyright (C) 2008,2009,2010 Intel Corporation.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Authors:
* Robert Bragg <robert@linux.intel.com>
*/
#include "cogl-config.h"
#include "cogl-xlib-renderer.h"
#include "cogl-util.h"
#include "cogl-object.h"
#include "cogl-output-private.h"
#include "cogl-renderer-private.h"
#include "cogl-xlib-renderer-private.h"
#include "cogl-x11-renderer-private.h"
#include "cogl-poll-private.h"
#include "winsys/cogl-winsys-private.h"
#include <X11/Xlib.h>
#include <X11/extensions/Xdamage.h>
#include <X11/extensions/Xrandr.h>
#include <stdlib.h>
#include <string.h>
static char *_cogl_x11_display_name = NULL;
static GList *_cogl_xlib_renderers = NULL;
static void
_xlib_renderer_data_free (CoglXlibRenderer *data)
{
if (data->xvisinfo)
XFree (data->xvisinfo);
g_slice_free (CoglXlibRenderer, data);
}
CoglXlibRenderer *
_cogl_xlib_renderer_get_data (CoglRenderer *renderer)
{
/* Constructs a CoglXlibRenderer struct on demand and attaches it to
the object using user data. It's done this way instead of using a
subclassing hierarchy in the winsys data because all EGL winsys's
need the EGL winsys data but only one of them wants the Xlib
data. */
if (!renderer->custom_winsys_user_data)
renderer->custom_winsys_user_data = g_slice_new0 (CoglXlibRenderer);
return renderer->custom_winsys_user_data;
}
static void
register_xlib_renderer (CoglRenderer *renderer)
{
GList *l;
for (l = _cogl_xlib_renderers; l; l = l->next)
if (l->data == renderer)
return;
_cogl_xlib_renderers = g_list_prepend (_cogl_xlib_renderers, renderer);
}
static void
unregister_xlib_renderer (CoglRenderer *renderer)
{
_cogl_xlib_renderers = g_list_remove (_cogl_xlib_renderers, renderer);
}
static CoglRenderer *
get_renderer_for_xdisplay (Display *xdpy)
{
GList *l;
for (l = _cogl_xlib_renderers; l; l = l->next)
{
CoglRenderer *renderer = l->data;
CoglXlibRenderer *xlib_renderer =
_cogl_xlib_renderer_get_data (renderer);
if (xlib_renderer->xdpy == xdpy)
return renderer;
}
return NULL;
}
static int
error_handler (Display *xdpy,
XErrorEvent *error)
{
CoglRenderer *renderer;
CoglXlibRenderer *xlib_renderer;
renderer = get_renderer_for_xdisplay (xdpy);
xlib_renderer = _cogl_xlib_renderer_get_data (renderer);
g_assert (xlib_renderer->trap_state);
xlib_renderer->trap_state->trapped_error_code = error->error_code;
return 0;
}
void
_cogl_xlib_renderer_trap_errors (CoglRenderer *renderer,
CoglXlibTrapState *state)
{
CoglXlibRenderer *xlib_renderer;
xlib_renderer = _cogl_xlib_renderer_get_data (renderer);
state->trapped_error_code = 0;
state->old_error_handler = XSetErrorHandler (error_handler);
state->old_state = xlib_renderer->trap_state;
xlib_renderer->trap_state = state;
}
int
_cogl_xlib_renderer_untrap_errors (CoglRenderer *renderer,
CoglXlibTrapState *state)
{
CoglXlibRenderer *xlib_renderer;
xlib_renderer = _cogl_xlib_renderer_get_data (renderer);
g_assert (state == xlib_renderer->trap_state);
XSetErrorHandler (state->old_error_handler);
xlib_renderer->trap_state = state->old_state;
return state->trapped_error_code;
}
static Display *
assert_xlib_display (CoglRenderer *renderer, GError **error)
{
Display *xdpy = cogl_xlib_renderer_get_foreign_display (renderer);
CoglXlibRenderer *xlib_renderer = _cogl_xlib_renderer_get_data (renderer);
/* A foreign display may have already been set... */
if (xdpy)
{
xlib_renderer->xdpy = xdpy;
return xdpy;
}
xdpy = XOpenDisplay (_cogl_x11_display_name);
if (xdpy == NULL)
{
g_set_error (error,
COGL_RENDERER_ERROR,
COGL_RENDERER_ERROR_XLIB_DISPLAY_OPEN,
"Failed to open X Display %s", _cogl_x11_display_name);
return NULL;
}
xlib_renderer->xdpy = xdpy;
return xdpy;
}
static int
compare_outputs (CoglOutput *a,
CoglOutput *b)
{
return strcmp (a->name, b->name);
}
#define CSO(X) COGL_SUBPIXEL_ORDER_ ## X
static CoglSubpixelOrder subpixel_map[6][6] = {
{ CSO(UNKNOWN), CSO(NONE), CSO(HORIZONTAL_RGB), CSO(HORIZONTAL_BGR),
CSO(VERTICAL_RGB), CSO(VERTICAL_BGR) }, /* 0 */
{ CSO(UNKNOWN), CSO(NONE), CSO(VERTICAL_RGB), CSO(VERTICAL_BGR),
CSO(HORIZONTAL_BGR), CSO(HORIZONTAL_RGB) }, /* 90 */
{ CSO(UNKNOWN), CSO(NONE), CSO(HORIZONTAL_BGR), CSO(HORIZONTAL_RGB),
CSO(VERTICAL_BGR), CSO(VERTICAL_RGB) }, /* 180 */
{ CSO(UNKNOWN), CSO(NONE), CSO(VERTICAL_BGR), CSO(VERTICAL_RGB),
CSO(HORIZONTAL_RGB), CSO(HORIZONTAL_BGR) }, /* 270 */
{ CSO(UNKNOWN), CSO(NONE), CSO(HORIZONTAL_BGR), CSO(HORIZONTAL_RGB),
CSO(VERTICAL_RGB), CSO(VERTICAL_BGR) }, /* Reflect_X */
{ CSO(UNKNOWN), CSO(NONE), CSO(HORIZONTAL_RGB), CSO(HORIZONTAL_BGR),
CSO(VERTICAL_BGR), CSO(VERTICAL_RGB) }, /* Reflect_Y */
};
#undef CSO
static void
update_outputs (CoglRenderer *renderer,
gboolean notify)
{
CoglXlibRenderer *xlib_renderer =
_cogl_xlib_renderer_get_data (renderer);
XRRScreenResources *resources;
CoglXlibTrapState state;
gboolean error = FALSE;
GList *new_outputs = NULL;
GList *l, *m;
gboolean changed = FALSE;
int i;
xlib_renderer->outputs_update_serial = XNextRequest (xlib_renderer->xdpy);
resources = XRRGetScreenResources (xlib_renderer->xdpy,
DefaultRootWindow (xlib_renderer->xdpy));
_cogl_xlib_renderer_trap_errors (renderer, &state);
for (i = 0; resources && i < resources->ncrtc && !error; i++)
{
XRRCrtcInfo *crtc_info = NULL;
XRROutputInfo *output_info = NULL;
CoglOutput *output;
float refresh_rate = 0;
int j;
crtc_info = XRRGetCrtcInfo (xlib_renderer->xdpy,
resources, resources->crtcs[i]);
if (crtc_info == NULL)
{
error = TRUE;
goto next;
}
if (crtc_info->mode == None)
goto next;
for (j = 0; j < resources->nmode; j++)
{
if (resources->modes[j].id == crtc_info->mode)
refresh_rate = (resources->modes[j].dotClock /
((float)resources->modes[j].hTotal *
resources->modes[j].vTotal));
}
output_info = XRRGetOutputInfo (xlib_renderer->xdpy,
resources,
crtc_info->outputs[0]);
if (output_info == NULL)
{
error = TRUE;
goto next;
}
output = _cogl_output_new (output_info->name);
output->x = crtc_info->x;
output->y = crtc_info->y;
output->width = crtc_info->width;
output->height = crtc_info->height;
if ((crtc_info->rotation & (RR_Rotate_90 | RR_Rotate_270)) != 0)
{
output->mm_width = output_info->mm_height;
output->mm_height = output_info->mm_width;
}
else
{
output->mm_width = output_info->mm_width;
output->mm_height = output_info->mm_height;
}
output->refresh_rate = refresh_rate;
switch (output_info->subpixel_order)
{
case SubPixelUnknown:
default:
output->subpixel_order = COGL_SUBPIXEL_ORDER_UNKNOWN;
break;
case SubPixelNone:
output->subpixel_order = COGL_SUBPIXEL_ORDER_NONE;
break;
case SubPixelHorizontalRGB:
output->subpixel_order = COGL_SUBPIXEL_ORDER_HORIZONTAL_RGB;
break;
case SubPixelHorizontalBGR:
output->subpixel_order = COGL_SUBPIXEL_ORDER_HORIZONTAL_BGR;
break;
case SubPixelVerticalRGB:
output->subpixel_order = COGL_SUBPIXEL_ORDER_VERTICAL_RGB;
break;
case SubPixelVerticalBGR:
output->subpixel_order = COGL_SUBPIXEL_ORDER_VERTICAL_BGR;
break;
}
output->subpixel_order = COGL_SUBPIXEL_ORDER_HORIZONTAL_RGB;
/* Handle the effect of rotation and reflection on subpixel order (ugh) */
for (j = 0; j < 6; j++)
{
if ((crtc_info->rotation & (1 << j)) != 0)
output->subpixel_order = subpixel_map[j][output->subpixel_order];
}
new_outputs = g_list_prepend (new_outputs, output);
next:
if (crtc_info != NULL)
XFree (crtc_info);
if (output_info != NULL)
XFree (output_info);
}
XFree (resources);
if (!error)
{
new_outputs = g_list_sort (new_outputs, (GCompareFunc)compare_outputs);
l = new_outputs;
m = renderer->outputs;
while (l || m)
{
int cmp;
CoglOutput *output_l = l ? (CoglOutput *)l->data : NULL;
CoglOutput *output_m = m ? (CoglOutput *)m->data : NULL;
if (l && m)
cmp = compare_outputs (output_l, output_m);
else if (l)
cmp = -1;
else
cmp = 1;
if (cmp == 0)
{
GList *m_next = m->next;
if (!_cogl_output_values_equal (output_l, output_m))
{
renderer->outputs = g_list_remove_link (renderer->outputs, m);
renderer->outputs = g_list_insert_before (renderer->outputs,
m_next, output_l);
cogl_object_ref (output_l);
changed = TRUE;
}
l = l->next;
m = m_next;
}
else if (cmp < 0)
{
renderer->outputs =
g_list_insert_before (renderer->outputs, m, output_l);
cogl_object_ref (output_l);
changed = TRUE;
l = l->next;
}
else
{
GList *m_next = m->next;
renderer->outputs = g_list_remove_link (renderer->outputs, m);
changed = TRUE;
m = m_next;
}
}
}
g_list_free_full (new_outputs, (GDestroyNotify)cogl_object_unref);
_cogl_xlib_renderer_untrap_errors (renderer, &state);
if (changed)
{
const CoglWinsysVtable *winsys = renderer->winsys_vtable;
if (notify)
COGL_NOTE (WINSYS, "Outputs changed:");
else
COGL_NOTE (WINSYS, "Outputs:");
for (l = renderer->outputs; l; l = l->next)
{
CoglOutput *output = l->data;
const char *subpixel_string;
switch (output->subpixel_order)
{
case COGL_SUBPIXEL_ORDER_UNKNOWN:
default:
subpixel_string = "unknown";
break;
case COGL_SUBPIXEL_ORDER_NONE:
subpixel_string = "none";
break;
case COGL_SUBPIXEL_ORDER_HORIZONTAL_RGB:
subpixel_string = "horizontal_rgb";
break;
case COGL_SUBPIXEL_ORDER_HORIZONTAL_BGR:
subpixel_string = "horizontal_bgr";
break;
case COGL_SUBPIXEL_ORDER_VERTICAL_RGB:
subpixel_string = "vertical_rgb";
break;
case COGL_SUBPIXEL_ORDER_VERTICAL_BGR:
subpixel_string = "vertical_bgr";
break;
}
COGL_NOTE (WINSYS,
" %10s: +%d+%dx%dx%d mm=%dx%d dpi=%.1fx%.1f "
"subpixel_order=%s refresh_rate=%.3f",
output->name,
output->x, output->y, output->width, output->height,
output->mm_width, output->mm_height,
output->width / (output->mm_width / 25.4),
output->height / (output->mm_height / 25.4),
subpixel_string,
output->refresh_rate);
}
if (notify && winsys->renderer_outputs_changed != NULL)
winsys->renderer_outputs_changed (renderer);
}
}
static CoglFilterReturn
randr_filter (XEvent *event,
void *data)
{
CoglRenderer *renderer = data;
CoglXlibRenderer *xlib_renderer =
_cogl_xlib_renderer_get_data (renderer);
CoglX11Renderer *x11_renderer =
(CoglX11Renderer *) xlib_renderer;
if (x11_renderer->randr_base != -1 &&
(event->xany.type == x11_renderer->randr_base + RRScreenChangeNotify ||
event->xany.type == x11_renderer->randr_base + RRNotify) &&
event->xany.serial >= xlib_renderer->outputs_update_serial)
update_outputs (renderer, TRUE);
return COGL_FILTER_CONTINUE;
}
static int64_t
prepare_xlib_events_timeout (void *user_data)
{
CoglRenderer *renderer = user_data;
CoglXlibRenderer *xlib_renderer = _cogl_xlib_renderer_get_data (renderer);
return XPending (xlib_renderer->xdpy) ? 0 : -1;
}
static void
dispatch_xlib_events (void *user_data, int revents)
{
CoglRenderer *renderer = user_data;
CoglXlibRenderer *xlib_renderer = _cogl_xlib_renderer_get_data (renderer);
if (renderer->xlib_enable_event_retrieval)
while (XPending (xlib_renderer->xdpy))
{
XEvent xevent;
XNextEvent (xlib_renderer->xdpy, &xevent);
cogl_xlib_renderer_handle_event (renderer, &xevent);
}
}
gboolean
_cogl_xlib_renderer_connect (CoglRenderer *renderer, GError **error)
{
CoglXlibRenderer *xlib_renderer =
_cogl_xlib_renderer_get_data (renderer);
CoglX11Renderer *x11_renderer =
(CoglX11Renderer *) xlib_renderer;
int damage_error;
int randr_error;
if (!assert_xlib_display (renderer, error))
return FALSE;
if (getenv ("COGL_X11_SYNC"))
XSynchronize (xlib_renderer->xdpy, TRUE);
/* Check whether damage events are supported on this display */
if (!XDamageQueryExtension (xlib_renderer->xdpy,
&x11_renderer->damage_base,
&damage_error))
x11_renderer->damage_base = -1;
/* Check whether randr is supported on this display */
if (!XRRQueryExtension (xlib_renderer->xdpy,
&x11_renderer->randr_base,
&randr_error))
x11_renderer->randr_base = -1;
xlib_renderer->trap_state = NULL;
if (renderer->xlib_enable_event_retrieval)
{
_cogl_poll_renderer_add_fd (renderer,
ConnectionNumber (xlib_renderer->xdpy),
COGL_POLL_FD_EVENT_IN,
prepare_xlib_events_timeout,
dispatch_xlib_events,
renderer);
}
XRRSelectInput(xlib_renderer->xdpy,
DefaultRootWindow (xlib_renderer->xdpy),
RRScreenChangeNotifyMask
| RRCrtcChangeNotifyMask
| RROutputPropertyNotifyMask);
update_outputs (renderer, FALSE);
register_xlib_renderer (renderer);
cogl_xlib_renderer_add_filter (renderer,
randr_filter,
renderer);
return TRUE;
}
void
_cogl_xlib_renderer_disconnect (CoglRenderer *renderer)
{
CoglXlibRenderer *xlib_renderer =
_cogl_xlib_renderer_get_data (renderer);
g_list_free_full (renderer->outputs, (GDestroyNotify)cogl_object_unref);
renderer->outputs = NULL;
if (!renderer->foreign_xdpy && xlib_renderer->xdpy)
XCloseDisplay (xlib_renderer->xdpy);
g_clear_pointer (&renderer->custom_winsys_user_data, _xlib_renderer_data_free);
unregister_xlib_renderer (renderer);
}
Display *
cogl_xlib_renderer_get_display (CoglRenderer *renderer)
{
CoglXlibRenderer *xlib_renderer;
g_return_val_if_fail (cogl_is_renderer (renderer), NULL);
xlib_renderer = _cogl_xlib_renderer_get_data (renderer);
return xlib_renderer->xdpy;
}
CoglFilterReturn
cogl_xlib_renderer_handle_event (CoglRenderer *renderer,
XEvent *event)
{
return _cogl_renderer_handle_native_event (renderer, event);
}
void
cogl_xlib_renderer_add_filter (CoglRenderer *renderer,
CoglXlibFilterFunc func,
void *data)
{
_cogl_renderer_add_native_filter (renderer,
(CoglNativeFilterFunc)func, data);
}
void
cogl_xlib_renderer_remove_filter (CoglRenderer *renderer,
CoglXlibFilterFunc func,
void *data)
{
_cogl_renderer_remove_native_filter (renderer,
(CoglNativeFilterFunc)func, data);
}
CoglOutput *
_cogl_xlib_renderer_output_for_rectangle (CoglRenderer *renderer,
int x,
int y,
int width,
int height)
{
int max_overlap = 0;
CoglOutput *max_overlapped = NULL;
GList *l;
int xa1 = x, xa2 = x + width;
int ya1 = y, ya2 = y + height;
for (l = renderer->outputs; l; l = l->next)
{
CoglOutput *output = l->data;
int xb1 = output->x, xb2 = output->x + output->width;
int yb1 = output->y, yb2 = output->y + output->height;
int overlap_x = MIN(xa2, xb2) - MAX(xa1, xb1);
int overlap_y = MIN(ya2, yb2) - MAX(ya1, yb1);
if (overlap_x > 0 && overlap_y > 0)
{
int overlap = overlap_x * overlap_y;
if (overlap > max_overlap)
{
max_overlap = overlap;
max_overlapped = output;
}
}
}
return max_overlapped;
}
XVisualInfo *
cogl_xlib_renderer_get_visual_info (CoglRenderer *renderer)
{
CoglXlibRenderer *xlib_renderer;
g_return_val_if_fail (cogl_is_renderer (renderer), NULL);
xlib_renderer = _cogl_xlib_renderer_get_data (renderer);
return xlib_renderer->xvisinfo;
}