[Mesa-dev] [PATCH 1/2] mesa: implement a display list / glBitmap texture atlas

Nicolai Hähnle nhaehnle at gmail.com
Mon Feb 15 16:32:25 UTC 2016


On 15.02.2016 11:31, Brian Paul wrote:
> 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?

Yes, that sounds good.

>
>>
>>> +
>>> +   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.

Thanks!

Nicolai

> I'll post a v2 in a bit.  Thanks for reviewing.
>
> -Brian
>


More information about the mesa-dev mailing list