Game Development Community

Text Corruption when using GuiMLTextCtrl on iPhone

by Damir Slogar · in iTorque 2D · 04/27/2009 (7:22 am) · 18 replies

This happens consistently when using setText();
Text is corrupted around where there is a line break (redraws last text that was set)

Edit: This also happens with GuiTextCtrl - I think its a bug in the iPhone implementation of dglDrawTextN in dgl.cc

Here's the profile used:

if(!isObject(HelpTextProfile)) new GuiControlProfile (HelpTextProfile)
{
   border = 0;
   fontType = "Lucida Console";
   fontSize = "14";
   fontColor = "255 255 255";
   fontColorHL = "255 255 255";
   fillColorHL = "0 0 0 0";
   justify = "center";
};

and the control definition:

new GuiMLTextCtrl(helpText) {
      canSaveDynamicFields = "0";
      isContainer = "0";
      Profile = "HelpTextProfile";
      HorizSizing = "right";
      VertSizing = "bottom";
      Position = "36 305";
      Extent = "250 90";
      MinExtent = "250 90";
      canSave = "1";
      Visible = "1";
      hovertime = "1000";
      text = "Default";
      maxLength = "1024";
   };

#1
04/27/2009 (10:25 am)
Could you post your script and a screenshot of what happens?
#2
04/27/2009 (11:47 am)
This code gets called by a GuiBitmapButtonCtrl
using Command = "award_click(7);";, for example

function award_click(%index)
{
   playSE( MenuClickSound, 1.0 );
   awardTitle.setText($awardTitles[%index]);
   awardText1.setText($awardDesc1[%index]);    
   awardText2.setText($awardDesc2[%index]);         
}

Here's a screen shot of a typical before and after situation:

Before:
www.popatronic.com/images/IMG00055-20090427-1428.jpg

After:
www.popatronic.com/images/IMG00056-20090427-1429.jpg

Notice the "ing" stays behind the "25";

#3
04/27/2009 (3:17 pm)
That is strange, first geuss would be that your UFT files are complete, try building with the full alphabet and number set.

But it is weird that it looks like the old value is staying, try setting an intermediate text. i.e. a bunch of --- or even just blank.
#4
04/27/2009 (6:36 pm)
A bit unorthodox of a solution but it's quick and requires the least amount of work (unless we're talking about large amount of text here)
#5
04/28/2009 (5:33 am)
I've tried that and it doesn't work;
I've tried a wide range of things and am stumped as to what it could be.
BTW are there any tools that let you work with uft files in a better way?
#6
04/28/2009 (7:49 am)
Seems like my first assumption was correct:
It was a bug in dglDrawTextN in dgl.cc
Here is the fix

change:
if(currentPt)
   {
	   //PUAP-Mat when using a triangle strip, the last point of one char will draw a line to the first point in the next char
	   //unless you draw each char separately
	   for( int i = 0; i < n; i++ ) {
		   glEnableClientState ( GL_VERTEX_ARRAY );
		   glVertexPointer     ( 2, GL_FLOAT, sizeof(TextVertex), &(vert[4*i].p) );
	   
		   glEnableClientState ( GL_COLOR_ARRAY );
		   glColorPointer      ( 4, GL_UNSIGNED_BYTE, sizeof(TextVertex), &(vert[4*i].c) );
	   
		   glEnableClientState ( GL_TEXTURE_COORD_ARRAY );
		   glTexCoordPointer   ( 2, GL_FLOAT, sizeof(TextVertex), &(vert[4*i].t) );
	   
	   
		   glBindTexture(GL_TEXTURE_2D, lastTexture->texGLName);
		   glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
	   }

   }

