/* * Cogl * * A Low Level GPU Graphics and Utilities API * * Copyright (C) 2011 Collabora Ltd. * Copyright (C) 2012 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: * Tomeu Vizoso * Robert Bragg * Neil Roberts * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #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-framebuffer-gl-private.h" #include "cogl-onscreen-template-private.h" #include "cogl-renderer-private.h" #include "cogl-swap-chain-private.h" #include "cogl-texture-2d-gl.h" #include "cogl-texture-2d-private.h" #include "cogl-pipeline-opengl-private.h" #include "cogl-error-private.h" #include "cogl-gtype-private.h" static void _cogl_gles2_context_free (CoglGLES2Context *gles2_context); COGL_OBJECT_DEFINE (GLES2Context, gles2_context); COGL_GTYPE_DEFINE_CLASS (GLES2Context, gles2_context); static CoglGLES2Context *current_gles2_context; static CoglUserDataKey offscreen_wrapper_key; /* The application's main function is renamed to this so that we can * provide an alternative main function */ #define MAIN_WRAPPER_REPLACEMENT_NAME "_c31" /* This uniform is used to flip the rendering or not depending on * whether we are rendering to an offscreen buffer or not */ #define MAIN_WRAPPER_FLIP_UNIFORM "_cogl_flip_vector" /* These comments are used to delimit the added wrapper snippet so * that we can remove it again when the shader source is requested via * glGetShaderSource */ #define MAIN_WRAPPER_BEGIN "/*_COGL_WRAPPER_BEGIN*/" #define MAIN_WRAPPER_END "/*_COGL_WRAPPER_END*/" /* This wrapper function around 'main' is appended to every vertex shader * so that we can add some extra code to flip the rendering when * rendering to an offscreen buffer */ static const char main_wrapper_function[] = MAIN_WRAPPER_BEGIN "\n" "uniform vec4 " MAIN_WRAPPER_FLIP_UNIFORM ";\n" "\n" "void\n" "main ()\n" "{\n" " " MAIN_WRAPPER_REPLACEMENT_NAME " ();\n" " gl_Position *= " MAIN_WRAPPER_FLIP_UNIFORM ";\n" "}\n" MAIN_WRAPPER_END; enum { RESTORE_FB_NONE, RESTORE_FB_FROM_OFFSCREEN, RESTORE_FB_FROM_ONSCREEN, }; uint32_t _cogl_gles2_context_error_quark (void) { return g_quark_from_static_string ("cogl-gles2-context-error-quark"); } static void shader_data_unref (CoglGLES2Context *context, CoglGLES2ShaderData *shader_data) { if (--shader_data->ref_count < 1) /* Removing the hash table entry should also destroy the data */ g_hash_table_remove (context->shader_map, GINT_TO_POINTER (shader_data->object_id)); } static void program_data_unref (CoglGLES2ProgramData *program_data) { if (--program_data->ref_count < 1) /* Removing the hash table entry should also destroy the data */ g_hash_table_remove (program_data->context->program_map, GINT_TO_POINTER (program_data->object_id)); } static void detach_shader (CoglGLES2ProgramData *program_data, CoglGLES2ShaderData *shader_data) { GList *l; for (l = program_data->attached_shaders; l; l = l->next) { if (l->data == shader_data) { shader_data_unref (program_data->context, shader_data); program_data->attached_shaders = g_list_delete_link (program_data->attached_shaders, l); break; } } } static CoglBool is_symbol_character (char ch) { return g_ascii_isalnum (ch) || ch == '_'; } static void replace_token (char *string, const char *token, const char *replacement, int length) { char *token_pos; char *last_pos = string; char *end = string + length; int token_length = strlen (token); /* NOTE: this assumes token and replacement are the same length */ while ((token_pos = _cogl_util_memmem (last_pos, end - last_pos, token, token_length))) { /* Make sure this isn't in the middle of some longer token */ if ((token_pos <= string || !is_symbol_character (token_pos[-1])) && (token_pos + token_length == end || !is_symbol_character (token_pos[token_length]))) memcpy (token_pos, replacement, token_length); last_pos = token_pos + token_length; } } static void update_current_flip_state (CoglGLES2Context *gles2_ctx) { CoglGLES2FlipState new_flip_state; if (gles2_ctx->current_fbo_handle == 0 && cogl_is_offscreen (gles2_ctx->write_buffer)) new_flip_state = COGL_GLES2_FLIP_STATE_FLIPPED; else new_flip_state = COGL_GLES2_FLIP_STATE_NORMAL; /* If the flip state has changed then we need to reflush all of the * dependent state */ if (new_flip_state != gles2_ctx->current_flip_state) { gles2_ctx->viewport_dirty = TRUE; gles2_ctx->scissor_dirty = TRUE; gles2_ctx->front_face_dirty = TRUE; gles2_ctx->current_flip_state = new_flip_state; } } static GLuint get_current_texture_2d_object (CoglGLES2Context *gles2_ctx) { return g_array_index (gles2_ctx->texture_units, CoglGLES2TextureUnitData, gles2_ctx->current_texture_unit).current_texture_2d; } static void set_texture_object_data (CoglGLES2Context *gles2_ctx, GLenum target, GLint level, GLenum internal_format, GLsizei width, GLsizei height) { GLuint texture_id = get_current_texture_2d_object (gles2_ctx); CoglGLES2TextureObjectData *texture_object; /* We want to keep track of all texture objects where the data is * created by this context so that we can delete them later */ texture_object = g_hash_table_lookup (gles2_ctx->texture_object_map, GUINT_TO_POINTER (texture_id)); if (texture_object == NULL) { texture_object = g_slice_new0 (CoglGLES2TextureObjectData); texture_object->object_id = texture_id; g_hash_table_insert (gles2_ctx->texture_object_map, GUINT_TO_POINTER (texture_id), texture_object); } switch (target) { case GL_TEXTURE_2D: texture_object->target = GL_TEXTURE_2D; /* We want to keep track of the dimensions of any texture object * setting the GL_TEXTURE_2D target */ if (level == 0) { texture_object->width = width; texture_object->height = height; texture_object->format = internal_format; } break; case GL_TEXTURE_CUBE_MAP_POSITIVE_X: case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: texture_object->target = GL_TEXTURE_CUBE_MAP; break; } } static void copy_flipped_texture (CoglGLES2Context *gles2_ctx, int level, int src_x, int src_y, int dst_x, int dst_y, int width, int height) { GLuint tex_id = get_current_texture_2d_object (gles2_ctx); CoglGLES2TextureObjectData *tex_object_data; CoglContext *ctx; const CoglWinsysVtable *winsys; CoglTexture2D *dst_texture; CoglPixelFormat internal_format; tex_object_data = g_hash_table_lookup (gles2_ctx->texture_object_map, GUINT_TO_POINTER (tex_id)); /* We can't do anything if the application hasn't set a level 0 * image on this texture object */ if (tex_object_data == NULL || tex_object_data->target != GL_TEXTURE_2D || tex_object_data->width <= 0 || tex_object_data->height <= 0) return; switch (tex_object_data->format) { case GL_RGB: internal_format = COGL_PIXEL_FORMAT_RGB_888; break; case GL_RGBA: internal_format = COGL_PIXEL_FORMAT_RGBA_8888_PRE; break; case GL_ALPHA: internal_format = COGL_PIXEL_FORMAT_A_8; break; case GL_LUMINANCE: internal_format = COGL_PIXEL_FORMAT_G_8; break; default: /* We can't handle this format so just give up */ return; } ctx = gles2_ctx->context; winsys = ctx->display->renderer->winsys_vtable; /* We need to make sure the rendering on the GLES2 context is * complete before the blit will be ready in the GLES2 context */ ctx->glFinish (); /* We need to force Cogl to rebind the texture because according to * the GL spec a shared texture isn't guaranteed to be updated until * is rebound */ _cogl_get_texture_unit (0)->dirty_gl_texture = TRUE; /* Temporarily switch back to the Cogl context */ winsys->restore_context (ctx); dst_texture = cogl_gles2_texture_2d_new_from_handle (gles2_ctx->context, gles2_ctx, tex_id, tex_object_data->width, tex_object_data->height, internal_format); if (dst_texture) { CoglTexture *src_texture = COGL_OFFSCREEN (gles2_ctx->read_buffer)->texture; CoglPipeline *pipeline = cogl_pipeline_new (ctx); const CoglOffscreenFlags flags = COGL_OFFSCREEN_DISABLE_DEPTH_AND_STENCIL; CoglOffscreen *offscreen = _cogl_offscreen_new_with_texture_full (COGL_TEXTURE (dst_texture), flags, level); int src_width = cogl_texture_get_width (src_texture); int src_height = cogl_texture_get_height (src_texture); /* The framebuffer size might be different from the texture size * if a level > 0 is used */ int dst_width = cogl_framebuffer_get_width (COGL_FRAMEBUFFER (offscreen)); int dst_height = cogl_framebuffer_get_height (COGL_FRAMEBUFFER (offscreen)); float x_1, y_1, x_2, y_2, s_1, t_1, s_2, t_2; cogl_pipeline_set_layer_texture (pipeline, 0, src_texture); cogl_pipeline_set_blend (pipeline, "RGBA = ADD(SRC_COLOR, 0)", NULL); cogl_pipeline_set_layer_filters (pipeline, 0, /* layer_num */ COGL_PIPELINE_FILTER_NEAREST, COGL_PIPELINE_FILTER_NEAREST); x_1 = dst_x * 2.0f / dst_width - 1.0f; y_1 = dst_y * 2.0f / dst_height - 1.0f; x_2 = x_1 + width * 2.0f / dst_width; y_2 = y_1 + height * 2.0f / dst_height; s_1 = src_x / (float) src_width; t_1 = 1.0f - src_y / (float) src_height; s_2 = (src_x + width) / (float) src_width; t_2 = 1.0f - (src_y + height) / (float) src_height; cogl_framebuffer_draw_textured_rectangle (COGL_FRAMEBUFFER (offscreen), pipeline, x_1, y_1, x_2, y_2, s_1, t_1, s_2, t_2); _cogl_framebuffer_flush_journal (COGL_FRAMEBUFFER (offscreen)); /* We need to make sure the rendering is complete before the * blit will be ready in the GLES2 context */ ctx->glFinish (); cogl_object_unref (pipeline); cogl_object_unref (dst_texture); cogl_object_unref (offscreen); } winsys->set_gles2_context (gles2_ctx, NULL); /* From what I understand of the GL spec, changes to a shared object * are not guaranteed to be propagated to another context until that * object is rebound in that context so we can just rebind it * here */ gles2_ctx->vtable->glBindTexture (GL_TEXTURE_2D, tex_id); } /* 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); update_current_flip_state (gles2_ctx); } 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_framebuffer_gl_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_framebuffer_gl_bind() if it's an offscreen * framebuffer because _cogl_framebuffer_gl_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_framebuffer_gl_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); /* If the read buffer is a CoglOffscreen then the data will be * upside down compared to what GL expects so we need to flip it */ if (gles2_ctx->current_fbo_handle == 0 && cogl_is_offscreen (gles2_ctx->read_buffer)) { int bpp, bytes_per_row, stride, y; uint8_t *bytes = pixels; uint8_t *temprow; /* Try to determine the bytes per pixel for the given * format/type combination. If there's a format which doesn't * make sense then we'll just give up because GL will probably * have just thrown an error */ switch (format) { case GL_RGB: switch (type) { case GL_UNSIGNED_BYTE: bpp = 3; break; case GL_UNSIGNED_SHORT_5_6_5: bpp = 2; break; default: return; } break; case GL_RGBA: switch (type) { case GL_UNSIGNED_BYTE: bpp = 4; break; case GL_UNSIGNED_SHORT_4_4_4_4: case GL_UNSIGNED_SHORT_5_5_5_1: bpp = 2; break; default: return; } break; case GL_ALPHA: switch (type) { case GL_UNSIGNED_BYTE: bpp = 1; break; default: return; } break; default: return; } bytes_per_row = bpp * width; stride = ((bytes_per_row + gles2_ctx->pack_alignment - 1) & ~(gles2_ctx->pack_alignment - 1)); temprow = g_alloca (bytes_per_row); /* vertically flip the buffer in-place */ for (y = 0; y < height / 2; y++) { if (y != height - y - 1) /* skip center row */ { memcpy (temprow, bytes + y * stride, bytes_per_row); memcpy (bytes + y * stride, bytes + (height - y - 1) * stride, bytes_per_row); memcpy (bytes + (height - y - 1) * stride, temprow, bytes_per_row); } } } } static void gl_copy_tex_image_2d_wrapper (GLenum target, GLint level, GLenum internal_format, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) { CoglGLES2Context *gles2_ctx = current_gles2_context; /* If we are reading from a CoglOffscreen buffer then the image will * be upside down with respect to what GL expects so we can't use * glCopyTexImage2D. Instead we we'll try to use the Cogl API to * flip it */ if (gles2_ctx->current_fbo_handle == 0 && cogl_is_offscreen (gles2_ctx->read_buffer)) { /* This will only work with the GL_TEXTURE_2D target. FIXME: * GLES2 also supports setting cube map textures with * glTexImage2D so we need to handle that too */ if (target != GL_TEXTURE_2D) return; /* Create an empty texture to hold the data */ gles2_ctx->vtable->glTexImage2D (target, level, internal_format, width, height, border, internal_format, /* format */ GL_UNSIGNED_BYTE, /* type */ NULL /* data */); copy_flipped_texture (gles2_ctx, level, x, y, /* src_x/src_y */ 0, 0, /* dst_x/dst_y */ width, height); } else { int restore_mode = transient_bind_read_buffer (gles2_ctx); gles2_ctx->context->glCopyTexImage2D (target, level, internal_format, x, y, width, height, border); restore_write_buffer (gles2_ctx, restore_mode); set_texture_object_data (gles2_ctx, target, level, internal_format, width, height); } } 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; /* If we are reading from a CoglOffscreen buffer then the image will * be upside down with respect to what GL expects so we can't use * glCopyTexSubImage2D. Instead we we'll try to use the Cogl API to * flip it */ if (gles2_ctx->current_fbo_handle == 0 && cogl_is_offscreen (gles2_ctx->read_buffer)) { /* This will only work with the GL_TEXTURE_2D target. FIXME: * GLES2 also supports setting cube map textures with * glTexImage2D so we need to handle that too */ if (target != GL_TEXTURE_2D) return; copy_flipped_texture (gles2_ctx, level, x, y, /* src_x/src_y */ xoffset, yoffset, /* dst_x/dst_y */ width, height); } else { 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 GLuint gl_create_shader_wrapper (GLenum type) { CoglGLES2Context *gles2_ctx = current_gles2_context; GLuint id; id = gles2_ctx->context->glCreateShader (type); if (id != 0) { CoglGLES2ShaderData *data = g_slice_new (CoglGLES2ShaderData); data->object_id = id; data->type = type; data->ref_count = 1; data->deleted = FALSE; g_hash_table_insert (gles2_ctx->shader_map, GINT_TO_POINTER (id), data); } return id; } static void gl_delete_shader_wrapper (GLuint shader) { CoglGLES2Context *gles2_ctx = current_gles2_context; CoglGLES2ShaderData *shader_data; if ((shader_data = g_hash_table_lookup (gles2_ctx->shader_map, GINT_TO_POINTER (shader))) && !shader_data->deleted) { shader_data->deleted = TRUE; shader_data_unref (gles2_ctx, shader_data); } gles2_ctx->context->glDeleteShader (shader); } static GLuint gl_create_program_wrapper (void) { CoglGLES2Context *gles2_ctx = current_gles2_context; GLuint id; id = gles2_ctx->context->glCreateProgram (); if (id != 0) { CoglGLES2ProgramData *data = g_slice_new (CoglGLES2ProgramData); data->object_id = id; data->attached_shaders = NULL; data->ref_count = 1; data->deleted = FALSE; data->context = gles2_ctx; data->flip_vector_location = 0; data->flip_vector_state = COGL_GLES2_FLIP_STATE_UNKNOWN; g_hash_table_insert (gles2_ctx->program_map, GINT_TO_POINTER (id), data); } return id; } static void gl_delete_program_wrapper (GLuint program) { CoglGLES2Context *gles2_ctx = current_gles2_context; CoglGLES2ProgramData *program_data; if ((program_data = g_hash_table_lookup (gles2_ctx->program_map, GINT_TO_POINTER (program))) && !program_data->deleted) { program_data->deleted = TRUE; program_data_unref (program_data); } gles2_ctx->context->glDeleteProgram (program); } static void gl_use_program_wrapper (GLuint program) { CoglGLES2Context *gles2_ctx = current_gles2_context; CoglGLES2ProgramData *program_data; program_data = g_hash_table_lookup (gles2_ctx->program_map, GINT_TO_POINTER (program)); if (program_data) program_data->ref_count++; if (gles2_ctx->current_program) program_data_unref (gles2_ctx->current_program); gles2_ctx->current_program = program_data; gles2_ctx->context->glUseProgram (program); } static void gl_attach_shader_wrapper (GLuint program, GLuint shader) { CoglGLES2Context *gles2_ctx = current_gles2_context; CoglGLES2ProgramData *program_data; CoglGLES2ShaderData *shader_data; if ((program_data = g_hash_table_lookup (gles2_ctx->program_map, GINT_TO_POINTER (program))) && (shader_data = g_hash_table_lookup (gles2_ctx->shader_map, GINT_TO_POINTER (shader))) && /* Ignore attempts to attach a shader that is already attached */ g_list_find (program_data->attached_shaders, shader_data) == NULL) { shader_data->ref_count++; program_data->attached_shaders = g_list_prepend (program_data->attached_shaders, shader_data); } gles2_ctx->context->glAttachShader (program, shader); } static void gl_detach_shader_wrapper (GLuint program, GLuint shader) { CoglGLES2Context *gles2_ctx = current_gles2_context; CoglGLES2ProgramData *program_data; CoglGLES2ShaderData *shader_data; if ((program_data = g_hash_table_lookup (gles2_ctx->program_map, GINT_TO_POINTER (program))) && (shader_data = g_hash_table_lookup (gles2_ctx->shader_map, GINT_TO_POINTER (shader)))) detach_shader (program_data, shader_data); gles2_ctx->context->glDetachShader (program, shader); } static void gl_shader_source_wrapper (GLuint shader, GLsizei count, const char *const *string, const GLint *length) { CoglGLES2Context *gles2_ctx = current_gles2_context; CoglGLES2ShaderData *shader_data; if ((shader_data = g_hash_table_lookup (gles2_ctx->shader_map, GINT_TO_POINTER (shader))) && shader_data->type == GL_VERTEX_SHADER) { char **string_copy = g_alloca ((count + 1) * sizeof (char *)); int *length_copy = g_alloca ((count + 1) * sizeof (int)); int i; /* Replace any occurences of the symbol 'main' with a different * symbol so that we can provide our own wrapper main * function */ for (i = 0; i < count; i++) { int string_length; if (length == NULL || length[i] < 0) string_length = strlen (string[i]); else string_length = length[i]; string_copy[i] = g_memdup (string[i], string_length); replace_token (string_copy[i], "main", MAIN_WRAPPER_REPLACEMENT_NAME, string_length); length_copy[i] = string_length; } string_copy[count] = (char *) main_wrapper_function; length_copy[count] = sizeof (main_wrapper_function) - 1; gles2_ctx->context->glShaderSource (shader, count + 1, (const char *const *) string_copy, length_copy); /* Note: we don't need to free the last entry in string_copy[] * because it is our static wrapper string... */ for (i = 0; i < count; i++) g_free (string_copy[i]); } else gles2_ctx->context->glShaderSource (shader, count, string, length); } static void gl_get_shader_source_wrapper (GLuint shader, GLsizei buf_size, GLsizei *length_out, GLchar *source) { CoglGLES2Context *gles2_ctx = current_gles2_context; CoglGLES2ShaderData *shader_data; GLsizei length; gles2_ctx->context->glGetShaderSource (shader, buf_size, &length, source); if ((shader_data = g_hash_table_lookup (gles2_ctx->shader_map, GINT_TO_POINTER (shader))) && shader_data->type == GL_VERTEX_SHADER) { GLsizei copy_length = MIN (length, buf_size - 1); static const char wrapper_marker[] = MAIN_WRAPPER_BEGIN; char *wrapper_start; /* Strip out the wrapper snippet we added when the source was * specified */ wrapper_start = _cogl_util_memmem (source, copy_length, wrapper_marker, sizeof (wrapper_marker) - 1); if (wrapper_start) { length = wrapper_start - source; copy_length = length; *wrapper_start = '\0'; } /* Correct the name of the main function back to its original */ replace_token (source, MAIN_WRAPPER_REPLACEMENT_NAME, "main", copy_length); } if (length_out) *length_out = length; } static void gl_link_program_wrapper (GLuint program) { CoglGLES2Context *gles2_ctx = current_gles2_context; CoglGLES2ProgramData *program_data; gles2_ctx->context->glLinkProgram (program); program_data = g_hash_table_lookup (gles2_ctx->program_map, GINT_TO_POINTER (program)); if (program_data) { GLint status; gles2_ctx->context->glGetProgramiv (program, GL_LINK_STATUS, &status); if (status) program_data->flip_vector_location = gles2_ctx->context->glGetUniformLocation (program, MAIN_WRAPPER_FLIP_UNIFORM); } } static void gl_get_program_iv_wrapper (GLuint program, GLenum pname, GLint *params) { CoglGLES2Context *gles2_ctx = current_gles2_context; gles2_ctx->context->glGetProgramiv (program, pname, params); switch (pname) { case GL_ATTACHED_SHADERS: /* Decrease the number of shaders to try and hide the shader * wrapper we added */ if (*params > 1) (*params)--; break; } } static void flush_viewport_state (CoglGLES2Context *gles2_ctx) { if (gles2_ctx->viewport_dirty) { int y; if (gles2_ctx->current_flip_state == COGL_GLES2_FLIP_STATE_FLIPPED) { /* We need to know the height of the current framebuffer in * order to flip the viewport. Fortunately we don't need to * track the height of the FBOs created within the GLES2 * context because we would never be flipping if they are * bound so we can just assume Cogl's framebuffer is bound * when we are flipping */ int fb_height = cogl_framebuffer_get_height (gles2_ctx->write_buffer); y = fb_height - (gles2_ctx->viewport[1] + gles2_ctx->viewport[3]); } else y = gles2_ctx->viewport[1]; gles2_ctx->context->glViewport (gles2_ctx->viewport[0], y, gles2_ctx->viewport[2], gles2_ctx->viewport[3]); gles2_ctx->viewport_dirty = FALSE; } } static void flush_scissor_state (CoglGLES2Context *gles2_ctx) { if (gles2_ctx->scissor_dirty) { int y; if (gles2_ctx->current_flip_state == COGL_GLES2_FLIP_STATE_FLIPPED) { /* See comment above about the viewport flipping */ int fb_height = cogl_framebuffer_get_height (gles2_ctx->write_buffer); y = fb_height - (gles2_ctx->scissor[1] + gles2_ctx->scissor[3]); } else y = gles2_ctx->scissor[1]; gles2_ctx->context->glScissor (gles2_ctx->scissor[0], y, gles2_ctx->scissor[2], gles2_ctx->scissor[3]); gles2_ctx->scissor_dirty = FALSE; } } static void flush_front_face_state (CoglGLES2Context *gles2_ctx) { if (gles2_ctx->front_face_dirty) { GLenum front_face; if (gles2_ctx->current_flip_state == COGL_GLES2_FLIP_STATE_FLIPPED) { if (gles2_ctx->front_face == GL_CW) front_face = GL_CCW; else front_face = GL_CW; } else front_face = gles2_ctx->front_face; gles2_ctx->context->glFrontFace (front_face); gles2_ctx->front_face_dirty = FALSE; } } static void pre_draw_wrapper (CoglGLES2Context *gles2_ctx) { /* If there's no current program then we'll just let GL report an * error */ if (gles2_ctx->current_program == NULL) return; flush_viewport_state (gles2_ctx); flush_scissor_state (gles2_ctx); flush_front_face_state (gles2_ctx); /* We want to flip rendering when the application is rendering to a * Cogl offscreen buffer in order to maintain the flipped texture * coordinate origin */ if (gles2_ctx->current_flip_state != gles2_ctx->current_program->flip_vector_state) { GLuint location = gles2_ctx->current_program->flip_vector_location; float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; if (gles2_ctx->current_flip_state == COGL_GLES2_FLIP_STATE_FLIPPED) value[1] = -1.0f; gles2_ctx->context->glUniform4fv (location, 1, value); gles2_ctx->current_program->flip_vector_state = gles2_ctx->current_flip_state; } } static void gl_clear_wrapper (GLbitfield mask) { CoglGLES2Context *gles2_ctx = current_gles2_context; /* Clearing is affected by the scissor state so we need to ensure * that's flushed */ flush_scissor_state (gles2_ctx); gles2_ctx->context->glClear (mask); } static void gl_draw_elements_wrapper (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices) { CoglGLES2Context *gles2_ctx = current_gles2_context; pre_draw_wrapper (gles2_ctx); gles2_ctx->context->glDrawElements (mode, count, type, indices); } static void gl_draw_arrays_wrapper (GLenum mode, GLint first, GLsizei count) { CoglGLES2Context *gles2_ctx = current_gles2_context; pre_draw_wrapper (gles2_ctx); gles2_ctx->context->glDrawArrays (mode, first, count); } static void gl_get_program_info_log_wrapper (GLuint program, GLsizei buf_size, GLsizei *length_out, GLchar *info_log) { CoglGLES2Context *gles2_ctx = current_gles2_context; GLsizei length; gles2_ctx->context->glGetProgramInfoLog (program, buf_size, &length, info_log); replace_token (info_log, MAIN_WRAPPER_REPLACEMENT_NAME, "main", MIN (length, buf_size)); if (length_out) *length_out = length; } static void gl_get_shader_info_log_wrapper (GLuint shader, GLsizei buf_size, GLsizei *length_out, GLchar *info_log) { CoglGLES2Context *gles2_ctx = current_gles2_context; GLsizei length; gles2_ctx->context->glGetShaderInfoLog (shader, buf_size, &length, info_log); replace_token (info_log, MAIN_WRAPPER_REPLACEMENT_NAME, "main", MIN (length, buf_size)); if (length_out) *length_out = length; } static void gl_front_face_wrapper (GLenum mode) { CoglGLES2Context *gles2_ctx = current_gles2_context; /* If the mode doesn't make any sense then we'll just let the * context deal with it directly so that it will throw an error */ if (mode != GL_CW && mode != GL_CCW) gles2_ctx->context->glFrontFace (mode); else { gles2_ctx->front_face = mode; gles2_ctx->front_face_dirty = TRUE; } } static void gl_viewport_wrapper (GLint x, GLint y, GLsizei width, GLsizei height) { CoglGLES2Context *gles2_ctx = current_gles2_context; /* If the viewport is invalid then we'll just let the context deal * with it directly so that it will throw an error */ if (width < 0 || height < 0) gles2_ctx->context->glViewport (x, y, width, height); else { gles2_ctx->viewport[0] = x; gles2_ctx->viewport[1] = y; gles2_ctx->viewport[2] = width; gles2_ctx->viewport[3] = height; gles2_ctx->viewport_dirty = TRUE; } } static void gl_scissor_wrapper (GLint x, GLint y, GLsizei width, GLsizei height) { CoglGLES2Context *gles2_ctx = current_gles2_context; /* If the scissor is invalid then we'll just let the context deal * with it directly so that it will throw an error */ if (width < 0 || height < 0) gles2_ctx->context->glScissor (x, y, width, height); else { gles2_ctx->scissor[0] = x; gles2_ctx->scissor[1] = y; gles2_ctx->scissor[2] = width; gles2_ctx->scissor[3] = height; gles2_ctx->scissor_dirty = TRUE; } } static void gl_get_boolean_v_wrapper (GLenum pname, GLboolean *params) { CoglGLES2Context *gles2_ctx = current_gles2_context; switch (pname) { case GL_VIEWPORT: { int i; for (i = 0; i < 4; i++) params[i] = !!gles2_ctx->viewport[i]; } break; case GL_SCISSOR_BOX: { int i; for (i = 0; i < 4; i++) params[i] = !!gles2_ctx->scissor[i]; } break; default: gles2_ctx->context->glGetBooleanv (pname, params); } } static void gl_get_integer_v_wrapper (GLenum pname, GLint *params) { CoglGLES2Context *gles2_ctx = current_gles2_context; switch (pname) { case GL_VIEWPORT: { int i; for (i = 0; i < 4; i++) params[i] = gles2_ctx->viewport[i]; } break; case GL_SCISSOR_BOX: { int i; for (i = 0; i < 4; i++) params[i] = gles2_ctx->scissor[i]; } break; case GL_FRONT_FACE: params[0] = gles2_ctx->front_face; break; default: gles2_ctx->context->glGetIntegerv (pname, params); } } static void gl_get_float_v_wrapper (GLenum pname, GLfloat *params) { CoglGLES2Context *gles2_ctx = current_gles2_context; switch (pname) { case GL_VIEWPORT: { int i; for (i = 0; i < 4; i++) params[i] = gles2_ctx->viewport[i]; } break; case GL_SCISSOR_BOX: { int i; for (i = 0; i < 4; i++) params[i] = gles2_ctx->scissor[i]; } break; case GL_FRONT_FACE: params[0] = gles2_ctx->front_face; break; default: gles2_ctx->context->glGetFloatv (pname, params); } } static void gl_pixel_store_i_wrapper (GLenum pname, GLint param) { CoglGLES2Context *gles2_ctx = current_gles2_context; gles2_ctx->context->glPixelStorei (pname, param); if (pname == GL_PACK_ALIGNMENT && (param == 1 || param == 2 || param == 4 || param == 8)) gles2_ctx->pack_alignment = param; } static void gl_active_texture_wrapper (GLenum texture) { CoglGLES2Context *gles2_ctx = current_gles2_context; int texture_unit; gles2_ctx->context->glActiveTexture (texture); texture_unit = texture - GL_TEXTURE0; /* If the application is binding some odd looking texture unit * numbers then we'll just ignore it and hope that GL has generated * an error */ if (texture_unit >= 0 && texture_unit < 512) { gles2_ctx->current_texture_unit = texture_unit; g_array_set_size (gles2_ctx->texture_units, MAX (texture_unit, gles2_ctx->texture_units->len)); } } static void gl_delete_textures_wrapper (GLsizei n, const GLuint *textures) { CoglGLES2Context *gles2_ctx = current_gles2_context; int texture_index; int texture_unit; gles2_ctx->context->glDeleteTextures (n, textures); for (texture_index = 0; texture_index < n; texture_index++) { /* Reset any texture units that have any of these textures bound */ for (texture_unit = 0; texture_unit < gles2_ctx->texture_units->len; texture_unit++) { CoglGLES2TextureUnitData *unit = &g_array_index (gles2_ctx->texture_units, CoglGLES2TextureUnitData, texture_unit); if (unit->current_texture_2d == textures[texture_index]) unit->current_texture_2d = 0; } /* Remove the binding. We can do this immediately because unlike * shader objects the deletion isn't delayed until the object is * unbound */ g_hash_table_remove (gles2_ctx->texture_object_map, GUINT_TO_POINTER (textures[texture_index])); } } static void gl_bind_texture_wrapper (GLenum target, GLuint texture) { CoglGLES2Context *gles2_ctx = current_gles2_context; gles2_ctx->context->glBindTexture (target, texture); if (target == GL_TEXTURE_2D) { CoglGLES2TextureUnitData *unit = &g_array_index (gles2_ctx->texture_units, CoglGLES2TextureUnitData, gles2_ctx->current_texture_unit); unit->current_texture_2d = texture; } } static void gl_tex_image_2d_wrapper (GLenum target, GLint level, GLint internal_format, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels) { CoglGLES2Context *gles2_ctx = current_gles2_context; gles2_ctx->context->glTexImage2D (target, level, internal_format, width, height, border, format, type, pixels); set_texture_object_data (gles2_ctx, target, level, internal_format, width, height); } static void _cogl_gles2_offscreen_free (CoglGLES2Offscreen *gles2_offscreen) { _cogl_list_remove (&gles2_offscreen->link); g_slice_free (CoglGLES2Offscreen, gles2_offscreen); } static void force_delete_program_object (CoglGLES2Context *context, CoglGLES2ProgramData *program_data) { if (!program_data->deleted) { context->context->glDeleteProgram (program_data->object_id); program_data->deleted = TRUE; program_data_unref (program_data); } } static void force_delete_shader_object (CoglGLES2Context *context, CoglGLES2ShaderData *shader_data) { if (!shader_data->deleted) { context->context->glDeleteShader (shader_data->object_id); shader_data->deleted = TRUE; shader_data_unref (context, shader_data); } } static void force_delete_texture_object (CoglGLES2Context *context, CoglGLES2TextureObjectData *texture_data) { context->context->glDeleteTextures (1, &texture_data->object_id); } static void _cogl_gles2_context_free (CoglGLES2Context *gles2_context) { CoglContext *ctx = gles2_context->context; const CoglWinsysVtable *winsys; GList *objects, *l; if (gles2_context->current_program) program_data_unref (gles2_context->current_program); /* Try to forcibly delete any shaders, programs and textures so that * they won't get leaked. Because all GLES2 contexts are in the same * share list as Cogl's context these won't get deleted by default. * FIXME: we should do this for all of the other resources too, like * textures */ objects = g_hash_table_get_values (gles2_context->program_map); for (l = objects; l; l = l->next) force_delete_program_object (gles2_context, l->data); g_list_free (objects); objects = g_hash_table_get_values (gles2_context->shader_map); for (l = objects; l; l = l->next) force_delete_shader_object (gles2_context, l->data); g_list_free (objects); objects = g_hash_table_get_values (gles2_context->texture_object_map); for (l = objects; l; l = l->next) force_delete_texture_object (gles2_context, l->data); g_list_free (objects); /* All of the program and shader objects should now be destroyed */ if (g_hash_table_size (gles2_context->program_map) > 0) g_warning ("Program objects have been leaked from a CoglGLES2Context"); if (g_hash_table_size (gles2_context->shader_map) > 0) g_warning ("Shader objects have been leaked from a CoglGLES2Context"); g_hash_table_destroy (gles2_context->program_map); g_hash_table_destroy (gles2_context->shader_map); g_hash_table_destroy (gles2_context->texture_object_map); g_array_free (gles2_context->texture_units, TRUE); winsys = ctx->display->renderer->winsys_vtable; winsys->destroy_gles2_context (gles2_context); while (!_cogl_list_empty (&gles2_context->foreign_offscreens)) { CoglGLES2Offscreen *gles2_offscreen = _cogl_container_of (gles2_context->foreign_offscreens.next, CoglGLES2Offscreen, link); /* 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); } g_free (gles2_context->vtable); g_free (gles2_context); } static void free_shader_data (CoglGLES2ShaderData *data) { g_slice_free (CoglGLES2ShaderData, data); } static void free_program_data (CoglGLES2ProgramData *data) { while (data->attached_shaders) detach_shader (data, data->attached_shaders->data); g_slice_free (CoglGLES2ProgramData, data); } static void free_texture_object_data (CoglGLES2TextureObjectData *data) { g_slice_free (CoglGLES2TextureObjectData, data); } CoglGLES2Context * cogl_gles2_context_new (CoglContext *ctx, CoglError **error) { CoglGLES2Context *gles2_ctx; const CoglWinsysVtable *winsys; if (!cogl_has_feature (ctx, COGL_FEATURE_ID_GLES2_CONTEXT)) { _cogl_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)); 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) { g_free (gles2_ctx); return NULL; } gles2_ctx->current_flip_state = COGL_GLES2_FLIP_STATE_UNKNOWN; gles2_ctx->viewport_dirty = TRUE; gles2_ctx->scissor_dirty = TRUE; gles2_ctx->front_face_dirty = TRUE; gles2_ctx->front_face = GL_CCW; gles2_ctx->pack_alignment = 4; 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 = (void *) 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 = (void *) gl_bind_framebuffer_wrapper; gles2_ctx->vtable->glReadPixels = (void *) gl_read_pixels_wrapper; gles2_ctx->vtable->glCopyTexImage2D = (void *) gl_copy_tex_image_2d_wrapper; gles2_ctx->vtable->glCopyTexSubImage2D = (void *) gl_copy_tex_sub_image_2d_wrapper; gles2_ctx->vtable->glCreateShader = gl_create_shader_wrapper; gles2_ctx->vtable->glDeleteShader = gl_delete_shader_wrapper; gles2_ctx->vtable->glCreateProgram = gl_create_program_wrapper; gles2_ctx->vtable->glDeleteProgram = gl_delete_program_wrapper; gles2_ctx->vtable->glUseProgram = gl_use_program_wrapper; gles2_ctx->vtable->glAttachShader = gl_attach_shader_wrapper; gles2_ctx->vtable->glDetachShader = gl_detach_shader_wrapper; gles2_ctx->vtable->glShaderSource = gl_shader_source_wrapper; gles2_ctx->vtable->glGetShaderSource = gl_get_shader_source_wrapper; gles2_ctx->vtable->glLinkProgram = gl_link_program_wrapper; gles2_ctx->vtable->glGetProgramiv = gl_get_program_iv_wrapper; gles2_ctx->vtable->glGetProgramInfoLog = gl_get_program_info_log_wrapper; gles2_ctx->vtable->glGetShaderInfoLog = gl_get_shader_info_log_wrapper; gles2_ctx->vtable->glClear = gl_clear_wrapper; gles2_ctx->vtable->glDrawElements = gl_draw_elements_wrapper; gles2_ctx->vtable->glDrawArrays = gl_draw_arrays_wrapper; gles2_ctx->vtable->glFrontFace = gl_front_face_wrapper; gles2_ctx->vtable->glViewport = gl_viewport_wrapper; gles2_ctx->vtable->glScissor = gl_scissor_wrapper; gles2_ctx->vtable->glGetBooleanv = gl_get_boolean_v_wrapper; gles2_ctx->vtable->glGetIntegerv = gl_get_integer_v_wrapper; gles2_ctx->vtable->glGetFloatv = gl_get_float_v_wrapper; gles2_ctx->vtable->glPixelStorei = gl_pixel_store_i_wrapper; gles2_ctx->vtable->glActiveTexture = gl_active_texture_wrapper; gles2_ctx->vtable->glDeleteTextures = gl_delete_textures_wrapper; gles2_ctx->vtable->glBindTexture = gl_bind_texture_wrapper; gles2_ctx->vtable->glTexImage2D = gl_tex_image_2d_wrapper; gles2_ctx->shader_map = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, /* key_destroy */ (GDestroyNotify) free_shader_data); gles2_ctx->program_map = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, /* key_destroy */ (GDestroyNotify) free_program_data); gles2_ctx->texture_object_map = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, /* key_destroy */ (GDestroyNotify) free_texture_object_data); gles2_ctx->texture_units = g_array_new (FALSE, /* not zero terminated */ TRUE, /* clear */ sizeof (CoglGLES2TextureUnitData)); gles2_ctx->current_texture_unit = 0; g_array_set_size (gles2_ctx->texture_units, 1); 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, CoglError **error) { CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (offscreen); const CoglWinsysVtable *winsys; CoglError *internal_error = NULL; CoglGLES2Offscreen *gles2_offscreen; int level_width; int level_height; if (!framebuffer->allocated && !cogl_framebuffer_allocate (framebuffer, error)) { return NULL; } _cogl_list_for_each (gles2_offscreen, &gles2_context->foreign_offscreens, link) { 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); cogl_error_free (internal_error); _cogl_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); _cogl_texture_get_level_size (offscreen->texture, offscreen->texture_level, &level_width, &level_height, NULL); if (!_cogl_framebuffer_try_creating_gl_fbo (gles2_context->context, offscreen->texture, offscreen->texture_level, level_width, level_height, offscreen->depth_texture, &COGL_FRAMEBUFFER (offscreen)->config, offscreen->allocation_flags, &gles2_offscreen->gl_framebuffer)) { winsys->restore_context (framebuffer->context); g_slice_free (CoglGLES2Offscreen, gles2_offscreen); _cogl_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 (&gles2_context->foreign_offscreens, &gles2_offscreen->link); /* 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, CoglError **error) { const CoglWinsysVtable *winsys = ctx->display->renderer->winsys_vtable; CoglError *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); update_current_flip_state (gles2_ctx); } if (!winsys->set_gles2_context (gles2_ctx, &internal_error)) { winsys->restore_context (ctx); cogl_error_free (internal_error); _cogl_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; /* If this is the first time this gles2 context has been used then * we'll force the viewport and scissor to the right size. GL has * the semantics that the viewport and scissor default to the size * of the first surface the context is used with. If the first * CoglFramebuffer that this context is used with is an offscreen, * then the surface from GL's point of view will be the 1x1 dummy * surface so the viewport will be wrong. Therefore we just override * the default viewport and scissor here */ if (!gles2_ctx->has_been_bound) { int fb_width = cogl_framebuffer_get_width (write_buffer); int fb_height = cogl_framebuffer_get_height (write_buffer); gles2_ctx->vtable->glViewport (0, 0, /* x/y */ fb_width, fb_height); gles2_ctx->vtable->glScissor (0, 0, /* x/y */ fb_width, fb_height); gles2_ctx->has_been_bound = TRUE; } 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 format) { return cogl_texture_2d_gl_new_from_foreign (ctx, handle, width, height, format); } CoglBool cogl_gles2_texture_get_handle (CoglTexture *texture, unsigned int *handle, unsigned int *target) { return cogl_texture_get_gl_texture (texture, handle, target); }