/*
* 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
#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;
/* 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"
/* This wrapper function around 'main' is appended to every program in
* a separate 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[] =
"uniform vec4 " MAIN_WRAPPER_FLIP_UNIFORM ";\n"
"\n"
"void\n"
MAIN_WRAPPER_REPLACEMENT_NAME " ();\n"
"\n"
"void\n"
"main ()\n"
"{\n"
" " MAIN_WRAPPER_REPLACEMENT_NAME " ();\n"
" gl_Position *= " MAIN_WRAPPER_FLIP_UNIFORM ";\n"
"}\n";
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");
}
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;
}
}
/* 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_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);
/* 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 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 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);
gles2_ctx->context->glAttachShader (id, gles2_ctx->wrapper_shader);
}
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 * sizeof (char *));
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 = length ? length[i] : strlen (string[i]);
string_copy[i] = g_memdup (string[i], string_length);
replace_token (string_copy[i],
"main",
MAIN_WRAPPER_REPLACEMENT_NAME,
string_length);
}
gles2_ctx->context->glShaderSource (shader,
count,
(const char *const *) string_copy,
length);
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);
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
gl_get_attached_shaders_wrapper (GLuint program,
GLsizei max_count,
GLsizei *count_ret,
GLuint *obj)
{
CoglGLES2Context *gles2_ctx = current_gles2_context;
GLuint *tmp_buf;
GLsizei count, count_out;
int i;
/* We need to remove the wrapper shader we added from this list */
/* Allocate a temporary buffer that is one larger than the buffer
* passed in in case the application allocated exactly the size
* returned by GL_ATTACHED_SHADERS. */
tmp_buf = g_alloca (sizeof (GLuint) * (max_count + 1));
gles2_ctx->context->glGetAttachedShaders (program,
max_count + 1,
&count,
tmp_buf);
for (i = 0, count_out = 0; i < count; i++)
if (tmp_buf[i] != gles2_ctx->wrapper_shader)
obj[count_out++] = tmp_buf[i];
if (count_ret)
*count_ret = count_out;
}
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
_cogl_gles2_offscreen_free (CoglGLES2Offscreen *gles2_offscreen)
{
COGL_LIST_REMOVE (gles2_offscreen, list_node);
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
_cogl_gles2_context_free (CoglGLES2Context *gles2_context)
{
CoglContext *ctx = gles2_context->context;
const CoglWinsysVtable *winsys;
GList *objects, *l;
ctx->glDeleteShader (gles2_context->wrapper_shader);
if (gles2_context->current_program)
program_data_unref (gles2_context->current_program);
/* Try to forcibly delete any shaders and programs 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);
/* 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);
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);
}
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 GLuint
create_wrapper_shader (CoglContext *ctx)
{
const char *strings = main_wrapper_function;
GLint length = sizeof (main_wrapper_function) - 1;
GLint status;
GLuint shader;
shader = ctx->glCreateShader (GL_VERTEX_SHADER);
ctx->glShaderSource (shader, 1, &strings, &length);
ctx->glCompileShader (shader);
ctx->glGetShaderiv (shader, GL_COMPILE_STATUS, &status);
if (!status)
{
char buf[512];
ctx->glGetShaderInfoLog (shader,
sizeof (buf),
NULL, /* length */
buf);
g_warning ("Compiling wrapper shader failed:\n%s", buf);
}
return shader;
}
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->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 = 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->wrapper_shader = create_wrapper_shader (ctx);
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;
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->glGetAttachedShaders = gl_get_attached_shaders_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;
/* FIXME: we need to do something with glCopyTexImage2D and
* glCopySubTexImage2D so that it will flip the data if it is read
* from a CoglOffscreen */
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);
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);
update_current_flip_state (gles2_ctx);
}
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;
/* 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 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);
}