mutter/cogl/cogl/cogl-renderer.c

783 lines
20 KiB
C

/*
* Cogl
*
* A Low Level GPU Graphics and Utilities API
*
* Copyright (C) 2011 Intel Corporation.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Authors:
* Robert Bragg <robert@linux.intel.com>
*/
#include "cogl-config.h"
#include <gio/gio.h>
#include <stdlib.h>
#include <string.h>
#include "cogl/cogl-util.h"
#include "cogl/cogl-private.h"
#include "cogl/cogl-object.h"
#include "cogl/cogl-context-private.h"
#include "cogl/cogl-mutter.h"
#include "cogl/cogl-renderer.h"
#include "cogl/cogl-renderer-private.h"
#include "cogl/cogl-display-private.h"
#include "cogl/cogl-gtype-private.h"
#include "cogl/winsys/cogl-winsys-private.h"
#ifdef COGL_HAS_EGL_PLATFORM_XLIB_SUPPORT
#include "cogl/winsys/cogl-winsys-egl-x11-private.h"
#endif
#ifdef COGL_HAS_GLX_SUPPORT
#include "cogl/winsys/cogl-winsys-glx-private.h"
#endif
#ifdef COGL_HAS_XLIB
#include "cogl/cogl-xlib-renderer.h"
#endif
#ifdef HAVE_COGL_GL
extern const CoglTextureDriver _cogl_texture_driver_gl;
extern const CoglDriverVtable _cogl_driver_gl;
#endif
#if defined (HAVE_COGL_GLES2)
extern const CoglTextureDriver _cogl_texture_driver_gles;
extern const CoglDriverVtable _cogl_driver_gles;
#endif
extern const CoglDriverVtable _cogl_driver_nop;
typedef struct _CoglDriverDescription
{
CoglDriver id;
const char *name;
/* It would be nice to make this a pointer and then use a compound
* literal from C99 to initialise it but we probably can't get away
* with using C99 here. Instead we'll just use a fixed-size array.
* GCC should complain if someone adds an 8th feature to a
* driver. */
const CoglPrivateFeature private_features[8];
const CoglDriverVtable *vtable;
const CoglTextureDriver *texture_driver;
const char *libgl_name;
} CoglDriverDescription;
static CoglDriverDescription _cogl_drivers[] =
{
#ifdef HAVE_COGL_GL
{
COGL_DRIVER_GL3,
"gl3",
{ COGL_PRIVATE_FEATURE_ANY_GL,
-1 },
&_cogl_driver_gl,
&_cogl_texture_driver_gl,
COGL_GL_LIBNAME,
},
#endif
#ifdef HAVE_COGL_GLES2
{
COGL_DRIVER_GLES2,
"gles2",
{ COGL_PRIVATE_FEATURE_ANY_GL,
-1 },
&_cogl_driver_gles,
&_cogl_texture_driver_gles,
COGL_GLES2_LIBNAME,
},
#endif
{
COGL_DRIVER_NOP,
"nop",
{ -1 },
&_cogl_driver_nop,
NULL, /* texture driver */
NULL /* libgl_name */
}
};
static CoglWinsysVtableGetter _cogl_winsys_vtable_getters[] =
{
#ifdef COGL_HAS_GLX_SUPPORT
_cogl_winsys_glx_get_vtable,
#endif
#ifdef COGL_HAS_EGL_PLATFORM_XLIB_SUPPORT
_cogl_winsys_egl_xlib_get_vtable,
#endif
};
static void _cogl_renderer_free (CoglRenderer *renderer);
COGL_OBJECT_DEFINE (Renderer, renderer);
COGL_GTYPE_DEFINE_CLASS (Renderer, renderer);
typedef struct _CoglNativeFilterClosure
{
CoglNativeFilterFunc func;
void *data;
} CoglNativeFilterClosure;
uint32_t
cogl_renderer_error_quark (void)
{
return g_quark_from_static_string ("cogl-renderer-error-quark");
}
static const CoglWinsysVtable *
_cogl_renderer_get_winsys (CoglRenderer *renderer)
{
return renderer->winsys_vtable;
}
static void
native_filter_closure_free (CoglNativeFilterClosure *closure)
{
g_free (closure);
}
static void
_cogl_renderer_free (CoglRenderer *renderer)
{
const CoglWinsysVtable *winsys = _cogl_renderer_get_winsys (renderer);
_cogl_closure_list_disconnect_all (&renderer->idle_closures);
if (winsys)
winsys->renderer_disconnect (renderer);
if (renderer->libgl_module)
g_module_close (renderer->libgl_module);
g_slist_free_full (renderer->event_filters,
(GDestroyNotify) native_filter_closure_free);
g_array_free (renderer->poll_fds, TRUE);
g_free (renderer);
}
CoglRenderer *
cogl_renderer_new (void)
{
CoglRenderer *renderer = g_new0 (CoglRenderer, 1);
_cogl_init ();
renderer->connected = FALSE;
renderer->event_filters = NULL;
renderer->poll_fds = g_array_new (FALSE, TRUE, sizeof (CoglPollFD));
_cogl_list_init (&renderer->idle_closures);
#ifdef COGL_HAS_XLIB
renderer->xlib_enable_event_retrieval = TRUE;
#endif
return _cogl_renderer_object_new (renderer);
}
#ifdef COGL_HAS_XLIB
void
cogl_xlib_renderer_set_foreign_display (CoglRenderer *renderer,
Display *xdisplay)
{
g_return_if_fail (cogl_is_renderer (renderer));
/* NB: Renderers are considered immutable once connected */
g_return_if_fail (!renderer->connected);
renderer->foreign_xdpy = xdisplay;
/* If the application is using a foreign display then we can assume
it will also do its own event retrieval */
renderer->xlib_enable_event_retrieval = FALSE;
}
Display *
cogl_xlib_renderer_get_foreign_display (CoglRenderer *renderer)
{
g_return_val_if_fail (cogl_is_renderer (renderer), NULL);
return renderer->foreign_xdpy;
}
void
cogl_xlib_renderer_request_reset_on_video_memory_purge (CoglRenderer *renderer,
gboolean enable)
{
g_return_if_fail (cogl_is_renderer (renderer));
g_return_if_fail (!renderer->connected);
renderer->xlib_want_reset_on_video_memory_purge = enable;
}
#endif /* COGL_HAS_XLIB */
gboolean
cogl_renderer_check_onscreen_template (CoglRenderer *renderer,
CoglOnscreenTemplate *onscreen_template,
GError **error)
{
CoglDisplay *display;
if (!cogl_renderer_connect (renderer, error))
return FALSE;
display = cogl_display_new (renderer, onscreen_template);
if (!cogl_display_setup (display, error))
{
cogl_object_unref (display);
return FALSE;
}
cogl_object_unref (display);
return TRUE;
}
typedef gboolean (*CoglDriverCallback) (CoglDriverDescription *description,
void *user_data);
static void
foreach_driver_description (CoglDriver driver_override,
CoglDriverCallback callback,
void *user_data)
{
#ifdef COGL_DEFAULT_DRIVER
const CoglDriverDescription *default_driver = NULL;
#endif
int i;
if (driver_override != COGL_DRIVER_ANY)
{
for (i = 0; i < G_N_ELEMENTS (_cogl_drivers); i++)
{
if (_cogl_drivers[i].id == driver_override)
{
callback (&_cogl_drivers[i], user_data);
return;
}
}
g_warn_if_reached ();
return;
}
#ifdef COGL_DEFAULT_DRIVER
for (i = 0; i < G_N_ELEMENTS (_cogl_drivers); i++)
{
const CoglDriverDescription *desc = &_cogl_drivers[i];
if (g_ascii_strcasecmp (desc->name, COGL_DEFAULT_DRIVER) == 0)
{
default_driver = desc;
break;
}
}
if (default_driver)
{
if (!callback (default_driver, user_data))
return;
}
#endif
for (i = 0; i < G_N_ELEMENTS (_cogl_drivers); i++)
{
#ifdef COGL_DEFAULT_DRIVER
if (&_cogl_drivers[i] == default_driver)
continue;
#endif
if (!callback (&_cogl_drivers[i], user_data))
return;
}
}
static CoglDriver
driver_name_to_id (const char *name)
{
int i;
for (i = 0; i < G_N_ELEMENTS (_cogl_drivers); i++)
{
if (g_ascii_strcasecmp (_cogl_drivers[i].name, name) == 0)
return _cogl_drivers[i].id;
}
return COGL_DRIVER_ANY;
}
static const char *
driver_id_to_name (CoglDriver id)
{
switch (id)
{
case COGL_DRIVER_GL3:
return "gl3";
case COGL_DRIVER_GLES2:
return "gles2";
case COGL_DRIVER_NOP:
return "nop";
case COGL_DRIVER_ANY:
g_warn_if_reached ();
return "any";
}
g_warn_if_reached ();
return "unknown";
}
typedef struct _SatisfyConstraintsState
{
const CoglDriverDescription *driver_description;
} SatisfyConstraintsState;
/* XXX this is still uglier than it needs to be */
static gboolean
satisfy_constraints (CoglDriverDescription *description,
void *user_data)
{
SatisfyConstraintsState *state = user_data;
state->driver_description = description;
return FALSE;
}
static gboolean
_cogl_renderer_choose_driver (CoglRenderer *renderer,
GError **error)
{
const char *driver_name = g_getenv ("COGL_DRIVER");
CoglDriver driver_override = COGL_DRIVER_ANY;
const char *invalid_override = NULL;
const char *libgl_name;
SatisfyConstraintsState state;
const CoglDriverDescription *desc;
int i;
if (driver_name)
{
driver_override = driver_name_to_id (driver_name);
if (driver_override == COGL_DRIVER_ANY)
invalid_override = driver_name;
}
if (renderer->driver_override != COGL_DRIVER_ANY)
{
if (driver_override != COGL_DRIVER_ANY &&
renderer->driver_override != driver_override)
{
g_set_error (error, COGL_RENDERER_ERROR,
COGL_RENDERER_ERROR_BAD_CONSTRAINT,
"Application driver selection conflicts with driver "
"specified in configuration");
return FALSE;
}
driver_override = renderer->driver_override;
}
if (driver_override != COGL_DRIVER_ANY)
{
gboolean found = FALSE;
int i;
for (i = 0; i < G_N_ELEMENTS (_cogl_drivers); i++)
{
if (_cogl_drivers[i].id == driver_override)
{
found = TRUE;
break;
}
}
if (!found)
invalid_override = driver_id_to_name (driver_override);
}
if (invalid_override)
{
g_set_error (error, COGL_RENDERER_ERROR,
COGL_RENDERER_ERROR_BAD_CONSTRAINT,
"Driver \"%s\" is not available",
invalid_override);
return FALSE;
}
state.driver_description = NULL;
foreach_driver_description (driver_override,
satisfy_constraints,
&state);
if (!state.driver_description)
{
g_set_error (error, COGL_RENDERER_ERROR,
COGL_RENDERER_ERROR_BAD_CONSTRAINT,
"No suitable driver found");
return FALSE;
}
desc = state.driver_description;
renderer->driver = desc->id;
renderer->driver_vtable = desc->vtable;
renderer->texture_driver = desc->texture_driver;
libgl_name = desc->libgl_name;
memset(renderer->private_features, 0, sizeof (renderer->private_features));
for (i = 0; desc->private_features[i] != -1; i++)
COGL_FLAGS_SET (renderer->private_features,
desc->private_features[i], TRUE);
if (COGL_FLAGS_GET (renderer->private_features,
COGL_PRIVATE_FEATURE_ANY_GL))
{
renderer->libgl_module = g_module_open (libgl_name,
G_MODULE_BIND_LAZY);
if (renderer->libgl_module == NULL)
{
g_set_error (error, COGL_DRIVER_ERROR,
COGL_DRIVER_ERROR_FAILED_TO_LOAD_LIBRARY,
"Failed to dynamically open the GL library \"%s\"",
libgl_name);
return FALSE;
}
}
return TRUE;
}
/* Final connection API */
void
cogl_renderer_set_custom_winsys (CoglRenderer *renderer,
CoglCustomWinsysVtableGetter winsys_vtable_getter,
void *user_data)
{
renderer->custom_winsys_user_data = user_data;
renderer->custom_winsys_vtable_getter = winsys_vtable_getter;
}
static gboolean
connect_custom_winsys (CoglRenderer *renderer,
GError **error)
{
const CoglWinsysVtable *winsys;
GError *tmp_error = NULL;
GString *error_message;
winsys = renderer->custom_winsys_vtable_getter (renderer);
renderer->winsys_vtable = winsys;
error_message = g_string_new ("");
if (!winsys->renderer_connect (renderer, &tmp_error))
{
g_string_append_c (error_message, '\n');
g_string_append (error_message, tmp_error->message);
g_error_free (tmp_error);
}
else
{
renderer->connected = TRUE;
g_string_free (error_message, TRUE);
return TRUE;
}
renderer->winsys_vtable = NULL;
g_set_error (error, COGL_WINSYS_ERROR, COGL_WINSYS_ERROR_INIT,
"Failed to connected to any renderer: %s", error_message->str);
g_string_free (error_message, TRUE);
return FALSE;
}
gboolean
cogl_renderer_connect (CoglRenderer *renderer, GError **error)
{
int i;
g_autoptr (GString) error_message = NULL;
gboolean constraints_failed = FALSE;
if (renderer->connected)
return TRUE;
/* The driver needs to be chosen before connecting the renderer
because eglInitialize requires the library containing the GL API
to be loaded before its called */
if (!_cogl_renderer_choose_driver (renderer, error))
return FALSE;
if (renderer->custom_winsys_vtable_getter)
return connect_custom_winsys (renderer, error);
error_message = g_string_new ("");
for (i = 0; i < G_N_ELEMENTS (_cogl_winsys_vtable_getters); i++)
{
const CoglWinsysVtable *winsys = _cogl_winsys_vtable_getters[i]();
GError *tmp_error = NULL;
GList *l;
gboolean skip_due_to_constraints = FALSE;
if (renderer->winsys_id_override != COGL_WINSYS_ID_ANY)
{
if (renderer->winsys_id_override != winsys->id)
continue;
}
else
{
char *user_choice = getenv ("COGL_RENDERER");
if (user_choice &&
g_ascii_strcasecmp (winsys->name, user_choice) != 0)
continue;
}
for (l = renderer->constraints; l; l = l->next)
{
CoglRendererConstraint constraint = GPOINTER_TO_UINT (l->data);
if (!(winsys->constraints & constraint))
{
skip_due_to_constraints = TRUE;
break;
}
}
if (skip_due_to_constraints)
{
constraints_failed |= TRUE;
continue;
}
/* At least temporarily we will associate this winsys with
* the renderer in-case ->renderer_connect calls API that
* wants to query the current winsys... */
renderer->winsys_vtable = winsys;
if (!winsys->renderer_connect (renderer, &tmp_error))
{
g_string_append_c (error_message, '\n');
g_string_append (error_message, tmp_error->message);
g_error_free (tmp_error);
}
else
{
renderer->connected = TRUE;
return TRUE;
}
}
if (!renderer->connected)
{
if (constraints_failed)
{
g_set_error (error, COGL_RENDERER_ERROR,
COGL_RENDERER_ERROR_BAD_CONSTRAINT,
"Failed to connected to any renderer due to constraints");
return FALSE;
}
renderer->winsys_vtable = NULL;
g_set_error (error, COGL_WINSYS_ERROR, COGL_WINSYS_ERROR_INIT,
"Failed to connected to any renderer: %s",
error_message->str);
return FALSE;
}
return TRUE;
}
CoglFilterReturn
_cogl_renderer_handle_native_event (CoglRenderer *renderer,
void *event)
{
GSList *l, *next;
/* Pass the event on to all of the registered filters in turn */
for (l = renderer->event_filters; l; l = next)
{
CoglNativeFilterClosure *closure = l->data;
/* The next pointer is taken now so that we can handle the
closure being removed during emission */
next = l->next;
if (closure->func (event, closure->data) == COGL_FILTER_REMOVE)
return COGL_FILTER_REMOVE;
}
/* If the backend for the renderer also wants to see the events, it
should just register its own filter */
return COGL_FILTER_CONTINUE;
}
void
_cogl_renderer_add_native_filter (CoglRenderer *renderer,
CoglNativeFilterFunc func,
void *data)
{
CoglNativeFilterClosure *closure;
closure = g_new0 (CoglNativeFilterClosure, 1);
closure->func = func;
closure->data = data;
renderer->event_filters = g_slist_prepend (renderer->event_filters, closure);
}
void
_cogl_renderer_remove_native_filter (CoglRenderer *renderer,
CoglNativeFilterFunc func,
void *data)
{
GSList *l, *prev = NULL;
for (l = renderer->event_filters; l; prev = l, l = l->next)
{
CoglNativeFilterClosure *closure = l->data;
if (closure->func == func && closure->data == data)
{
native_filter_closure_free (closure);
if (prev)
prev->next = g_slist_delete_link (prev->next, l);
else
renderer->event_filters =
g_slist_delete_link (renderer->event_filters, l);
break;
}
}
}
void
cogl_renderer_set_winsys_id (CoglRenderer *renderer,
CoglWinsysID winsys_id)
{
g_return_if_fail (!renderer->connected);
renderer->winsys_id_override = winsys_id;
}
CoglWinsysID
cogl_renderer_get_winsys_id (CoglRenderer *renderer)
{
g_return_val_if_fail (renderer->connected, 0);
return renderer->winsys_vtable->id;
}
void *
_cogl_renderer_get_proc_address (CoglRenderer *renderer,
const char *name,
gboolean in_core)
{
const CoglWinsysVtable *winsys = _cogl_renderer_get_winsys (renderer);
return winsys->renderer_get_proc_address (renderer, name, in_core);
}
void
cogl_renderer_add_constraint (CoglRenderer *renderer,
CoglRendererConstraint constraint)
{
g_return_if_fail (!renderer->connected);
renderer->constraints = g_list_prepend (renderer->constraints,
GUINT_TO_POINTER (constraint));
}
void
cogl_renderer_remove_constraint (CoglRenderer *renderer,
CoglRendererConstraint constraint)
{
g_return_if_fail (!renderer->connected);
renderer->constraints = g_list_remove (renderer->constraints,
GUINT_TO_POINTER (constraint));
}
void
cogl_renderer_set_driver (CoglRenderer *renderer,
CoglDriver driver)
{
g_return_if_fail (!renderer->connected);
renderer->driver_override = driver;
}
CoglDriver
cogl_renderer_get_driver (CoglRenderer *renderer)
{
g_return_val_if_fail (renderer->connected, 0);
return renderer->driver;
}
void
cogl_renderer_foreach_output (CoglRenderer *renderer,
CoglOutputCallback callback,
void *user_data)
{
GList *l;
g_return_if_fail (renderer->connected);
g_return_if_fail (callback != NULL);
for (l = renderer->outputs; l; l = l->next)
callback (l->data, user_data);
}
CoglDmaBufHandle *
cogl_renderer_create_dma_buf (CoglRenderer *renderer,
int width,
int height,
GError **error)
{
const CoglWinsysVtable *winsys = _cogl_renderer_get_winsys (renderer);
if (winsys->renderer_create_dma_buf)
return winsys->renderer_create_dma_buf (renderer, width, height, error);
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"CoglRenderer doesn't support creating DMA buffers");
return NULL;
}
gboolean
cogl_renderer_is_dma_buf_supported (CoglRenderer *renderer)
{
const CoglWinsysVtable *winsys = _cogl_renderer_get_winsys (renderer);
if (winsys->renderer_is_dma_buf_supported)
return winsys->renderer_is_dma_buf_supported (renderer);
else
return FALSE;
}
void
cogl_renderer_bind_api (CoglRenderer *renderer)
{
const CoglWinsysVtable *winsys = _cogl_renderer_get_winsys (renderer);
winsys->renderer_bind_api (renderer);
}