cogl-gles2-context: Wrap glCopyTex{Sub,}Image2D to flip the result

When the CoglGLES2Context is bound to read from a CoglOffscreen then
the result will be upside down from what GL expects if
glCopyTexImage2D is used directly. To fix that, this patch now wraps
glCopyTexImage2D and glCopyTexSubImage2D so that the copy is doing by
binding an FBO to the target texture and then rendering a quad
sampling from the texture in the offscreen framebuffer.

The rendering is done using the Cogl context rather than the GLES2
context because otherwise it would have to do a fair bit of work to
try and stash the old state on the context before setting up the state
to do the blit. The down side of this is that the contexts need to be
synchronized so that the rendering will be up-to-date. As far as I
understand from the GL spec, this requires a glFinish and then the
texture needs to be rebound in the new context because updates to
shared objects are guaranteed to be reflected until the object is
rebound.

GLES2 supports using glCopyTexImage2D for cube map textures. As Cogl
doesn't currently have support for cube maps, it is quite hard to get
that to work with this patch. For now attempts to copy to a cube map
texture will just be sliently ignored.

This patch also includes a test case which renders an image to the
framebuffer and then copies it to a texture. The texture is then
rendered back to the framebuffer and the contents are checked for the
correct orientation using glReadPixels.

Reviewed-by: Robert Bragg <robert@linux.intel.com>

(cherry picked from commit 30b6da8134bad95267265e26685c7475f6c351c9)
This commit is contained in:
Neil Roberts 2012-08-09 16:03:35 +01:00 committed by Robert Bragg
parent 77edc1f204
commit 19ccb72b80
3 changed files with 442 additions and 14 deletions

View File

