[Mesa-dev] [PATCH 1/2] mesa: implement a display list / glBitmap texture atlas
Brian Paul
brianp at vmware.com
Mon Feb 15 16:31:30 UTC 2016
On 02/15/2016 08:45 AM, Nicolai Hähnle wrote:
>
>
> On 12.02.2016 20:07, Brian Paul wrote:
>> This improves the performance of applications which use glXUseXFont()
>> or wglUseFontBitmaps() and glCallLists() to draw bitmap text.
>>
>> Basically, we collect all the glBitmap images from the display lists
>> and put them into a texture atlas. To render the bitmaps for a
>> glCallLists() command, we render a set of textured quads where each
>> quad is textured with one bitmap image. Actually, the rendering part
>> has to be done by the Mesa driver or Mesa/gallium state tracker.
>>
>> Note that GLUT demos that use glutBitmapCharacter() don't benefit
>> from this.
>> ---
>> src/mesa/main/dd.h | 8 ++
>> src/mesa/main/dlist.c | 383
>> +++++++++++++++++++++++++++++++++++++++++++++++++
>> src/mesa/main/dlist.h | 38 +++++
>> src/mesa/main/mtypes.h | 1 +
>> src/mesa/main/shared.c | 15 ++
>> 5 files changed, 445 insertions(+)
>>
>> diff --git a/src/mesa/main/dd.h b/src/mesa/main/dd.h
>> index 19ef304..5d1370c 100644
>> --- a/src/mesa/main/dd.h
>> +++ b/src/mesa/main/dd.h
>> @@ -35,6 +35,7 @@
>>
>> #include "glheader.h"
>>
>> +struct gl_bitmap_atlas;
>> struct gl_buffer_object;
>> struct gl_context;
>> struct gl_display_list;
>> @@ -154,6 +155,13 @@ struct dd_function_table {
>> GLint x, GLint y, GLsizei width, GLsizei height,
>> const struct gl_pixelstore_attrib *unpack,
>> const GLubyte *bitmap );
>> +
>> + /**
>> + * Called by display list code for optimized glCallLists/glBitmap
>> rendering
>> + */
>> + void (*DrawAtlasBitmaps)(struct gl_context *ctx,
>> + const struct gl_bitmap_atlas *atlas,
>> + GLuint count, const GLubyte *ids);
>> /*@}*/
>>
>>
>> diff --git a/src/mesa/main/dlist.c b/src/mesa/main/dlist.c
>> index 0e25efb..1927068 100644
>> --- a/src/mesa/main/dlist.c
>> +++ b/src/mesa/main/dlist.c
>> @@ -72,6 +72,9 @@
>> #include "vbo/vbo.h"
>>
>>
>> +#define USE_BITMAP_ATLAS 1
>> +
>> +
>>
>> /**
>> * Other parts of Mesa (such as the VBO module) can plug into the
>> display
>> @@ -606,6 +609,259 @@ void mesa_print_display_list(GLuint list);
>>
>>
>> /**
>> + * Does the given display list only contain a single glBitmap call?
>> + */
>> +static bool
>> +is_bitmap_list(const struct gl_display_list *dlist)
>> +{
>> + const Node *n = dlist->Head;
>> + if (n[0].opcode == OPCODE_BITMAP) {
>> + n += InstSize[OPCODE_BITMAP];
>> + if (n[0].opcode == OPCODE_END_OF_LIST)
>> + return true;
>> + }
>> + return false;
>> +}
>> +
>> +
>> +/**
>> + * Is the given display list an empty list?
>> + */
>> +static bool
>> +is_empty_list(const struct gl_display_list *dlist)
>> +{
>> + const Node *n = dlist->Head;
>> + return n[0].opcode == OPCODE_END_OF_LIST;
>> +}
>> +
>> +
>> +/**
>> + * Delete/free a gl_bitmap_atlas. Called during context tear-down.
>> + */
>> +void
>> +_mesa_delete_bitmap_atlas(struct gl_context *ctx, struct
>> gl_bitmap_atlas *atlas)
>> +{
>> + if (atlas->texObj) {
>> + ctx->Driver.DeleteTexture(ctx, atlas->texObj);
>> + }
>> + free(atlas->glyphs);
>> +}
>> +
>> +
>> +/**
>> + * Lookup a gl_bitmap_atlas by listBase ID.
>> + */
>> +static struct gl_bitmap_atlas *
>> +lookup_bitmap_atlas(struct gl_context *ctx, GLuint listBase)
>> +{
>> + struct gl_bitmap_atlas *atlas;
>> +
>> + assert(listBase > 0);
>> + atlas = _mesa_HashLookup(ctx->Shared->BitmapAtlas, listBase);
>> + return atlas;
>> +}
>> +
>> +
>> +/**
>> + * Create new bitmap atlas and insert into hash table.
>> + */
>> +static struct gl_bitmap_atlas *
>> +alloc_bitmap_atlas(struct gl_context *ctx, GLuint listBase)
>> +{
>> + struct gl_bitmap_atlas *atlas;
>> +
>> + assert(listBase > 0);
>> + assert(_mesa_HashLookup(ctx->Shared->BitmapAtlas, listBase) == NULL);
>> +
>> + atlas = calloc(1, sizeof(*atlas));
>> + if (atlas) {
>> + _mesa_HashInsert(ctx->Shared->BitmapAtlas, listBase, atlas);
>> + }
>> +
>> + return atlas;
>> +}
>> +
>> +
>> +/**
>> + * Try to build a bitmap atlas. This involves examining a sequence of
>> + * display lists which contain glBitmap commands and putting the bitmap
>> + * images into a texture map (the atlas).
>> + * If we succeed, gl_bitmap_atlas::complete will be set to true.
>> + * If we fail, gl_bitmap_atlas::incomplete will be set to true.
>> + */
>> +static void
>> +build_bitmap_atlas(struct gl_context *ctx, struct gl_bitmap_atlas
>> *atlas,
>> + GLuint listBase)
>> +{
>> + unsigned i, row_height = 0, xpos = 0, ypos = 0;
>> + GLubyte *map;
>> + GLint map_stride;
>> +
>> + assert(atlas);
>> + assert(!atlas->complete);
>> + assert(atlas->numBitmaps > 0);
>> +
>> + /* We use a rectangle texture (non-normalized coords) for the
>> atlas */
>> + assert(ctx->Extensions.NV_texture_rectangle);
>> +
>> + atlas->texWidth = 1024;
>> + atlas->texHeight = 0; /* determined below */
>
> I don't see explicit checks for either NV_texture_rectangle or max
> texture size >= 1024 anywhere.
>
> I see two alternative ways of handling this: either add an explicit
> check in render_bitmap_atlas, or expect drivers to only install
> DrawAtlasBitmaps when those preconditions are satisfied (in which case
> this should probably be documented in dd.h, and the st/mesa patch
> adjusted).
I'll add a comment in dd.h that DrawAtlasBitmaps() requires texture
rectangles of at least width 1024. There's an assertion in
build_bitmap_atlas() that checks for NV_texture_rectangle. I can add a
min size check too.
As for the gallium state tracker, all gallium drivers support
NV_texture_rectangle (see extensions->NV_texture_rectangle = GL_TRUE in
st_init_extensions()). And the smallest max size is 2048 (i915 and
vc4). If a driver came along that didn't meet that the assertion in
dlist.c would fail and we could adjust it.
Sound OK?
>
>> +
>> + atlas->glyphs = malloc(atlas->numBitmaps * sizeof(atlas->glyphs[0]));
>> + if (!atlas->glyphs) {
>> + /* give up */
>> + atlas->incomplete = true;
>> + return;
>> + }
>> +
>> + /* Loop over the display lists. They should all contain a single
>> glBitmap
>> + * call. If not, bail out. Also, compute the position and sizes
>> of each
>> + * bitmap in the atlas to determine the texture atlas size.
>> + */
>> + for (i = 0; i < atlas->numBitmaps; i++) {
>> + const struct gl_display_list *list = _mesa_lookup_list(ctx,
>> listBase + i);
>> + const Node *n;
>> + struct gl_bitmap_glyph *g = &atlas->glyphs[i];
>> + unsigned bitmap_width, bitmap_height;
>> + float bitmap_xmove, bitmap_ymove, bitmap_xorig, bitmap_yorig;
>> +
>> + if (!list || is_empty_list(list)) {
>> + /* stop here */
>> + atlas->numBitmaps = i;
>> + break;
>> + }
>> +
>> + if (!is_bitmap_list(list)) {
>> + /* This list does not contain exactly one glBitmap command.
>> Give up. */
>> + atlas->incomplete = true;
>> + return;
>> + }
>> +
>> + /* get bitmap info from the display list command */
>> + n = list->Head;
>> + assert(n[0].opcode == OPCODE_BITMAP);
>> + bitmap_width = n[1].i;
>> + bitmap_height = n[2].i;
>> + bitmap_xorig = n[3].f;
>> + bitmap_yorig = n[4].f;
>> + bitmap_xmove = n[5].f;
>> + bitmap_ymove = n[6].f;
>> +
>> + if (xpos + bitmap_width > atlas->texWidth) {
>> + /* advance to the next row of the texture */
>> + xpos = 0;
>> + ypos += row_height;
>> + row_height = 0;
>> + }
>> +
>> + /* save the bitmap's position in the atlas */
>> + g->x = xpos;
>> + g->y = ypos;
>> + g->w = bitmap_width;
>> + g->h = bitmap_height;
>> + g->xorig = bitmap_xorig;
>> + g->yorig = bitmap_yorig;
>> + g->xmove = bitmap_xmove;
>> + g->ymove = bitmap_ymove;
>> +
>> + xpos += bitmap_width;
>> +
>> + /* keep track of tallest bitmap in the row */
>> + row_height = MAX2(row_height, bitmap_height);
>> + }
>> +
>> + /* Now we know the texture height */
>> + atlas->texHeight = ypos + row_height;
>> +
>> + if (atlas->texHeight == 0) {
>> + /* no glyphs found, give up */
>> + goto fail;
>> + }
>> + else if (atlas->texHeight > ctx->Const.MaxTextureRectSize) {
>> + /* too large, give up */
>> + goto fail;
>> + }
>> +
>> + /* Create atlas texture (texture ID is irrelevant) */
>> + atlas->texObj = ctx->Driver.NewTextureObject(ctx, 999,
>> GL_TEXTURE_RECTANGLE);
>> + if (!atlas->texObj) {
>> + goto out_of_memory;
>> + }
>> +
>> + atlas->texObj->Sampler.MinFilter = GL_NEAREST;
>> + atlas->texObj->Sampler.MagFilter = GL_NEAREST;
>> + atlas->texObj->MaxLevel = 0;
>> + atlas->texObj->Immutable = GL_TRUE;
>> +
>> + atlas->texImage = _mesa_get_tex_image(ctx, atlas->texObj,
>> + GL_TEXTURE_RECTANGLE, 0);
>> + if (!atlas->texImage) {
>> + goto out_of_memory;
>> + }
>> +
>> + _mesa_init_teximage_fields(ctx, atlas->texImage,
>> + atlas->texWidth, atlas->texHeight, 1, 0,
>> + GL_ALPHA, MESA_FORMAT_A_UNORM8);
>> +
>> + /* alloc image storage */
>> + if (!ctx->Driver.AllocTextureImageBuffer(ctx, atlas->texImage)) {
>> + goto out_of_memory;
>> + }
>> +
>> + /* map teximage, load with bitmap glyphs */
>> + ctx->Driver.MapTextureImage(ctx, atlas->texImage, 0,
>> + 0, 0, atlas->texWidth, atlas->texHeight,
>> + GL_MAP_WRITE_BIT, &map, &map_stride);
>> + if (!map) {
>> + goto out_of_memory;
>> + }
>> +
>> + /* Background/clear pixels are 0xff, foreground/set pixels are 0x0 */
>> + memset(map, 0xff, map_stride * atlas->texHeight);
>> +
>> + for (i = 0; i < atlas->numBitmaps; i++) {
>> + const struct gl_display_list *list = _mesa_lookup_list(ctx,
>> listBase + i);
>> + const Node *n = list->Head;
>> +
>> + assert(n[0].opcode == OPCODE_BITMAP ||
>> + n[0].opcode == OPCODE_END_OF_LIST);
>> +
>> + if (n[0].opcode == OPCODE_BITMAP) {
>> + unsigned bitmap_width = n[1].i;
>> + unsigned bitmap_height = n[2].i;
>> + unsigned xpos = atlas->glyphs[i].x;
>> + unsigned ypos = atlas->glyphs[i].y;
>> + const void *bitmap_image = get_pointer(&n[7]);
>> +
>> + assert(atlas->glyphs[i].w == bitmap_width);
>> + assert(atlas->glyphs[i].h == bitmap_height);
>> +
>> + /* put the bitmap image into the texture image */
>> + _mesa_expand_bitmap(bitmap_width, bitmap_height,
>> + &ctx->DefaultPacking, bitmap_image,
>> + map + map_stride * ypos + xpos, /* dest
>> addr */
>> + map_stride, 0x0);
>> + }
>> + }
>> +
>> + ctx->Driver.UnmapTextureImage(ctx, atlas->texImage, 0);
>> +
>> + atlas->complete = true;
>> +
>> + return;
>> +
>> +out_of_memory:
>> + _mesa_error(ctx, GL_OUT_OF_MEMORY, "Display list bitmap atlas");
>> +fail:
>> + if (atlas->texObj) {
>> + ctx->Driver.DeleteTexture(ctx, atlas->texObj);
>> + }
>> + free(atlas->glyphs);
>> + atlas->incomplete = true;
>> +}
>> +
>> +
>> +/**
>> * Allocate a gl_display_list object with an initial block of storage.
>> * \param count how many display list nodes/tokens to allocate
>> */
>> @@ -856,6 +1112,30 @@ _mesa_delete_list(struct gl_context *ctx, struct
>> gl_display_list *dlist)
>>
>>
>> /**
>> + * Called by _mesa_HashWalk() to check if a display list which is being
>> + * deleted belongs to a bitmap texture atlas.
>> + */
>> +static void
>> +check_atlas_for_deleted_list(GLuint atlas_id, void *data, void
>> *userData)
>> +{
>> + struct gl_bitmap_atlas *atlas = (struct gl_bitmap_atlas *) data;
>> + GLuint list_id = *((GLuint *) userData); /* the list being
>> deleted */
>> +
>> + /* See if the list_id falls in the range contained in this texture
>> atlas */
>> + if (atlas->complete &&
>> + list_id >= atlas_id &&
>> + list_id < atlas_id + atlas->numBitmaps) {
>> + /* Mark the atlas as incomplete so it doesn't get used. But don't
>> + * delete it yet since we don't want to try to recreate it in
>> the next
>> + * glCallLists.
>> + */
>> + atlas->complete = false;
>> + atlas->incomplete = true;
>> + }
>> +}
>> +
>> +
>> +/**
>> * Destroy a display list and remove from hash table.
>> * \param list - display list number
>> */
>> @@ -871,6 +1151,16 @@ destroy_list(struct gl_context *ctx, GLuint list)
>> if (!dlist)
>> return;
>>
>> + if (is_bitmap_list(dlist)) {
>> + /* If we're destroying a simple glBitmap display list, there's a
>> + * chance that we're destroying a bitmap image that's in a texture
>> + * atlas. Examine all atlases to see if that's the case. There's
>> + * usually few (if any) atlases so this isn't expensive.
>> + */
>> + _mesa_HashWalk(ctx->Shared->BitmapAtlas,
>> + check_atlas_for_deleted_list, &list);
>> + }
>> +
>> _mesa_delete_list(ctx, dlist);
>> _mesa_HashRemove(ctx->Shared->DisplayList, list);
>> }
>> @@ -8898,6 +9188,18 @@ _mesa_DeleteLists(GLuint list, GLsizei range)
>> for (i = list; i < list + range; i++) {
>> destroy_list(ctx, i);
>> }
>> +
>> + if (range > 1) {
>> + /* We may be deleting a set of bitmap lists. See if there's a
>> + * bitmap atlas to free.
>> + */
>> + struct gl_bitmap_atlas *atlas = lookup_bitmap_atlas(ctx, list);
>> + if (atlas) {
>> + _mesa_delete_bitmap_atlas(ctx, atlas);
>> + _mesa_HashRemove(ctx->Shared->BitmapAtlas, list);
>> + }
>> + }
>> +
>
> This is a really minor point, but I believe this could be moved above
> the for-loop to reduce the number of calls to
> check_atlas_for_deleted_list when the lists are destroyed.
Sure, I can do that. I'll extend my piglit test to hit this too. I had
hacked the piglit test for this during development but it's worth
cleaning up and keeping.
I'll post a v2 in a bit. Thanks for reviewing.
-Brian
More information about the mesa-dev
mailing list