Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » [A5] TTF Fonts not caching correctly?

This thread is locked; no one can reply to it. rss feed Print
 1   2 
[A5] TTF Fonts not caching correctly?
jmasterx
Member #11,410
October 2009

Thanks Elias, that worked perfectly!

@Trent
I just tried my little example in latest SVN and it still happens. But at least now if you have any patches for this that need testing I'm ready!

Trent Gamblin
Member #261
April 2000
avatar

I tried to recreate what's happening in the ttf addon but without using the ttf addon. This example works, though I can confirm that yours doesn't. Right now I'm not sure why, perhaps this example may shed a little light though so I'll post it here.

#SelectExpand
1#include <allegro5/allegro.h> 2#include <allegro5/allegro_direct3d.h> 3 4ALLEGRO_BITMAP *pages[50] = { NULL, }; 5 6void check_page(int index) 7{ 8 if (index < 3) index = 3; 9 int w = al_get_bitmap_width(pages[index]); 10 int h = al_get_bitmap_height(pages[index]); 11 for (int y = 0; y+index < h; y += index) { 12 for (int x = 0; x+index < w; x += index) { 13 ALLEGRO_LOCKED_REGION *lr; 14 lr = al_lock_bitmap_region( 15 pages[index], 16 x, y, index, index, 17 ALLEGRO_PIXEL_FORMAT_ARGB_8888, 18 ALLEGRO_LOCK_WRITEONLY 19 ); 20 for (int j = 0; j < index; j++) { 21 if (j == 0 || j == index-1) continue; 22 for (int i = 0; i < index; i++) { 23 if (i == 0 || i == index-1) continue; 24 uint8_t *p = 25 (uint8_t *)lr->data; 26 p += j * lr->pitch; 27 p += i * 4; 28 *((uint32_t *)p) = 0xFF00FF00; 29 } 30 } 31 al_unlock_bitmap(pages[index]); 32 } 33 } 34} 35 36int main(int argc, char **argv) 37{ 38 al_init(); 39 40 al_set_new_display_flags(ALLEGRO_RESIZABLE | ALLEGRO_DIRECT3D); 41 42 ALLEGRO_DISPLAY *display = al_create_display(500, 500); 43 ALLEGRO_EVENT_QUEUE *queue = al_create_event_queue(); 44 al_register_event_source(queue, al_get_display_event_source(display)); 45 46 for (int i = 0; i < 50; i++) { 47 int w = 100 + i * 10; 48 int h = 100 + i * 10; 49 pages[i] = al_create_bitmap(w, h); 50 al_set_target_bitmap(pages[i]); 51 al_clear_to_color(al_map_rgb(0, 0, 0)); 52 } 53 54 al_set_target_backbuffer(display); 55 56 while (true) { 57 while (!al_event_queue_is_empty(queue)) { 58 ALLEGRO_EVENT e; 59 al_get_next_event(queue, &e); 60 if (e.type == ALLEGRO_EVENT_DISPLAY_RESIZE) { 61 al_acknowledge_resize(display); 62 } 63 else if (e.type == ALLEGRO_EVENT_DISPLAY_CLOSE) { 64 return 0; 65 } 66 } 67 68 float window_width = al_get_display_width(display); 69 window_width -= 100; 70 window_width /= 400; 71 window_width *= 50; 72 if (window_width < 0) window_width = 0; 73 if (window_width > 49) window_width = 49; 74 check_page(window_width); 75 76 al_draw_bitmap(pages[(int)window_width], 0, 0, 0); 77 78 al_flip_display(); 79 }; 80}

jmasterx
Member #11,410
October 2009

Here's what I found and wrote in the other thread. Maybe it helps:

It really has to do with resizing because I had it display the alphabet at varying sizes for a few minutes. The second I resize, the letter Z is no longer rendered.

The problem seems to happen when d3d_acknowledge_resize is called.

I suspect the problem is somewhere in _al_d3d_refresh_texture_memory

I tried having it rest a few seconds but it still caused the problem.

I'm not familiar with D3D so I cannot be much help in debugging this area.

Edit:
I think I've somewhat narrowed it down to d3d_sync_bitmap_texture(), or at least there's something there.

When I remove the first call to _al_convert... , the letter Z stops disappearing. However the letters that I get the width of still exhibit the issue.

Edit 2/10/12:
THIS solution seems to somewhat the trick, when the device is lost the bitmaps were not being prepared for reset:
Change:

   if (hr == D3DERR_DEVICELOST) {
      d3d_display->device_lost = true;
     _al_d3d_prepare_bitmaps_for_reset(d3d_display);
      return;
   }
   else {
      _al_d3d_prepare_bitmaps_for_reset(d3d_display);
   }

