![]() |
|
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: 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
![]() |
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 |
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 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. 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. Thanks for your help and patience! |
SiegeLord
Member #7,827
October 2006
![]() |
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 |
Edgar Reynaldo
Major Reynaldo
May 2007
![]() |
IIRC, calling al_get_text_width causes the character cache to be drawn - it is some form of lazy creation. The character creation code could be the problem, but I haven't looked at this at all. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
SiegeLord
Member #7,827
October 2006
![]() |
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 |
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. 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: 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
![]() |
Yamakuzure said: 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
-- |
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)
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
![]() |
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. -- |
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.) -- |
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! 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. -- |
Thomas Fjellstrom
Member #476
June 2000
![]() |
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...) -- |
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... |
|