Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Characters missing after al_get_text_width()

This thread is locked; no one can reply to it. rss feed Print
Characters missing after al_get_text_width()
Yamakuzure
Member #15,863
January 2015

Hello everybody:

In the following thread an issue was discussed about TTF fonts losing glyphs after window resizes:
https://www.allegro.cc/forums/thread/611833/0

This bug was fixed in allegro-5.0.9.

Unfortunately fonts loses glyphs on linux using allegro-5.0.11 after using al_get_text_width(). (My applications window is not resizable)

My current workaround is to load each font twice and then use al_get_text_width() only with the second one, while doing al_text_draw*() with the first one.

SiegeLord
Member #7,827
October 2006
avatar

Could you make a minimal test case for this that illustrates the issue? Also, what OS is this?

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

Yamakuzure
Member #15,863
January 2015

I'll create a test case next week. Beforehand some information:

System Hardware: 4th Gen i7 Quad Core 3.2 GHz, 32GiB RAM, Intel HD + nvidia Hybrid
System OS: Gentoo Linux
Compiler: gcc-4.9.2
Fonts tested: DejaVuSansMono-Bold.ttf, FreeMonoBold.ttf and FreeSansBold.ttf.

I first suspected that I have an error that accidentally writes over an array bound garbling the font or something like that. So I tried address sanitizer and leak sanitizer plus strong stack protection. Neither came up with anything.

To check whether the sanitizers didn't miss anything crucial I switched the fonts, so the one used for the width calculation is declared first. Currently the one for drawing is declared first. If anything corrupted any stack the display should be garbled again, but it isn't.

Then I added font reloading before each drawing block, and see, the display is fine when reloaded. But I can not reload the font for every frame. It was just a test.

The only possibility left is, that al_get_text_width() somehow corrupts the font memory, but only if the font is loaded into video memory.
If I put a al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP) just before the font loading, everything is fine and the FPS rate drops from a fixed 60 FPS to ~10 FPS.

See the mini screenshots for examples.

However, I just tried to run the game using primusrun/optirun (bumblebee, a system to use any program with the nvidia graphics card instead of intel HD) an noticed something strange.

I am describing it here, because it might be be caused by me missing some important step, that is somehow responsible for the missing glyphs as well. One might never know.

The screenshots are from the "Shopping screen" of the game, which can be seen before each round. Before entering here al_get_text_width() is nowhere used, so the missing glyphs show up here first.
Whenever the display is updated, it switches between the old screen (main menu of the game with loading / starting options) and the shopping screen. It is like al_flip_display() is flipping the displays backbuffer with an old state, too.
So I clear the backbuffer to a green color, draw the shop on it and it is shown. The next frame has the old screen with only the shop stuff drawn to it.
How can this be? Is there something special to be done when using nvidia cards?

Thanks for your help and patience!

SiegeLord
Member #7,827
October 2006
avatar

No ideas just yet, but I'm planning on investigating this later this weekend. This wouldn't be the first time something broke in the TTF addon, so perhaps it's a real issue.

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

Edgar Reynaldo
Member #8,592
May 2007
avatar

SiegeLord
Member #7,827
October 2006
avatar

Could you try compiling with Allegro 5.1? There have been quite a few bug fixes in the ttf addon that haven't been backported.

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

Yamakuzure
Member #15,863
January 2015

Sorry for the late reply. I was off sick last week with a bad cold, couldn't even look straight.

I do not think, and I am sorry that my previous post didn't make this very clear, that al_get_text_width() is the actual culprit. I think it is just the messenger.

This morning I wrote a simple test program that used al_get_text_width() a lot (code follows below for reference) and all is well there. No glyphs disappear.

What puzzles me is, that none of the gcc-4.9.2 sanitizers (leak, address or thread) nor the stack protector can find any issues. Nowhere in the code anything is written out of bounds.

So my best bet is, that something in the drawing of the mentioned "shop" is actually responsible for that, or the background thread that creates the next level wreaks havoc.

