This change makes st/egl build a single egl_gallium.so and multiple st_<API>.so and pipe_<HW>.so. When a display is initialized, the corresponding pipe driver will be loaded. When a context is created, the corresponding state tracker will be loaded. Unlike DRI drivers, no ABI compatibility is maintained. egl_gallium, pipe drivers and state trackers should always be distributed as a single package. As such, there is only a single src/gallium/targets/egl/ that builds everything for the package.tags/mesa-7.9-rc1
@@ -39,7 +39,7 @@ | |||
/* XXX Need to decide how to do dynamic name lookup on Windows */ | |||
static const char *DefaultDriverNames[] = { | |||
"egl_gallium_swrast" | |||
"egl_gallium" | |||
}; | |||
typedef HMODULE lib_handle; | |||
@@ -68,6 +68,7 @@ library_suffix(void) | |||
static const char *DefaultDriverNames[] = { | |||
"egl_gallium", | |||
"egl_dri2", | |||
"egl_glx" | |||
}; | |||
@@ -294,71 +295,6 @@ _eglLoaderFile(const char *dir, size_t len, void *loader_data) | |||
} | |||
/** | |||
* A loader function for use with _eglPreloadForEach. The loader data is the | |||
* pattern (prefix) of the files to look for. | |||
*/ | |||
static EGLBoolean | |||
_eglLoaderPattern(const char *dir, size_t len, void *loader_data) | |||
{ | |||
#if defined(_EGL_OS_UNIX) | |||
const char *prefix, *suffix; | |||
size_t prefix_len, suffix_len; | |||
DIR *dirp; | |||
struct dirent *dirent; | |||
char path[1024]; | |||
if (len + 2 > sizeof(path)) | |||
return EGL_TRUE; | |||
if (len) { | |||
memcpy(path, dir, len); | |||
path[len++] = '/'; | |||
} | |||
path[len] = '\0'; | |||
dirp = opendir(path); | |||
if (!dirp) | |||
return EGL_TRUE; | |||
prefix = (const char *) loader_data; | |||
prefix_len = strlen(prefix); | |||
suffix = library_suffix(); | |||
suffix_len = (suffix) ? strlen(suffix) : 0; | |||
while ((dirent = readdir(dirp))) { | |||
_EGLDriver *drv; | |||
size_t dirent_len = strlen(dirent->d_name); | |||
const char *p; | |||
/* match the prefix */ | |||
if (strncmp(dirent->d_name, prefix, prefix_len) != 0) | |||
continue; | |||
/* match the suffix */ | |||
if (suffix) { | |||
p = dirent->d_name + dirent_len - suffix_len; | |||
if (p < dirent->d_name || strcmp(p, suffix) != 0) | |||
continue; | |||
} | |||
/* make a full path and load the driver */ | |||
if (len + dirent_len + 1 <= sizeof(path)) { | |||
strcpy(path + len, dirent->d_name); | |||
drv = _eglLoadDriver(path, NULL); | |||
if (drv) | |||
_eglGlobal.Drivers[_eglGlobal.NumDrivers++] = drv; | |||
} | |||
} | |||
closedir(dirp); | |||
return EGL_TRUE; | |||
#else /* _EGL_OS_UNIX */ | |||
/* stop immediately */ | |||
return EGL_FALSE; | |||
#endif | |||
} | |||
/** | |||
* Run the preload function on each driver directory and return the number of | |||
* drivers loaded. | |||
@@ -463,20 +399,6 @@ _eglPreloadUserDriver(void) | |||
} | |||
/** | |||
* Preload Gallium drivers. | |||
* | |||
* FIXME This makes libEGL a memory hog if an user driver is not specified and | |||
* there are many Gallium drivers | |||
*/ | |||
static EGLBoolean | |||
_eglPreloadGalliumDrivers(void) | |||
{ | |||
return (_eglPreloadForEach(_eglGetSearchPath(), | |||
_eglLoaderPattern, (void *) "egl_gallium_") > 0); | |||
} | |||
/** | |||
* Preload drivers. | |||
* | |||
@@ -497,8 +419,7 @@ _eglPreloadDrivers(void) | |||
return EGL_TRUE; | |||
} | |||
loaded = (_eglPreloadUserDriver() || | |||
_eglPreloadGalliumDrivers()); | |||
loaded = _eglPreloadUserDriver(); | |||
_eglUnlockMutex(_eglGlobal.Mutex); | |||
@@ -33,78 +33,6 @@ | |||
#include "state_tracker/drm_driver.h" | |||
#define X11_PROBE_MAGIC 0x11980BE /* "X11PROBE" */ | |||
static void | |||
x11_probe_destroy(struct native_probe *nprobe) | |||
{ | |||
if (nprobe->data) | |||
FREE(nprobe->data); | |||
FREE(nprobe); | |||
} | |||
static struct native_probe * | |||
x11_create_probe(void *dpy) | |||
{ | |||
struct native_probe *nprobe; | |||
struct x11_screen *xscr; | |||
int scr; | |||
const char *driver_name = NULL; | |||
Display *xdpy; | |||
nprobe = CALLOC_STRUCT(native_probe); | |||
if (!nprobe) | |||
return NULL; | |||
xdpy = dpy; | |||
if (!xdpy) { | |||
xdpy = XOpenDisplay(NULL); | |||
if (!xdpy) { | |||
FREE(nprobe); | |||
return NULL; | |||
} | |||
} | |||
scr = DefaultScreen(xdpy); | |||
xscr = x11_screen_create(xdpy, scr); | |||
if (xscr) { | |||
if (x11_screen_support(xscr, X11_SCREEN_EXTENSION_DRI2)) { | |||
driver_name = x11_screen_probe_dri2(xscr, NULL, NULL); | |||
if (driver_name) | |||
nprobe->data = strdup(driver_name); | |||
} | |||
x11_screen_destroy(xscr); | |||
} | |||
if (xdpy != dpy) | |||
XCloseDisplay(xdpy); | |||
nprobe->magic = X11_PROBE_MAGIC; | |||
nprobe->display = dpy; | |||
nprobe->destroy = x11_probe_destroy; | |||
return nprobe; | |||
} | |||
static enum native_probe_result | |||
x11_get_probe_result(struct native_probe *nprobe) | |||
{ | |||
if (!nprobe || nprobe->magic != X11_PROBE_MAGIC) | |||
return NATIVE_PROBE_UNKNOWN; | |||
/* this is a software driver */ | |||
if (!driver_descriptor.create_screen) | |||
return NATIVE_PROBE_SUPPORTED; | |||
/* the display does not support DRI2 or the driver mismatches */ | |||
if (!nprobe->data || strcmp(driver_descriptor.name, (const char *) nprobe->data) != 0) | |||
return NATIVE_PROBE_FALLBACK; | |||
return NATIVE_PROBE_EXACT; | |||
} | |||
static struct native_display * | |||
native_create_display(void *dpy, struct native_event_handler *event_handler, | |||
void *user_data) | |||
@@ -131,8 +59,8 @@ native_create_display(void *dpy, struct native_event_handler *event_handler, | |||
static const struct native_platform x11_platform = { | |||
"X11", /* name */ | |||
x11_create_probe, | |||
x11_get_probe_result, | |||
NULL, /* create_probe */ | |||
NULL, /* get_probe_result */ | |||
native_create_display | |||
}; | |||
@@ -2,8 +2,9 @@ | |||
# | |||
# This is the Makefile for EGL Gallium driver package. The package consists of | |||
# | |||
# egl_gallium_<HW>.so - EGL drivers | |||
# st_<API>.so - client API state trackers | |||
# egl_gallium.so - EGL driver | |||
# pipe_<HW>.so - pipe drivers | |||
# st_<API>.so - client API state trackers | |||
# | |||
# The following variables are examined | |||
# | |||
@@ -16,7 +17,7 @@ TOP = ../../../.. | |||
include $(TOP)/configs/current | |||
ST_PREFIX := st_ | |||
PIPE_PREFIX := egl_gallium_ | |||
PIPE_PREFIX := pipe_ | |||
common_CPPFLAGS := \ | |||
-I$(TOP)/src/gallium/auxiliary \ | |||
@@ -34,11 +35,9 @@ common_LIBS := \ | |||
egl_CPPFLAGS := \ | |||
-I$(TOP)/src/gallium/state_trackers/egl \ | |||
-I$(TOP)/src/egl/main \ | |||
-DST_PREFIX=\"$(ST_PREFIX)\" | |||
-DPIPE_PREFIX=\"$(PIPE_PREFIX)\" -DST_PREFIX=\"$(ST_PREFIX)\" | |||
egl_SYS := -lm -ldl -lEGL | |||
egl_LIBS := \ | |||
$(TOP)/src/gallium/state_trackers/egl/libegl.a \ | |||
$(TOP)/src/gallium/drivers/softpipe/libsoftpipe.a | |||
egl_LIBS := $(TOP)/src/gallium/state_trackers/egl/libegl.a | |||
ifneq ($(findstring x11, $(EGL_PLATFORMS)),) | |||
egl_SYS += -lX11 -lXext -lXfixes | |||
@@ -67,13 +66,6 @@ egl_CPPFLAGS += -DFEATURE_VG=1 | |||
endif | |||
egl_CPPFLAGS := $(sort $(egl_CPPFLAGS)) | |||
# LLVM | |||
ifeq ($(MESA_LLVM),1) | |||
common_SYS += $(LLVM_LIBS) | |||
egl_LIBS += $(TOP)/src/gallium/drivers/llvmpipe/libllvmpipe.a | |||
LDFLAGS += $(LLVM_LDFLAGS) | |||
endif | |||
# i915 pipe driver | |||
i915_CPPFLAGS := | |||
i915_SYS := -ldrm_intel | |||
@@ -112,9 +104,17 @@ vmwgfx_LIBS := \ | |||
$(TOP)/src/gallium/drivers/svga/libsvga.a | |||
# swrast (pseudo) pipe driver | |||
swrast_CPPFLAGS := | |||
swrast_SYS := | |||
swrast_LIBS := | |||
swrast_CPPFLAGS := -DGALLIUM_SOFTPIPE -DGALLIUM_RBUG -DGALLIUM_TRACE | |||
swrast_SYS := -lm | |||
swrast_LIBS := $(TOP)/src/gallium/drivers/softpipe/libsoftpipe.a | |||
# LLVM | |||
ifeq ($(MESA_LLVM),1) | |||
common_SYS += $(LLVM_LIBS) | |||
swrast_CPPFLAGS += -DGALLIUM_LLVMPIPE | |||
swrast_LIBS += $(TOP)/src/gallium/drivers/llvmpipe/libllvmpipe.a | |||
LDFLAGS += $(LLVM_LDFLAGS) | |||
endif | |||
# OpenGL state tracker | |||
GL_CPPFLAGS := -I$(TOP)/src/mesa $(API_DEFINES) | |||
@@ -158,21 +158,14 @@ endif | |||
OUTPUTS += swrast | |||
OUTPUTS := $(addprefix $(PIPE_PREFIX), $(OUTPUTS)) | |||
# state trackers | |||
OUTPUTS += $(addprefix $(ST_PREFIX), $(EGL_CLIENT_APIS)) | |||
# EGL driver and state trackers | |||
OUTPUTS += egl_gallium $(addprefix $(ST_PREFIX), $(EGL_CLIENT_APIS)) | |||
OUTPUTS := $(addsuffix .so, $(OUTPUTS)) | |||
OUTPUTS := $(addprefix $(OUTPUT_PATH)/, $(OUTPUTS)) | |||
default: $(OUTPUTS) | |||
define mklib-egl | |||
$(MKLIB) -o $(notdir $@) -noprefix -linker '$(CC)' -ldflags '$(LDFLAGS)' \ | |||
-install $(OUTPUT_PATH) $(MKLIB_OPTIONS) $< egl.o \ | |||
-Wl,--start-group $(common_LIBS) $(egl_LIBS) $($(1)_LIBS) -Wl,--end-group \ | |||
$(common_SYS) $(egl_SYS) $($(1)_SYS) | |||
endef | |||
define mklib | |||
$(MKLIB) -o $(notdir $@) -noprefix -linker '$(CC)' -ldflags '$(LDFLAGS)' \ | |||
-install $(OUTPUT_PATH) $(MKLIB_OPTIONS) $< \ | |||
@@ -180,24 +173,28 @@ $(MKLIB) -o $(notdir $@) -noprefix -linker '$(CC)' -ldflags '$(LDFLAGS)' \ | |||
$(common_SYS) $($(1)_SYS) | |||
endef | |||
# EGL drivers | |||
$(OUTPUT_PATH)/$(PIPE_PREFIX)i915.so: pipe_i915.o egl.o $(egl_LIBS) $(i915_LIBS) | |||
$(call mklib-egl,i915) | |||
# EGL driver | |||
$(OUTPUT_PATH)/egl_gallium.so: egl.o $(egl_LIBS) | |||
$(call mklib,egl) | |||
# pipe drivers | |||
$(OUTPUT_PATH)/$(PIPE_PREFIX)i915.so: pipe_i915.o $(i915_LIBS) | |||
$(call mklib,i915) | |||
$(OUTPUT_PATH)/$(PIPE_PREFIX)i965.so: pipe_i965.o egl.o $(egl_LIBS) $(i965_LIBS) | |||
$(call mklib-egl,i965) | |||
$(OUTPUT_PATH)/$(PIPE_PREFIX)i965.so: pipe_i965.o $(i965_LIBS) | |||
$(call mklib,i965) | |||
$(OUTPUT_PATH)/$(PIPE_PREFIX)nouveau.so: pipe_nouveau.o egl.o $(egl_LIBS) $(nouveau_LIBS) | |||
$(call mklib-egl,nouveau) | |||
$(OUTPUT_PATH)/$(PIPE_PREFIX)nouveau.so: pipe_nouveau.o $(nouveau_LIBS) | |||
$(call mklib,nouveau) | |||
$(OUTPUT_PATH)/$(PIPE_PREFIX)radeon.so: pipe_radeon.o egl.o $(egl_LIBS) $(radeon_LIBS) | |||
$(call mklib-egl,radeon) | |||
$(OUTPUT_PATH)/$(PIPE_PREFIX)radeon.so: pipe_radeon.o $(radeon_LIBS) | |||
$(call mklib,radeon) | |||
$(OUTPUT_PATH)/$(PIPE_PREFIX)vmwgfx.so: pipe_vmwgfx.o egl.o $(egl_LIBS) $(vmwgfx_LIBS) | |||
$(call mklib-egl,vmwgfx) | |||
$(OUTPUT_PATH)/$(PIPE_PREFIX)vmwgfx.so: pipe_vmwgfx.o $(vmwgfx_LIBS) | |||
$(call mklib,vmwgfx) | |||
$(OUTPUT_PATH)/$(PIPE_PREFIX)swrast.so: pipe_swrast.o egl.o $(egl_LIBS) $(swrast_LIBS) | |||
$(call mklib-egl,swrast) | |||
$(OUTPUT_PATH)/$(PIPE_PREFIX)swrast.so: pipe_swrast.o $(swrast_LIBS) | |||
$(call mklib,swrast) | |||
# state trackers | |||
$(OUTPUT_PATH)/$(ST_PREFIX)$(GL_LIB).so: st_GL.o $(GL_LIBS) |
@@ -25,13 +25,13 @@ if env['platform'] == 'windows': | |||
drivers += [llvmpipe] | |||
drivers += [identity, trace, rbug] | |||
egl_gallium_swrast = env.SharedLibrary( | |||
target ='egl_gallium_swrast', | |||
egl_gallium = env.SharedLibrary( | |||
target ='egl_gallium', | |||
source = ['egl.c', 'pipe_swrast.c'], | |||
LIBS = st_egl_gdi + ws_gdi + drivers + gallium + egl + env['LIBS'], | |||
) | |||
env.InstallSharedLibrary(egl_gallium_swrast) | |||
env.InstallSharedLibrary(egl_gallium) | |||
api_libs = { | |||
'OpenVG': vgapi + st_vega, |
@@ -34,21 +34,38 @@ | |||
#include "egllog.h" | |||
#include "state_tracker/st_api.h" | |||
#include "softpipe/sp_public.h" | |||
#include "llvmpipe/lp_public.h" | |||
#include "target-helpers/wrap_screen.h" | |||
#include "common/egl_g3d_loader.h" | |||
#include "state_tracker/drm_driver.h" | |||
#include "common/egl_g3d_loader.h" | |||
struct egl_g3d_loader egl_g3d_loader; | |||
static struct st_module { | |||
boolean initialized; | |||
const char *name; | |||
char *name; | |||
struct util_dl_library *lib; | |||
struct st_api *stapi; | |||
} st_modules[ST_API_COUNT]; | |||
static struct pipe_module { | |||
boolean initialized; | |||
char *name; | |||
struct util_dl_library *lib; | |||
const struct drm_driver_descriptor *drmdd; | |||
struct pipe_screen *(*swrast_create_screen)(struct sw_winsys *); | |||
} pipe_modules[16]; | |||
static char * | |||
loader_strdup(const char *s) | |||
{ | |||
size_t len = (s) ? strlen(s) : 0; | |||
char *t = MALLOC(len + 1); | |||
if (t) { | |||
memcpy(t, s, len); | |||
t[len] = '\0'; | |||
} | |||
return t; | |||
} | |||
static EGLBoolean | |||
dlopen_st_module_cb(const char *dir, size_t len, void *callback_data) | |||
{ | |||
@@ -81,7 +98,7 @@ load_st_module(struct st_module *stmod, | |||
{ | |||
struct st_api *(*create_api)(void); | |||
stmod->name = name; | |||
stmod->name = loader_strdup(name); | |||
if (stmod->name) | |||
_eglSearchPathForEach(dlopen_st_module_cb, (void *) stmod); | |||
else | |||
@@ -99,12 +116,77 @@ load_st_module(struct st_module *stmod, | |||
} | |||
} | |||
if (!stmod->stapi) | |||
if (!stmod->stapi) { | |||
FREE(stmod->name); | |||
stmod->name = NULL; | |||
} | |||
return (stmod->stapi != NULL); | |||
} | |||
static EGLBoolean | |||
dlopen_pipe_module_cb(const char *dir, size_t len, void *callback_data) | |||
{ | |||
struct pipe_module *pmod = (struct pipe_module *) callback_data; | |||
char path[1024]; | |||
int ret; | |||
if (len) { | |||
ret = util_snprintf(path, sizeof(path), | |||
"%.*s/" PIPE_PREFIX "%s" UTIL_DL_EXT, len, dir, pmod->name); | |||
} | |||
else { | |||
ret = util_snprintf(path, sizeof(path), | |||
PIPE_PREFIX "%s" UTIL_DL_EXT, pmod->name); | |||
} | |||
if (ret > 0 && ret < sizeof(path)) { | |||
pmod->lib = util_dl_open(path); | |||
if (pmod->lib) | |||
_eglLog(_EGL_DEBUG, "loaded %s", path); | |||
} | |||
return !(pmod->lib); | |||
} | |||
static boolean | |||
load_pipe_module(struct pipe_module *pmod, const char *name) | |||
{ | |||
pmod->name = loader_strdup(name); | |||
if (!pmod->name) | |||
return FALSE; | |||
_eglSearchPathForEach(dlopen_pipe_module_cb, (void *) pmod); | |||
if (pmod->lib) { | |||
pmod->drmdd = (const struct drm_driver_descriptor *) | |||
util_dl_get_proc_address(pmod->lib, "driver_descriptor"); | |||
if (pmod->drmdd) { | |||
if (pmod->drmdd->driver_name) { | |||
/* driver name mismatch */ | |||
if (strcmp(pmod->drmdd->driver_name, pmod->name) != 0) | |||
pmod->drmdd = NULL; | |||
} | |||
else { | |||
/* swrast */ | |||
pmod->swrast_create_screen = | |||
(struct pipe_screen *(*)(struct sw_winsys *)) | |||
util_dl_get_proc_address(pmod->lib, "swrast_create_screen"); | |||
if (!pmod->swrast_create_screen) | |||
pmod->drmdd = NULL; | |||
} | |||
} | |||
if (!pmod->drmdd) { | |||
util_dl_close(pmod->lib); | |||
pmod->lib = NULL; | |||
} | |||
} | |||
if (!pmod->drmdd) | |||
pmod->name = NULL; | |||
return (pmod->drmdd != NULL); | |||
} | |||
static struct st_api * | |||
get_st_api(enum st_api_type api) | |||
{ | |||
@@ -206,27 +288,47 @@ guess_gl_api(void) | |||
return stapi; | |||
} | |||
static struct pipe_module * | |||
get_pipe_module(const char *name) | |||
{ | |||
struct pipe_module *pmod = NULL; | |||
int i; | |||
if (!name) | |||
return NULL; | |||
for (i = 0; i < Elements(pipe_modules); i++) { | |||
if (!pipe_modules[i].initialized || | |||
strcmp(pipe_modules[i].name, name) == 0) { | |||
pmod = &pipe_modules[i]; | |||
break; | |||
} | |||
} | |||
if (!pmod) | |||
return NULL; | |||
if (!pmod->initialized) { | |||
load_pipe_module(pmod, name); | |||
pmod->initialized = TRUE; | |||
} | |||
return pmod; | |||
} | |||
static struct pipe_screen * | |||
create_drm_screen(const char *name, int fd) | |||
{ | |||
return (driver_descriptor.driver_name && name && | |||
strcmp(driver_descriptor.driver_name, name) == 0) ? | |||
driver_descriptor.create_screen(fd) : NULL; | |||
struct pipe_module *pmod = get_pipe_module(name); | |||
return (pmod && pmod->drmdd->create_screen) ? | |||
pmod->drmdd->create_screen(fd) : NULL; | |||
} | |||
static struct pipe_screen * | |||
create_sw_screen(struct sw_winsys *ws) | |||
{ | |||
struct pipe_screen *screen = NULL; | |||
#if defined(GALLIUM_LLVMPIPE) | |||
if (!screen && !debug_get_bool_option("GALLIUM_NO_LLVM", FALSE)) | |||
screen = llvmpipe_create_screen(ws); | |||
#endif | |||
if (!screen) | |||
screen = softpipe_create_screen(ws); | |||
return (screen) ? gallium_wrap_screen(screen) : NULL; | |||
struct pipe_module *pmod = get_pipe_module("swrast"); | |||
return (pmod && pmod->swrast_create_screen) ? | |||
pmod->swrast_create_screen(ws) : NULL; | |||
} | |||
static const struct egl_g3d_loader * | |||
@@ -273,9 +375,30 @@ loader_fini(void) | |||
util_dl_close(stmod->lib); | |||
stmod->lib = NULL; | |||
} | |||
stmod->name = NULL; | |||
if (stmod->name) { | |||
FREE(stmod->name); | |||
stmod->name = NULL; | |||
} | |||
stmod->initialized = FALSE; | |||
} | |||
for (i = 0; i < Elements(pipe_modules); i++) { | |||
struct pipe_module *pmod = &pipe_modules[i]; | |||
if (!pmod->initialized) | |||
break; | |||
pmod->drmdd = NULL; | |||
pmod->swrast_create_screen = NULL; | |||
if (pmod->lib) { | |||
util_dl_close(pmod->lib); | |||
pmod->lib = NULL; | |||
} | |||
if (pmod->name) { | |||
FREE(pmod->name); | |||
pmod->name = NULL; | |||
} | |||
pmod->initialized = FALSE; | |||
} | |||
} | |||
static void |
@@ -27,4 +27,5 @@ create_screen(int fd) | |||
return screen; | |||
} | |||
PUBLIC | |||
DRM_DRIVER_DESCRIPTOR("i965", "i965", create_screen) |
@@ -17,4 +17,5 @@ create_screen(int fd) | |||
return screen; | |||
} | |||
PUBLIC | |||
DRM_DRIVER_DESCRIPTOR("nouveau", "nouveau", create_screen) |
@@ -23,4 +23,5 @@ create_screen(int fd) | |||
return screen; | |||
} | |||
PUBLIC | |||
DRM_DRIVER_DESCRIPTOR("radeon", "radeon", create_screen) |
@@ -1,4 +1,22 @@ | |||
#include "target-helpers/inline_sw_helper.h" | |||
#include "target-helpers/inline_debug_helper.h" | |||
#include "state_tracker/drm_driver.h" | |||
PUBLIC struct pipe_screen * | |||
swrast_create_screen(struct sw_winsys *ws); | |||
PUBLIC | |||
DRM_DRIVER_DESCRIPTOR("swrast", NULL, NULL) | |||
struct pipe_screen * | |||
swrast_create_screen(struct sw_winsys *ws) | |||
{ | |||
struct pipe_screen *screen; | |||
screen = sw_screen_create(ws); | |||
if (screen) | |||
screen = debug_screen_wrap(screen); | |||
return screen; | |||
} |
@@ -23,4 +23,5 @@ create_screen(int fd) | |||
return screen; | |||
} | |||
PUBLIC | |||
DRM_DRIVER_DESCRIPTOR("vmwgfx", "vmwgfx", create_screen) |