summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNeil Roberts <neil@linux.intel.com>2012-08-09 16:03:35 +0100
committerNeil Roberts <neil@linux.intel.com>2012-08-09 16:31:50 +0100
commitb35d50001b9d53a4f496173f38bef218be7267ad (patch)
tree638a6375b08cb4cf9d9a96cdc5f9bda8fb449fd6
parent6f01bec2a8a21d2af679cedfe2581cd2184e1ca7 (diff)
downloadcogl-wip/gles2-flip-fbo.tar.gz
cogl-gles2-context: Wrap glCopyTex{Sub,}Image2D to flip the resultwip/gles2-flip-fbo
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.
-rw-r--r--cogl/cogl-gles2-context.c224
-rw-r--r--tests/conform/test-conform-main.c1
-rw-r--r--tests/conform/test-gles2-context.c231
3 files changed, 442 insertions, 14 deletions
diff --git a/cogl/cogl-gles2-context.c b/cogl/cogl-gles2-context.c
index a7507c62..35857056 100644
--- a/cogl/cogl-gles2-context.c
+++ b/cogl/cogl-gles2-context.c
@@ -43,6 +43,7 @@
#include "cogl-renderer-private.h"
#include "cogl-swap-chain-private.h"
#include "cogl-texture-2d-private.h"
+#include "cogl-pipeline-opengl-private.h"
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 can instead bind the write_framebuffer passed to
* cogl_push_gles2_context().
@@ -437,18 +577,51 @@ gl_copy_tex_image_2d_wrapper (GLenum target,
GLint border)
{
CoglGLES2Context *gles2_ctx = current_gles2_context;
- int restore_mode = transient_bind_read_buffer (gles2_ctx);
- gles2_ctx->context->glCopyTexImage2D (target, level, internal_format,
- x, y, width, height, border);
+ /* 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);
- restore_write_buffer (gles2_ctx, restore_mode);
+ gles2_ctx->context->glCopyTexImage2D (target, level, internal_format,
+ x, y, width, height, border);
- set_texture_object_data (gles2_ctx,
- target,
- level,
- internal_format,
- width, height);
+ restore_write_buffer (gles2_ctx, restore_mode);
+
+ set_texture_object_data (gles2_ctx,
+ target,
+ level,
+ internal_format,
+ width, height);
+ }
}
static void
@@ -462,13 +635,36 @@ gl_copy_tex_sub_image_2d_wrapper (GLenum target,
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);
+ /* 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);
+ restore_write_buffer (gles2_ctx, restore_mode);
+ }
}
static GLuint
diff --git a/tests/conform/test-conform-main.c b/tests/conform/test-conform-main.c
index a9d7d6a2..74756850 100644
--- a/tests/conform/test-conform-main.c
+++ b/tests/conform/test-conform-main.c
@@ -104,6 +104,7 @@ main (int argc, char **argv)
ADD_TEST (test_gles2_context, 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);
diff --git a/tests/conform/test-gles2-context.c b/tests/conform/test-gles2-context.c
index f9d01752..a55920b5 100644
--- a/tests/conform/test-gles2-context.c
+++ b/tests/conform/test-gles2-context.c
@@ -743,3 +743,234 @@ test_gles2_context_fbo (void)
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 (test_fb);
+ int fb_height = cogl_framebuffer_get_height (test_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 (test_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 (test_ctx);
+
+ cogl_object_unref (offscreen);
+ cogl_object_unref (gles2_ctx);
+ cogl_object_unref (pipeline);
+ cogl_object_unref (offscreen_texture);
+}