I already found out that allegro 5 can not handle different threads drawing something at the same time using primitives, even when the drawing takes place into different targets. So I impose a general lock and ensure the correct target is set.
But is it enough? I'll investigate this further.

Here is the code I wrote this morning for testing. It is very rudimentary and I left most checks out as it is just a small test. As can be suspected, it works without any glyphs going missing:

#SelectExpand
1#ifndef VERSION 2# define VERSION "0.1.5" 3#endif // VERSION 4 5#include <cstdint> 6#include <iostream> 7 8#include <allegro5/allegro.h> 9#include <allegro5/allegro_color.h> 10#include <allegro5/allegro_image.h> 11#include <allegro5/allegro_font.h> 12#include <allegro5/allegro_ttf.h> 13 14using std::cerr; 15using std::cout; 16using std::endl; 17 18int32_t main (int32_t, char**) 19{ 20 int32_t result = EXIT_SUCCESS; 21 22 if (!al_init()) { 23 cerr << "Unable to start Allegro 5." << endl; 24 return EXIT_FAILURE; 25 } 26 27 al_set_new_display_flags(ALLEGRO_WINDOWED); 28 al_set_new_display_option(ALLEGRO_COLOR_SIZE, 32, ALLEGRO_SUGGEST); 29 al_set_new_display_option(ALLEGRO_CAN_DRAW_INTO_BITMAP, 1, ALLEGRO_SUGGEST); 30 al_set_new_display_option(ALLEGRO_UPDATE_DISPLAY_REGION, 1, ALLEGRO_SUGGEST); 31 al_set_new_display_option(ALLEGRO_RENDER_METHOD, 1, ALLEGRO_SUGGEST); 32 33 ALLEGRO_DISPLAY* display = al_create_display(900, 100); 34 35 // Debug print extra options: 36 cerr << "Can draw into bitmap : " 37 << ( (1 == al_get_display_option(display, ALLEGRO_CAN_DRAW_INTO_BITMAP)) 38 ? "Yes" : "No") << endl; 39 cerr << "Can update region wise: " 40 << ( (1 == al_get_display_option(display, ALLEGRO_UPDATE_DISPLAY_REGION)) 41 ? "Yes" : "No") << endl; 42 43 al_set_target_backbuffer(display); 44 al_set_window_title(display, "AL5 Font Test " VERSION); 45 46 // Assume success for all of the following, this is just a test program. 47 al_install_keyboard(); 48 al_init_image_addon(); 49 al_init_font_addon(); 50 al_init_ttf_addon(); 51 52 // Load the font to use: 53 ALLEGRO_FONT* font = al_load_ttf_font("/usr/share/fonts/freefont/FreeSansBold.ttf", 54 18, 0); 55 56 // Colours for background and the test text: 57 ALLEGRO_COLOR DARK_GREEN = al_map_rgb (0x00, 0x50, 0x00); 58 ALLEGRO_COLOR BLACK = al_map_rgb (0x00, 0x00, 0x00); 59 ALLEGRO_COLOR BLACK_SHADE = al_map_rgba(0x00, 0x00, 0x00, 0x40); 60 61 // static test string: 62 static char test_string[] = "abcdefghijklmnopqrstuvwxyz" 63 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 64 "0123456789,.-;:_+*~#'!\"ยง$%&/()="; 65 uint32_t test_width = al_get_text_width(font, test_string); 66 67 // Set and clear drawing area 68 ALLEGRO_BITMAP* canvas = al_get_backbuffer(display); 69 al_set_target_bitmap(canvas); 70 al_clear_to_color(DARK_GREEN); 71 al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA); 72 al_draw_text(font, BLACK_SHADE, 450 - (test_width / 2) + 2, 22, 0, test_string); 73 al_draw_text(font, BLACK, 450 - (test_width / 2), 20, 0, test_string); 74 al_flip_display(); 75 76 // Setup event system 77 ALLEGRO_EVENT event; 78 ALLEGRO_EVENT_QUEUE* queue = al_create_event_queue(); 79 al_register_event_source(queue, al_get_keyboard_event_source()); 80 al_register_event_source(queue, al_get_display_event_source(display)); 81 82 // The main loop: 83 bool done = false; 84 bool need_draw = false; 85 char text[4096] = { 0 }; 86 char* txt_p = text; 87 int32_t txt_i = 0; 88 89 while (!done) { 90 al_wait_for_event(queue, &event); 91 92 if (ALLEGRO_EVENT_DISPLAY_CLOSE == event.type) 93 done = true; 94 else if (ALLEGRO_EVENT_KEY_CHAR == event.type) { 95 char chr = event.keyboard.unichar & 0xff; 96 if (chr) { 97 if (ALLEGRO_KEY_BACKSPACE == event.keyboard.keycode) { 98 if (txt_i > 0) 99 text[--txt_i] = 0x0; 100 } else { 101 text[txt_i++] = chr; 102 103 // Overwrite last, do not scroll. 104 if (txt_i >= 4095) 105 txt_i = 4095; 106 107 // Limit length to the display: 108 while (al_get_text_width(font, txt_p) > 880) 109 ++txt_p; 110 } 111 112 need_draw = true; 113 } // End of having a character 114 } // End of having a key press 115 116 if (need_draw) { 117 al_clear_to_color(DARK_GREEN); 118 119 al_draw_text(font, BLACK_SHADE, 450 - (test_width / 2) + 2, 22, 0, test_string); 120 al_draw_text(font, BLACK, 450 - (test_width / 2), 20, 0, test_string); 121 122 al_draw_text(font, BLACK_SHADE, 12, 42, 0, txt_p); 123 al_draw_text(font, BLACK, 10, 40, 0, txt_p); 124 125 al_flip_display(); 126 need_draw = false; 127 } 128 129 } // End of !done 130 131 132 // clean up: 133 al_unregister_event_source(queue, al_get_display_event_source(display)); 134 al_unregister_event_source(queue, al_get_keyboard_event_source()); 135 al_flush_event_queue(queue); 136 al_destroy_event_queue(queue); 137 138 if (font) 139 al_destroy_font(font); 140 141 return result; 142}

