Bug in OpenGl_tXfm.c, function: loadTexFont

The MS Windows specific funtion loadTexFont in OpenGL_tXfm.c has a bug. It only occurs, when there are more than one occ-window for an application open.

Test case:
- 2 occ-windows (in one application) with an importet cad-model
- moving the modell constantly around with AIS_InteractiveContext.SetLocation and update the display with V3d_View.Redraw

What happens:
- for a short time everything works fine, but then there is suddenly a great slow-down from one redraw to an other
- the taskmanager of windows displays, that there is _much_ cpu-time for the kernel is used from this point on
- after a few minutes, the hole application crashs

The reason:
- the function loadTexFont in OpenGl_tXfm.c is called _very_ often
- windows ressources are created, but never freed (font, hDC, hBmp) so windows runs out of ressources

The main problem is, that loadTexFont is called so often when using multiple occ-windows. If font, hDC and hBmp is freed at the end of the function, the hole animation slows down to a constant lower frame rate, since much time is used by the kernel for creating and freeing the ressources.

Analyzing the function shows, that there are only two different fonts loaded. So there is now need to call the loadTexFont thousands of times. As a quick fix i added a statical list, that stores every loaded font, so that when loadTexFont is called the second time for a font, only the list has to be used. With this everything works.

Below is the changed function:

/*
* list to handle already loaded fonts
*/
struct font_cache_list {
struct font_cache_list *next;
char *font_name;
GLint tex_id;
};

static GLint loadTexFont(char* fontName, TM_FONT_HANDLE* fontHandle)
{
static struct font_cache_list *font_list = NULL;
struct font_cache_list *element = NULL;

GLint tex_id = -1;
HFONT font;
HDC hMemDC, hDC = NULL;
char str[2] = " ";
int i, j, l, num, code;
BITMAPINFO bi;
HBITMAP hBmp, hOldBmp;
const int spacing = 2; /* spacing between characters in a string */
GLubyte fontBits [256 * 256 * 3]; /* font bitmap array: RGB */
GLubyte ifontBits[256 * 256 * 2]; /* texture array: luminance and alpha */
int charWidths[MAX_NB_CHARS];

if (!fontHandle) {
return tex_id;
}

/*
* check if the font is already in the list
*/
element = font_list;
while (element != NULL && strcmp(fontName, element->font_name) != 0) {
element = element->next;
}
if (element != NULL) {
return element->tex_id;
}

memset(fontHandle, 0, sizeof(TM_FONT_HANDLE));

fontHandle->curRC = wglGetCurrentContext();
fontHandle->texSize = 256;
fontHandle->scale = 1.f;

/* draw the font glyphs on the square bitmap */
font = CreateFont(-16, /* Height Of Font*/
0, /* Width Of Font*/
0, /* Angle Of Escapement*/
0, /* Orientation Angle*/
FW_BOLD, /* Font Weight*/
FALSE, /* Italic*/
FALSE, /* Underline*/
FALSE, /* Strikeout*/
ANSI_CHARSET, /* Character Set Identifier*/
OUT_TT_PRECIS, /* Output Precision*/
CLIP_DEFAULT_PRECIS, /* Clipping Precision*/
NONANTIALIASED_QUALITY, /* Output Quality*/
FF_MODERN|DEFAULT_PITCH, /* Family And Pitch*/
fontName); /* Font Name*/

hDC = wglGetCurrentDC();
hMemDC = CreateCompatibleDC(hDC);
hBmp = CreateCompatibleBitmap(hDC, fontHandle->texSize, fontHandle->texSize);
hOldBmp = (HBITMAP)SelectObject(hMemDC, hBmp);
SelectObject(hMemDC, font);
SetTextColor(hMemDC, RGB(255, 255, 255));
SetBkColor (hMemDC, RGB(0, 0, 0));
GetCharWidth32(hDC, 0, MAX_NB_CHARS - 1, charWidths);

for (i = 0; i for (j = 0; j code = i * 16 + j;
str[0] = (char)code;
l = strlen(str);
TextOut(hMemDC, j * CHAR_SIZE, i * CHAR_SIZE, str, l);
/* calculate advance ratio for each character */
fontHandle->charRatios[code] = (float) (charWidths[code] + spacing) / (float) CHAR_SIZE;
}
}

/* retrieve the font bitmap */
memset(&bi, 0, sizeof(BITMAPINFOHEADER));
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = 24;
bi.bmiHeader.biHeight = -fontHandle->texSize;
bi.bmiHeader.biWidth = fontHandle->texSize;
bi.bmiHeader.biCompression = BI_RGB;
SelectObject(hMemDC, hOldBmp);
num = GetDIBits(hMemDC, hBmp, 0, fontHandle->texSize, fontBits, &bi, DIB_RGB_COLORS);

/* prepare an array of alpha and luminance values */
for (i = 0; i texSize; i++) {
for(j = 0; j texSize; j++) {
ifontBits[(i * fontHandle->texSize + j) * 2] = fontBits[(i * fontHandle->texSize + j) * 3];
ifontBits[(i * fontHandle->texSize + j) * 2 + 1] = fontBits[(i * fontHandle->texSize + j) * 3];
}
}

/* create the font texture */
glGenTextures(1, &tex_id);
glBindTexture(GL_TEXTURE_2D, tex_id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_NEAREST);

glTexImage2D(GL_TEXTURE_2D,
0,
GL_INTENSITY,
fontHandle->texSize,
fontHandle->texSize,
0,
GL_LUMINANCE_ALPHA,
GL_UNSIGNED_BYTE,
ifontBits);

fontHandle->textureId = tex_id;

/*
* add the new font to the list
*/
element = (struct font_cache_list *) malloc(sizeof(struct font_cache_list));
element->next = NULL;
element->font_name = fontName;
element->tex_id = tex_id;
if (font_list == NULL) {
font_list = element;
} else {
struct font_cache_list *last_element = font_list;
while (last_element->next != NULL) {
last_element = last_element->next;
}
last_element->next = element;
}

/*
* free windows ressources
*/
DeleteObject(font);
DeleteObject(hBmp);
DeleteDC(hMemDC);

return tex_id;
}

