/* * 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 * . * * Authors: * Tomeu Vizoso * Robert Bragg * */ #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); }