Thomas Fjellstrom
Member #476
June 2000
avatar

I already found out that allegro 5 can not handle different threads drawing something at the same time using primitives, even when the drawing takes place into different targets.

If they share the same Context that is true. If you create a separate context for each thread (in a5, a context is tied to a display). OpenGL just doesn't support letting multiple threads access the same context. D3D may be similar. I think this comes under "undefined" behavior.

There are ways to overcome that, but it may not gain you much. Without using multiple contexts and "context sharing"[1], you can only render from one thread at a time, and you need to manually switch which thread owns the context, which means you gain nothing in terms of performance, and it may actually decrease performance considerably as it can cause a large number of state changes.

References

  1. This probably also won't help as drivers tend to be single threaded, which just causes the commands from each thread to pile into a single driver thread to be dispatched one after the other, which still will cause a ton of state switching and not speed anything up. Some OSs and drivers support multi threaded dispatch, but I don't know of any that enable it by default

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Yamakuzure
Member #15,863
January 2015

Thanks, Thomas.

I have only one display. The background thread is there so while the player(s) do their shopping, the next level can be prepared. Currently the outline is this for all drawing operations while these two threads work in parallel: (Within the game round itself only the main thread does any drawing operations to the display)

  1. lock the display globally using a spinlock or mutex

  2. set allegro target to the bitmap to draw on

  3. Lock the bitmap (region) if pixel putting and/or reading is done (quite a lot)

  4. Do the drawing (or reading if I need pixel colors)

  5. Unlock the bitmap

  6. set allegro target to nullptr to release the display context

  7. unlock the global lock.

