[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