010d16f647
This makes it possible to integrate existing GLES2 code with applications using Cogl as the rendering api. Currently all GLES2 usage is handled with separate GLES2 contexts to ensure that GLES2 api usage doesn't interfere with Cogl's own use of OpenGL[ES]. The api has been designed though so we can provide tighter integration later. The api would allow us to support GLES2 virtualized on top of an OpenGL/GLX driver as well as GLES2 virtualized on the core rendering api of Cogl itself. Virtualizing the GLES2 support on Cogl will allow us to take advantage of Cogl debugging facilities as well as let us optimize the cost of allocating multiple GLES2 contexts and switching between them which can both be very expensive with many drivers. As as a side effect of this patch Cogl can also now be used as a portable window system binding API for GLES2 as an alternative to EGL. Parts of this patch are based on work done by Tomeu Vizoso <tomeu.vizoso@collabora.com> who did the first iteration of adding GLES2 API support to Cogl so that WebGL support could be added to webkit-clutter. This patch adds a very minimal cogl-gles2-context example that shows how to create a gles2 context, clear the screen to a random color and also draw a triangle with the cogl api. Reviewed-by: Neil Roberts <neil@linux.intel.com> (cherry picked from commit 4bb6eff3dbd50d8fef7d6bdbed55c5aaa70036a8)
543 lines
17 KiB
C
543 lines
17 KiB
C
/*
|
|
* Cogl
|
|
*
|
|
* An object oriented GL/GLES Abstraction/Utility Layer
|
|
*
|
|
* Copyright (C) 2011 Collabora Ltd.
|
|
* Copyright (C) 2012 Intel Corporation.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors:
|
|
* Tomeu Vizoso <tomeu.vizoso@collabora.com>
|
|
* Robert Bragg <robert@linux.intel.com>
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "cogl-gles2.h"
|
|
#include "cogl-gles2-context-private.h"
|
|
|
|
#include "cogl-context-private.h"
|
|
#include "cogl-display-private.h"
|
|
#include "cogl-framebuffer-private.h"
|
|
#include "cogl-onscreen-template-private.h"
|
|
#include "cogl-renderer-private.h"
|
|
#include "cogl-swap-chain-private.h"
|
|
#include "cogl-texture-2d-private.h"
|
|
|
|
static void _cogl_gles2_context_free (CoglGLES2Context *gles2_context);
|
|
|
|
COGL_OBJECT_DEFINE (GLES2Context, gles2_context);
|
|
|
|
static CoglGLES2Context *current_gles2_context;
|
|
|
|
static CoglUserDataKey offscreen_wrapper_key;
|
|
|
|
enum {
|
|
RESTORE_FB_NONE,
|
|
RESTORE_FB_FROM_OFFSCREEN,
|
|
RESTORE_FB_FROM_ONSCREEN,
|
|
};
|
|
|
|
GQuark
|
|
_cogl_gles2_context_error_quark (void)
|
|
{
|
|
return g_quark_from_static_string ("cogl-gles2-context-error-quark");
|
|
}
|
|
|
|
/* We wrap glBindFramebuffer so that when framebuffer 0 is bound
|
|
* we can instead bind the write_framebuffer passed to
|
|
* cogl_push_gles2_context().
|
|
*/
|
|
static void
|
|
gl_bind_framebuffer_wrapper (GLenum target, GLuint framebuffer)
|
|
{
|
|
CoglGLES2Context *gles2_ctx = current_gles2_context;
|
|
|
|
gles2_ctx->current_fbo_handle = framebuffer;
|
|
|
|
if (framebuffer == 0 && cogl_is_offscreen (gles2_ctx->write_buffer))
|
|
{
|
|
CoglGLES2Offscreen *write = gles2_ctx->gles2_write_buffer;
|
|
framebuffer = write->gl_framebuffer.fbo_handle;
|
|
}
|
|
|
|
gles2_ctx->context->glBindFramebuffer (target, framebuffer);
|
|
}
|
|
|
|
static int
|
|
transient_bind_read_buffer (CoglGLES2Context *gles2_ctx)
|
|
{
|
|
if (gles2_ctx->current_fbo_handle == 0)
|
|
{
|
|
if (cogl_is_offscreen (gles2_ctx->read_buffer))
|
|
{
|
|
CoglGLES2Offscreen *read = gles2_ctx->gles2_read_buffer;
|
|
GLuint read_fbo_handle = read->gl_framebuffer.fbo_handle;
|
|
|
|
gles2_ctx->context->glBindFramebuffer (GL_FRAMEBUFFER,
|
|
read_fbo_handle);
|
|
|
|
return RESTORE_FB_FROM_OFFSCREEN;
|
|
}
|
|
else
|
|
{
|
|
_cogl_gl_framebuffer_bind (gles2_ctx->read_buffer,
|
|
0 /* target ignored */);
|
|
|
|
return RESTORE_FB_FROM_ONSCREEN;
|
|
}
|
|
}
|
|
else
|
|
return RESTORE_FB_NONE;
|
|
}
|
|
|
|
static void
|
|
restore_write_buffer (CoglGLES2Context *gles2_ctx,
|
|
int restore_mode)
|
|
{
|
|
switch (restore_mode)
|
|
{
|
|
case RESTORE_FB_FROM_OFFSCREEN:
|
|
|
|
gl_bind_framebuffer_wrapper (GL_FRAMEBUFFER, 0);
|
|
|
|
break;
|
|
case RESTORE_FB_FROM_ONSCREEN:
|
|
|
|
/* Note: we can't restore the original write buffer using
|
|
* _cogl_gl_framebuffer_bind() if it's an offscreen
|
|
* framebuffer because _cogl_gl_framebuffer_bind() doesn't
|
|
* know about the fbo handle owned by the gles2 context.
|
|
*/
|
|
if (cogl_is_offscreen (gles2_ctx->write_buffer))
|
|
gl_bind_framebuffer_wrapper (GL_FRAMEBUFFER, 0);
|
|
else
|
|
_cogl_gl_framebuffer_bind (gles2_ctx->write_buffer, GL_FRAMEBUFFER);
|
|
|
|
break;
|
|
case RESTORE_FB_NONE:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* We wrap glReadPixels so when framebuffer 0 is bound then we can
|
|
* read from the read_framebuffer passed to cogl_push_gles2_context().
|
|
*/
|
|
static void
|
|
gl_read_pixels_wrapper (GLint x,
|
|
GLint y,
|
|
GLsizei width,
|
|
GLsizei height,
|
|
GLenum format,
|
|
GLenum type,
|
|
GLvoid *pixels)
|
|
{
|
|
CoglGLES2Context *gles2_ctx = current_gles2_context;
|
|
int restore_mode = transient_bind_read_buffer (gles2_ctx);
|
|
|
|
gles2_ctx->context->glReadPixels (x, y, width, height, format, type, pixels);
|
|
|
|
restore_write_buffer (gles2_ctx, restore_mode);
|
|
}
|
|
|
|
static void
|
|
gl_copy_tex_image_2d_wrapper (GLenum target,
|
|
GLint level,
|
|
GLenum internalformat,
|
|
GLint x,
|
|
GLint y,
|
|
GLsizei width,
|
|
GLsizei height,
|
|
GLint border)
|
|
{
|
|
CoglGLES2Context *gles2_ctx = current_gles2_context;
|
|
int restore_mode = transient_bind_read_buffer (gles2_ctx);
|
|
|
|
gles2_ctx->context->glCopyTexImage2D (target, level, internalformat,
|
|
x, y, width, height, border);
|
|
|
|
restore_write_buffer (gles2_ctx, restore_mode);
|
|
}
|
|
|
|
static void
|
|
gl_copy_tex_sub_image_2d_wrapper (GLenum target,
|
|
GLint level,
|
|
GLint xoffset,
|
|
GLint yoffset,
|
|
GLint x,
|
|
GLint y,
|
|
GLsizei width,
|
|
GLsizei height)
|
|
{
|
|
CoglGLES2Context *gles2_ctx = current_gles2_context;
|
|
int restore_mode = transient_bind_read_buffer (gles2_ctx);
|
|
|
|
gles2_ctx->context->glCopyTexSubImage2D (target, level,
|
|
xoffset, yoffset,
|
|
x, y, width, height);
|
|
|
|
restore_write_buffer (gles2_ctx, restore_mode);
|
|
}
|
|
|
|
static void
|
|
_cogl_gles2_offscreen_free (CoglGLES2Offscreen *gles2_offscreen)
|
|
{
|
|
COGL_LIST_REMOVE (gles2_offscreen, list_node);
|
|
g_slice_free (CoglGLES2Offscreen, gles2_offscreen);
|
|
}
|
|
|
|
static void
|
|
_cogl_gles2_context_free (CoglGLES2Context *gles2_context)
|
|
{
|
|
CoglContext *ctx = gles2_context->context;
|
|
const CoglWinsysVtable *winsys;
|
|
|
|
winsys = ctx->display->renderer->winsys_vtable;
|
|
winsys->destroy_gles2_context (gles2_context);
|
|
|
|
while (gles2_context->foreign_offscreens.lh_first)
|
|
{
|
|
CoglGLES2Offscreen *gles2_offscreen =
|
|
gles2_context->foreign_offscreens.lh_first;
|
|
|
|
/* Note: this will also indirectly free the gles2_offscreen by
|
|
* calling the destroy notify for the _user_data */
|
|
cogl_object_set_user_data (COGL_OBJECT (gles2_offscreen->original_offscreen),
|
|
&offscreen_wrapper_key,
|
|
NULL,
|
|
NULL);
|
|
}
|
|
|
|
cogl_object_unref (gles2_context->context);
|
|
|
|
g_free (gles2_context->vtable);
|
|
|
|
g_free (gles2_context);
|
|
}
|
|
|
|
CoglGLES2Context *
|
|
cogl_gles2_context_new (CoglContext *ctx, GError **error)
|
|
{
|
|
CoglGLES2Context *gles2_ctx;
|
|
const CoglWinsysVtable *winsys;
|
|
|
|
if (!cogl_has_feature (ctx, COGL_FEATURE_ID_GLES2_CONTEXT))
|
|
{
|
|
g_set_error (error, COGL_GLES2_CONTEXT_ERROR,
|
|
COGL_GLES2_CONTEXT_ERROR_UNSUPPORTED,
|
|
"Backend doesn't support creating GLES2 contexts");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
gles2_ctx = g_malloc0 (sizeof (CoglGLES2Context));
|
|
|
|
cogl_object_ref (ctx);
|
|
gles2_ctx->context = ctx;
|
|
|
|
COGL_LIST_INIT (&gles2_ctx->foreign_offscreens);
|
|
|
|
winsys = ctx->display->renderer->winsys_vtable;
|
|
gles2_ctx->winsys = winsys->context_create_gles2_context (ctx, error);
|
|
if (gles2_ctx->winsys == NULL)
|
|
{
|
|
cogl_object_unref (gles2_ctx->context);
|
|
g_free (gles2_ctx);
|
|
return NULL;
|
|
}
|
|
|
|
gles2_ctx->vtable = g_malloc0 (sizeof (CoglGLES2Vtable));
|
|
#define COGL_EXT_BEGIN(name, \
|
|
min_gl_major, min_gl_minor, \
|
|
gles_availability, \
|
|
extension_suffixes, extension_names)
|
|
|
|
#define COGL_EXT_FUNCTION(ret, name, args) \
|
|
gles2_ctx->vtable->name = ctx->name;
|
|
|
|
#define COGL_EXT_END()
|
|
|
|
#include "gl-prototypes/cogl-gles2-functions.h"
|
|
|
|
#undef COGL_EXT_BEGIN
|
|
#undef COGL_EXT_FUNCTION
|
|
#undef COGL_EXT_END
|
|
|
|
gles2_ctx->vtable->glBindFramebuffer = gl_bind_framebuffer_wrapper;
|
|
gles2_ctx->vtable->glReadPixels = gl_read_pixels_wrapper;
|
|
gles2_ctx->vtable->glCopyTexImage2D = gl_copy_tex_image_2d_wrapper;
|
|
gles2_ctx->vtable->glCopyTexSubImage2D = gl_copy_tex_sub_image_2d_wrapper;
|
|
|
|
return _cogl_gles2_context_object_new (gles2_ctx);
|
|
}
|
|
|
|
const CoglGLES2Vtable *
|
|
cogl_gles2_context_get_vtable (CoglGLES2Context *gles2_ctx)
|
|
{
|
|
return gles2_ctx->vtable;
|
|
}
|
|
|
|
/* When drawing to a CoglFramebuffer from a separate context we have
|
|
* to be able to allocate ancillary buffers for that context...
|
|
*/
|
|
static CoglGLES2Offscreen *
|
|
_cogl_gles2_offscreen_allocate (CoglOffscreen *offscreen,
|
|
CoglGLES2Context *gles2_context,
|
|
GError **error)
|
|
{
|
|
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (offscreen);
|
|
const CoglWinsysVtable *winsys;
|
|
GError *internal_error = NULL;
|
|
CoglGLES2Offscreen *gles2_offscreen;
|
|
|
|
if (!framebuffer->allocated &&
|
|
!cogl_framebuffer_allocate (framebuffer, error))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
for (gles2_offscreen = gles2_context->foreign_offscreens.lh_first;
|
|
gles2_offscreen;
|
|
gles2_offscreen = gles2_offscreen->list_node.le_next)
|
|
{
|
|
if (gles2_offscreen->original_offscreen == offscreen)
|
|
return gles2_offscreen;
|
|
}
|
|
|
|
winsys = _cogl_framebuffer_get_winsys (framebuffer);
|
|
winsys->save_context (framebuffer->context);
|
|
if (!winsys->set_gles2_context (gles2_context, &internal_error))
|
|
{
|
|
winsys->restore_context (framebuffer->context);
|
|
|
|
g_error_free (internal_error);
|
|
g_set_error (error, COGL_FRAMEBUFFER_ERROR,
|
|
COGL_FRAMEBUFFER_ERROR_ALLOCATE,
|
|
"Failed to bind gles2 context to create framebuffer");
|
|
return NULL;
|
|
}
|
|
|
|
gles2_offscreen = g_slice_new0 (CoglGLES2Offscreen);
|
|
if (!_cogl_framebuffer_try_creating_gl_fbo (gles2_context->context,
|
|
offscreen->texture,
|
|
offscreen->texture_level,
|
|
offscreen->texture_level_width,
|
|
offscreen->texture_level_height,
|
|
&COGL_FRAMEBUFFER (offscreen)->config,
|
|
offscreen->allocation_flags,
|
|
&gles2_offscreen->gl_framebuffer))
|
|
{
|
|
winsys->restore_context (framebuffer->context);
|
|
|
|
g_slice_free (CoglGLES2Offscreen, gles2_offscreen);
|
|
|
|
g_set_error (error, COGL_FRAMEBUFFER_ERROR,
|
|
COGL_FRAMEBUFFER_ERROR_ALLOCATE,
|
|
"Failed to create an OpenGL framebuffer object");
|
|
return NULL;
|
|
}
|
|
|
|
winsys->restore_context (framebuffer->context);
|
|
|
|
gles2_offscreen->original_offscreen = offscreen;
|
|
|
|
COGL_LIST_INSERT_HEAD (&gles2_context->foreign_offscreens,
|
|
gles2_offscreen,
|
|
list_node);
|
|
|
|
/* So we avoid building up an ever growing collection of ancillary
|
|
* buffers for wrapped framebuffers, we make sure that the wrappers
|
|
* get freed when the original offscreen framebuffer is freed. */
|
|
cogl_object_set_user_data (COGL_OBJECT (framebuffer),
|
|
&offscreen_wrapper_key,
|
|
gles2_offscreen,
|
|
(CoglUserDataDestroyCallback)
|
|
_cogl_gles2_offscreen_free);
|
|
|
|
return gles2_offscreen;
|
|
}
|
|
|
|
CoglBool
|
|
cogl_push_gles2_context (CoglContext *ctx,
|
|
CoglGLES2Context *gles2_ctx,
|
|
CoglFramebuffer *read_buffer,
|
|
CoglFramebuffer *write_buffer,
|
|
GError **error)
|
|
{
|
|
const CoglWinsysVtable *winsys = ctx->display->renderer->winsys_vtable;
|
|
GError *internal_error = NULL;
|
|
|
|
_COGL_RETURN_VAL_IF_FAIL (gles2_ctx != NULL, FALSE);
|
|
|
|
/* The read/write buffers are properties of the gles2 context and we
|
|
* don't currently track the read/write buffers as part of the stack
|
|
* entries so we explicitly don't allow the same context to be
|
|
* pushed multiple times. */
|
|
if (g_queue_find (&ctx->gles2_context_stack, gles2_ctx))
|
|
{
|
|
g_critical ("Pushing the same GLES2 context multiple times isn't "
|
|
"supported");
|
|
return FALSE;
|
|
}
|
|
|
|
if (ctx->gles2_context_stack.length == 0)
|
|
{
|
|
_cogl_journal_flush (read_buffer->journal);
|
|
if (write_buffer != read_buffer)
|
|
_cogl_journal_flush (write_buffer->journal);
|
|
winsys->save_context (ctx);
|
|
}
|
|
else
|
|
gles2_ctx->vtable->glFlush ();
|
|
|
|
if (gles2_ctx->read_buffer != read_buffer)
|
|
{
|
|
if (cogl_is_offscreen (read_buffer))
|
|
{
|
|
gles2_ctx->gles2_read_buffer =
|
|
_cogl_gles2_offscreen_allocate (COGL_OFFSCREEN (read_buffer),
|
|
gles2_ctx,
|
|
error);
|
|
/* XXX: what consistency guarantees should this api have?
|
|
*
|
|
* It should be safe to return at this point but we provide
|
|
* no guarantee to the caller whether their given buffers
|
|
* may be referenced and old buffers unreferenced even
|
|
* if the _push fails. */
|
|
if (!gles2_ctx->gles2_read_buffer)
|
|
return FALSE;
|
|
}
|
|
else
|
|
gles2_ctx->gles2_read_buffer = NULL;
|
|
if (gles2_ctx->read_buffer)
|
|
cogl_object_unref (gles2_ctx->read_buffer);
|
|
gles2_ctx->read_buffer = cogl_object_ref (read_buffer);
|
|
}
|
|
|
|
if (gles2_ctx->write_buffer != write_buffer)
|
|
{
|
|
if (cogl_is_offscreen (write_buffer))
|
|
{
|
|
gles2_ctx->gles2_write_buffer =
|
|
_cogl_gles2_offscreen_allocate (COGL_OFFSCREEN (write_buffer),
|
|
gles2_ctx,
|
|
error);
|
|
/* XXX: what consistency guarantees should this api have?
|
|
*
|
|
* It should be safe to return at this point but we provide
|
|
* no guarantee to the caller whether their given buffers
|
|
* may be referenced and old buffers unreferenced even
|
|
* if the _push fails. */
|
|
if (!gles2_ctx->gles2_write_buffer)
|
|
return FALSE;
|
|
}
|
|
else
|
|
gles2_ctx->gles2_write_buffer = NULL;
|
|
if (gles2_ctx->write_buffer)
|
|
cogl_object_unref (gles2_ctx->write_buffer);
|
|
gles2_ctx->write_buffer = cogl_object_ref (write_buffer);
|
|
}
|
|
|
|
if (!winsys->set_gles2_context (gles2_ctx, &internal_error))
|
|
{
|
|
winsys->restore_context (ctx);
|
|
|
|
g_error_free (internal_error);
|
|
g_set_error (error, COGL_GLES2_CONTEXT_ERROR,
|
|
COGL_GLES2_CONTEXT_ERROR_DRIVER,
|
|
"Driver failed to make GLES2 context current");
|
|
return FALSE;
|
|
}
|
|
|
|
g_queue_push_tail (&ctx->gles2_context_stack, gles2_ctx);
|
|
|
|
/* The last time this context was pushed may have been with a
|
|
* different offscreen draw framebuffer and so if GL framebuffer 0
|
|
* is bound for this GLES2 context we may need to bind a new,
|
|
* corresponding, window system framebuffer... */
|
|
if (gles2_ctx->current_fbo_handle == 0)
|
|
{
|
|
if (cogl_is_offscreen (gles2_ctx->write_buffer))
|
|
{
|
|
CoglGLES2Offscreen *write = gles2_ctx->gles2_write_buffer;
|
|
GLuint handle = write->gl_framebuffer.fbo_handle;
|
|
gles2_ctx->context->glBindFramebuffer (GL_FRAMEBUFFER, handle);
|
|
}
|
|
}
|
|
|
|
current_gles2_context = gles2_ctx;
|
|
return TRUE;
|
|
}
|
|
|
|
CoglGLES2Vtable *
|
|
cogl_gles2_get_current_vtable (void)
|
|
{
|
|
return current_gles2_context ? current_gles2_context->vtable : NULL;
|
|
}
|
|
|
|
void
|
|
cogl_pop_gles2_context (CoglContext *ctx)
|
|
{
|
|
CoglGLES2Context *gles2_ctx;
|
|
const CoglWinsysVtable *winsys = ctx->display->renderer->winsys_vtable;
|
|
|
|
_COGL_RETURN_IF_FAIL (ctx->gles2_context_stack.length > 0);
|
|
|
|
g_queue_pop_tail (&ctx->gles2_context_stack);
|
|
|
|
gles2_ctx = g_queue_peek_tail (&ctx->gles2_context_stack);
|
|
|
|
if (gles2_ctx)
|
|
{
|
|
winsys->set_gles2_context (gles2_ctx, NULL);
|
|
current_gles2_context = gles2_ctx;
|
|
}
|
|
else
|
|
{
|
|
winsys->restore_context (ctx);
|
|
current_gles2_context = NULL;
|
|
}
|
|
}
|
|
|
|
CoglTexture2D *
|
|
cogl_gles2_texture_2d_new_from_handle (CoglContext *ctx,
|
|
CoglGLES2Context *gles2_ctx,
|
|
unsigned int handle,
|
|
int width,
|
|
int height,
|
|
CoglPixelFormat internal_format,
|
|
GError **error)
|
|
{
|
|
return cogl_texture_2d_new_from_foreign (ctx,
|
|
handle,
|
|
width,
|
|
height,
|
|
internal_format,
|
|
error);
|
|
}
|
|
|
|
CoglBool
|
|
cogl_gles2_texture_get_handle (CoglTexture *texture,
|
|
unsigned int *handle,
|
|
unsigned int *target)
|
|
{
|
|
return cogl_texture_get_gl_texture (texture, handle, target);
|
|
}
|