In the meantime I have deferred the background thread to after the shopping is done. The glyphs keep disappearing, so the background thread is not the issue. At least I think I get nearer to the bottom of this.

The next change was to remove all calls to al_get_text_width() but one I really need. The others could be substituted by simply aligning the text to 'right'.

I have to add that this is not a new game. I am currently porting an old allegro 4 game to allegro 5. So basically it is currently plastered with #ifdef/#else/#endif macros to allow me to compile for both worlds on demand until I can be sure that the porting is done correctly.

It looks like a mess, but at least, if I screw up, I have the old working code right there. ;-)

If you dare to take a look, the file in question is this one: https://sourceforge.net/p/atanksaiupgrade/atanks_aiu/ci/master/tree/src/shop.cpp

I have not yet (fully) ported and tested the game loop, so the shop is the last progress. Once this works out, the rest will be done.

*sigh* I guess I have to single step it again...

Thomas Fjellstrom
Member #476
June 2000
avatar

What I tried to say is you just can't access the display contents or bitmap contents in any way from more than one thread at the same time. You will actually need to null the target in the owning thread before setting the target in another thread. Only one thread can own the context at a time, so you have to release it in the first thread before trying to capture it in another.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Elias
Member #358
May 2000

al_get_text_width() also counts a a rendering function, so you can only call it from the thread in which you also draw the text. If you call it from another thread, either there will be no OpenGL context and so it will mess up the internal texture used for storing glyphs, or it will take OpenGL away from the drawing thread and so again mess things up.

So if the only problem was that you called al_get_text_width() from another thread, then yes, that is expected to cause problems.

(There is one way to make it work, which is if you use thread locking to make sure that only one thread at a time uses graphics functions (including al_get_text_width). And it may require Allegro 5.1 instead of 5.0.)

--
"Either help out or stop whining" - Evert

Yamakuzure
Member #15,863
January 2015

Thank you very much for your help!

@Thomas: This is exactly what I am doing. Do a global lock, do what has to be done, null the target and then unlock. I will single step it all tonight to see whether I overlooked any place where the locking would have to be done but isn't.

@Elias: That is a good hint. Because right now I know of at least one place, and it is the only one remaining, where I use al_get_text_width() to fill a local constant, without getting the context first by (re-)setting the target.

Again, thank you very much for your input!

Edith can tell: Found it! *yay* !

In a different file, far far away (okay, not that far), the description text of the shop items gets printed line by line. And guess what, al_get_text_width() is used to determine when a line is full. And at that place, the current target bitmap is NULL. So that screwed it all up!
So after enclosing that check with the mentioned global lock/unlock and the target setting/releasing, all is well now.

Thanks again! You all saved my day!

( Completely unrelated : How can I mark this thread as SOLVED or FIXED?)

Elias
Member #358
May 2000

The truth is, ideally we'd fix this in Allegro anyway - al_get_text_width could just use a separate glyph cache in a memory bitmap. Maybe even with a lock to be thread save and then any number of threads could query text dimensions on the same font. In fact, would be worth investigating if glyphs even have to be rendered to get their size. Even if so, all we'd need to keep around is the dimensions and not the actual bitmaps.

--
"Either help out or stop whining" - Evert

Thomas Fjellstrom
Member #476
June 2000
avatar

Elias said:

In fact, would be worth investigating if glyphs even have to be rendered to get their size. Even if so, all we'd need to keep around is the dimensions and not the actual bitmaps.

Ideally I think that's probably the best route. I'd be surprised if it didn't have a measure function that skips the rendering step (that also didn't have to do all of the calculations required of a full rendering step that makes it pretty slow...)

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

beoran
Member #12,636
March 2011

IIRC the ttf addon the glyphs to bitmaps in the glyph cache just to measure them, because of kerning and other effects that make it hard to get the correct dimensions without rendering the glyph. But maybe there is a way to measure the withd of the glyphs without rendering them in libfreetype2 that I don't know of...

Go to: