Signed-off-by: Boyan Ding <boyan.j.d...@gmail.com> --- configure.ac | 3 + src/egl/drivers/dri2/Makefile.am | 5 + src/egl/drivers/dri2/egl_dri2.c | 65 +- src/egl/drivers/dri2/egl_dri2.h | 12 +- src/egl/drivers/dri2/platform_x11.c | 127 ++- src/egl/drivers/dri2/platform_x11_dri3.c | 1591 ++++++++++++++++++++++++++++++ src/egl/drivers/dri2/platform_x11_dri3.h | 140 +++ 7 files changed, 1926 insertions(+), 17 deletions(-) create mode 100644 src/egl/drivers/dri2/platform_x11_dri3.c create mode 100644 src/egl/drivers/dri2/platform_x11_dri3.h
diff --git a/configure.ac b/configure.ac index af61aa2..090e6c9 100644 --- a/configure.ac +++ b/configure.ac @@ -1545,6 +1545,9 @@ if test "x$enable_egl" = xyes; then if test "x$enable_shared_glapi" = xno; then AC_MSG_ERROR([egl_dri2 requires --enable-shared-glapi]) fi + if test "x$enable_dri3" = xyes; then + EGL_LIB_DEPS="$EGL_LIB_DEPS -lxcb-dri3 -lxcb-present -lXxf86vm -lxshmfence -lxcb-sync" + fi else # Avoid building an "empty" libEGL. Drop/update this # when other backends (haiku?) come along. diff --git a/src/egl/drivers/dri2/Makefile.am b/src/egl/drivers/dri2/Makefile.am index 55be4a7..d5b511c 100644 --- a/src/egl/drivers/dri2/Makefile.am +++ b/src/egl/drivers/dri2/Makefile.am @@ -52,6 +52,11 @@ if HAVE_EGL_PLATFORM_X11 libegl_dri2_la_SOURCES += platform_x11.c AM_CFLAGS += -DHAVE_X11_PLATFORM AM_CFLAGS += $(XCB_DRI2_CFLAGS) +if HAVE_DRI3 +libegl_dri2_la_SOURCES += \ + platform_x11_dri3.c \ + platform_x11_dri3.h +endif endif if HAVE_EGL_PLATFORM_WAYLAND diff --git a/src/egl/drivers/dri2/egl_dri2.c b/src/egl/drivers/dri2/egl_dri2.c index b1b65f7..052c579 100644 --- a/src/egl/drivers/dri2/egl_dri2.c +++ b/src/egl/drivers/dri2/egl_dri2.c @@ -322,6 +322,12 @@ struct dri2_extension_match { int offset; }; +static struct dri2_extension_match dri3_driver_extensions[] = { + { __DRI_CORE, 1, offsetof(struct dri2_egl_display, core) }, + { __DRI_IMAGE_DRIVER, 1, offsetof(struct dri2_egl_display, image_driver) }, + { NULL, 0, 0 } +}; + static struct dri2_extension_match dri2_driver_extensions[] = { { __DRI_CORE, 1, offsetof(struct dri2_egl_display, core) }, { __DRI_DRI2, 2, offsetof(struct dri2_egl_display, dri2) }, @@ -464,6 +470,24 @@ dri2_open_driver(_EGLDisplay *disp) } EGLBoolean +dri2_load_driver_dri3(_EGLDisplay *disp) +{ + struct dri2_egl_display *dri2_dpy = disp->DriverData; + const __DRIextension **extensions; + + extensions = dri2_open_driver(disp); + if (!extensions) + return EGL_FALSE; + + if (!dri2_bind_extensions(dri2_dpy, dri3_driver_extensions, extensions)) { + dlclose(dri2_dpy->driver); + } + dri2_dpy->driver_extensions = extensions; + + return EGL_TRUE; +} + +EGLBoolean dri2_load_driver(_EGLDisplay *disp) { struct dri2_egl_display *dri2_dpy = disp->DriverData; @@ -507,7 +531,9 @@ dri2_setup_screen(_EGLDisplay *disp) struct dri2_egl_display *dri2_dpy = dri2_egl_display(disp); unsigned int api_mask; - if (dri2_dpy->dri2) { + if (dri2_dpy->image_driver) { + api_mask = dri2_dpy->image_driver->getAPIMask(dri2_dpy->dri_screen); + } else if (dri2_dpy->dri2) { api_mask = dri2_dpy->dri2->getAPIMask(dri2_dpy->dri_screen); } else { assert(dri2_dpy->swrast); @@ -527,11 +553,12 @@ dri2_setup_screen(_EGLDisplay *disp) if (api_mask & (1 << __DRI_API_GLES3)) disp->ClientAPIs |= EGL_OPENGL_ES3_BIT_KHR; - assert(dri2_dpy->dri2 || dri2_dpy->swrast); + assert(dri2_dpy->image_driver || dri2_dpy->dri2 || dri2_dpy->swrast); disp->Extensions.KHR_surfaceless_context = EGL_TRUE; disp->Extensions.MESA_configless_context = EGL_TRUE; - if ((dri2_dpy->dri2 && dri2_dpy->dri2->base.version >= 3) || + if (dri2_dpy->image_driver || + (dri2_dpy->dri2 && dri2_dpy->dri2->base.version >= 3) || (dri2_dpy->swrast && dri2_dpy->swrast->base.version >= 3)) { disp->Extensions.KHR_create_context = EGL_TRUE; @@ -591,7 +618,14 @@ dri2_create_screen(_EGLDisplay *disp) dri2_dpy = disp->DriverData; - if (dri2_dpy->dri2) { + if (dri2_dpy->image_driver) { + dri2_dpy->dri_screen = + dri2_dpy->image_driver->createNewScreen2(0, dri2_dpy->fd, + dri2_dpy->extensions, + dri2_dpy->driver_extensions, + &dri2_dpy->driver_configs, + disp); + } else if (dri2_dpy->dri2) { if (dri2_dpy->dri2->base.version >= 4) { dri2_dpy->dri_screen = dri2_dpy->dri2->createNewScreen2(0, dri2_dpy->fd, @@ -627,7 +661,7 @@ dri2_create_screen(_EGLDisplay *disp) extensions = dri2_dpy->core->getExtensions(dri2_dpy->dri_screen); - if (dri2_dpy->dri2) { + if (dri2_dpy->image_driver || dri2_dpy->dri2) { if (!dri2_bind_extensions(dri2_dpy, dri2_core_extensions, extensions)) goto cleanup_dri_screen; } else { @@ -971,7 +1005,26 @@ dri2_create_context(_EGLDriver *drv, _EGLDisplay *disp, _EGLConfig *conf, else dri_config = NULL; - if (dri2_dpy->dri2) { + if (dri2_dpy->image_driver) { + unsigned error; + unsigned num_attribs = 8; + uint32_t ctx_attribs[8]; + + if (!dri2_fill_context_attribs(dri2_ctx, dri2_dpy, ctx_attribs, + &num_attribs)) + goto cleanup; + + dri2_ctx->dri_context = + dri2_dpy->image_driver->createContextAttribs(dri2_dpy->dri_screen, + api, + dri_config, + shared, + num_attribs / 2, + ctx_attribs, + & error, + dri2_ctx); + dri2_create_context_attribs_error(error); + } else if (dri2_dpy->dri2) { if (dri2_dpy->dri2->base.version >= 3) { unsigned error; unsigned num_attribs = 8; diff --git a/src/egl/drivers/dri2/egl_dri2.h b/src/egl/drivers/dri2/egl_dri2.h index f0cc6da..d258753 100644 --- a/src/egl/drivers/dri2/egl_dri2.h +++ b/src/egl/drivers/dri2/egl_dri2.h @@ -153,11 +153,16 @@ struct dri2_egl_display int dri2_major; int dri2_minor; + int dri3_major; + int dri3_minor; + int present_major; + int present_minor; __DRIscreen *dri_screen; int own_dri_screen; const __DRIconfig **driver_configs; void *driver; const __DRIcoreExtension *core; + const __DRIimageDriverExtension *image_driver; const __DRIdri2Extension *dri2; const __DRIswrastExtension *swrast; const __DRI2flushExtension *flush; @@ -189,6 +194,7 @@ struct dri2_egl_display #ifdef HAVE_X11_PLATFORM xcb_connection_t *conn; int screen; + Display *dpy; #endif #ifdef HAVE_WAYLAND_PLATFORM @@ -202,8 +208,9 @@ struct dri2_egl_display int formats; uint32_t capabilities; int is_render_node; - int is_different_gpu; #endif + + int is_different_gpu; }; struct dri2_egl_context @@ -324,6 +331,9 @@ EGLBoolean dri2_load_driver_swrast(_EGLDisplay *disp); EGLBoolean +dri2_load_driver_dri3(_EGLDisplay *disp); + +EGLBoolean dri2_create_screen(_EGLDisplay *disp); __DRIimage * diff --git a/src/egl/drivers/dri2/platform_x11.c b/src/egl/drivers/dri2/platform_x11.c index ad40bd5..2ce260f 100644 --- a/src/egl/drivers/dri2/platform_x11.c +++ b/src/egl/drivers/dri2/platform_x11.c @@ -45,6 +45,10 @@ #include "egl_dri2_fallbacks.h" #include "loader.h" +#ifdef HAVE_DRI3 +#include "platform_x11_dri3.h" +#endif + static EGLBoolean dri2_x11_swap_interval(_EGLDriver *drv, _EGLDisplay *disp, _EGLSurface *surf, EGLint interval); @@ -1094,13 +1098,17 @@ dri2_initialize_x11_swrast(_EGLDriver *drv, _EGLDisplay *disp) disp->DriverData = (void *) dri2_dpy; if (disp->PlatformDisplay == NULL) { - dri2_dpy->conn = xcb_connect(0, &dri2_dpy->screen); + dri2_dpy->dpy = XOpenDisplay(NULL); + + dri2_dpy->conn = XGetXCBConnection(dri2_dpy->dpy); + dri2_dpy->screen = DefaultScreen(dri2_dpy->dpy); + dri2_dpy->own_device = true; } else { - Display *dpy = disp->PlatformDisplay; + dri2_dpy->dpy = disp->PlatformDisplay; - dri2_dpy->conn = XGetXCBConnection(dpy); - dri2_dpy->screen = DefaultScreen(dpy); + dri2_dpy->conn = XGetXCBConnection(dri2_dpy->dpy); + dri2_dpy->screen = DefaultScreen(dri2_dpy->dpy); } if (xcb_connection_has_error(dri2_dpy->conn)) { @@ -1202,6 +1210,94 @@ dri2_x11_setup_swap_interval(struct dri2_egl_display *dri2_dpy) } } +#ifdef HAVE_DRI3 +static EGLBoolean +dri2_initialize_x11_dri3(_EGLDriver *drv, _EGLDisplay *disp) +{ + struct dri2_egl_display *dri2_dpy; + + dri2_dpy = calloc(1, sizeof *dri2_dpy); + if (!dri2_dpy) + return _eglError(EGL_BAD_ALLOC, "eglInitialize"); + + disp->DriverData = (void *) dri2_dpy; + if (disp->PlatformDisplay == NULL) { + dri2_dpy->dpy = XOpenDisplay(NULL); + + dri2_dpy->conn = XGetXCBConnection(dri2_dpy->dpy); + dri2_dpy->screen = DefaultScreen(dri2_dpy->dpy); + + dri2_dpy->own_device = true; + } else { + dri2_dpy->dpy = disp->PlatformDisplay; + + dri2_dpy->conn = XGetXCBConnection(dri2_dpy->dpy); + dri2_dpy->screen = DefaultScreen(dri2_dpy->dpy); + } + + if (xcb_connection_has_error(dri2_dpy->conn)) { + _eglLog(_EGL_WARNING, "DRI2: xcb_connect failed"); + goto cleanup_dpy; + } + + if (dri2_dpy->conn) { + if (!dri3_x11_connect(dri2_dpy)) + goto cleanup_conn; + } + + if (!dri2_load_driver_dri3(disp)) + goto cleanup_conn; + + dri2_dpy->extensions[0] = &dri3_image_loader_extension.base; + dri2_dpy->extensions[1] = &dri3_system_time_extension.base; + dri2_dpy->extensions[2] = &use_invalidate.base; + dri2_dpy->extensions[3] = &image_lookup_extension.base; + dri2_dpy->extensions[4] = NULL; + + dri2_dpy->swap_available = true; + dri2_dpy->invalidate_available = true; + + if (!dri2_create_screen(disp)) + goto cleanup_fd; + + dri2_x11_setup_swap_interval(dri2_dpy); + + disp->Extensions.NOK_texture_from_pixmap = EGL_TRUE; + disp->Extensions.CHROMIUM_sync_control = EGL_TRUE; + disp->Extensions.EXT_buffer_age = EGL_TRUE; + +#ifdef HAVE_WAYLAND_PLATFORM + disp->Extensions.WL_bind_wayland_display = EGL_TRUE; +#endif + + if (dri2_dpy->conn) { + if (!dri2_x11_add_configs_for_visuals(dri2_dpy, disp)) + goto cleanup_configs; + } + + /* Fill vtbl last to prevent accidentally calling virtual function during + * initialization. + */ + dri2_dpy->vtbl = &dri3_x11_display_vtbl; + + return EGL_TRUE; + + cleanup_configs: + _eglCleanupDisplay(disp); + dri2_dpy->core->destroyScreen(dri2_dpy->dri_screen); + dlclose(dri2_dpy->driver); + cleanup_fd: + close(dri2_dpy->fd); + cleanup_conn: + if (disp->PlatformDisplay == NULL) + xcb_disconnect(dri2_dpy->conn); + cleanup_dpy: + free(dri2_dpy); + + return EGL_FALSE; +} +#endif + static EGLBoolean dri2_initialize_x11_dri2(_EGLDriver *drv, _EGLDisplay *disp) { @@ -1213,13 +1309,17 @@ dri2_initialize_x11_dri2(_EGLDriver *drv, _EGLDisplay *disp) disp->DriverData = (void *) dri2_dpy; if (disp->PlatformDisplay == NULL) { - dri2_dpy->conn = xcb_connect(0, &dri2_dpy->screen); + dri2_dpy->dpy = XOpenDisplay(NULL); + + dri2_dpy->conn = XGetXCBConnection(dri2_dpy->dpy); + dri2_dpy->screen = DefaultScreen(dri2_dpy->dpy); + dri2_dpy->own_device = true; } else { - Display *dpy = disp->PlatformDisplay; + dri2_dpy->dpy = disp->PlatformDisplay; - dri2_dpy->conn = XGetXCBConnection(dpy); - dri2_dpy->screen = DefaultScreen(dpy); + dri2_dpy->conn = XGetXCBConnection(dri2_dpy->dpy); + dri2_dpy->screen = DefaultScreen(dri2_dpy->dpy); } if (xcb_connection_has_error(dri2_dpy->conn)) { @@ -1321,9 +1421,16 @@ dri2_initialize_x11(_EGLDriver *drv, _EGLDisplay *disp) int x11_dri2_accel = (getenv("LIBGL_ALWAYS_SOFTWARE") == NULL); if (x11_dri2_accel) { - if (!dri2_initialize_x11_dri2(drv, disp)) { - initialized = dri2_initialize_x11_swrast(drv, disp); +#ifdef HAVE_DRI3 + if (getenv("LIBGL_DRI3_ENABLE") == NULL || + !dri2_initialize_x11_dri3(drv, disp)) { +#endif + if (!dri2_initialize_x11_dri2(drv, disp)) { + initialized = dri2_initialize_x11_swrast(drv, disp); + } +#ifdef HAVE_DRI3 } +#endif } else { initialized = dri2_initialize_x11_swrast(drv, disp); } diff --git a/src/egl/drivers/dri2/platform_x11_dri3.c b/src/egl/drivers/dri2/platform_x11_dri3.c new file mode 100644 index 0000000..dcd7128 --- /dev/null +++ b/src/egl/drivers/dri2/platform_x11_dri3.c @@ -0,0 +1,1591 @@ +/* + * Copyright © 2013 Keith Packard + * + * 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 <fcntl.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/time.h> + +#ifdef XF86VIDMODE +#include <X11/extensions/xf86vmode.h> +#endif +#include <X11/xshmfence.h> +#include <xcb/xcb.h> +#include <xcb/dri3.h> +#include <xcb/present.h> +#include <xf86drm.h> + +#include "egl_dri2.h" +#include "egl_dri2_fallbacks.h" +#include "platform_x11_dri3.h" + +#include "loader.h" + +static inline void +dri3_fence_reset(xcb_connection_t *c, struct dri3_buffer *buffer) +{ + xshmfence_reset(buffer->shm_fence); +} + +static inline void +dri3_fence_set(struct dri3_buffer *buffer) +{ + xshmfence_trigger(buffer->shm_fence); +} + +static inline void +dri3_fence_trigger(xcb_connection_t *c, struct dri3_buffer *buffer) +{ + xcb_sync_trigger_fence(c, buffer->sync_fence); +} + +static inline void +dri3_fence_await(xcb_connection_t *c, struct dri3_buffer *buffer) +{ + xcb_flush(c); + xshmfence_await(buffer->shm_fence); +} + +static void +dri3_update_num_back(struct dri3_egl_surface *dri3_surf) +{ + dri3_surf->num_back = 1; + if (dri3_surf->flipping) { + if (!dri3_surf->is_pixmap && !(dri3_surf->present_capabilities & XCB_PRESENT_CAPABILITY_ASYNC)) + dri3_surf->num_back++; + dri3_surf->num_back++; + } + if (dri3_surf->base.SwapInterval == 0) + dri3_surf->num_back++; +} + +/** dri3_free_render_buffer + * + * Free everything associated with one render buffer including pixmap, fence + * stuff and the driver image + */ +static void +dri3_free_render_buffer(struct dri2_egl_display *dri2_dpy, + struct dri3_egl_surface *dri3_surf, + struct dri3_buffer *buffer) +{ + if (buffer->own_pixmap) + xcb_free_pixmap(dri2_dpy->conn, buffer->pixmap); + xcb_sync_destroy_fence(dri2_dpy->conn, buffer->sync_fence); + xshmfence_unmap_shm(buffer->shm_fence); + (*dri2_dpy->image->destroyImage)(buffer->image); + if (buffer->linear_buffer) + (*dri2_dpy->image->destroyImage)(buffer->linear_buffer); + free(buffer); +} + +static EGLBoolean +dri3_destroy_surface(_EGLDriver *drv, _EGLDisplay *disp, _EGLSurface *surf) +{ + struct dri2_egl_display *dri2_dpy = dri2_egl_display(disp); + struct dri3_egl_surface *dri3_surf = dri3_egl_surface(surf); + int i; + + (void) drv; + + if (!_eglPutSurface(surf)) + return EGL_TRUE; + + (*dri2_dpy->core->destroyDrawable) (dri3_surf->dri_drawable); + + for (i = 0; i < DRI3_NUM_BUFFERS; i++) { + if (dri3_surf->buffers[i]) + dri3_free_render_buffer(dri2_dpy, dri3_surf, dri3_surf->buffers[i]); + } + + if (dri3_surf->special_event) + xcb_unregister_for_special_event(dri2_dpy->conn, dri3_surf->special_event); + free(surf); + + return EGL_TRUE; +} + +static EGLBoolean +dri3_set_swap_interval(_EGLDriver *drv, _EGLDisplay *disp, _EGLSurface *surf, + EGLint interval) +{ + struct dri3_egl_surface *dri3_surf = dri3_egl_surface(surf); + + if (interval > surf->Config->MaxSwapInterval) + interval = surf->Config->MaxSwapInterval; + else if (interval < surf->Config->MinSwapInterval) + interval = surf->Config->MinSwapInterval; + + dri3_surf->base.SwapInterval = interval; + dri3_update_num_back(dri3_surf); + + return EGL_TRUE; +} + +static xcb_screen_t * +get_xcb_screen(xcb_screen_iterator_t iter, int screen) +{ + for (; iter.rem; --screen, xcb_screen_next(&iter)) + if (screen == 0) + return iter.data; + + return NULL; +} + +/* xDrawable in glx maps to drawable here */ +static _EGLSurface * +dri3_create_surface(_EGLDriver *drv, _EGLDisplay *disp, EGLint type, + _EGLConfig *conf, void *native_surface, + const EGLint *attrib_list) +{ + struct dri2_egl_display *dri2_dpy = dri2_egl_display(disp); + struct dri2_egl_config *dri2_conf = dri2_egl_config(conf); + struct dri3_egl_surface *dri3_surf; + xcb_drawable_t drawable; + xcb_get_geometry_cookie_t cookie; + xcb_get_geometry_reply_t *reply; + xcb_screen_iterator_t s; + xcb_generic_error_t *error; + xcb_screen_t *screen; + GLint vblank_mode = DRI_CONF_VBLANK_DEF_INTERVAL_1; + + STATIC_ASSERT(sizeof(uintptr_t) == sizeof(native_surface)); + drawable = (uintptr_t) native_surface; + + (void) drv; + + dri3_surf = calloc(1, sizeof *dri3_surf); + if (!dri3_surf) { + _eglError(EGL_BAD_ALLOC, "dri3_create_surface"); + return NULL; + } + + if (!_eglInitSurface(&dri3_surf->base, disp, type, conf, attrib_list)) + goto cleanup_surf; + + if (type == EGL_PBUFFER_BIT) { + s = xcb_setup_roots_iterator(xcb_get_setup(dri2_dpy->conn)); + screen = get_xcb_screen(s, dri2_dpy->screen); + if (!screen) { + _eglError(EGL_BAD_NATIVE_WINDOW, "dri3_create_surface"); + goto cleanup_surf; + } + + dri3_surf->drawable = xcb_generate_id(dri2_dpy->conn); + xcb_create_pixmap(dri2_dpy->conn, conf->BufferSize, + dri3_surf->drawable, screen->root, + dri3_surf->base.Width, dri3_surf->base.Height); + } else { + dri3_surf->drawable = drawable; + } + + dri3_surf->base.SwapInterval = 1; /* default may be overridden below */ + dri3_surf->have_back = 0; + dri3_surf->have_fake_front = 0; + dri3_surf->first_init = true; + + if (dri2_dpy->config) + dri2_dpy->config->configQueryi(dri2_dpy->dri_screen, + "vblank_mode", &vblank_mode); + + switch (vblank_mode) { + case DRI_CONF_VBLANK_NEVER: + case DRI_CONF_VBLANK_DEF_INTERVAL_0: + dri3_surf->base.SwapInterval = 0; + break; + case DRI_CONF_VBLANK_DEF_INTERVAL_1: + case DRI_CONF_VBLANK_ALWAYS_SYNC: + default: + dri3_surf->base.SwapInterval = 1; + break; + } + + dri3_update_num_back(dri3_surf); + + /* Create a new drawable */ + dri3_surf->dri_drawable = + (*dri2_dpy->image_driver->createNewDrawable) (dri2_dpy->dri_screen, + type == EGL_WINDOW_BIT ? + dri2_conf->dri_double_config : + dri2_conf->dri_single_config, + dri3_surf); + + if (!dri3_surf->dri_drawable) { + _eglError(EGL_BAD_ALLOC, "dri3->createNewDrawable"); + goto cleanup_pixmap; + } + + if (type != EGL_PBUFFER_BIT) { + cookie = xcb_get_geometry (dri2_dpy->conn, dri3_surf->drawable); + reply = xcb_get_geometry_reply (dri2_dpy->conn, cookie, &error); + if (reply == NULL || error != NULL) { + _eglError(EGL_BAD_ALLOC, "xcb_get_geometry"); + free(error); + goto cleanup_dri_drawable; + } + + dri3_surf->base.Width = reply->width; + dri3_surf->base.Height = reply->height; + dri3_surf->depth = reply->depth; + free(reply); + } + + /* + * Make sure server has the same swap interval we do for the new + * drawable. + */ + dri3_set_swap_interval(drv, disp, &dri3_surf->base, + dri3_surf->base.SwapInterval); + + return &dri3_surf->base; + + cleanup_dri_drawable: + dri2_dpy->core->destroyDrawable(dri3_surf->dri_drawable); + cleanup_pixmap: + if (type == EGL_PBUFFER_BIT) + xcb_free_pixmap(dri2_dpy->conn, dri3_surf->drawable); + cleanup_surf: + free(dri3_surf); + + return NULL; +} + +/** + * Called via eglCreateWindowSurface(), drv->API.CreateWindowSurface(). + */ +static _EGLSurface * +dri3_create_window_surface(_EGLDriver *drv, _EGLDisplay *disp, + _EGLConfig *conf, void *native_window, + const EGLint *attrib_list) +{ + struct dri2_egl_display *dri2_dpy = dri2_egl_display(disp); + _EGLSurface *surf; + + surf = dri3_create_surface(drv, disp, EGL_WINDOW_BIT, conf, + native_window, attrib_list); + if (surf != NULL) + dri3_set_swap_interval(drv, disp, surf, dri2_dpy->default_swap_interval); + + return surf; +} + +static _EGLSurface * +dri3_create_pixmap_surface(_EGLDriver *drv, _EGLDisplay *disp, + _EGLConfig *conf, void *native_pixmap, + const EGLint *attrib_list) +{ + return dri3_create_surface(drv, disp, EGL_PIXMAP_BIT, conf, + native_pixmap, attrib_list); +} + +static _EGLSurface * +dri3_create_pbuffer_surface(_EGLDriver *drv, _EGLDisplay *disp, + _EGLConfig *conf, const EGLint *attrib_list) +{ + return dri3_create_surface(drv, disp, EGL_PBUFFER_BIT, conf, + XCB_WINDOW_NONE, attrib_list); +} + +/* + * Process one Present event + */ +static void +dri3_handle_present_event(struct dri2_egl_display *dri2_dpy, + struct dri3_egl_surface *dri3_surf, + xcb_present_generic_event_t *ge) +{ + switch (ge->evtype) { + case XCB_PRESENT_CONFIGURE_NOTIFY: { + xcb_present_configure_notify_event_t *ce = (void *) ge; + + dri3_surf->base.Width = ce->width; + dri3_surf->base.Height = ce->height; + break; + } + case XCB_PRESENT_COMPLETE_NOTIFY: { + xcb_present_complete_notify_event_t *ce = (void *) ge; + + /* Compute the processed SBC number from the received 32-bit serial number merged + * with the upper 32-bits of the sent 64-bit serial number while checking for + * wrap + */ + if (ce->kind == XCB_PRESENT_COMPLETE_KIND_PIXMAP) { + dri3_surf->recv_sbc = (dri3_surf->send_sbc & 0xffffffff00000000LL) | ce->serial; + if (dri3_surf->recv_sbc > dri3_surf->send_sbc) + dri3_surf->recv_sbc -= 0x100000000; + switch (ce->mode) { + case XCB_PRESENT_COMPLETE_MODE_FLIP: + dri3_surf->flipping = true; + break; + case XCB_PRESENT_COMPLETE_MODE_COPY: + dri3_surf->flipping = false; + break; + } + dri3_update_num_back(dri3_surf); + + /* + if (psc->show_fps_interval) + show_fps(priv, ce->ust); */ + + dri3_surf->ust = ce->ust; + dri3_surf->msc = ce->msc; + } else { + dri3_surf->recv_msc_serial = ce->serial; + dri3_surf->notify_ust = ce->ust; + dri3_surf->notify_msc = ce->msc; + } + break; + } + case XCB_PRESENT_EVENT_IDLE_NOTIFY: { + xcb_present_idle_notify_event_t *ie = (void *) ge; + int b; + + for (b = 0; b < sizeof (dri3_surf->buffers) / sizeof (dri3_surf->buffers[0]); b++) { + struct dri3_buffer *buf = dri3_surf->buffers[b]; + + if (buf && buf->pixmap == ie->pixmap) { + buf->busy = 0; + if (dri3_surf->num_back <= b && b < DRI3_MAX_BACK) { + dri3_free_render_buffer(dri2_dpy, dri3_surf, buf); + dri3_surf->buffers[b] = NULL; + } + break; + } + } + break; + } + } + free(ge); +} + +static bool +dri3_wait_for_event(struct dri2_egl_display *dri2_dpy, + struct dri3_egl_surface *dri3_surf) +{ + xcb_generic_event_t *ev; + xcb_present_generic_event_t *ge; + + xcb_flush(dri2_dpy->conn); + ev = xcb_wait_for_special_event(dri2_dpy->conn, dri3_surf->special_event); + if (!ev) + return false; + ge = (void *) ev; + dri3_handle_present_event(dri2_dpy, dri3_surf, ge); + return true; +} + +/** dri3_wait_for_msc + * + * Get the X server to send an event when the target msc/divisor/remainder is + * reached. + */ +static EGLBoolean +dri3_wait_for_msc(struct dri2_egl_display *dri2_dpy, + struct dri3_egl_surface *dri3_surf, + int64_t target_msc, int64_t divisor, int64_t remainder, + int64_t *ust, int64_t *msc, int64_t *sbc) +{ + uint32_t msc_serial; + + msc_serial = ++dri3_surf->send_msc_serial; + xcb_present_notify_msc(dri2_dpy->conn, + dri3_surf->drawable, + msc_serial, + target_msc, + divisor, + remainder); + + xcb_flush(dri2_dpy->conn); + + /* Wait for the event */ + if (dri3_surf->special_event) { + while ((int32_t) (msc_serial - dri3_surf->recv_msc_serial) > 0) { + if (!dri3_wait_for_event(dri2_dpy, dri3_surf)) + return EGL_FALSE; + } + } + + *ust = dri3_surf->notify_ust; + *msc = dri3_surf->notify_msc; + *sbc = dri3_surf->recv_sbc; + + return EGL_TRUE; +} + +static EGLBoolean +dri3_get_sync_values(_EGLDisplay *display, _EGLSurface *surface, + EGLuint64KHR *ust, EGLuint64KHR *msc, + EGLuint64KHR *sbc) +{ + struct dri2_egl_display *dri2_dpy = dri2_egl_display(display); + struct dri3_egl_surface *dri3_surf = dri3_egl_surface(surface); + + return dri3_wait_for_msc(dri2_dpy, dri3_surf, 0, 0, 0, ust, msc, sbc); +} + +/** + * Asks the driver to flush any queued work necessary for serializing with the + * X command stream, and optionally the slightly more strict requirement of + * glFlush() equivalence (which would require flushing even if nothing had + * been drawn to a window system framebuffer, for example). + */ +static void +dri3_flush(struct dri2_egl_display *dri2_dpy, + struct dri3_egl_surface *dri3_surf, + unsigned flags, + enum __DRI2throttleReason throttle_reason) +{ + _EGLContext *ctx = _eglGetCurrentContext(); + struct dri2_egl_context *dri2_ctx = dri2_egl_context(ctx); + + if (dri2_ctx) + (*dri2_dpy->flush->flush_with_flags)(dri2_ctx->dri_context, + dri3_surf->dri_drawable, + flags, throttle_reason); +} + +static xcb_gcontext_t +dri3_drawable_gc(struct dri2_egl_display *dri2_dpy, + struct dri3_egl_surface *dri3_surf) +{ + if (!dri3_surf->gc) { + uint32_t v = 0; + xcb_create_gc(dri2_dpy->conn, + (dri3_surf->gc = xcb_generate_id(dri2_dpy->conn)), + dri3_surf->drawable, + XCB_GC_GRAPHICS_EXPOSURES, + &v); + } + return dri3_surf->gc; +} + + +static struct dri3_buffer * +dri3_back_buffer(struct dri3_egl_surface *dri3_surf) +{ + return dri3_surf->buffers[DRI3_BACK_ID(dri3_surf->cur_back)]; +} + +static struct dri3_buffer * +dri3_fake_front_buffer(struct dri3_egl_surface *dri3_surf) +{ + return dri3_surf->buffers[DRI3_FRONT_ID]; +} + +static void +dri3_copy_area (xcb_connection_t *c /**< */, + xcb_drawable_t src_drawable /**< */, + xcb_drawable_t dst_drawable /**< */, + xcb_gcontext_t gc /**< */, + int16_t src_x /**< */, + int16_t src_y /**< */, + int16_t dst_x /**< */, + int16_t dst_y /**< */, + uint16_t width /**< */, + uint16_t height /**< */) +{ + xcb_void_cookie_t cookie; + + cookie = xcb_copy_area_checked(c, + src_drawable, + dst_drawable, + gc, + src_x, + src_y, + dst_x, + dst_y, + width, + height); + xcb_discard_reply(c, cookie.sequence); +} + +/** + * Called by the driver when it needs to update the real front buffer with the + * contents of its fake front buffer. + */ +static void +dri3_flush_front_buffer(__DRIdrawable *driDrawable, void *loaderPrivate) +{ +#if 0 + struct glx_context *gc; + struct dri3_drawable *pdraw = loaderPrivate; + struct dri3_screen *psc; + + if (!pdraw) + return; + + if (!pdraw->base.psc) + return; + + psc = (struct dri3_screen *) pdraw->base.psc; + + (void) __glXInitialize(psc->base.dpy); + + gc = __glXGetCurrentContext(); + + dri3_flush(psc, pdraw, __DRI2_FLUSH_DRAWABLE, __DRI2_THROTTLE_FLUSHFRONT); + + dri3_wait_gl(gc); +#endif + /* FIXME */ + (void) driDrawable; + (void) loaderPrivate; +} + +static uint32_t +dri3_cpp_for_format(uint32_t format) { + switch (format) { + case __DRI_IMAGE_FORMAT_R8: + return 1; + case __DRI_IMAGE_FORMAT_RGB565: + case __DRI_IMAGE_FORMAT_GR88: + return 2; + case __DRI_IMAGE_FORMAT_XRGB8888: + case __DRI_IMAGE_FORMAT_ARGB8888: + case __DRI_IMAGE_FORMAT_ABGR8888: + case __DRI_IMAGE_FORMAT_XBGR8888: + case __DRI_IMAGE_FORMAT_XRGB2101010: + case __DRI_IMAGE_FORMAT_ARGB2101010: + case __DRI_IMAGE_FORMAT_SARGB8: + return 4; + case __DRI_IMAGE_FORMAT_NONE: + default: + return 0; + } +} + +/** dri3_alloc_render_buffer + * + * Use the driver createImage function to construct a __DRIimage, then + * get a file descriptor for that and create an X pixmap from that + * + * Allocate an xshmfence for synchronization + */ +static struct dri3_buffer * +dri3_alloc_render_buffer(struct dri2_egl_display *dri2_dpy, xcb_drawable_t draw, + unsigned int format, int width, int height, int depth) +{ + struct dri3_buffer *buffer; + __DRIimage *pixmap_buffer; + xcb_pixmap_t pixmap; + xcb_sync_fence_t sync_fence; + struct xshmfence *shm_fence; + int buffer_fd, fence_fd; + int stride; + + /* Create an xshmfence object and + * prepare to send that to the X server + */ + + fence_fd = xshmfence_alloc_shm(); + if (fence_fd < 0) { + _eglLog(_EGL_WARNING, "DRI3 Fence object allocation failure %s\n", strerror(errno)); + return NULL; + } + shm_fence = xshmfence_map_shm(fence_fd); + if (shm_fence == NULL) { + _eglLog(_EGL_WARNING, "DRI3 Fence object map failure %s\n", strerror(errno)); + goto no_shm_fence; + } + + /* Allocate the image from the driver + */ + buffer = calloc(1, sizeof (struct dri3_buffer)); + if (!buffer) + goto no_buffer; + + buffer->cpp = dri3_cpp_for_format(format); + if (!buffer->cpp) { + _eglLog(_EGL_WARNING, "DRI3 buffer format %d invalid\n", format); + goto no_image; + } + + if (!dri2_dpy->is_different_gpu) { + buffer->image = (*dri2_dpy->image->createImage) (dri2_dpy->dri_screen, + width, height, + format, + __DRI_IMAGE_USE_SHARE | + __DRI_IMAGE_USE_SCANOUT, + buffer); + pixmap_buffer = buffer->image; + + if (!buffer->image) { + _eglLog(_EGL_WARNING, "DRI3 gpu image creation failure\n"); + goto no_image; + } + } else { + buffer->image = (*dri2_dpy->image->createImage) (dri2_dpy->dri_screen, + width, height, + format, + 0, + buffer); + + if (!buffer->image) { + _eglLog(_EGL_WARNING, "DRI3 other gpu image creation failure\n"); + goto no_image; + } + + buffer->linear_buffer = (*dri2_dpy->image->createImage) (dri2_dpy->dri_screen, + width, height, + format, + __DRI_IMAGE_USE_SHARE | + __DRI_IMAGE_USE_LINEAR, + buffer); + pixmap_buffer = buffer->linear_buffer; + + if (!buffer->linear_buffer) { + _eglLog(_EGL_WARNING, "DRI3 gpu linear image creation failure\n"); + goto no_linear_buffer; + } + } + + /* X wants the stride, so ask the image for it + */ + if (!(*dri2_dpy->image->queryImage)(pixmap_buffer, __DRI_IMAGE_ATTRIB_STRIDE, &stride)) { + _eglLog(_EGL_WARNING, "DRI3 get image stride failed\n"); + goto no_buffer_attrib; + } + + buffer->pitch = stride; + + if (!(*dri2_dpy->image->queryImage)(pixmap_buffer, __DRI_IMAGE_ATTRIB_FD, &buffer_fd)) { + _eglLog(_EGL_WARNING, "DRI3 get image FD failed\n"); + goto no_buffer_attrib; + } + + xcb_dri3_pixmap_from_buffer(dri2_dpy->conn, + (pixmap = xcb_generate_id(dri2_dpy->conn)), + draw, + buffer->size, + width, height, buffer->pitch, + depth, buffer->cpp * 8, + buffer_fd); + + xcb_dri3_fence_from_fd(dri2_dpy->conn, + pixmap, + (sync_fence = xcb_generate_id(dri2_dpy->conn)), + false, + fence_fd); + + buffer->pixmap = pixmap; + buffer->own_pixmap = true; + buffer->sync_fence = sync_fence; + buffer->shm_fence = shm_fence; + buffer->width = width; + buffer->height = height; + + /* Mark the buffer as idle + */ + dri3_fence_set(buffer); + + return buffer; + +no_buffer_attrib: + (*dri2_dpy->image->destroyImage)(pixmap_buffer); +no_linear_buffer: + if (dri2_dpy->is_different_gpu) + (*dri2_dpy->image->destroyImage)(buffer->image); +no_image: + free(buffer); +no_buffer: + xshmfence_unmap_shm(shm_fence); +no_shm_fence: + close(fence_fd); + _eglLog(_EGL_WARNING, "DRI3 alloc_render_buffer failed\n"); + return NULL; +} + +/** dri3_flush_present_events + * + * Process any present events that have been received from the X server + */ +static void +dri3_flush_present_events(struct dri2_egl_display *dri2_dpy, + struct dri3_egl_surface *dri3_surf) +{ + /* Check to see if any configuration changes have occurred + * since we were last invoked + */ + if (dri3_surf->special_event) { + xcb_generic_event_t *ev; + + while ((ev = xcb_poll_for_special_event(dri2_dpy->conn, + dri3_surf->special_event)) != NULL) { + xcb_present_generic_event_t *ge = (void *) ev; + dri3_handle_present_event(dri2_dpy, dri3_surf, ge); + } + } +} + +/** dri3_update_drawable + * + * Called the first time we use the drawable and then + * after we receive present configure notify events to + * track the geometry of the drawable + */ +static int +dri3_update_drawable(__DRIdrawable *driDrawable, + struct dri3_egl_surface *dri3_surf, + struct dri2_egl_display *dri2_dpy) +{ + /* First time through, go get the current drawable geometry + */ + if (dri3_surf->first_init) { + xcb_void_cookie_t cookie; + xcb_generic_error_t *error; + xcb_present_query_capabilities_cookie_t present_capabilities_cookie; + xcb_present_query_capabilities_reply_t *present_capabilities_reply; + + dri3_surf->first_init = false; + + /* Try to select for input on the window. + * + * If the drawable is a window, this will get our events + * delivered. + * + * Otherwise, we'll get a BadWindow error back from this request which + * will let us know that the drawable is a pixmap instead. + */ + + + cookie = xcb_present_select_input_checked(dri2_dpy->conn, + (dri3_surf->eid = xcb_generate_id(dri2_dpy->conn)), + dri3_surf->drawable, + XCB_PRESENT_EVENT_MASK_CONFIGURE_NOTIFY| + XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY| + XCB_PRESENT_EVENT_MASK_IDLE_NOTIFY); + + present_capabilities_cookie = xcb_present_query_capabilities(dri2_dpy->conn, + dri3_surf->drawable); + + /* Create an XCB event queue to hold present events outside of the usual + * application event queue + */ + dri3_surf->special_event = xcb_register_for_special_xge(dri2_dpy->conn, + &xcb_present_id, + dri3_surf->eid, + dri3_surf->stamp); + + dri3_surf->is_pixmap = false; + + /* Check to see if our select input call failed. If it failed with a + * BadWindow error, then assume the drawable is a pixmap. Destroy the + * special event queue created above and mark the drawable as a pixmap + */ + + error = xcb_request_check(dri2_dpy->conn, cookie); + + present_capabilities_reply = xcb_present_query_capabilities_reply(dri2_dpy->conn, + present_capabilities_cookie, + NULL); + + if (present_capabilities_reply) { + dri3_surf->present_capabilities = present_capabilities_reply->capabilities; + free(present_capabilities_reply); + } else + dri3_surf->present_capabilities = 0; + + if (error) { + if (error->error_code != BadWindow) { + free(error); + return false; + } + dri3_surf->is_pixmap = true; + xcb_unregister_for_special_event(dri2_dpy->conn, dri3_surf->special_event); + dri3_surf->special_event = NULL; + } + } + dri3_flush_present_events(dri2_dpy, dri3_surf); + return true; +} + +/* the DRIimage createImage function takes __DRI_IMAGE_FORMAT codes, while + * the createImageFromFds call takes __DRI_IMAGE_FOURCC codes. To avoid + * complete confusion, just deal in __DRI_IMAGE_FORMAT codes for now and + * translate to __DRI_IMAGE_FOURCC codes in the call to createImageFromFds + */ +static int +image_format_to_fourcc(int format) +{ + + /* Convert from __DRI_IMAGE_FORMAT to __DRI_IMAGE_FOURCC (sigh) */ + switch (format) { + case __DRI_IMAGE_FORMAT_SARGB8: return __DRI_IMAGE_FOURCC_SARGB8888; + case __DRI_IMAGE_FORMAT_RGB565: return __DRI_IMAGE_FOURCC_RGB565; + case __DRI_IMAGE_FORMAT_XRGB8888: return __DRI_IMAGE_FOURCC_XRGB8888; + case __DRI_IMAGE_FORMAT_ARGB8888: return __DRI_IMAGE_FOURCC_ARGB8888; + case __DRI_IMAGE_FORMAT_ABGR8888: return __DRI_IMAGE_FOURCC_ABGR8888; + case __DRI_IMAGE_FORMAT_XBGR8888: return __DRI_IMAGE_FOURCC_XBGR8888; + } + return 0; +} + +/** dri3_get_pixmap_buffer + * + * Get the DRM object for a pixmap from the X server and + * wrap that with a __DRIimage structure using createImageFromFds + */ +static struct dri3_buffer * +dri3_get_pixmap_buffer(__DRIdrawable *driDrawable, + unsigned int format, + enum dri3_buffer_type buffer_type, + struct dri3_egl_surface *dri3_surf, + struct dri2_egl_display *dri2_dpy) +{ + int buf_id = dri3_pixmap_buf_id(buffer_type); + struct dri3_buffer *buffer = dri3_surf->buffers[buf_id]; + xcb_drawable_t pixmap; + xcb_dri3_buffer_from_pixmap_cookie_t bp_cookie; + xcb_dri3_buffer_from_pixmap_reply_t *bp_reply; + int *fds; + xcb_sync_fence_t sync_fence; + struct xshmfence *shm_fence; + int fence_fd; + __DRIimage *image_planar; + int stride, offset; + + if (buffer) + return buffer; + + pixmap = dri3_surf->drawable; + + buffer = calloc(1, sizeof (struct dri3_buffer)); + if (!buffer) + goto no_buffer; + + fence_fd = xshmfence_alloc_shm(); + if (fence_fd < 0) + goto no_fence; + shm_fence = xshmfence_map_shm(fence_fd); + if (shm_fence == NULL) { + close (fence_fd); + goto no_fence; + } + + xcb_dri3_fence_from_fd(dri2_dpy->conn, + pixmap, + (sync_fence = xcb_generate_id(dri2_dpy->conn)), + false, + fence_fd); + + /* Get an FD for the pixmap object + */ + bp_cookie = xcb_dri3_buffer_from_pixmap(dri2_dpy->conn, pixmap); + bp_reply = xcb_dri3_buffer_from_pixmap_reply(dri2_dpy->conn, + bp_cookie, NULL); + if (!bp_reply) + goto no_image; + fds = xcb_dri3_buffer_from_pixmap_reply_fds(dri2_dpy->conn, bp_reply); + + stride = bp_reply->stride; + offset = 0; + + /* createImageFromFds creates a wrapper __DRIimage structure which + * can deal with multiple planes for things like Yuv images. So, once + * we've gotten the planar wrapper, pull the single plane out of it and + * discard the wrapper. + */ + image_planar = (*dri2_dpy->image->createImageFromFds) (dri2_dpy->dri_screen, + bp_reply->width, + bp_reply->height, + image_format_to_fourcc(format), + fds, 1, + &stride, &offset, buffer); + close(fds[0]); + if (!image_planar) + goto no_image; + + buffer->image = (*dri2_dpy->image->fromPlanar)(image_planar, 0, buffer); + + (*dri2_dpy->image->destroyImage)(image_planar); + + if (!buffer->image) + goto no_image; + + buffer->pixmap = pixmap; + buffer->own_pixmap = false; + buffer->width = bp_reply->width; + buffer->height = bp_reply->height; + buffer->buffer_type = buffer_type; + buffer->shm_fence = shm_fence; + buffer->sync_fence = sync_fence; + + dri3_surf->buffers[buf_id] = buffer; + return buffer; + +no_image: + xcb_sync_destroy_fence(dri2_dpy->conn, sync_fence); + xshmfence_unmap_shm(shm_fence); +no_fence: + free(buffer); +no_buffer: + return NULL; +} + +/** dri3_find_back + * + * Find an idle back buffer. If there isn't one, then + * wait for a present idle notify event from the X server + */ +static int +dri3_find_back(struct dri2_egl_display *dri2_dpy, + struct dri3_egl_surface *dri3_surf) +{ + int b; + xcb_generic_event_t *ev; + xcb_present_generic_event_t *ge; + + for (;;) { + for (b = 0; b < dri3_surf->num_back; b++) { + int id = DRI3_BACK_ID((b + dri3_surf->cur_back) % dri3_surf->num_back); + struct dri3_buffer *buffer = dri3_surf->buffers[id]; + + if (!buffer || !buffer->busy) { + dri3_surf->cur_back = id; + return id; + } + } + xcb_flush(dri2_dpy->conn); + ev = xcb_wait_for_special_event(dri2_dpy->conn, dri3_surf->special_event); + if (!ev) + return -1; + ge = (void *) ev; + dri3_handle_present_event(dri2_dpy, dri3_surf, ge); + } +} + +/** dri3_get_buffer + * + * Find a front or back buffer, allocating new ones as necessary + */ +static struct dri3_buffer * +dri3_get_buffer(__DRIdrawable *driDrawable, + unsigned int format, + enum dri3_buffer_type buffer_type, + struct dri3_egl_surface *dri3_surf, + struct dri2_egl_display *dri2_dpy) +{ + struct dri3_buffer *buffer; + _EGLContext *ctx = _eglGetCurrentContext(); + struct dri2_egl_context *dri2_ctx = dri2_egl_context(ctx); + int buf_id; + + if (buffer_type == dri3_buffer_back) { + buf_id = dri3_find_back(dri2_dpy, dri3_surf); + + if (buf_id < 0) + return NULL; + } else { + buf_id = DRI3_FRONT_ID; + } + + buffer = dri3_surf->buffers[buf_id]; + + /* Allocate a new buffer if there isn't an old one, or if that + * old one is the wrong size + */ + if (!buffer || buffer->width != dri3_surf->base.Width || + buffer->height != dri3_surf->base.Height) { + struct dri3_buffer *new_buffer; + + /* Allocate the new buffers + */ + new_buffer = dri3_alloc_render_buffer(dri2_dpy, + dri3_surf->drawable, + format, + dri3_surf->base.Width, + dri3_surf->base.Height, + dri3_surf->depth); + if (!new_buffer) + return NULL; + + /* When resizing, copy the contents of the old buffer, waiting for that + * copy to complete using our fences before proceeding + */ + switch (buffer_type) { + case dri3_buffer_back: + if (buffer) { + if (!buffer->linear_buffer) { + dri3_fence_reset(dri2_dpy->conn, new_buffer); + dri3_fence_await(dri2_dpy->conn, buffer); + dri3_copy_area(dri2_dpy->conn, + buffer->pixmap, + new_buffer->pixmap, + dri3_drawable_gc(dri2_dpy, dri3_surf), + 0, 0, 0, 0, + dri3_surf->base.Width, + dri3_surf->base.Height); + dri3_fence_trigger(dri2_dpy->conn, new_buffer); + } else if (dri2_ctx->base.Resource.Display == dri3_surf->base.Resource.Display) { + dri2_dpy->image->blitImage(dri2_ctx->dri_context, + new_buffer->image, + buffer->image, + 0, 0, dri3_surf->base.Width, + dri3_surf->base.Height, + 0, 0, dri3_surf->base.Width, + dri3_surf->base.Height, 0); + } + dri3_free_render_buffer(dri2_dpy, dri3_surf, buffer); + } + break; + case dri3_buffer_front: + dri3_fence_reset(dri2_dpy->conn, new_buffer); + dri3_copy_area(dri2_dpy->conn, + dri3_surf->drawable, + new_buffer->pixmap, + dri3_drawable_gc(dri2_dpy, dri3_surf), + 0, 0, 0, 0, + dri3_surf->base.Width, + dri3_surf->base.Height); + dri3_fence_trigger(dri2_dpy->conn, new_buffer); + + if (new_buffer->linear_buffer && + dri2_ctx->base.Resource.Display == dri3_surf->base.Resource.Display) { + dri3_fence_await(dri2_dpy->conn, new_buffer); + dri2_dpy->image->blitImage(dri2_ctx->dri_context, + new_buffer->image, + new_buffer->linear_buffer, + 0, 0, dri3_surf->base.Width, + dri3_surf->base.Height, + 0, 0, dri3_surf->base.Width, + dri3_surf->base.Height, 0); + } + break; + } + buffer = new_buffer; + buffer->buffer_type = buffer_type; + dri3_surf->buffers[buf_id] = buffer; + } + dri3_fence_await(dri2_dpy->conn, buffer); + + /* Return the requested buffer */ + return buffer; +} + +/** dri3_free_buffers + * + * Free the front bufffer or all of the back buffers. Used + * when the application changes which buffers it needs + */ +static void +dri3_free_buffers(__DRIdrawable *driDrawable, + enum dri3_buffer_type buffer_type, + struct dri3_egl_surface *dri3_surf, + struct dri2_egl_display *dri2_dpy) +{ + struct dri3_buffer *buffer; + int first_id; + int n_id; + int buf_id; + + switch (buffer_type) { + case dri3_buffer_back: + first_id = DRI3_BACK_ID(0); + n_id = DRI3_MAX_BACK; + break; + case dri3_buffer_front: + first_id = DRI3_FRONT_ID; + n_id = 1; + } + + for (buf_id = first_id; buf_id < first_id + n_id; buf_id++) { + buffer = dri3_surf->buffers[buf_id]; + if (buffer) { + dri3_free_render_buffer(dri2_dpy, dri3_surf, buffer); + dri3_surf->buffers[buf_id] = NULL; + } + } +} + +/** dri3_get_buffers + * + * The published buffer allocation API. + * Returns all of the necessary buffers, allocating + * as needed. + */ +static int +dri3_get_buffers(__DRIdrawable *driDrawable, + unsigned int format, + uint32_t *stamp, + void *loaderPrivate, + uint32_t buffer_mask, + struct __DRIimageList *buffers) +{ + struct dri3_egl_surface *dri3_surf = loaderPrivate; + struct dri2_egl_display *dri2_dpy = + dri2_egl_display(dri3_surf->base.Resource.Display); + struct dri3_buffer *front, *back; + + buffers->image_mask = 0; + buffers->front = NULL; + buffers->back = NULL; + + front = NULL; + back = NULL; + + if (!dri3_update_drawable(driDrawable, dri3_surf, dri2_dpy)) + return false; + + /* pixmaps always have front buffers */ + if (dri3_surf->is_pixmap) + buffer_mask |= __DRI_IMAGE_BUFFER_FRONT; + + if (buffer_mask & __DRI_IMAGE_BUFFER_FRONT) { + /* All pixmaps are owned by the server gpu. + * When we use a different gpu, we can't use the pixmap + * as buffer since it is potentially tiled a way + * our device can't understand. In this case, use + * a fake front buffer. Hopefully the pixmap + * content will get synced with the fake front + * buffer. + */ + if (dri3_surf->is_pixmap && !dri2_dpy->is_different_gpu) + front = dri3_get_pixmap_buffer(driDrawable, + format, + dri3_buffer_front, + dri3_surf, + dri2_dpy); + else + front = dri3_get_buffer(driDrawable, + format, + dri3_buffer_front, + dri3_surf, + dri2_dpy); + + if (!front) + return false; + } else { + dri3_free_buffers(driDrawable, dri3_buffer_front, dri3_surf, dri2_dpy); + dri3_surf->have_fake_front = 0; + } + + if (buffer_mask & __DRI_IMAGE_BUFFER_BACK) { + back = dri3_get_buffer(driDrawable, + format, + dri3_buffer_back, + dri3_surf, + dri2_dpy); + if (!back) + return false; + dri3_surf->have_back = 1; + } else { + dri3_free_buffers(driDrawable, dri3_buffer_back, dri3_surf, dri2_dpy); + dri3_surf->have_back = 0; + } + + if (front) { + buffers->image_mask |= __DRI_IMAGE_BUFFER_FRONT; + buffers->front = front->image; + dri3_surf->have_fake_front = dri2_dpy->is_different_gpu || !dri3_surf->is_pixmap; + } + + if (back) { + buffers->image_mask |= __DRI_IMAGE_BUFFER_BACK; + buffers->back = back->image; + } + + dri3_surf->stamp = stamp; + + return true; +} + +const __DRIimageLoaderExtension dri3_image_loader_extension = { + .base = { __DRI_IMAGE_LOADER, 1 }, + + .getBuffers = dri3_get_buffers, + .flushFrontBuffer = dri3_flush_front_buffer, +}; + +static int +dri3_get_ust(int64_t *ust) +{ + struct timeval tv; + + if (ust == NULL) + return -EFAULT; + + if (gettimeofday(&tv, NULL) == 0) { + ust[0] = (tv.tv_sec * 1000000) + tv.tv_usec; + return 0; + } else + return -errno; +} + +static GLboolean +dri3_get_msc_rate(__DRIdrawable *draw, int32_t *numerator, + int32_t *denominator, void *loaderPrivate) +{ +#ifdef XF86VIDMODE + dri3_egl_surface *dri3_surf = loaderPrivate; + struct dri2_egl_display *dri2_dpy = + dri2_egl_display(dri3_surf->base.Resource.Display); + XF86VidModeModeLine mode_line; + int dot_clock; + int i; + + if (XF86VidModeQueryVersion(dri2_dpy->dpy, &i, &i) && + XF86VidModeGetModeLine(dri2_dpy->dpy, dri2_dpy->screen, + &dot_clock, &mode_line)) { + unsigned n = dot_clock * 1000; + unsigned d = mode_line.vtotal * mode_line.htotal; + +# define V_INTERLACE 0x010 +# define V_DBLSCAN 0x020 + + if (mode_line.flags & V_INTERLACE) + n *= 2; + else if (mode_line.flags & V_DBLSCAN) + d *= 2; + + /* The OML_sync_control spec requires that if the refresh rate is a + * whole number, that the returned numerator be equal to the refresh + * rate and the denominator be 1. + */ + + if (n % d == 0) { + n /= d; + d = 1; + } + else { + static const unsigned f[] = { 13, 11, 7, 5, 3, 2, 0 }; + + /* This is a poor man's way to reduce a fraction. It's far from + * perfect, but it will work well enough for this situation. + */ + + for (i = 0; f[i] != 0; i++) { + while (n % f[i] == 0 && d % f[i] == 0) { + d /= f[i]; + n /= f[i]; + } + } + } + + *numerator = n; + *denominator = d; + + return True; + +#endif + return False; +} + +const __DRIsystemTimeExtension dri3_system_time_extension = { + .base = { __DRI_SYSTEM_TIME, 1 }, + + .getUST = dri3_get_ust, + .getMSCRate = dri3_get_msc_rate, +}; + +/** dri3_swap_buffers_msc + * + * Make the current back buffer visible using the present extension + */ +static int64_t +dri3_swap_buffers_msc(_EGLDriver *drv, _EGLDisplay *disp, _EGLSurface *draw, + int64_t target_msc, int64_t divisor, + int64_t remainder) +{ + struct dri3_egl_surface *dri3_surf = dri3_egl_surface(draw); + struct dri2_egl_display *dri2_dpy = dri2_egl_display(disp); + struct dri3_buffer *back; + _EGLContext *ctx = _eglGetCurrentContext(); + struct dri2_egl_context *dri2_ctx = dri2_egl_context(ctx); + int64_t ret = 0; + uint32_t options = XCB_PRESENT_OPTION_NONE; + + dri2_flush_drawable_for_swapbuffers(disp, draw); + + back = dri3_surf->buffers[DRI3_BACK_ID(dri3_surf->cur_back)]; + if (dri2_dpy->is_different_gpu && back) { + /* Update the linear buffer before presenting the pixmap */ + dri2_dpy->image->blitImage(dri2_ctx->dri_context, + back->linear_buffer, + back->image, + 0, 0, back->width, + back->height, + 0, 0, back->width, + back->height, __BLIT_FLAG_FLUSH); + /* Update the fake front */ + if (dri3_surf->have_fake_front) + dri2_dpy->image->blitImage(dri2_ctx->dri_context, + dri3_surf->buffers[DRI3_FRONT_ID]->image, + back->image, + 0, 0, dri3_surf->base.Width, + dri3_surf->base.Height, + 0, 0, dri3_surf->base.Width, + dri3_surf->base.Height, + __BLIT_FLAG_FLUSH); + } + + dri3_flush_present_events(dri2_dpy, dri3_surf); + + if (back && !dri3_surf->is_pixmap) { + dri3_fence_reset(dri2_dpy->conn, back); + + /* Compute when we want the frame shown by taking the last known successful + * MSC and adding in a swap interval for each outstanding swap request. + * target_msc=divisor=remainder=0 means "Use glXSwapBuffers() semantic" + */ + ++dri3_surf->send_sbc; + if (target_msc == 0 && divisor == 0 && remainder == 0) + target_msc = dri3_surf->msc + dri3_surf->base.SwapInterval * (dri3_surf->send_sbc - dri3_surf->recv_sbc); + else if (divisor == 0 && remainder > 0) { + /* + * "If <divisor> = 0, the swap will occur when MSC becomes + * greater than or equal to <target_msc>." + * + * Note that there's no mention of the remainder. The Present extension + * throws BadValue for remainder != 0 with divisor == 0, so just drop + * the passed in value. + */ + remainder = 0; + } + + /* From the EGL 1.4 spec (page 53): + * + * "If <interval> is set to a value of 0, buffer swaps are not + * synchronized to a video frame." + * + * Implementation note: It is possible to enable triple buffering behaviour + * by not using XCB_PRESENT_OPTION_ASYNC, but this should not be the default. + */ + if (dri3_surf->base.SwapInterval == 0) + options |= XCB_PRESENT_OPTION_ASYNC; + + back->busy = 1; + back->last_swap = dri3_surf->send_sbc; + xcb_present_pixmap(dri2_dpy->conn, + dri3_surf->drawable, + back->pixmap, + (uint32_t) dri3_surf->send_sbc, + 0, /* valid */ + 0, /* update */ + 0, /* x_off */ + 0, /* y_off */ + None, /* target_crtc */ + None, + back->sync_fence, + options, + target_msc, + divisor, + remainder, 0, NULL); + ret = (int64_t) dri3_surf->send_sbc; + + /* If there's a fake front, then copy the source back buffer + * to the fake front to keep it up to date. This needs + * to reset the fence and make future users block until + * the X server is done copying the bits + */ + if (dri3_surf->have_fake_front && !dri2_dpy->is_different_gpu) { + dri3_fence_reset(dri2_dpy->conn, dri3_surf->buffers[DRI3_FRONT_ID]); + dri3_copy_area(dri2_dpy->conn, + back->pixmap, + dri3_surf->buffers[DRI3_FRONT_ID]->pixmap, + dri3_drawable_gc(dri2_dpy, dri3_surf), + 0, 0, 0, 0, + dri3_surf->base.Width, + dri3_surf->base.Height); + dri3_fence_trigger(dri2_dpy->conn, dri3_surf->buffers[DRI3_FRONT_ID]); + } + xcb_flush(dri2_dpy->conn); + if (dri3_surf->stamp) + ++(*dri3_surf->stamp); + } + + (*dri2_dpy->flush->invalidate)(dri3_surf->dri_drawable); + + return ret; +} + +static EGLBoolean +dri3_swap_buffers(_EGLDriver *drv, _EGLDisplay *disp, _EGLSurface *draw) +{ + return dri3_swap_buffers_msc(drv, disp, draw, 0, 0, 0) != -1; +} + +static int +dri3_query_buffer_age(_EGLDriver *drv, _EGLDisplay *dpy, _EGLSurface *surf) +{ + struct dri2_egl_display *dri2_dpy = dri2_egl_display(dpy); + struct dri3_egl_surface *dri3_surf = dri3_egl_surface(surf); + int back_id = DRI3_BACK_ID(dri3_find_back(dri2_dpy, dri3_surf)); + + if (back_id < 0 || !dri3_surf->buffers[back_id]) + return 0; + + if (dri3_surf->buffers[back_id]->last_swap != 0) + return dri3_surf->send_sbc - dri3_surf->buffers[back_id]->last_swap + 1; + else + return 0; +} + +/* FIXME: Is this right? Seems problematic for WL_bind_wayland_display */ +static int +dri3_authenticate(_EGLDisplay *disp, uint32_t id) +{ + return 0; +} + +struct dri2_egl_display_vtbl dri3_x11_display_vtbl = { + .authenticate = dri3_authenticate, + .create_window_surface = dri3_create_window_surface, + .create_pixmap_surface = dri3_create_pixmap_surface, + .create_pbuffer_surface = dri3_create_pbuffer_surface, + .destroy_surface = dri3_destroy_surface, + .create_image = dri2_create_image_khr, + .swap_interval = dri3_set_swap_interval, + .swap_buffers = dri3_swap_buffers, + .swap_buffers_with_damage = dri2_fallback_swap_buffers_with_damage, + .swap_buffers_region = dri2_fallback_swap_buffers_region, + .post_sub_buffer = dri2_fallback_post_sub_buffer, + .copy_buffers = dri2_fallback_copy_buffers, + .query_buffer_age = dri3_query_buffer_age, + .create_wayland_buffer_from_image = dri2_fallback_create_wayland_buffer_from_image, + .get_sync_values = dri3_get_sync_values, +}; + +/** dri3_open + * + * Wrapper around xcb_dri3_open + */ +static int +dri3_open(xcb_connection_t *conn, + xcb_window_t root, + uint32_t provider) +{ + xcb_dri3_open_cookie_t cookie; + xcb_dri3_open_reply_t *reply; + int fd; + + cookie = xcb_dri3_open(conn, + root, + provider); + + reply = xcb_dri3_open_reply(conn, cookie, NULL); + if (!reply) + return -1; + + if (reply->nfd != 1) { + free(reply); + return -1; + } + + fd = xcb_dri3_open_reply_fds(conn, reply)[0]; + fcntl(fd, F_SETFD, FD_CLOEXEC); + + return fd; +} + +EGLBoolean +dri3_x11_connect(struct dri2_egl_display *dri2_dpy) +{ + xcb_dri3_query_version_reply_t *dri3_query; + xcb_dri3_query_version_cookie_t dri3_query_cookie; + xcb_present_query_version_reply_t *present_query; + xcb_present_query_version_cookie_t present_query_cookie; + xcb_generic_error_t *error; + xcb_screen_iterator_t s; + xcb_screen_t *screen; + const xcb_query_extension_reply_t *extension; + + xcb_prefetch_extension_data (dri2_dpy->conn, &xcb_dri3_id); + xcb_prefetch_extension_data (dri2_dpy->conn, &xcb_present_id); + + extension = xcb_get_extension_data(dri2_dpy->conn, &xcb_dri3_id); + if (!(extension && extension->present)) + return EGL_FALSE; + + extension = xcb_get_extension_data(dri2_dpy->conn, &xcb_present_id); + if (!(extension && extension->present)) + return EGL_FALSE; + + dri3_query_cookie = xcb_dri3_query_version(dri2_dpy->conn, + XCB_DRI3_MAJOR_VERSION, + XCB_DRI3_MINOR_VERSION); + + present_query_cookie = xcb_present_query_version(dri2_dpy->conn, + XCB_PRESENT_MAJOR_VERSION, + XCB_PRESENT_MINOR_VERSION); + + /* FIXME: a little bit memory leak here */ + dri3_query = + xcb_dri3_query_version_reply(dri2_dpy->conn, dri3_query_cookie, &error); + if (dri3_query == NULL || error != NULL) { + _eglLog(_EGL_WARNING, "DRI2: failed to query dri3 version"); + free(error); + return EGL_FALSE; + } + dri2_dpy->dri3_major = dri3_query->major_version; + dri2_dpy->dri3_minor = dri3_query->minor_version; + free(dri3_query); + + present_query = + xcb_present_query_version_reply(dri2_dpy->conn, + present_query_cookie, &error); + if (present_query == NULL || error != NULL) { + _eglLog(_EGL_WARNING, "DRI2: failed to query Present version"); + free(error); + return EGL_FALSE; + } + dri2_dpy->present_major = present_query->major_version; + dri2_dpy->present_minor = present_query->minor_version; + free(present_query); + + s = xcb_setup_roots_iterator(xcb_get_setup(dri2_dpy->conn)); + screen = get_xcb_screen(s, dri2_dpy->screen); + if (!screen) { + _eglError(EGL_BAD_NATIVE_WINDOW, "dri3_x11_connect"); + return EGL_FALSE; + } + + dri2_dpy->fd = dri3_open(dri2_dpy->conn, screen->root, 0); + if (dri2_dpy->fd < 0) { + int conn_error = xcb_connection_has_error(dri2_dpy->conn); + _eglLog(_EGL_WARNING, "DRI2: Screen seem not DRI3 capable"); + + if (conn_error) + _eglLog(_EGL_WARNING, "DRI2: Failed to initialize DRI3"); + + return EGL_FALSE; + } + + dri2_dpy->fd = loader_get_user_preferred_fd(dri2_dpy->fd, &dri2_dpy->is_different_gpu); + + dri2_dpy->driver_name = loader_get_driver_for_fd(dri2_dpy->fd, 0); + if (!dri2_dpy->driver_name) { + _eglLog(_EGL_WARNING, "DRI2: No driver found"); + close(dri2_dpy->fd); + return EGL_FALSE; + } + + dri2_dpy->device_name = loader_get_device_name_for_fd(dri2_dpy->fd); + if (!dri2_dpy->device_name) { + _eglLog(_EGL_WARNING, "DRI2: Cannot find device name"); + close(dri2_dpy->fd); + return EGL_FALSE; + } + + return EGL_TRUE; +} diff --git a/src/egl/drivers/dri2/platform_x11_dri3.h b/src/egl/drivers/dri2/platform_x11_dri3.h new file mode 100644 index 0000000..efdfcdb --- /dev/null +++ b/src/egl/drivers/dri2/platform_x11_dri3.h @@ -0,0 +1,140 @@ +/* + * Copyright © 2013 Keith Packard + * + * 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. + */ + +#ifndef EGL_X11_DRI3_INCLUDED +#define EGL_X11_DRI3_INCLUDED + +#include <stdbool.h> +#include <xcb/xcb.h> +#include <xcb/present.h> + +#include "egl_dri2.h" + +_EGL_DRIVER_TYPECAST(dri3_egl_surface, _EGLSurface, obj) +enum dri3_buffer_type { + dri3_buffer_back = 0, + dri3_buffer_front = 1 +}; + +struct dri3_buffer { + __DRIimage *image; + __DRIimage *linear_buffer; + uint32_t pixmap; + + /* Synchronization between the client and X server is done using an + * xshmfence that is mapped into an X server SyncFence. This lets the + * client check whether the X server is done using a buffer with a simple + * xshmfence call, rather than going to read X events from the wire. + * + * However, we can only wait for one xshmfence to be triggered at a time, + * so we need to know *which* buffer is going to be idle next. We do that + * by waiting for a PresentIdleNotify event. When that event arrives, the + * 'busy' flag gets cleared and the client knows that the fence has been + * triggered, and that the wait call will not block. + */ + + uint32_t sync_fence; /* XID of X SyncFence object */ + struct xshmfence *shm_fence; /* pointer to xshmfence object */ + bool busy; /* Set on swap, cleared on IdleNotify */ + bool own_pixmap; /* We allocated the pixmap ID, free on destroy */ + + uint32_t size; + uint32_t pitch; + uint32_t cpp; + uint32_t flags; + uint32_t width, height; + uint64_t last_swap; + + enum dri3_buffer_type buffer_type; +}; + + +#define DRI3_MAX_BACK 4 +#define DRI3_BACK_ID(i) (i) +#define DRI3_FRONT_ID (DRI3_MAX_BACK) + +static inline int +dri3_pixmap_buf_id(enum dri3_buffer_type buffer_type) +{ + if (buffer_type == dri3_buffer_back) + return DRI3_BACK_ID(0); + else + return DRI3_FRONT_ID; +} + +#define DRI3_NUM_BUFFERS (1 + DRI3_MAX_BACK) + +struct dri3_egl_surface { + _EGLSurface base; + __DRIdrawable *dri_drawable; + xcb_drawable_t drawable; + int depth; + uint8_t have_back; + uint8_t have_fake_front; + uint8_t is_pixmap; + uint8_t flipping; + + /* Present extension capabilities + */ + uint32_t present_capabilities; + + /* SBC numbers are tracked by using the serial numbers + * in the present request and complete events + */ + uint64_t send_sbc; + uint64_t recv_sbc; + + /* Last received UST/MSC values for pixmap present complete */ + uint64_t ust, msc; + + /* Last received UST/MSC values from present notify msc event */ + uint64_t notify_ust, notify_msc; + + /* Serial numbers for tracking wait_for_msc events */ + uint32_t send_msc_serial; + uint32_t recv_msc_serial; + + struct dri3_buffer *buffers[DRI3_NUM_BUFFERS]; + int cur_back; + int num_back; + + uint32_t *stamp; + + xcb_present_event_t eid; + xcb_gcontext_t gc; + xcb_special_event_t *special_event; + + /* LIBGL_SHOW_FPS support */ + uint64_t previous_ust; + unsigned frames; + + bool first_init; +}; + +extern const __DRIimageLoaderExtension dri3_image_loader_extension; +extern const __DRIsystemTimeExtension dri3_system_time_extension; +extern struct dri2_egl_display_vtbl dri3_x11_display_vtbl; + +EGLBoolean +dri3_x11_connect(struct dri2_egl_display *dri2_dpy); + +#endif -- 2.4.4 _______________________________________________ mesa-dev mailing list mesa-dev@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/mesa-dev