[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