to:
if(currentPt)
   {
	   //PUAP-Mat when using a triangle strip, the last point of one char will draw a line to the first point in the next char
	   //unless you draw each char separately

           glEnableClientState ( GL_VERTEX_ARRAY );
           glEnableClientState ( GL_COLOR_ARRAY );
           glEnableClientState ( GL_TEXTURE_COORD_ARRAY );
           glBindTexture(GL_TEXTURE_2D, lastTexture->texGLName);

	   for( int i = 0; i < (currentPt>>2); i++ ) {
		   
		   glVertexPointer     ( 2, GL_FLOAT, sizeof(TextVertex), &(vert[4*i].p) );	   
		   glColorPointer      ( 4, GL_UNSIGNED_BYTE, sizeof(TextVertex), &(vert[4*i].c) );
		   glTexCoordPointer   ( 2, GL_FLOAT, sizeof (TextVertex), &(vert[4*i].t) );	  
		   glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
	   }

   }


There's another spot above this where glDrawArrays is used and i suspect that it might need to be changed to match this; although when i set a breakpoint on it, it never stopped there.
#7
05/08/2009 (1:26 am)
I was having a different type of corruption with 'setText' - instead of text overlapping, entire objects would become solid white, leaving me with random solid white buttons and text blocks on random screens. Your code fixed that as well - great catch!
#8
05/08/2009 (3:20 am)
Nice one Damir!

I was looking closer into that function and it is currently horribly unoptimal. You can not only make the change that you did there, but you can also move out the setting of the vertex/color/texcoord pointer to be once prior to the loop, instead of on each iteration of the loop (set to index[0]), then use the offset param in glDrawArraw, so:
if(currentPt)
   {
	   glEnableClientState ( GL_VERTEX_ARRAY );
	   glVertexPointer     ( 2, GL_FLOAT, sizeof(TextVertex), &(vert[0].p) );
	   
	   glEnableClientState ( GL_COLOR_ARRAY );
	   glColorPointer      ( 4, GL_UNSIGNED_BYTE, sizeof(TextVertex), &(vert[0].c) );
	   
	   glEnableClientState ( GL_TEXTURE_COORD_ARRAY );
	   glTexCoordPointer   ( 2, GL_FLOAT, sizeof(TextVertex), &(vert[0].t) );
	   glBindTexture(GL_TEXTURE_2D, lastTexture->texGLName);

	   //PUAP-Mat when using a triangle strip, the last point of one char will draw a line to the first point in the next char
	   //unless you draw each char separately
	   for( int i = 0; i < n; i++ ) {
		   glDrawArrays(GL_TRIANGLE_STRIP, i*4, 4);
	   }
   }

I've also seen another big problem with the function in that mid-way through the character loop, there is a check to see if the texture has changed, and if so, it flushes the current buffer:

TextureObject *newObj = font->getTextureHandle(ci.bitmapIndex);
      if(newObj != lastTexture)
      {
         if(currentPt)
         {
            glBindTexture(GL_TEXTURE_2D, lastTexture->texGLName);
			glDrawArrays(GL_TRIANGLE_STRIP, 0, 4*n);// PUAP -Mat untested
            currentPt = 0;
         }
         lastTexture = newObj;
      }


The problem here is that this will cause 4*n characters to render from whatever the previous vertex array offset was set in the bottom of the loop render section (ie. the above code), would could cause some SERIOUSLY scary results...

So, what should actually happen is that just like the standard TGE version of this code, the vertex arrays should ONLY BE SET ONCE AT THE START OF THE FUNCTION like so:

glDisable(GL_LIGHTING);
	
	glEnable(GL_TEXTURE_2D);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);
	
	glEnableClientState ( GL_VERTEX_ARRAY );
	glVertexPointer     ( 2, GL_FLOAT, sizeof(TextVertex), &(vert[0].p) );
	
	glEnableClientState ( GL_COLOR_ARRAY );
	glColorPointer      ( 4, GL_UNSIGNED_BYTE, sizeof(TextVertex), &(vert[0].c) );
	
	glEnableClientState ( GL_TEXTURE_COORD_ARRAY );
	glTexCoordPointer   ( 2, GL_FLOAT, sizeof(TextVertex), &(vert[0].t) );


And the 2 rendering sections (the portion of code after the 2 if(currentPt) checks), should then look identical as the following:

glBindTexture(GL_TEXTURE_2D, lastTexture->texGLName);
     for (int i=0; i<currentPt; i+=4) {
	glDrawArrays( GL_TRIANGLE_STRIP, i, 4 );
     }

This code also changes to make sure that only "currentPt" number of characters are rendered, instead of the full "n" number of characters, which will render the correct number of verts (as we increment by 4 now instead of 1).


The scariness of this function makes me think that anyone with text-intensive games would do well to run the Torque Profiler and see if you are spending any kind of time in this function...
#9
05/08/2009 (7:41 am)
Good stuff Luke.
This code should work for PC and Mac as well so I'm not quite sure why there is an #ifdef TORQUE_OS_IPHONE around it... the code should have been just changed for iTGB in the interest of improving the workflow (and getting similar results when developing on PC/Mac then seeing the game run on an iPhone.)
#10
05/08/2009 (9:04 am)
This goes into my snippet collection. I have a growing list of things I suspect they won't have time to add/fix before 1.3.
#11
05/08/2009 (11:22 am)
The reason that there is special code for iTGB is that standard TGB uses GL QUAD to render the characters with a single glDrawArray call (which is much more optimal than looping through and rendering each character on its own), and OpenGL ES doesn't support that primitive type.
#12
05/08/2009 (11:52 am)
Ya I know... but it doesnt really matter if my dev environment build (PC) is a bit slower as long as the final results (iPhone) are emulated better.
#13
05/09/2009 (2:16 am)
Is this still a bug in 1.2?
#14
05/10/2009 (5:28 am)
I haven't installed 1.2 yet but the svn of iTGB as of Friday didn't have the changes in it. You can always just check out the source code yourself and see :)
#15
05/15/2009 (9:08 pm)
yes this is still a bug in 1.2
#16
05/30/2009 (1:44 pm)
Logged and fixed for v1.2.1
#17
05/31/2009 (3:28 pm)
So, just to make sure I've implemented this correctly:

At the top of U32 dglDrawTextN(...), this is the new start of the gl environment enable
glDisable(GL_LIGHTING);

   glEnable(GL_TEXTURE_2D);
   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   glEnable(GL_BLEND);

   glEnableClientState ( GL_VERTEX_ARRAY );
   glVertexPointer     ( 2, GL_FLOAT, sizeof(TextVertex), &(vert[0].p) );
   
   glEnableClientState ( GL_COLOR_ARRAY );
   glColorPointer      ( 4, GL_UNSIGNED_BYTE, sizeof(TextVertex), &(vert[0].c) );
   
   glEnableClientState ( GL_TEXTURE_COORD_ARRAY );
   glTexCoordPointer   ( 2, GL_FLOAT, sizeof(TextVertex), &(vert[0].t) );

Then at line 470, this is the new rendering code:
TextureObject *newObj = font->getTextureHandle(ci.bitmapIndex);
if(newObj != lastTexture)
{
   if(currentPt)
   {
      glBindTexture(GL_TEXTURE_2D, lastTexture->texGLName);
      for (int i=0; i<currentPt; i+=4)
      {
	glDrawArrays( GL_TRIANGLE_STRIP, i, 4 );
      }
   }
   lastTexture = newObj;
}

New code at line 517:
if(currentPt)
   {
	   //PUAP-Mat when using a triangle strip, the last point of one char will draw a line to the first point in the next char
	   //unless you draw each char separately
	   glBindTexture(GL_TEXTURE_2D, lastTexture->texGLName);
	   for (int i=0; i<currentPt; i+=4) 
	   {
		   glDrawArrays( GL_TRIANGLE_STRIP, i, 4 );
	   }

   }

If anyone can spot holes in it while I'm testing, let me know. Luke, any thoughts?
#18
07/15/2009 (4:30 pm)
Mich,
I've updated the code exactly as you described and made sure I've got all the .utf in my font dir and I still don't see the font. am i missing anything? it works on the Mac.
Eyal.