From 8b4034cd06035e49bbe709daf2977d828cb0f263 Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Fri, 12 Nov 2010 11:02:13 -0500 Subject: [PATCH] Use FBOs and use cogl_read_pixels() to efficiently read partial textures * cogl_texture_get_data() is converted to use _cogl_texture_foreach_sub_texture_in_region() to iterate through the underlying textures. * When we need to read only a portion of the underlying texture, we set up a FBO and use _cogl_read_pixels() to read the portion we need. This is enormously more efficient for reading a small portion of a large atlas texture. * The CoglAtlasTexture, CoglSubTexture, and CoglTexture2dSliced implementation of get_texture() are removed. http://bugzilla.clutter-project.org/show_bug.cgi?id=2414 --- clutter/cogl/cogl/cogl-atlas-texture.c | 17 +- clutter/cogl/cogl/cogl-sub-texture.c | 70 +------- clutter/cogl/cogl/cogl-texture-2d-sliced.c | 149 +--------------- clutter/cogl/cogl/cogl-texture.c | 191 ++++++++++++++++++++- 4 files changed, 186 insertions(+), 241 deletions(-) diff --git a/clutter/cogl/cogl/cogl-atlas-texture.c b/clutter/cogl/cogl/cogl-atlas-texture.c index 2ad7445bd..9194da3e4 100644 --- a/clutter/cogl/cogl/cogl-atlas-texture.c +++ b/clutter/cogl/cogl/cogl-atlas-texture.c @@ -439,21 +439,6 @@ _cogl_atlas_texture_set_region (CoglTexture *tex, bmp); } -static gboolean -_cogl_atlas_texture_get_data (CoglTexture *tex, - CoglPixelFormat format, - unsigned int rowstride, - guint8 *data) -{ - CoglAtlasTexture *atlas_tex = COGL_ATLAS_TEXTURE (tex); - - /* Forward on to the sub texture */ - return cogl_texture_get_data (atlas_tex->sub_texture, - format, - rowstride, - data); -} - static CoglPixelFormat _cogl_atlas_texture_get_format (CoglTexture *tex) { @@ -632,7 +617,7 @@ static const CoglTextureVtable cogl_atlas_texture_vtable = { _cogl_atlas_texture_set_region, - _cogl_atlas_texture_get_data, + NULL, /* get_data */ _cogl_atlas_texture_foreach_sub_texture_in_region, _cogl_atlas_texture_get_max_waste, _cogl_atlas_texture_is_sliced, diff --git a/clutter/cogl/cogl/cogl-sub-texture.c b/clutter/cogl/cogl/cogl-sub-texture.c index 9260f13cc..1730c871e 100644 --- a/clutter/cogl/cogl/cogl-sub-texture.c +++ b/clutter/cogl/cogl/cogl-sub-texture.c @@ -416,74 +416,6 @@ _cogl_sub_texture_set_region (CoglTexture *tex, bmp); } -static void -_cogl_sub_texture_copy_region (guint8 *dst, - const guint8 *src, - int dst_x, int dst_y, - int src_x, int src_y, - int width, int height, - int dst_rowstride, - int src_rowstride, - int bpp) -{ - int y; - - dst += dst_x * bpp + dst_y * dst_rowstride; - src += src_x * bpp + src_y * src_rowstride; - - for (y = 0; y < height; y++) - { - memcpy (dst, src, bpp * width); - dst += dst_rowstride; - src += src_rowstride; - } -} - -static gboolean -_cogl_sub_texture_get_data (CoglTexture *tex, - CoglPixelFormat format, - unsigned int rowstride, - guint8 *data) -{ - CoglSubTexture *sub_tex = COGL_SUB_TEXTURE (tex); - unsigned int full_rowstride; - guint8 *full_data; - gboolean ret = TRUE; - int bpp; - int full_tex_width, full_tex_height; - - /* FIXME: This gets the full data from the full texture and then - copies a subregion of that. It would be better if there was a - texture_get_sub_data virtual and it can just munge the texture - coordinates */ - - full_tex_width = cogl_texture_get_width (sub_tex->full_texture); - full_tex_height = cogl_texture_get_height (sub_tex->full_texture); - - bpp = _cogl_get_format_bpp (format); - - full_rowstride = _cogl_get_format_bpp (format) * full_tex_width; - full_data = g_malloc (full_rowstride * full_tex_height); - - if (cogl_texture_get_data (sub_tex->full_texture, format, - full_rowstride, full_data)) - _cogl_sub_texture_copy_region (data, full_data, - 0, 0, - sub_tex->sub_x, - sub_tex->sub_y, - sub_tex->sub_width, - sub_tex->sub_height, - rowstride, - full_rowstride, - bpp); - else - ret = FALSE; - - g_free (full_data); - - return ret; -} - static CoglPixelFormat _cogl_sub_texture_get_format (CoglTexture *tex) { @@ -520,7 +452,7 @@ static const CoglTextureVtable cogl_sub_texture_vtable = { _cogl_sub_texture_set_region, - _cogl_sub_texture_get_data, + NULL, /* get_data */ _cogl_sub_texture_foreach_sub_texture_in_region, _cogl_sub_texture_get_max_waste, _cogl_sub_texture_is_sliced, diff --git a/clutter/cogl/cogl/cogl-texture-2d-sliced.c b/clutter/cogl/cogl/cogl-texture-2d-sliced.c index dbaa7460e..5e3f65ad6 100644 --- a/clutter/cogl/cogl/cogl-texture-2d-sliced.c +++ b/clutter/cogl/cogl/cogl-texture-2d-sliced.c @@ -1335,153 +1335,6 @@ _cogl_texture_2d_sliced_set_region (CoglTexture *tex, return TRUE; } -static gboolean -_cogl_texture_2d_sliced_download_from_gl ( - CoglTexture2DSliced *tex_2ds, - CoglBitmap *target_bmp, - GLuint target_gl_format, - GLuint target_gl_type) -{ - CoglSpan *x_span; - CoglSpan *y_span; - CoglHandle slice_tex; - int bpp; - int x, y; - CoglBitmap *slice_bmp; - CoglPixelFormat target_format = _cogl_bitmap_get_format (target_bmp); - - bpp = _cogl_get_format_bpp (target_format); - - /* Iterate vertical slices */ - for (y = 0; y < tex_2ds->slice_y_spans->len; ++y) - { - y_span = &g_array_index (tex_2ds->slice_y_spans, CoglSpan, y); - - /* Iterate horizontal slices */ - for (x = 0; x < tex_2ds->slice_x_spans->len; ++x) - { - /*if (x != 0 || y != 1) continue;*/ - x_span = &g_array_index (tex_2ds->slice_x_spans, CoglSpan, x); - - /* Pick the sliced texture */ - slice_tex = g_array_index (tex_2ds->slice_textures, CoglHandle, - y * tex_2ds->slice_x_spans->len + x); - - /* If there's any waste we need to copy manually - (no glGetTexSubImage) */ - - if (y_span->waste != 0 || x_span->waste != 0) - { - int rowstride = x_span->size * bpp; - guint8 *data = g_malloc (rowstride * y_span->size); - - /* Setup temp bitmap for slice subregion */ - slice_bmp = _cogl_bitmap_new_from_data (data, - target_format, - x_span->size, - y_span->size, - rowstride, - (CoglBitmapDestroyNotify) - g_free, - NULL); - - /* Setup gl alignment to 0,0 top-left corner */ - _cogl_texture_driver_prep_gl_for_pixels_download (rowstride, bpp); - - if (!cogl_texture_get_data (slice_tex, - target_format, - rowstride, - data)) - { - /* Free temp bitmap */ - cogl_object_unref (slice_bmp); - return FALSE; - } - - /* Copy portion of slice from temp to target bmp */ - _cogl_bitmap_copy_subregion (slice_bmp, - target_bmp, - 0, 0, - x_span->start, - y_span->start, - x_span->size - x_span->waste, - y_span->size - y_span->waste); - /* Free temp bitmap */ - cogl_object_unref (slice_bmp); - } - else - { - guint8 *data; - GLvoid *dst; - gboolean ret; - int rowstride = _cogl_bitmap_get_rowstride (target_bmp); - - data = _cogl_bitmap_map (target_bmp, - COGL_BUFFER_ACCESS_WRITE, - 0); - if (data == NULL) - return FALSE; - - dst = data + x_span->start * bpp + y_span->start * rowstride; - - _cogl_texture_driver_prep_gl_for_pixels_download (rowstride, bpp); - - /* Download slice image data */ - ret = cogl_texture_get_data (slice_tex, - target_format, - rowstride, - dst); - - _cogl_bitmap_unmap (target_bmp); - - if (!ret) - return ret; - } - } - } - - return TRUE; -} - -static gboolean -_cogl_texture_2d_sliced_get_data (CoglTexture *tex, - CoglPixelFormat format, - unsigned int rowstride, - guint8 *data) -{ - CoglTexture2DSliced *tex_2ds = COGL_TEXTURE_2D_SLICED (tex); - int bpp; - GLenum gl_format; - GLenum gl_type; - CoglBitmap *target_bmp; - gboolean ret; - - bpp = _cogl_get_format_bpp (format); - - _cogl_pixel_format_to_gl (format, - NULL, /* internal format */ - &gl_format, - &gl_type); - - target_bmp = _cogl_bitmap_new_from_data (data, - format, - tex_2ds->width, - tex_2ds->height, - rowstride, - NULL, /* destroy_fn */ - NULL /* destroy_fn_data */); - - /* Retrieve data from slices */ - ret = _cogl_texture_2d_sliced_download_from_gl (tex_2ds, - target_bmp, - gl_format, - gl_type); - - cogl_object_unref (target_bmp); - - return ret; -} - static CoglPixelFormat _cogl_texture_2d_sliced_get_format (CoglTexture *tex) { @@ -1528,7 +1381,7 @@ static const CoglTextureVtable cogl_texture_2d_sliced_vtable = { _cogl_texture_2d_sliced_set_region, - _cogl_texture_2d_sliced_get_data, + NULL, /* get_data */ _cogl_texture_2d_sliced_foreach_sub_texture_in_region, _cogl_texture_2d_sliced_get_max_waste, _cogl_texture_2d_sliced_is_sliced, diff --git a/clutter/cogl/cogl/cogl-texture.c b/clutter/cogl/cogl/cogl-texture.c index c4f40a1b9..15835aba7 100644 --- a/clutter/cogl/cogl/cogl-texture.c +++ b/clutter/cogl/cogl/cogl-texture.c @@ -4,6 +4,7 @@ * An object oriented GL/GLES Abstraction/Utility Layer * * Copyright (C) 2007,2008,2009 Intel Corporation. + * Copyright (C) 2010 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -37,6 +38,7 @@ #include "cogl-bitmap-private.h" #include "cogl-buffer-private.h" #include "cogl-pixel-array-private.h" +#include "cogl-private.h" #include "cogl-texture-private.h" #include "cogl-texture-driver.h" #include "cogl-texture-2d-sliced-private.h" @@ -1181,6 +1183,169 @@ _cogl_texture_draw_and_read (CoglHandle handle, return TRUE; } +static gboolean +get_texture_bits_via_offscreen (CoglHandle texture_handle, + int x, + int y, + int width, + int height, + guint8 *dst_bits, + unsigned int dst_rowstride, + CoglPixelFormat dst_format) +{ + CoglFramebuffer *framebuffer; + + _COGL_GET_CONTEXT (ctx, FALSE); + + if (!cogl_features_available (COGL_FEATURE_OFFSCREEN)) + return FALSE; + + framebuffer = _cogl_offscreen_new_to_texture_full + (texture_handle, + COGL_OFFSCREEN_DISABLE_DEPTH_AND_STENCIL, + 0); + + if (framebuffer == NULL) + return FALSE; + + cogl_push_framebuffer (framebuffer); + + _cogl_read_pixels_with_rowstride (x, y, width, height, + COGL_READ_PIXELS_COLOR_BUFFER, + dst_format, dst_bits, dst_rowstride); + + cogl_pop_framebuffer (); + + cogl_object_unref (framebuffer); + + return TRUE; +} + +static gboolean +get_texture_bits_via_copy (CoglHandle texture_handle, + int x, + int y, + int width, + int height, + guint8 *dst_bits, + unsigned int dst_rowstride, + CoglPixelFormat dst_format) +{ + CoglTexture *tex = COGL_TEXTURE (texture_handle); + unsigned int full_rowstride; + guint8 *full_bits; + gboolean ret = TRUE; + int bpp; + int full_tex_width, full_tex_height; + + full_tex_width = cogl_texture_get_width (texture_handle); + full_tex_height = cogl_texture_get_height (texture_handle); + + bpp = _cogl_get_format_bpp (dst_format); + + full_rowstride = bpp * full_tex_width; + full_bits = g_malloc (full_rowstride * full_tex_height); + + if (tex->vtable->get_data (tex, + dst_format, + full_rowstride, + full_bits)) + { + guint8 *dst = dst_bits; + guint8 *src = full_bits + x * bpp + y * full_rowstride; + int i; + + for (i = 0; i < height; i++) + { + memcpy (dst, src, bpp * width); + dst += dst_rowstride; + src += full_rowstride; + } + } + else + ret = FALSE; + + g_free (full_bits); + + return ret; +} + +typedef struct +{ + int orig_width; + int orig_height; + CoglBitmap *target_bmp; + guint8 *target_bits; + gboolean success; +} CoglTextureGetData; + +static void +texture_get_cb (CoglHandle texture_handle, + const float *subtexture_coords, + const float *virtual_coords, + void *user_data) +{ + CoglTexture *tex = COGL_TEXTURE (texture_handle); + CoglTextureGetData *tg_data = user_data; + CoglPixelFormat format = _cogl_bitmap_get_format (tg_data->target_bmp); + int bpp = _cogl_get_format_bpp (format); + unsigned int rowstride = _cogl_bitmap_get_rowstride (tg_data->target_bmp); + int subtexture_width = cogl_texture_get_width (texture_handle); + int subtexture_height = cogl_texture_get_height (texture_handle); + + int x_in_subtexture = (int) (0.5 + subtexture_width * subtexture_coords[0]); + int y_in_subtexture = (int) (0.5 + subtexture_height * subtexture_coords[1]); + int width = ((int) (0.5 + subtexture_width * subtexture_coords[2]) + - x_in_subtexture); + int height = ((int) (0.5 + subtexture_height * subtexture_coords[3]) + - y_in_subtexture); + int x_in_bitmap = (int) (0.5 + tg_data->orig_width * virtual_coords[0]); + int y_in_bitmap = (int) (0.5 + tg_data->orig_height * virtual_coords[1]); + + guint8 *dst_bits; + + if (!tg_data->success) + return; + + dst_bits = tg_data->target_bits + x_in_bitmap * bpp + y_in_bitmap * rowstride; + + /* If we can read everything as a single slice, then go ahead and do that + * to avoid allocating an FBO. We'll leave it up to the GL implementation to + * do glGetTexImage as efficiently as possible. (GLES doesn't have that, + * so we'll fall through) */ + if (x_in_subtexture == 0 && y_in_subtexture == 0 && + width == subtexture_width && height == subtexture_height) + { + if (tex->vtable->get_data (tex, + format, + rowstride, + dst_bits)) + return; + } + + /* Next best option is a FBO and glReadPixels */ + if (get_texture_bits_via_offscreen (texture_handle, + x_in_subtexture, y_in_subtexture, + width, height, + dst_bits, + rowstride, + format)) + return; + + /* Getting ugly: read the entire texture, copy out the part we want */ + if (get_texture_bits_via_copy (texture_handle, + x_in_subtexture, y_in_subtexture, + width, height, + dst_bits, + rowstride, + format)) + return; + + /* No luck, the caller will fall back to the draw-to-backbuffer and + * read implementation */ + tg_data->success = FALSE; +} + int cogl_texture_get_data (CoglHandle handle, CoglPixelFormat format, @@ -1196,13 +1361,14 @@ cogl_texture_get_data (CoglHandle handle, GLenum closest_gl_type; CoglBitmap *target_bmp; CoglBitmap *new_bmp; - gboolean success; guint8 *src; guint8 *dst; int y; int tex_width; int tex_height; + CoglTextureGetData tg_data; + if (!cogl_is_texture (handle)) return 0; @@ -1253,17 +1419,26 @@ cogl_texture_get_data (CoglHandle handle, NULL); } - if ((dst = _cogl_bitmap_map (target_bmp, COGL_BUFFER_ACCESS_WRITE, - COGL_BUFFER_MAP_HINT_DISCARD)) == NULL) + tg_data.orig_width = tex_width; + tg_data.orig_height = tex_height; + tg_data.target_bmp = target_bmp; + tg_data.target_bits = _cogl_bitmap_map (target_bmp, COGL_BUFFER_ACCESS_WRITE, + COGL_BUFFER_MAP_HINT_DISCARD); + if (tg_data.target_bits == NULL) { cogl_object_unref (target_bmp); return 0; } + tg_data.success = TRUE; - success = tex->vtable->get_data (tex, - closest_format, - rowstride, - dst); + /* Iterating through the subtextures allows piecing together + * the data for a sliced texture, and allows us to do the + * read-from-framebuffer logic here in a simple fashion rather than + * passing offsets down through the code. */ + _cogl_texture_foreach_sub_texture_in_region (handle, + 0, 0, 1, 1, + texture_get_cb, + &tg_data); _cogl_bitmap_unmap (target_bmp); @@ -1271,7 +1446,7 @@ cogl_texture_get_data (CoglHandle handle, * to read back the texture data; such as for GLES which doesn't * support glGetTexImage, so here we fallback to drawing the * texture and reading the pixels from the framebuffer. */ - if (!success) + if (!tg_data.success) _cogl_texture_draw_and_read (tex, target_bmp, closest_gl_format, closest_gl_type);