Jan Brüninghaus's picture

This bug exists also in OCC 6.2.0.

Pawel's picture

Hi Jan,

I'm not sure if your patch solves the issue completely. I've applied it to OC6.2 (although I didn't notice increased memory consumption while switching between views in my app) and the axes names (X, Y, Z) of the trihedron were not displayed anymore (just a yelow box instead).

Pawel

Jan Brüninghaus's picture

Hmm, I have a trihedron here too and it is displayed correctly. *confused*

Have you more things with letters in the occ-window?

Francois Lauzon's picture

We did apply the patch, and we have the same problem, all text objects are displayed as box instead.

Jan Brüninghaus's picture

Solves the following the problem? I forgot the fontHandle was given by reference for the function. :-/

As i wrote in the first post, problems only occur when using multiple occ-windows and _a lot_ of switching between them (for example simulating movement with different views on it). If speed is not important the three Delete* statements at the end of the function frees all ressources. But since they _slow_ down the hole thing if called very rapidly i added the rest.

Index: OpenGl_tXfm.c
===================================================================
--- OpenGl_tXfm.c (revision 17)
+++ OpenGl_tXfm.c (working copy)
@@ -718,6 +718,7 @@
*/
struct font_cache_list {
struct font_cache_list *next;
+ TM_FONT_HANDLE *fontHandle;
char *font_name;
GLint tex_id;
};
@@ -750,6 +751,11 @@
element = element->next;
}
if (element != NULL) {
+ /*
+ * can a fontHandle be deleted?
+ * need to check out this...
+ */
+ fontHandle = element->fontHandle;
return element->tex_id;
}