New patch attached.

It solves the case where the Z was vanishing but the get text width problem still prevails.

Edit 2/13/12:
I noticed that the results it gives when it messes up are fairly predictable. I tried changing the order that the fonts are loaded in and now it looks like this:
{"name":"605587","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/a\/e\/ae4e7551b10f00cba71ed1fbfde447bc.png","w":634,"h":245,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/a\/e\/ae4e7551b10f00cba71ed1fbfde447bc"}605587

As can be seen here, the problem seems to be that when it either makes the backup or restores from backup it grabs from the wrong font. Hopefully this helps :)

Trent Gamblin
Member #261
April 2000
avatar

Still not sure what it is. The font addon uses sub-bitmaps and those don't change when backing up/restoring the sheets. Don't know if that's relevant but it just occurred to me. Doesn't seem relevant since the sub bitmap parent pointer doesn't change and the rectangle stays the same. Maybe digging a little deeper with some debugging drawing from within the ttf addon, maybe drawing each sheet that you can cycle through or something, would help.

jmasterx
Member #11,410
October 2009

I did some digging around in the ttf.c and changed:

 glyph_data = alloc_glyph_region(font_data, w, h, false, glyph, lock_more);

to

 glyph_data = alloc_glyph_region(font_data, w, h, true, glyph, lock_more);

And the problem completely disappears, the returned text width is correct and it uses video bitmaps.

Does this indicate anything to you?

I think it causes every glyph to be on its own page, which wastes a ton of memory, but the problem vanishes.

Trent Gamblin
Member #261
April 2000
avatar

Hm, that piques my interest. Obviously that's not a solution (using true will create a whole new page for each character), but it made me think... maybe Peter Wang who optimized the addon could tell us, does the ttf addon hold a lock on glyphs between drawing calls? Because maybe the page(s) are locked when D3D tries to backup/restore them. Not sure how to work around that since some of the data (relock, possibly other things) is held in the font and the D3D driver doesn't have access. But if it does hold a lock, releasing the lock before backup/restore (in a proper way as to respect all of the ttf internal variables), may solve the problem.

jmasterx
Member #11,410
October 2009

I have found something even better now.

The whole notion of locking got me realizing that the renderGlyph function calls lock_more on the cache function with false.

I created a new function called _renderGlyph which is basically renderGlyph WITHOUT the call to al_draw_bitmap. Calling it like this returns the correct text width and the problem vanishes.

#SelectExpand
1static int ttf_text_length(ALLEGRO_FONT const *f, const ALLEGRO_USTR *text) 2{ 3 ALLEGRO_TTF_FONT_DATA *data = f->data; 4 FT_Face face = data->face; 5 int pos = 0; 6 int prev_ft_index = -1; 7 int x = 0; 8 int32_t ch; 9 bool hold; 10 11 hold = al_is_bitmap_drawing_held(); 12 13 while ((ch = al_ustr_get_next(text, &pos)) >= 0) { 14 int ft_index = FT_Get_Char_Index(face, ch); 15 ALLEGRO_TTF_GLYPH_DATA *glyph = get_glyph(data, ft_index); 16 17 x += _render_glyph(f,al_map_rgb(255,255,255),prev_ft_index,ft_index,0,0); 18 19 prev_ft_index = ft_index; 20 } 21 22 al_hold_bitmap_drawing(hold); 23 24 return x; 25} 26 27static int _render_glyph(ALLEGRO_FONT const *f, 28 ALLEGRO_COLOR color, int prev_ft_index, int ft_index, 29 float xpos, float ypos) 30{ 31 ALLEGRO_TTF_FONT_DATA *data = f->data; 32 FT_Face face = data->face; 33 ALLEGRO_TTF_GLYPH_DATA *glyph = get_glyph(data, ft_index); 34 int advance = 0; 35 36 /* We don't try to cache all glyphs in a pre-pass before drawing them. 37 * While that would indeed save us making separate texture uploads, it 38 * implies two passes over a string even in the common case when all glyphs 39 * are already cached. This turns out to have an measureable impact on 40 * performance. 41 */ 42 cache_glyph(data, face, ft_index, glyph, false); 43 unlock_current_page(data); 44 45 advance += get_kerning(data, face, prev_ft_index, ft_index); 46 advance += glyph->advance; 47 48 return advance; 49}

I can confirm that with this method, the problem in my game completely is gone!

Trent Gamblin
Member #261
April 2000
avatar

What if you add at line 571 this:

Giving you:

