cogl-atlas-texture: Try to do texture blits using an FBO

When reorganizing the textures, we can avoid downloading the entire
texture data if we bind the source texture in a framebuffer object and
copy the destination using glCopyTexSubImage2D. This is also
implemented using a much faster path in Mesa.

Currently it is calling the GL framebuffer API directly but ideally it
would use the Cogl offscreen API. However there is no way to tell Cogl
not to create a stencil renderbuffer which seems like a waste in this
situation.

If FBOs are not available it will fallback to reading back the entire
texture data as before.
This commit is contained in:
Neil Roberts 2009-12-05 13:24:01 +00:00
parent 636cef1bd6
commit 3ebe48105d

View File

@ -41,12 +41,169 @@
#include <stdlib.h> #include <stdlib.h>
#ifdef HAVE_COGL_GLES2
#include "../gles/cogl-gles2-wrapper.h"
#else /* HAVE_COGL_GLES2 */
#define glGenFramebuffers ctx->drv.pf_glGenFramebuffers
#define glBindFramebuffer ctx->drv.pf_glBindFramebuffer
#define glFramebufferTexture2D ctx->drv.pf_glFramebufferTexture2D
#define glCheckFramebufferStatus ctx->drv.pf_glCheckFramebufferStatus
#define glDeleteFramebuffers ctx->drv.pf_glDeleteFramebuffers
#endif /* HAVE_COGL_GLES2 */
#ifndef GL_FRAMEBUFFER
#define GL_FRAMEBUFFER 0x8D40
#endif
#ifndef GL_FRAMEBUFFER_BINDING
#define GL_FRAMEBUFFER_BINDING 0x8CA6
#endif
#ifndef GL_COLOR_ATTACHMENT0
#define GL_COLOR_ATTACHMENT0 0x8CE0
#endif
#ifndef GL_FRAMEBUFFER_COMPLETE
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
#endif
static void _cogl_atlas_texture_free (CoglAtlasTexture *sub_tex); static void _cogl_atlas_texture_free (CoglAtlasTexture *sub_tex);
COGL_HANDLE_DEFINE (AtlasTexture, atlas_texture); COGL_HANDLE_DEFINE (AtlasTexture, atlas_texture);
static const CoglTextureVtable cogl_atlas_texture_vtable; static const CoglTextureVtable cogl_atlas_texture_vtable;
/* If we want to do mulitple blits from a texture (such as when
reorganizing the atlas) then it's quicker to download all of the
data once and upload multiple times from that. This struct is used
to keep the image data for a series of blits */
typedef struct _CoglAtlasTextureBlitData
{
CoglHandle src_tex, dst_tex;
/* If we're using an FBO to blit, then FBO will be non-zero and
old_fbo will be the previous framebuffer binding */
GLuint fbo, old_fbo;
/* If we're not using an FBO then we g_malloc a buffer and copy the
complete texture data in */
unsigned char *image_data;
CoglPixelFormat format;
gint bpp;
guint src_height, src_width;
GLenum dst_gl_target;
} CoglAtlasTextureBlitData;
static void
_cogl_atlas_texture_blit_begin (CoglAtlasTextureBlitData *data,
CoglHandle dst_tex,
CoglHandle src_tex)
{
GLenum src_gl_target;
GLuint src_gl_texture;
GLuint dst_gl_texture;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
data->dst_tex = dst_tex;
data->src_tex = src_tex;
data->fbo = 0;
/* If we can use an FBO then we don't need to download the data and
we can tell GL to blit directly between the textures */
if (cogl_features_available (COGL_FEATURE_OFFSCREEN) &&
!cogl_texture_is_sliced (dst_tex) &&
cogl_texture_get_gl_texture (src_tex, &src_gl_texture, &src_gl_target) &&
cogl_texture_get_gl_texture (dst_tex, &dst_gl_texture,
&data->dst_gl_target))
{
/* Ideally we would use the cogl-offscreen API here, but I'd
rather avoid creating a stencil renderbuffer which you can't
currently do */
/* Preserve the previous framebuffer binding so we don't trample
on cogl-offscreen */
data->old_fbo = 0;
GE( glGetIntegerv (GL_FRAMEBUFFER_BINDING, (GLint *) &data->old_fbo) );
_cogl_texture_set_filters (src_tex, GL_NEAREST, GL_NEAREST);
/* Create an FBO to read from the src texture */
GE( glGenFramebuffers (1, &data->fbo) );
GE( glBindFramebuffer (GL_FRAMEBUFFER, data->fbo) );
GE( glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
src_gl_target, src_gl_texture, 0) );
if (glCheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
/* The FBO failed for whatever reason so we'll fallback to
reading the texture data */
GE( glBindFramebuffer (GL_FRAMEBUFFER, data->old_fbo) );
GE( glDeleteFramebuffers (1, &data->fbo) );
data->fbo = 0;
}
GE( glBindTexture (data->dst_gl_target, dst_gl_texture) );
}
if (data->fbo)
COGL_NOTE (ATLAS, "Blit set up using an FBO");
else
{
/* We need to retrieve the entire texture data (there is no
glGetTexSubImage2D) */
data->format = cogl_texture_get_format (src_tex);
data->bpp = _cogl_get_format_bpp (data->format);
data->src_width = cogl_texture_get_width (src_tex);
data->src_height = cogl_texture_get_height (src_tex);
data->image_data = g_malloc (data->bpp * data->src_width *
data->src_height);
cogl_texture_get_data (src_tex, data->format,
data->src_width * data->bpp, data->image_data);
}
}
static void
_cogl_atlas_texture_blit (CoglAtlasTextureBlitData *data,
guint src_x,
guint src_y,
guint dst_x,
guint dst_y,
guint width,
guint height)
{
/* If we have an FBO then we can do a fast blit */
if (data->fbo)
GE( glCopyTexSubImage2D (data->dst_gl_target, 0, dst_x, dst_y, src_x, src_y,
width, height) );
else
cogl_texture_set_region (data->dst_tex,
src_x, src_y,
dst_x, dst_y,
width, height,
data->src_width, data->src_height,
data->format,
data->src_width * data->bpp,
data->image_data);
}
static void
_cogl_atlas_texture_blit_end (CoglAtlasTextureBlitData *data)
{
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
if (data->fbo)
{
GE( glBindFramebuffer (GL_FRAMEBUFFER, data->old_fbo) );
GE( glDeleteFramebuffers (1, &data->fbo) );
}
else
g_free (data->image_data);
}
static void static void
_cogl_atlas_texture_foreach_sub_texture_in_region ( _cogl_atlas_texture_foreach_sub_texture_in_region (
CoglTexture *tex, CoglTexture *tex,
@ -367,24 +524,9 @@ _cogl_atlas_texture_migrate (guint n_textures,
CoglAtlasTexture *skip_texture) CoglAtlasTexture *skip_texture)
{ {
guint i; guint i;
guint8 *data; CoglAtlasTextureBlitData blit_data;
CoglPixelFormat format;
gint bpp;
guint old_height, old_width;
format = cogl_texture_get_format (old_texture); _cogl_atlas_texture_blit_begin (&blit_data, new_texture, old_texture);
bpp = _cogl_get_format_bpp (format);
old_width = cogl_texture_get_width (old_texture);
old_height = cogl_texture_get_height (old_texture);
/* Get the existing data for the texture. FIXME: we should use a PBO
or maybe copy the texture data via an FBO */
data = g_malloc (bpp *
cogl_texture_get_width (old_texture) *
cogl_texture_get_height (old_texture));
cogl_texture_get_data (old_texture, format,
old_width * bpp,
data);
for (i = 0; i < n_textures; i++) for (i = 0; i < n_textures; i++)
{ {
@ -392,17 +534,13 @@ _cogl_atlas_texture_migrate (guint n_textures,
any data yet */ any data yet */
if (textures[i].texture != skip_texture) if (textures[i].texture != skip_texture)
{ {
cogl_texture_set_region (new_texture, _cogl_atlas_texture_blit (&blit_data,
textures[i].texture->rectangle.x, textures[i].texture->rectangle.x,
textures[i].texture->rectangle.y, textures[i].texture->rectangle.y,
textures[i].new_position.x, textures[i].new_position.x,
textures[i].new_position.y, textures[i].new_position.y,
textures[i].new_position.width, textures[i].new_position.width,
textures[i].new_position.height, textures[i].new_position.height);
old_width, old_height,
format,
old_width * bpp,
data);
/* Update the sub texture */ /* Update the sub texture */
cogl_handle_unref (textures[i].texture->sub_texture); cogl_handle_unref (textures[i].texture->sub_texture);
textures[i].texture->sub_texture = textures[i].texture->sub_texture =
@ -414,7 +552,7 @@ _cogl_atlas_texture_migrate (guint n_textures,
textures[i].texture->rectangle = textures[i].new_position; textures[i].texture->rectangle = textures[i].new_position;
} }
g_free (data); _cogl_atlas_texture_blit_end (&blit_data);
} }
typedef struct _CoglAtlasTextureGetRectanglesData typedef struct _CoglAtlasTextureGetRectanglesData