@@ -844,6 +850,7 @@
element = (struct font_cache_list *) malloc(sizeof(struct font_cache_list));
element->next = NULL;
element->font_name = fontName;
+ element->fontHandle = fontHandle;
element->tex_id = tex_id;
if (font_list == NULL) {
font_list = element;

Pawel's picture

Hi Jan,

i applied the other patch and recompiled the whole library (on w2k, vs2005-sp1) but the yellow boxes are still there.

I can also (besides my app) observe the effect with the AISBasic sample when I open multiple documents.

Pawel

tlacombe's picture

Hi all,

As explained in the thread 10458, I'm facing Heap problems with OCC 6.x when using more than one view. Following Pete's (Dolbey ) advice, I tried the hereby patch on a test plateform with WinXP SP2 and VS2005 SP1. Of course, I compiled all OCC libraries in debug mode and used the AISBasic and AISSelect applications. Here are the conclusion :
- Debugger does not trigger anymore due to heap problem (what a relief!!!!)
- When closing both applications, VS detects memory leaks
- In AISBasic, as soon as there are two views, thrihedron display goes wrong showing the famous yellow boxes...

By the way, I'd like to ask something to all of you, who are experienced c/c++ progammers, about this patch. I can see a malloc call when a new struct is created, but I don't see any free like call in order to release the memory taken by this font cache list... Is there something I'm missing or could this explain the memory leak detected by vs (in this case next question would be where insert the free call in those c functions...)?

Regards

Thierry

Jan Brüninghaus's picture

Yes, there is no free. I use the struct for caching the TexFonts, since creating and releasing of windows-ressourcen slows down the possible frame-rate one can achieve for visualisating an movement-simulation (one of the main tasks i have to do with occ). Since it is unlikely to use very many different fonts in an occ-window, this should not use much memory and when the programm exits the kernel frees the memory anyway.

If you have only a problem with the not freed windows-ressources use the original function and add only the following directly before the return:

DeleteObject(font);
DeleteObject(hBmp);
DeleteDC(hMemDC);

I will have a look, if i can reproduce the yellow boxes on my system. Perhaps that leeds my to the real fault.

Pawel's picture

Hi Jan,

freeing the ressources seems to works correctly - no yellow boxes!

Thanks for the patch
Pawel

P Dolbey's picture

OCCPATCH marker, Pete

Switrol's picture

Jan,

your patch help me very much - I surely state I would not be able to figure out this by myself - I was thinking about memory leaking :(. My symptoms were at rebuilt OCC 6.2.0 && VS2005:

- two occ views (among others)
- updating one required to be updated the other one (simulation like)
- after updating process started ~100MB was eaten, then _slow_slow_down_...

If I deleted all ais object (empty occ screen + no trihedron), there is no problem, but even one drawn vertex draw the application crazy. The best is that your code solved the problem :)

Thank you very much!

Best regards,

Peter

suresh's picture

As you said, deletion of window handles are needed.
DeleteObject(font);
DeleteObject(hBmp);
DeleteDC(hMemDC);

loadTexFont is called many times and it should cache the texture instead of creating new textures each time. It is designed like that only but, there was a bug in Opencascade code. Please see the fix below

Fix:
In the functions WNTFindFont & WNTUseTexMappedFont in OpenGl_tXfm.c,
Replace the line
if (fe->tfh[i].curRC == curRC) with
if (fe->tfh[i].curRC == 0 || fe->tfh[i].curRC == curRC)

Paul Jimenez's picture

I have an application with multiple views (4), with the proposed patch applied, and if I run it through the debugger it crashes with an access violation error. Error is as follows:

First-chance exception at 0x0eb76fca in wxWidgetsOCC.exe: 0xC0000005: Access violation writing location 0x23170728.
Unhandled exception at 0x0eb76fca in wxWidgetsOCC.exe: 0xC0000005: Access violation writing location 0x23170728.

A backtrace points to that function, the crashing line being:

/* create the font texture */
glGenTextures(1, &tex_id);
glBindTexture(GL_TEXTURE_2D, tex_id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // <-- THIS ONE

The backtrace is as follows:

iglicd32.dll!0eb76fca()
[Frames below may be incorrect and/or missing, no symbols loaded for iglicd32.dll]
iglicd32.dll!0eb7725b()
TKOpenGl.dll!loadTexFont(char * fontName=0x066edf48, _tm_font_handle * fontHandle=0x066eea48) Line 808 C
TKOpenGl.dll!WNTUseTexMappedFont(int flag=1) Line 932 + 0x20 bytes C
TKOpenGl.dll!TsmSetAttri(int n=1, ...) Line 1089 + 0x11 bytes C
TKOpenGl.dll!TextureMappedFontDisplay(TSM_ELEM_DATA_UNION data={...}, int n=0, CMN_KEY * * k=0x0012ccc0) Line 57 + 0xb bytes C
TKOpenGl.dll!TsmSendMessage(TelType el=TelTextureMappedFont, TMsgType msg=DisplayTraverse, TSM_ELEM_DATA_UNION data={...}, int n=0, ...) Line 134 + 0x12 bytes C
TKOpenGl.dll!ExecuteStructureDisplay(TSM_ELEM_DATA_UNION data={...}, int n=1, CMN_KEY * * k=0x0012cf48) Line 565 + 0x18 bytes C
TKOpenGl.dll!TsmSendMessage(TelType el=TelExecuteStructure, TMsgType msg=DisplayTraverse, TSM_ELEM_DATA_UNION data={...}, int n=1, ...) Line 134 + 0x12 bytes C
TKOpenGl.dll!ExecuteStructureDisplay(TSM_ELEM_DATA_UNION data={...}, int n=1, CMN_KEY * * k=0x0012d1d4) Line 423 + 0x1c bytes C
TKOpenGl.dll!TsmSendMessage(TelType el=TelExecuteStructure, TMsgType msg=DisplayTraverse, TSM_ELEM_DATA_UNION data={...}, int n=1, ...) Line 134 + 0x12 bytes C
TKOpenGl.dll!TsmDisplayStructure(int stid=1, int wsid=1) Line 339 + 0x13 bytes C
TKOpenGl.dll!redraw_all_structs(int wsid=1, int vstid=1) Line 567 + 0xd bytes C
TKOpenGl.dll!call_func_redraw_all_structs_proc(int wsid=1) Line 1322 + 0xd bytes C
TKOpenGl.dll!call_togl_redraw(CALL_DEF_VIEW * aview=0x0012d514, CALL_DEF_LAYER * anunderlayer=0x0012d4c4, CALL_DEF_LAYER * anoverlayer=0x0012d4ec) Line 115 + 0xb bytes C
TKOpenGl.dll!OpenGl_GraphicDriver::Redraw(const CALL_DEF_VIEW & ACView={...}, const CALL_DEF_LAYER & ACUnderLayer={...}, const CALL_DEF_LAYER & ACOverLayer={...}, const int x=0, const int y=0, const int width=0, const int height=0) Line 287 + 0x1a bytes C++
TKV3d.dll!Visual3d_View::Redraw(const Handle_Visual3d_Layer & AnUnderLayer={...}, const Handle_Visual3d_Layer & AnOverLayer={...}) Line 1787 C++
TKV3d.dll!Visual3d_View::Redraw() Line 1749 C++
TKV3d.dll!V3d_View::MustBeResized() Line 595 C++

"iglicd32.dll" seems to be Intel's implementation of OpenGL for a 915G chipset.

As you can see, it's triggered by a call to MustBeResized(). It doesn't happen when I run the application without a debugger attached to it.

Any ideas on what to look for?

suresh's picture

Hi Paul Jimenez,

I also faced some problem like these even after applying the patch of Jan Brüninghaus.
As he mentioned, his solution is a quick fix and a temporary solution for caching the fonts.

Opencascade already has the code to cache the fonts but there is a small bug in the
code to check the conditions. After some debugging, i found the bug and fixed it.

If you fix this bug, there is no need to apply the patch of Jan Brüninghaus.

Fix:
In the functions WNTFindFont() & WNTUseTexMappedFont() in OpenGl_tXfm.c,
Replace the line
if (fe->tfh[i].curRC == curRC) with
if (fe->tfh[i].curRC == 0 || fe->tfh[i].curRC == curRC)

Another thing is, add the following lines in the function loadTexFont() (in OpenGl_tXfm.c) before the return statement.

DeleteObject(font);
DeleteObject(hBmp);
DeleteDC(hMemDC);