#SelectExpand
1static int ttf_text_length(ALLEGRO_FONT const *f, const ALLEGRO_USTR *text) 2{ 3 ALLEGRO_TTF_FONT_DATA *data = f->data; 4 FT_Face face = data->face; 5 int pos = 0; 6 int prev_ft_index = -1; 7 int x = 0; 8 int32_t ch; 9 bool hold; 10 11 hold = al_is_bitmap_drawing_held(); 12 al_hold_bitmap_drawing(false); 13 14 while ((ch = al_ustr_get_next(text, &pos)) >= 0) { 15 int ft_index = FT_Get_Char_Index(face, ch); 16 ALLEGRO_TTF_GLYPH_DATA *glyph = get_glyph(data, ft_index); 17 18 cache_glyph(data, face, ft_index, glyph, true); 19 20 x += get_kerning(data, face, prev_ft_index, ft_index); 21 x += glyph->advance; 22 23 prev_ft_index = ft_index; 24 } 25 26 al_hold_bitmap_drawing(hold); 27 28 return x; 29}

Added note: I think with your method if you used al_get_text_width before drawing you might run into problems.

jmasterx
Member #11,410
October 2009

Nope that does not work.

I tried my method with getting the width at various places (as I do in my game) and there does not seem to be a problem with it. What sort of problems do you foresee?

What does the magic is

static void unlock_current_page(ALLEGRO_TTF_FONT_DATA *data)
{
   if (data->page_lr) {
      ALLEGRO_BITMAP **back = _al_vector_ref_back(&data->page_bitmaps);
      ASSERT(al_is_bitmap_locked(*back));
      al_unlock_bitmap(*back);
      data->page_lr = NULL;
   }
}

Maybe we should check if a bitmap is locked before making a backup and relock it if it was locked?

Currently the d3d code does:

if (dest->locked) {
      return;
   }

Which does not seem right.

Another option could be to lock the font page back up after getting the text width; That might work.

Trent Gamblin
Member #261
April 2000
avatar

jmasterx said:

Maybe we should check if a bitmap is locked before making a backup and relock it if it was locked?

That sounds reasonable. So check if it's locked, if it is store the lock info (x, y, width, height, format, other?), unlock it, back it up, then re-lock it. This assumes the ttf addon doesn't hold any pointers within the lock->data region. If that works then it's a good solution.

jmasterx
Member #11,410
October 2009

Locking the ttf as readwrite rather than write only, seems to have a different effect. I'm going to look into trying to understand why the other technique works.

Edit:
After trying several different locking / unlocking, it really feels like my solution is the best solution. It is simply 1 line like this:

#SelectExpand
1static int ttf_text_length(ALLEGRO_FONT const *f, const ALLEGRO_USTR *text) 2{ 3 ALLEGRO_TTF_FONT_DATA *data = f->data; 4 FT_Face face = data->face; 5 int pos = 0; 6 int prev_ft_index = -1; 7 int x = 0; 8 int32_t ch; 9 bool hold; 10 11 hold = al_is_bitmap_drawing_held(); 12 13 while ((ch = al_ustr_get_next(text, &pos)) >= 0) { 14 int ft_index = FT_Get_Char_Index(face, ch); 15 ALLEGRO_TTF_GLYPH_DATA *glyph = get_glyph(data, ft_index); 16 17 cache_glyph(data, face, ft_index, glyph, true); 18 unlock_current_page(data); //Only change 19 x += get_kerning(data, face, prev_ft_index, ft_index); 20 x += glyph->advance; 21 22 prev_ft_index = ft_index; 23 } 24 25 al_hold_bitmap_drawing(hold); 26 27 return x; 28}

literally the only change here is the call to:

unlock_current_page(data);

I have tested in several situations with and without rendering anything and I do not see a performance impact nor are results inconsistent.

If you could tell me what sorts of problems I should be looking for or problems doing this could cause, please let me know. I do not see a problem because when rendering a glyph, this is called. And Allegro's state is always 'ready to render' since a new scene begins when another has ended.

I tested with GL too and results are nice and consistent here too. If you're worried it might affect other platforms somehow you could ifdef the call for D3D only, but I do not think that is necessary.

I also tried 2 calls to al_get_text_width in a row before any rendering even occurs.

Note that it should be done to ttf_get_text_dimensions as well.

Trent Gamblin
Member #261
April 2000
avatar

Please post a patch to the [AD] mailing list. I'm not comfortable applying this without Peter first having a look at it. He did all of the optimization in the ttf addon. He'd probably know if this change is ok. I don't know if he's following this thread.

jmasterx
Member #11,410
October 2009

I'll just start a thread in the Allegro Development section.
http://www.allegro.cc/forums/thread/609548/947404#target

 1   2 


Go to: