2008-11-02 08:26:08 -07:00
|
|
|
/*
|
|
|
|
* Copyright © 2008 Red Hat, Inc
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef HAVE_DIX_CONFIG_H
|
|
|
|
#include <dix-config.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <errno.h>
|
2011-11-05 07:32:40 -06:00
|
|
|
#include <dlfcn.h>
|
2008-11-02 08:26:08 -07:00
|
|
|
#include <sys/time.h>
|
|
|
|
#include <GL/gl.h>
|
|
|
|
#include <GL/glxtokens.h>
|
|
|
|
#include <GL/internal/dri_interface.h>
|
|
|
|
#include <os.h>
|
2019-07-27 01:57:06 -06:00
|
|
|
#include "extinit.h"
|
2008-11-02 08:26:08 -07:00
|
|
|
#include "glxserver.h"
|
2014-05-02 13:27:46 -06:00
|
|
|
#include "glxext.h"
|
2008-11-02 08:26:08 -07:00
|
|
|
#include "glxcontext.h"
|
|
|
|
#include "glxscreens.h"
|
|
|
|
#include "glxdricommon.h"
|
|
|
|
|
|
|
|
#define __ATTRIB(attrib, field) \
|
|
|
|
{ attrib, offsetof(__GLXconfig, field) }
|
|
|
|
|
2012-06-10 07:21:05 -06:00
|
|
|
static const struct {
|
|
|
|
unsigned int attrib, offset;
|
|
|
|
} attribMap[] = {
|
|
|
|
__ATTRIB(__DRI_ATTRIB_BUFFER_SIZE, rgbBits),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_LEVEL, level),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_RED_SIZE, redBits),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_GREEN_SIZE, greenBits),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_BLUE_SIZE, blueBits),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_ALPHA_SIZE, alphaBits),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_DEPTH_SIZE, depthBits),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_STENCIL_SIZE, stencilBits),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_ACCUM_RED_SIZE, accumRedBits),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_ACCUM_GREEN_SIZE, accumGreenBits),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_ACCUM_BLUE_SIZE, accumBlueBits),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_ACCUM_ALPHA_SIZE, accumAlphaBits),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_SAMPLE_BUFFERS, sampleBuffers),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_SAMPLES, samples),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_DOUBLE_BUFFER, doubleBufferMode),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_STEREO, stereoMode),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_AUX_BUFFERS, numAuxBuffers),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_TRANSPARENT_TYPE, transparentPixel),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_TRANSPARENT_INDEX_VALUE, transparentPixel),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_TRANSPARENT_RED_VALUE, transparentRed),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_TRANSPARENT_GREEN_VALUE, transparentGreen),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_TRANSPARENT_BLUE_VALUE, transparentBlue),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_TRANSPARENT_ALPHA_VALUE, transparentAlpha),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_RED_MASK, redMask),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_GREEN_MASK, greenMask),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_BLUE_MASK, blueMask),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_ALPHA_MASK, alphaMask),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_MAX_PBUFFER_WIDTH, maxPbufferWidth),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_MAX_PBUFFER_HEIGHT, maxPbufferHeight),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_MAX_PBUFFER_PIXELS, maxPbufferPixels),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_OPTIMAL_PBUFFER_WIDTH, optimalPbufferWidth),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_OPTIMAL_PBUFFER_HEIGHT, optimalPbufferHeight),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_SWAP_METHOD, swapMethod),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_BIND_TO_TEXTURE_RGB, bindToTextureRgb),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_BIND_TO_TEXTURE_RGBA, bindToTextureRgba),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_BIND_TO_MIPMAP_TEXTURE, bindToMipmapTexture),
|
2014-05-02 13:27:46 -06:00
|
|
|
__ATTRIB(__DRI_ATTRIB_YINVERTED, yInverted),
|
|
|
|
__ATTRIB(__DRI_ATTRIB_FRAMEBUFFER_SRGB_CAPABLE, sRGBCapable),
|
|
|
|
};
|
2008-11-02 08:26:08 -07:00
|
|
|
|
|
|
|
static void
|
2012-06-10 07:21:05 -06:00
|
|
|
setScalar(__GLXconfig * config, unsigned int attrib, unsigned int value)
|
2008-11-02 08:26:08 -07:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(attribMap); i++)
|
2012-06-10 07:21:05 -06:00
|
|
|
if (attribMap[i].attrib == attrib) {
|
|
|
|
*(unsigned int *) ((char *) config + attribMap[i].offset) = value;
|
|
|
|
return;
|
|
|
|
}
|
2008-11-02 08:26:08 -07:00
|
|
|
}
|
|
|
|
|
2017-12-08 08:01:59 -07:00
|
|
|
static Bool
|
|
|
|
render_type_is_pbuffer_only(unsigned renderType)
|
|
|
|
{
|
|
|
|
/* The GL_ARB_color_buffer_float spec says:
|
|
|
|
*
|
|
|
|
* "Note that floating point rendering is only supported for
|
|
|
|
* GLXPbuffer drawables. The GLX_DRAWABLE_TYPE attribute of the
|
|
|
|
* GLXFBConfig must have the GLX_PBUFFER_BIT bit set and the
|
|
|
|
* GLX_RENDER_TYPE attribute must have the GLX_RGBA_FLOAT_BIT set."
|
|
|
|
*/
|
|
|
|
return !!(renderType & (__DRI_ATTRIB_UNSIGNED_FLOAT_BIT
|
|
|
|
| __DRI_ATTRIB_FLOAT_BIT));
|
|
|
|
}
|
|
|
|
|
2008-11-02 08:26:08 -07:00
|
|
|
static __GLXconfig *
|
2012-06-10 07:21:05 -06:00
|
|
|
createModeFromConfig(const __DRIcoreExtension * core,
|
|
|
|
const __DRIconfig * driConfig,
|
2018-02-18 10:16:37 -07:00
|
|
|
unsigned int visualType,
|
|
|
|
GLboolean duplicateForComp)
|
2008-11-02 08:26:08 -07:00
|
|
|
{
|
|
|
|
__GLXDRIconfig *config;
|
2014-05-02 13:27:46 -06:00
|
|
|
GLint renderType = 0;
|
2017-12-08 08:01:59 -07:00
|
|
|
unsigned int attrib, value, drawableType = GLX_PBUFFER_BIT;
|
2008-11-02 08:26:08 -07:00
|
|
|
int i;
|
|
|
|
|
2018-02-18 10:16:37 -07:00
|
|
|
|
2014-05-02 13:27:46 -06:00
|
|
|
config = calloc(1, sizeof *config);
|
2008-11-02 08:26:08 -07:00
|
|
|
|
|
|
|
config->driConfig = driConfig;
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
while (core->indexConfigAttrib(driConfig, i++, &attrib, &value)) {
|
2012-06-10 07:21:05 -06:00
|
|
|
switch (attrib) {
|
|
|
|
case __DRI_ATTRIB_RENDER_TYPE:
|
|
|
|
if (value & __DRI_ATTRIB_RGBA_BIT)
|
2014-05-02 13:27:46 -06:00
|
|
|
renderType |= GLX_RGBA_BIT;
|
2012-06-10 07:21:05 -06:00
|
|
|
if (value & __DRI_ATTRIB_COLOR_INDEX_BIT)
|
2014-05-02 13:27:46 -06:00
|
|
|
renderType |= GLX_COLOR_INDEX_BIT;
|
|
|
|
if (value & __DRI_ATTRIB_FLOAT_BIT)
|
|
|
|
renderType |= GLX_RGBA_FLOAT_BIT_ARB;
|
|
|
|
if (value & __DRI_ATTRIB_UNSIGNED_FLOAT_BIT)
|
|
|
|
renderType |= GLX_RGBA_UNSIGNED_FLOAT_BIT_EXT;
|
2012-06-10 07:21:05 -06:00
|
|
|
break;
|
|
|
|
case __DRI_ATTRIB_CONFIG_CAVEAT:
|
|
|
|
if (value & __DRI_ATTRIB_NON_CONFORMANT_CONFIG)
|
|
|
|
config->config.visualRating = GLX_NON_CONFORMANT_CONFIG;
|
|
|
|
else if (value & __DRI_ATTRIB_SLOW_BIT)
|
|
|
|
config->config.visualRating = GLX_SLOW_CONFIG;
|
|
|
|
else
|
|
|
|
config->config.visualRating = GLX_NONE;
|
|
|
|
break;
|
|
|
|
case __DRI_ATTRIB_BIND_TO_TEXTURE_TARGETS:
|
|
|
|
config->config.bindToTextureTargets = 0;
|
|
|
|
if (value & __DRI_ATTRIB_TEXTURE_1D_BIT)
|
|
|
|
config->config.bindToTextureTargets |= GLX_TEXTURE_1D_BIT_EXT;
|
|
|
|
if (value & __DRI_ATTRIB_TEXTURE_2D_BIT)
|
|
|
|
config->config.bindToTextureTargets |= GLX_TEXTURE_2D_BIT_EXT;
|
|
|
|
if (value & __DRI_ATTRIB_TEXTURE_RECTANGLE_BIT)
|
|
|
|
config->config.bindToTextureTargets |=
|
|
|
|
GLX_TEXTURE_RECTANGLE_BIT_EXT;
|
|
|
|
break;
|
2018-02-18 10:16:37 -07:00
|
|
|
case __DRI_ATTRIB_SWAP_METHOD:
|
|
|
|
/* Workaround for broken dri drivers */
|
|
|
|
if (value != GLX_SWAP_UNDEFINED_OML &&
|
|
|
|
value != GLX_SWAP_COPY_OML &&
|
|
|
|
value != GLX_SWAP_EXCHANGE_OML)
|
|
|
|
value = GLX_SWAP_UNDEFINED_OML;
|
|
|
|
/* Fall through. */
|
2012-06-10 07:21:05 -06:00
|
|
|
default:
|
|
|
|
setScalar(&config->config, attrib, value);
|
|
|
|
break;
|
|
|
|
}
|
2008-11-02 08:26:08 -07:00
|
|
|
}
|
|
|
|
|
2017-12-08 08:01:59 -07:00
|
|
|
if (!render_type_is_pbuffer_only(renderType))
|
|
|
|
drawableType |= GLX_WINDOW_BIT | GLX_PIXMAP_BIT;
|
|
|
|
|
2008-11-02 08:26:08 -07:00
|
|
|
config->config.next = NULL;
|
|
|
|
config->config.visualType = visualType;
|
2014-05-02 13:27:46 -06:00
|
|
|
config->config.renderType = renderType;
|
2010-12-05 08:36:02 -07:00
|
|
|
config->config.drawableType = drawableType;
|
2014-05-02 13:27:46 -06:00
|
|
|
config->config.yInverted = GL_TRUE;
|
2008-11-02 08:26:08 -07:00
|
|
|
|
2018-02-18 10:16:37 -07:00
|
|
|
#ifdef COMPOSITE
|
2019-07-27 01:57:06 -06:00
|
|
|
if (!noCompositeExtension) {
|
|
|
|
/*
|
|
|
|
* Here we decide what fbconfigs will be duplicated for compositing.
|
|
|
|
* fgbconfigs marked with duplicatedForConf will be reserved for
|
|
|
|
* compositing visuals.
|
|
|
|
* It might look strange to do this decision this late when translation
|
|
|
|
* from a __DRIConfig is already done, but using the __DRIConfig
|
|
|
|
* accessor function becomes worse both with respect to code complexity
|
|
|
|
* and CPU usage.
|
|
|
|
*/
|
|
|
|
if (duplicateForComp &&
|
|
|
|
(render_type_is_pbuffer_only(renderType) ||
|
|
|
|
config->config.rgbBits != 32 ||
|
|
|
|
config->config.redBits != 8 ||
|
|
|
|
config->config.greenBits != 8 ||
|
|
|
|
config->config.blueBits != 8 ||
|
|
|
|
config->config.visualRating != GLX_NONE ||
|
|
|
|
config->config.sampleBuffers != 0)) {
|
|
|
|
free(config);
|
|
|
|
return NULL;
|
|
|
|
}
|
2018-02-18 10:16:37 -07:00
|
|
|
|
2019-07-27 01:57:06 -06:00
|
|
|
config->config.duplicatedForComp = duplicateForComp;
|
|
|
|
}
|
2018-02-18 10:16:37 -07:00
|
|
|
#endif
|
|
|
|
|
2008-11-02 08:26:08 -07:00
|
|
|
return &config->config;
|
|
|
|
}
|
|
|
|
|
|
|
|
__GLXconfig *
|
2012-06-10 07:21:05 -06:00
|
|
|
glxConvertConfigs(const __DRIcoreExtension * core,
|
2017-12-08 08:01:59 -07:00
|
|
|
const __DRIconfig ** configs)
|
2008-11-02 08:26:08 -07:00
|
|
|
{
|
|
|
|
__GLXconfig head, *tail;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
tail = &head;
|
|
|
|
head.next = NULL;
|
|
|
|
|
|
|
|
for (i = 0; configs[i]; i++) {
|
2018-02-18 10:16:37 -07:00
|
|
|
tail->next = createModeFromConfig(core, configs[i], GLX_TRUE_COLOR,
|
|
|
|
GL_FALSE);
|
2012-06-10 07:21:05 -06:00
|
|
|
if (tail->next == NULL)
|
|
|
|
break;
|
|
|
|
tail = tail->next;
|
2008-11-02 08:26:08 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; configs[i]; i++) {
|
2018-02-18 10:16:37 -07:00
|
|
|
tail->next = createModeFromConfig(core, configs[i], GLX_DIRECT_COLOR,
|
|
|
|
GL_FALSE);
|
2012-06-10 07:21:05 -06:00
|
|
|
if (tail->next == NULL)
|
|
|
|
break;
|
2008-11-02 08:26:08 -07:00
|
|
|
|
2012-06-10 07:21:05 -06:00
|
|
|
tail = tail->next;
|
2008-11-02 08:26:08 -07:00
|
|
|
}
|
|
|
|
|
2018-02-18 10:16:37 -07:00
|
|
|
#ifdef COMPOSITE
|
2019-07-27 01:57:06 -06:00
|
|
|
if (!noCompositeExtension) {
|
|
|
|
/* Duplicate fbconfigs for use with compositing visuals */
|
|
|
|
for (i = 0; configs[i]; i++) {
|
|
|
|
tail->next = createModeFromConfig(core, configs[i], GLX_TRUE_COLOR,
|
|
|
|
GL_TRUE);
|
|
|
|
if (tail->next == NULL)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
tail = tail->next;
|
|
|
|
}
|
2018-02-18 10:16:37 -07:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2008-11-02 08:26:08 -07:00
|
|
|
return head.next;
|
|
|
|
}
|
2011-11-05 07:32:40 -06:00
|
|
|
|
|
|
|
static const char dri_driver_path[] = DRI_DRIVER_PATH;
|
|
|
|
|
2013-12-28 07:40:01 -07:00
|
|
|
/* Temporary define to allow building without a dri_interface.h from
|
|
|
|
* updated Mesa. Some day when we don't care about Mesa that old any
|
|
|
|
* more this can be removed.
|
|
|
|
*/
|
|
|
|
#ifndef __DRI_DRIVER_GET_EXTENSIONS
|
|
|
|
#define __DRI_DRIVER_GET_EXTENSIONS "__driDriverGetExtensions"
|
|
|
|
#endif
|
|
|
|
|
2011-11-05 07:32:40 -06:00
|
|
|
void *
|
|
|
|
glxProbeDriver(const char *driverName,
|
2012-06-10 07:21:05 -06:00
|
|
|
void **coreExt, const char *coreName, int coreVersion,
|
|
|
|
void **renderExt, const char *renderName, int renderVersion)
|
2011-11-05 07:32:40 -06:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
void *driver;
|
|
|
|
char filename[PATH_MAX];
|
2013-12-28 07:40:01 -07:00
|
|
|
char *get_extensions_name;
|
|
|
|
const __DRIextension **extensions = NULL;
|
2019-07-27 01:57:06 -06:00
|
|
|
const char *path = NULL;
|
|
|
|
|
|
|
|
/* Search in LIBGL_DRIVERS_PATH if we're not setuid. */
|
|
|
|
if (!PrivsElevated())
|
|
|
|
path = getenv("LIBGL_DRIVERS_PATH");
|
|
|
|
|
|
|
|
if (!path)
|
|
|
|
path = dri_driver_path;
|
|
|
|
|
|
|
|
do {
|
|
|
|
const char *next;
|
|
|
|
int path_len;
|
|
|
|
|
|
|
|
next = strchr(path, ':');
|
|
|
|
if (next) {
|
|
|
|
path_len = next - path;
|
|
|
|
next++;
|
|
|
|
} else {
|
|
|
|
path_len = strlen(path);
|
|
|
|
next = NULL;
|
|
|
|
}
|
2011-11-05 07:32:40 -06:00
|
|
|
|
2019-07-27 01:57:06 -06:00
|
|
|
snprintf(filename, sizeof filename, "%.*s/%s_dri.so", path_len, path,
|
|
|
|
driverName);
|
|
|
|
|
|
|
|
driver = dlopen(filename, RTLD_LAZY | RTLD_LOCAL);
|
|
|
|
if (driver != NULL)
|
|
|
|
break;
|
2011-11-05 07:32:40 -06:00
|
|
|
|
2012-06-10 07:21:05 -06:00
|
|
|
LogMessage(X_ERROR, "AIGLX error: dlopen of %s failed (%s)\n",
|
|
|
|
filename, dlerror());
|
2019-07-27 01:57:06 -06:00
|
|
|
|
|
|
|
path = next;
|
|
|
|
} while (path);
|
|
|
|
|
|
|
|
if (driver == NULL) {
|
|
|
|
LogMessage(X_ERROR, "AIGLX error: unable to load driver %s\n",
|
|
|
|
driverName);
|
2012-06-10 07:21:05 -06:00
|
|
|
goto cleanup_failure;
|
2011-11-05 07:32:40 -06:00
|
|
|
}
|
|
|
|
|
2013-12-28 07:40:01 -07:00
|
|
|
if (asprintf(&get_extensions_name, "%s_%s",
|
|
|
|
__DRI_DRIVER_GET_EXTENSIONS, driverName) != -1) {
|
|
|
|
const __DRIextension **(*get_extensions)(void);
|
|
|
|
|
|
|
|
get_extensions = dlsym(driver, get_extensions_name);
|
|
|
|
if (get_extensions)
|
|
|
|
extensions = get_extensions();
|
|
|
|
free(get_extensions_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!extensions)
|
|
|
|
extensions = dlsym(driver, __DRI_DRIVER_EXTENSIONS);
|
2011-11-05 07:32:40 -06:00
|
|
|
if (extensions == NULL) {
|
2012-06-10 07:21:05 -06:00
|
|
|
LogMessage(X_ERROR, "AIGLX error: %s exports no extensions (%s)\n",
|
|
|
|
driverName, dlerror());
|
|
|
|
goto cleanup_failure;
|
2011-11-05 07:32:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; extensions[i]; i++) {
|
2012-06-10 07:21:05 -06:00
|
|
|
if (strcmp(extensions[i]->name, coreName) == 0 &&
|
|
|
|
extensions[i]->version >= coreVersion) {
|
|
|
|
*coreExt = (void *) extensions[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strcmp(extensions[i]->name, renderName) == 0 &&
|
|
|
|
extensions[i]->version >= renderVersion) {
|
|
|
|
*renderExt = (void *) extensions[i];
|
|
|
|
}
|
2011-11-05 07:32:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
if (*coreExt == NULL || *renderExt == NULL) {
|
2012-06-10 07:21:05 -06:00
|
|
|
LogMessage(X_ERROR,
|
|
|
|
"AIGLX error: %s does not export required DRI extension\n",
|
|
|
|
driverName);
|
|
|
|
goto cleanup_failure;
|
2011-11-05 07:32:40 -06:00
|
|
|
}
|
|
|
|
return driver;
|
|
|
|
|
2012-06-10 07:21:05 -06:00
|
|
|
cleanup_failure:
|
2011-11-05 07:32:40 -06:00
|
|
|
if (driver)
|
2012-06-10 07:21:05 -06:00
|
|
|
dlclose(driver);
|
2011-11-05 07:32:40 -06:00
|
|
|
*coreExt = *renderExt = NULL;
|
|
|
|
return NULL;
|
|
|
|
}
|