@ -43,6 +43,7 @@
#include "cogl-renderer-private.h" #include "cogl-renderer-private.h"
#include "cogl-swap-chain-private.h" #include "cogl-swap-chain-private.h"
#include "cogl-texture-2d-private.h" #include "cogl-texture-2d-private.h"
#include "cogl-pipeline-opengl-private.h"
static void _cogl_gles2_context_free (CoglGLES2Context *gles2_context); static void _cogl_gles2_context_free (CoglGLES2Context *gles2_context);
@ -241,6 +242,145 @@ set_texture_object_data (CoglGLES2Context *gles2_ctx,
} }
} }
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,
NULL /* error */);
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_to_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 wrap glBindFramebuffer so that when framebuffer 0 is bound
* we can instead bind the write_framebuffer passed to * we can instead bind the write_framebuffer passed to
* cogl_push_gles2_context(). * cogl_push_gles2_context().
@ -437,18 +577,51 @@ gl_copy_tex_image_2d_wrapper (GLenum target,
GLint border) GLint border)
{ {
CoglGLES2Context *gles2_ctx = current_gles2_context; CoglGLES2Context *gles2_ctx = current_gles2_context;
int restore_mode = transient_bind_read_buffer (gles2_ctx);
gles2_ctx->context->glCopyTexImage2D (target, level, internal_format, /* If we are reading from a CoglOffscreen buffer then the image will
x, y, width, height, border); * 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;
restore_write_buffer (gles2_ctx, restore_mode); /* 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 */);
set_texture_object_data (gles2_ctx, copy_flipped_texture (gles2_ctx,
target, level,
level, x, y, /* src_x/src_y */
internal_format, 0, 0, /* dst_x/dst_y */
width, height); 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 static void
@ -462,13 +635,36 @@ gl_copy_tex_sub_image_2d_wrapper (GLenum target,
GLsizei height) GLsizei height)
{ {
CoglGLES2Context *gles2_ctx = current_gles2_context; CoglGLES2Context *gles2_ctx = current_gles2_context;
int restore_mode = transient_bind_read_buffer (gles2_ctx);
gles2_ctx->context->glCopyTexSubImage2D (target, level, /* If we are reading from a CoglOffscreen buffer then the image will
xoffset, yoffset, * be upside down with respect to what GL expects so we can't use
x, y, width, height); * 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;
restore_write_buffer (gles2_ctx, restore_mode); 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 static GLuint

View File

@ -104,6 +104,7 @@ main (int argc, char **argv)
ADD_TEST (test_gles2_context, TEST_REQUIREMENT_GLES2_CONTEXT); ADD_TEST (test_gles2_context, TEST_REQUIREMENT_GLES2_CONTEXT);
ADD_TEST (test_gles2_context_fbo, TEST_REQUIREMENT_GLES2_CONTEXT); ADD_TEST (test_gles2_context_fbo, TEST_REQUIREMENT_GLES2_CONTEXT);
ADD_TEST (test_gles2_context_copy_tex_image, TEST_REQUIREMENT_GLES2_CONTEXT);
ADD_TEST (test_euler_quaternion, 0); ADD_TEST (test_euler_quaternion, 0);

View File

@ -743,3 +743,234 @@ test_gles2_context_fbo (void)
0x0000ffff); 0x0000ffff);
} }
} }
/* Position to draw a rectangle in. The top half of this rectangle
* will be red, and the bottom will be blue */
#define RECTANGLE_DRAW_X 10
#define RECTANGLE_DRAW_Y 15
/* Position to copy the rectangle to in the destination texture */
#define RECTANGLE_COPY_X 110
#define RECTANGLE_COPY_Y 115
#define RECTANGLE_WIDTH 30
#define RECTANGLE_HEIGHT 40
static void
verify_region (const CoglGLES2Vtable *gles2,
int x,
int y,
int width,
int height,
uint32_t expected_pixel)
{
uint8_t *buf, *p;
buf = g_malloc (width * height * 4);
gles2->glReadPixels (x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buf);
for (p = buf + width * height * 4; p > buf; p -= 4)
test_utils_compare_pixel (p - 4, expected_pixel);
g_free (buf);
}
void
test_gles2_context_copy_tex_image (void)
{
static const char vertex_shader_source[] =
"attribute vec2 pos;\n"
"attribute vec2 tex_coord_attrib;\n"
"varying vec2 tex_coord_varying;\n"
"\n"
"void\n"
"main ()\n"
"{\n"
" gl_Position = vec4 (pos, 0.0, 1.0);\n"
" tex_coord_varying = tex_coord_attrib;\n"
"}\n";
static const char fragment_shader_source[] =
"precision mediump float;\n"
"varying vec2 tex_coord_varying;\n"
"uniform sampler2D tex;\n"
"\n"
"void\n"
"main ()\n"
"{\n"
" gl_FragColor = texture2D (tex, tex_coord_varying);\n"
"}\n";
static const float verts[] =
{
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
int fb_width = cogl_framebuffer_get_width (fb);
int fb_height = cogl_framebuffer_get_height (fb);
CoglTexture *offscreen_texture;
CoglOffscreen *offscreen;
CoglPipeline *pipeline;
CoglGLES2Context *gles2_ctx;
const CoglGLES2Vtable *gles2;
GError *error = NULL;
GLuint tex;
GLint tex_uniform_location;
GLint pos_location;
GLint tex_coord_location;
GLuint program;
create_gles2_context (&offscreen_texture,
&offscreen,
&pipeline,
&gles2_ctx,
&gles2);
if (!cogl_push_gles2_context (ctx,
gles2_ctx,
COGL_FRAMEBUFFER (offscreen),
COGL_FRAMEBUFFER (offscreen),
&error))
g_error ("Failed to push gles2 context: %s\n", error->message);
gles2->glClearColor (1.0, 1.0, 0.0, 1.0);
gles2->glClear (GL_COLOR_BUFFER_BIT);
/* Draw a rectangle using clear and the scissor so that we don't
* have to create a shader */
gles2->glEnable (GL_SCISSOR_TEST);
/* Top half red */
gles2->glScissor (RECTANGLE_DRAW_X,
RECTANGLE_DRAW_Y + RECTANGLE_HEIGHT / 2,
RECTANGLE_WIDTH,
RECTANGLE_HEIGHT / 2);
gles2->glClearColor (1.0, 0.0, 0.0, 1.0);
gles2->glClear (GL_COLOR_BUFFER_BIT);
/* Bottom half blue */
gles2->glScissor (RECTANGLE_DRAW_X,
RECTANGLE_DRAW_Y,
RECTANGLE_WIDTH,
RECTANGLE_HEIGHT / 2);
gles2->glClearColor (0.0, 0.0, 1.0, 1.0);
gles2->glClear (GL_COLOR_BUFFER_BIT);
/* Draw where the rectangle would be if the coordinates were flipped
* in white to make it obvious that that is the problem if the
* assertion fails */
gles2->glScissor (RECTANGLE_DRAW_X,
fb_width - (RECTANGLE_DRAW_Y + RECTANGLE_HEIGHT),
RECTANGLE_WIDTH,
RECTANGLE_HEIGHT);
gles2->glClearColor (1.0, 1.0, 1.0, 1.0);
gles2->glClear (GL_COLOR_BUFFER_BIT);
gles2->glDisable (GL_SCISSOR_TEST);
/* Create a texture */
gles2->glGenTextures (1, &tex);
gles2->glBindTexture (GL_TEXTURE_2D, tex);
gles2->glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
gles2->glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
/* Copy the entire framebuffer into the texture */
gles2->glCopyTexImage2D (GL_TEXTURE_2D,
0, /* level */
GL_RGBA,
0, 0, /* x/y */
fb_width, fb_height,
0 /* border */);
/* Copy the rectangle into another part of the texture */
gles2->glCopyTexSubImage2D (GL_TEXTURE_2D,
0, /* level */
RECTANGLE_COPY_X,
RECTANGLE_COPY_Y,
RECTANGLE_DRAW_X,
RECTANGLE_DRAW_Y,
RECTANGLE_WIDTH,
RECTANGLE_HEIGHT);
/* Clear the framebuffer to make the test more thorough */
gles2->glClearColor (1.0, 1.0, 0.0, 1.0);
gles2->glClear (GL_COLOR_BUFFER_BIT);
/* Create a program to render the texture */
program = create_program (gles2,
vertex_shader_source,
fragment_shader_source);
pos_location =
gles2->glGetAttribLocation (program, "pos");
if (pos_location == -1)
g_error ("Couldn't find pos attribute");
tex_coord_location =
gles2->glGetAttribLocation (program, "tex_coord_attrib");
if (tex_coord_location == -1)
g_error ("Couldn't find tex_coord_attrib attribute");
tex_uniform_location =
gles2->glGetUniformLocation (program, "tex");
if (tex_uniform_location == -1)
g_error ("Couldn't find tex uniform");
gles2->glUseProgram (program);
gles2->glUniform1i (tex_uniform_location, 0);
/* Render the texture to fill the framebuffer */
gles2->glEnableVertexAttribArray (pos_location);
gles2->glVertexAttribPointer (pos_location,
2, /* n_components */
GL_FLOAT,
FALSE, /* normalized */
sizeof (float) * 4,
verts);
gles2->glEnableVertexAttribArray (tex_coord_location);
gles2->glVertexAttribPointer (tex_coord_location,
2, /* n_components */
GL_FLOAT,
FALSE, /* normalized */
sizeof (float) * 4,
verts + 2);
gles2->glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
/* Verify top of drawn rectangle is red */
verify_region (gles2,
RECTANGLE_DRAW_X,
RECTANGLE_DRAW_Y + RECTANGLE_HEIGHT / 2,
RECTANGLE_WIDTH,
RECTANGLE_HEIGHT / 2,
0xff0000ff);
/* Verify bottom of drawn rectangle is blue */
verify_region (gles2,
RECTANGLE_DRAW_X,
RECTANGLE_DRAW_Y,
RECTANGLE_WIDTH,
RECTANGLE_HEIGHT / 2,
0x0000ffff);
/* Verify top of copied rectangle is red */
verify_region (gles2,
RECTANGLE_COPY_X,
RECTANGLE_COPY_Y + RECTANGLE_HEIGHT / 2,
RECTANGLE_WIDTH,
RECTANGLE_HEIGHT / 2,
0xff0000ff);
/* Verify bottom of copied rectangle is blue */
verify_region (gles2,
RECTANGLE_COPY_X,
RECTANGLE_COPY_Y,
RECTANGLE_WIDTH,
RECTANGLE_HEIGHT / 2,
0x0000ffff);
cogl_pop_gles2_context (ctx);
cogl_object_unref (offscreen);
cogl_object_unref (gles2_ctx);
cogl_object_unref (pipeline);
cogl_object_unref (offscreen_texture);
}