| Trolltech Home | Qt-interest Home | Recent Threads | All Threads | Author | Date | |
| All threads index page 1 | |
Hi everyone, I'm a Qt newbie, recently joined KDE development (coding a GL-based molecule renderer for kalzium for KDE4). I found (sorry :) I mean no offense) that QGLWidget::renderText() was not doing was I needed, so I wrote a replacement for it, in the form of a small class, TextRenderer. It renders characters on the fly, and uses a QHash<QChar, ...> to cache the renderer characters. As it might perhaps be useful to other people than me, here it is. I attach the source code to this message, and just in case you can also download it here: http://www.math.jussieu.fr/~jacob/textrenderer.h http://www.math.jussieu.fr/~jacob/textrenderer.cpp The .h contains a HOWTO in a comment (just before the declaration of TextRenderer). If I'm not mistaken, the advantages over QGLWidget::renderText are: - it's encoding-safe, can render any QString - it's font-safe, can use any QFont - it's memory-efficient : only renders the characters that are actually needed, on-the-fly, and uses 8bpp alpha maps instead of 32bpp ARGB. - it can be much faster (depending on the implementation) because it's texture-based, instead of using glDrawPixels. - also, being texture-based allows to easily implement nice effects (for instance, 3D text, rotated text...) - it is 100% Qt-based (by contrast, on X11, renderText calls directly FreeType functions) Please be kind with me -- I started with Qt development only very recently, so there might be a big mistake in my code. I hope this will be useful to some people. Cheers Benoit
/***************************************************************************
/***************************************************************************
copyright : (C) 2006 by Benoit Jacob
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include <QGLWidget>
#include <QPainter>
#include <QImage>
#include <QChar>
#include <QHash>
/** This is a helper class for TextRenderer, and should probably never be
* used directly. See TextRenderer.
*
* The CharRenderer class represents a character stored as OpenGL rendering
* data : a texture object and a display list mapping it on a quad and then
* translating to the right of it.
*
* See the m_charTable member of TextRenderer for an example of use of
* this class.
*/
class CharRenderer
{
protected:
/**
* The OpenGL texture object
*/
GLuint m_texture;
/**
* The OpenGL display list
*/
GLuint m_displayList;
/**
* Width and height in pixels of the rendered character
*/
int m_width, m_height;
public:
CharRenderer();
~CharRenderer();
bool initialize( QChar c, const QFont &font );
inline void draw()
{
glCallList( m_displayList );
}
};
/** This class renders text inside a QGLWidget. It replaces the functionality
* of QGLWidget::renderText(). The advantages over renderText() include:
* - supports any font, any character encoding supported by Qt
* (renderText is 8-bit-only and can only use "OpenGL-compatible" fonts)
* - does not use any library outside Qt (renderText uses FreeType on X11)
* - renders characters as textured quads instead of calling glDrawPixels,
* which does not make much of a difference on MesaGL, but can be a lot
* faster and safer with other (buggy) OpenGL implementations. It will also
* allow to add more graphical effects in the future, like rotation,
* if we ever need that.
* - the characters are stored as 8bpp Alpha, which takes 4 times less
* memory than the 32bpp RGBA used by renderText.
* - the characters are rendered on-the-fly on the first time they appear
* in a QString being printed. This is achieved using a QHash to test whether
* a character has already been rendered.
*
* Recommended usage:
* The TextRender class is meant to be used from inside a child class of
* QGLWidget, say MyGLWidget.
*
* In the declaration of MyGLWidget, please declare a TextRenderer member:
*
* @code
class MyGLWidget : public QGLWidget
{
...
TextRenderer m_textRenderer;
...
};
* @endcode
*
* Now, in the constructor of MyGLWidget, please call setup() along these lines:
*
* @code
QFont f;
f.setStyleHint( QFont::SansSerif, QFont::PreferAntialias );
m_textRenderer.setup( this, f );
* @endcode
*
* The setup() method should be called only once, which means you have to choose
* a font once and for all, in the lifetime of your TextRenderer. Any QFont can
* be used, the above is just an example. Now, to actually render text, in
* the MyGLWidget::paintGL() method, you can call
* @code
m_textRenderer.print( x, y, string );
* @endcode
* where x,y are ints and string is any QString. If you want to choose a color,
* please call glColor3f or glColor4f before calling print(). Of course you can
* also call qglColor. You can achieve semitransparent text at
* no additional cost by choosing a semitransparent color.
*
* If you wish to do several calls to print(), it will improve performance
* to enclose them between a call to begin() and a call to end(), like that:
*
* @code
m_textRenderer.begin();
m_textRenderer.print( x1, y1, string1 );
m_textRenderer.print( x2, y2, string2 );
m_textRenderer.print( x3, y2, string3 );
m_textRenderer.end();
* @endcode
*
* Please make sure, though, that no relevant OpenGL state change occurs between
* begin() and end(), except the state changes performed by the TextRenderer
* itself. In other words, please avoid calling glSomething() between begin() and
* end(), except if you are sure that this call won't perform a relevant state
* change.
*
* The print() method when called alone, or the begin()-print()-end() group,
* do restore the OpenGL state as they found it, including the matrices.
*
* If you experience rendering problems, you can try the following:
* - disable some OpenGL state bits. For instance, TextRenderer automatically
* disables fog and lighting during rendering, because it doesn't work
* correctly with them enabled. There probably are other OpenGL state bits
* that have to be disabled, so if your program enables some of them, you
* might have to disable them before rendering text.
* - if you experience poor font quality, please consider using an antialiased
* font.
*
* @author Benoit Jacob
*/
class TextRenderer
{
protected:
/**
* The font used for rendering the chars. This is set
* once and for all by setup(). Note that it is stored
* by value, so the caller doesn't have to keep it alive.
*/
QFont m_font;
/**
* This hash gives the correspondence table between QChars
* (the keys) and the corresponding CharRenderers (the values).
* Every time a QChar is being met, either it is found in this
* table, in which case it can be directly rendered, or it is
* not found, in which case a new CharRenderer is created for
* it and added to this table.
*/
QHash<QChar, CharRenderer*> m_charTable;
/**
* The QGLWidget in which to render. This is set
* once and for all by setup().
*/
const QGLWidget *m_glwidget;
/**
* This equals true if begin() has been called, but end() hasn't
* since.
*/
GLboolean m_isBetweenBeginAndEnd;
/**
* These members are used to remember the OpenGL state in order
* to be able to restore it after rendering. See do_end().
*/
GLboolean m_wasEnabled_LIGHTING;
GLboolean m_wasEnabled_TEXTURE_2D;
GLboolean m_wasEnabled_FOG;
GLboolean m_wasEnabled_BLEND;
GLboolean m_wasEnabled_DEPTH_TEST;
/**
* Stores the relevant part of the OpenGL state, and prepares
* for rendering
*/
void do_begin();
/**
* Restores the OpenGL state
*/
void do_end();
public:
TextRenderer();
~TextRenderer();
/**
* This should be called only once, before any printing occurs.
* @param glwidget The QGLWidget in which to render.
* See m_glwidget member.
* @param font The QFont to use. See m_font member.
*/
void setup( const QGLWidget *glwidget, const QFont &font );
/**
* Prints text at the position (x,y) in window coordinates
* (0,0) is the bottom left corner
* @param x the x-coordinate
* @param y the y-coordinate
* @param string the QString to print
*/
void print( int x, int y, const QString &string);
/**
* Call this before doing multiple calls to print(). This is
* not necessary, but will improve performance. Don't forget,
* then, to call end() after.
*/
void begin();
/**
* Call this after having called begin() and print().
*/
void end();
};
} // namespace KalziumGL
#endif // KALZIUMGLHELPERCLASSES_H
/***************************************************************************
/***************************************************************************
copyright : (C) 2006 by Benoit Jacob
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "textrenderer.h"
CharRenderer::CharRenderer()
{
m_texture = 0;
m_displayList = 0;
}
CharRenderer::~CharRenderer()
{
if( m_texture ) glDeleteTextures( 1, &m_texture );
if( m_displayList ) glDeleteLists( m_displayList, 1 );
}
bool CharRenderer::initialize( QChar c, const QFont &font )
{
if( m_displayList ) return true;
QFontMetrics fontMetrics ( font );
m_width = fontMetrics.width( c );
m_height = fontMetrics.height();
if( m_width == 0 || m_height == 0 ) return false;
QImage image( m_width, m_height, QImage::Format_RGB32 );
QPainter painter;
painter.begin( &image );
painter.setFont( font );
painter.setRenderHint( QPainter::TextAntialiasing );
painter.setBackground( Qt::black );
painter.eraseRect( image.rect() );
painter.setPen( Qt::blue );
painter.drawText ( 0, 0, m_width, m_height, Qt::AlignBottom, c );
painter.end();
GLubyte *bitmap = new GLubyte[ m_width * m_height ];
if( bitmap == 0 ) return false;
for( int j = m_height - 1, n = 0; j >= 0; j-- )
for( int i = 0; i < m_width; i++, n++ )
{
bitmap[n] = qBlue( image.pixel( i, j ) );
}
glGenTextures( 1, &m_texture );
if( m_texture == 0 ) return false;
glBindTexture( GL_TEXTURE_2D, m_texture );
glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_ALPHA,
m_width,
m_height,
0,
GL_ALPHA,
GL_UNSIGNED_BYTE,
bitmap );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
delete [] bitmap;
m_displayList = glGenLists(1);
if( m_displayList == 0 ) return false;
glNewList( m_displayList, GL_COMPILE );
glBindTexture( GL_TEXTURE_2D, m_texture );
glBegin( GL_QUADS );
glTexCoord2f( 0, 0);
glVertex2f( 0 , 0 );
glTexCoord2f( 1, 0);
glVertex2f( m_width , 0 );
glTexCoord2f( 1, 1);
glVertex2f( m_width, m_height );
glTexCoord2f( 0, 1);
glVertex2f( 0 , m_height );
glEnd();
glTranslatef( m_width, 0, 0 );
glEndList();
return true;
}
TextRenderer::TextRenderer()
{
m_glwidget = 0;
m_isBetweenBeginAndEnd = false;
}
TextRenderer::~TextRenderer()
{
QHash<QChar, CharRenderer *>::iterator i = m_charTable.begin();
while( i != m_charTable.end() )
{
delete i.value();
i = m_charTable.erase(i);
}
}
void TextRenderer::setup( const QGLWidget *glwidget, const QFont &font )
{
if( m_glwidget ) return;
m_glwidget = glwidget;
m_font = font;
}
void TextRenderer::do_begin()
{
m_wasEnabled_LIGHTING = glIsEnabled( GL_LIGHTING );
m_wasEnabled_FOG = glIsEnabled( GL_FOG );
m_wasEnabled_TEXTURE_2D = glIsEnabled( GL_TEXTURE_2D );
m_wasEnabled_BLEND = glIsEnabled( GL_BLEND );
m_wasEnabled_DEPTH_TEST = glIsEnabled( GL_DEPTH_TEST );
glDisable( GL_LIGHTING );
glDisable( GL_FOG );
glEnable( GL_TEXTURE_2D );
glEnable( GL_BLEND );
glDisable( GL_DEPTH_TEST );
glMatrixMode( GL_PROJECTION );
glPushMatrix();
glLoadIdentity();
glOrtho( 0, m_glwidget->width(), 0, m_glwidget->height(), -1, 1 );
glMatrixMode( GL_MODELVIEW );
}
void TextRenderer::begin()
{
if( ! m_glwidget ) return;
if( m_isBetweenBeginAndEnd ) return;
m_isBetweenBeginAndEnd = true;
do_begin();
}
void TextRenderer::do_end()
{
if( ! m_wasEnabled_TEXTURE_2D ) glDisable( GL_TEXTURE_2D);
if( ! m_wasEnabled_BLEND ) glDisable( GL_BLEND );
if( m_wasEnabled_DEPTH_TEST ) glEnable( GL_DEPTH_TEST );
if( m_wasEnabled_LIGHTING ) glEnable( GL_LIGHTING );
if( m_wasEnabled_FOG ) glEnable( GL_FOG );
glMatrixMode( GL_PROJECTION );
glPopMatrix();
glMatrixMode( GL_MODELVIEW );
}
void TextRenderer::end()
{
if( m_isBetweenBeginAndEnd ) do_end();
m_isBetweenBeginAndEnd = false;
}
void TextRenderer::print( int x, int y, const QString &string )
{
if( ! m_glwidget ) return;
if( string.isEmpty() ) return;
if( ! m_isBetweenBeginAndEnd ) do_begin();
glPushMatrix();
glLoadIdentity();
glTranslatef( x, y, 0 );
for( int i = 0; i < string.size(); i++ )
{
if( m_charTable.contains( string[i] ) )
m_charTable.value( string[i] )->draw();
else
{
CharRenderer *c = new CharRenderer;
if( c->initialize( string[i], m_font ) )
{
m_charTable.insert( string[i], c);
c->draw();
}
else delete c;
}
}
glPopMatrix();
if( ! m_isBetweenBeginAndEnd ) do_end();
}
Hey Benoit, That's interesting. I just ran across your message. I also found Qt's renderText less than satisfactory. I was interested in scaling text and getting past various display list errors I was getting when using renderText on an ancient Win2k laptop with a crappy entegrated gfx chipset. I've hacked these two classes up. The texture class is mainly optimized for very quickly sucking large images (like jpeg's) off the hard drive and getting them into gfx memory so they can be splatted onto quads. The texture class is partially based on this class. If either of these GPL'ed classes are helpful to other open source Qt developers out there more power to you. If Benoit or others can suggest improvements in either of these let me know. I'm using 32bits/pixel textures which use a bit more space for fonts, but on the flip side for sucking in large images for textures I'm taking advantage of a few extensions when availble, like NPOT extensions and avoiding multiple memory copies when availble on some Apple hardware. http://svn.sourceforge.net/viewcvs.cgi/albumshaper/trunk/clay/src/_openGLTools/openGLFont.h?view=markup&rev=903 http://svn.sourceforge.net/viewcvs.cgi/albumshaper/trunk/clay/src/_openGLTools/openGLFont.cpp?view=markup&rev=907 http://svn.sourceforge.net/viewcvs.cgi/albumshaper/trunk/clay/src/_openGLTools/openGLTexture.h?view=markup&rev=902 http://svn.sourceforge.net/viewcvs.cgi/albumshaper/trunk/clay/src/_openGLTools/openGLTexture.cpp?view=markup&rev=902 -Will On 7/3/06, Benoit Jacob <jacob.benoit.1@xxxxxxxxx> wrote: > Hi everyone, > > I'm a Qt newbie, recently joined KDE development (coding a GL-based > molecule renderer for kalzium for KDE4). > > I found (sorry :) I mean no offense) that QGLWidget::renderText() was > not doing was I needed, so I wrote a replacement for it, in the form > of a small class, TextRenderer. It renders characters on the fly, and > uses a QHash<QChar, ...> to cache the renderer characters. As it might > perhaps be useful to other people than me, here it is. I attach the > source code to this message, and just in case you can also download it > here: > > http://www.math.jussieu.fr/~jacob/textrenderer.h > http://www.math.jussieu.fr/~jacob/textrenderer.cpp > > The .h contains a HOWTO in a comment (just before the declaration of > TextRenderer). > > If I'm not mistaken, the advantages over QGLWidget::renderText are: > - it's encoding-safe, can render any QString > - it's font-safe, can use any QFont > - it's memory-efficient : only renders the characters that are > actually needed, on-the-fly, and uses 8bpp alpha maps instead of 32bpp > ARGB. > - it can be much faster (depending on the implementation) because it's > texture-based, instead of using glDrawPixels. > - also, being texture-based allows to easily implement nice effects > (for instance, 3D text, rotated text...) > - it is 100% Qt-based (by contrast, on X11, renderText calls directly > FreeType functions) > > Please be kind with me -- I started with Qt development only very > recently, so there might be a big mistake in my code. > > I hope this will be useful to some people. > > Cheers > Benoit > > > -- [ signature omitted ]
> I was interested in scaling text
> and getting past various display list errors I was getting when using
> renderText on an ancient Win2k laptop with a crappy entegrated gfx
> chipset. I've hacked these two classes up.
Related to the various contributions Will and Benoit have made here, I will
be in the future needing a class for doing rich text rendering. I.e. I'd
like to do
renderRichText( myqglwidget,
"a<sup>2</sup> + b<sup>2</sup> = c<sup>2</sup>" );
Just checking before I do that...
a) Anyone out there already done this? That would be great.
b) Anyone else out there desperately in need of this? I guess that would
induce me to try it sooner.
I would be using QSimpleRichText to render on images off-screen and then
creating textures from them.
Nathan
--
[ signature omitted ]
On Monday 17 July 2006 16:12, Nathan Carter wrote: > > I was interested in scaling text > > and getting past various display list errors I was getting when using > > renderText on an ancient Win2k laptop with a crappy entegrated gfx > > chipset. I've hacked these two classes up. > > Related to the various contributions Will and Benoit have made here, I will > be in the future needing a class for doing rich text rendering. I.e. I'd > like to do > > renderRichText( myqglwidget, > "a<sup>2</sup> + b<sup>2</sup> = c<sup>2</sup>" ); > > Just checking before I do that... > > a) Anyone out there already done this? That would be great. > > b) Anyone else out there desperately in need of this? I guess that would > induce me to try it sooner. > > I would be using QSimpleRichText to render on images off-screen and then > creating textures from them. As a sidenote: In Qt 4.2 the text rendering on an OpenGL widget through QPainter uses a similar technique (pre-rendered glyph textures), and that of course works with richtext, too. Simon
Attachment:
Attachment:
pgp1d7N0L45DY.pgp
Attachment:
Attachment:
pgphv217jb5mu.pgp
Description: PGP signature
Message 5 in thread
> As a sidenote: In Qt 4.2 the text rendering on an OpenGL widget through
> QPainter uses a similar technique (pre-rendered glyph textures), and that of
> course works with richtext, too.
That's very interesting! I'd like to ask a few questions in order to
determine if I should use this new Qt feature instead of my own stuff:
- is your implementation unicode-safe ?
- the notion of "OpenGL-compatible fonts" has disappeared, right?
- how memory efficient is your implementation ? Precisely: do you only
render textures for the actually needed characters ? And, how many
bits-per-pixel do your textures use? I mean, if I only need one color
for text on top of a transparent background, 8 bpp are enough.
Out of curiosity, I'd also like to know- did you also use a QHash on
QChars? Or something different?
2006/7/17, Simon Hausmann <simon.hausmann@xxxxxxxxxxxxx>:
> On Monday 17 July 2006 16:12, Nathan Carter wrote:
> > > I was interested in scaling text
> > > and getting past various display list errors I was getting when using
> > > renderText on an ancient Win2k laptop with a crappy entegrated gfx
> > > chipset. I've hacked these two classes up.
> >
> > Related to the various contributions Will and Benoit have made here, I
> will
> > be in the future needing a class for doing rich text rendering. I.e. I'd
> > like to do
> >
> > renderRichText( myqglwidget,
> > "a<sup>2</sup> + b<sup>2</sup> = c<sup>2</sup>" );
> >
> > Just checking before I do that...
> >
> > a) Anyone out there already done this? That would be great.
> >
> > b) Anyone else out there desperately in need of this? I guess that would
> > induce me to try it sooner.
> >
> > I would be using QSimpleRichText to render on images off-screen and then
> > creating textures from them.
>
> As a sidenote: In Qt 4.2 the text rendering on an OpenGL widget through
> QPainter uses a similar technique (pre-rendered glyph textures), and that of
> course works with richtext, too.
>
>
> Simon
>
>
--
[ signature omitted ]
Message 6 in thread
On Thursday 20 July 2006 12:43, Benoit Jacob wrote:
> > As a sidenote: In Qt 4.2 the text rendering on an OpenGL widget through
> > QPainter uses a similar technique (pre-rendered glyph textures), and that
> > of course works with richtext, too.
>
> That's very interesting! I'd like to ask a few questions in order to
> determine if I should use this new Qt feature instead of my own stuff:
>
> - is your implementation unicode-safe ?
Yes, because it maps from glyph index to the glyph texture.
> - the notion of "OpenGL-compatible fonts" has disappeared, right?
Basically ... yes :)
> - how memory efficient is your implementation ? Precisely: do you only
> render textures for the actually needed characters ? And, how many
> bits-per-pixel do your textures use? I mean, if I only need one color
> for text on top of a transparent background, 8 bpp are enough.
There is basically one texture per physical font and inside the texture only
the actually used glyphs are rendered. Rendering all glyphs that the font
contains would obviously quickly exceed the available texture memory. The
texture (and therefore the glyphs) contain only alpha values with 8 bits per
value, so they can be re-used independent of the resulting text color.
> Out of curiosity, I'd also like to know- did you also use a QHash on
> QChars? Or something different?
We do not use a mapping from a QChar because that is not unicode safe. In some
languages you can for example have multiple characters turning into one
single glyph. That is why the caching of glyphs needs to be done after the
text shaping, based on the resulting glyph indices in the font.
Simon
Description: PGP signature
Message 7 in thread
This all sounds very interesting. If it really is as good as it sounds
I may end up dropping my own font renderer as well. I have a few
questions of my own:
-How do you determine what characters need to be rendered into the
texture? As you say, rendering all glyphs would result in a texture
that is way to big to fit into texture memory, esp on weaker gfx
hardware
-Do you make use of NPOT extentions or detect if OpenGL2.0 support is
present to avoid padding the font texture to a power of two
resolution? This could also considerably reduce the texture memory
usage
-Another trick you could pull that I currently do with my texture
class is to avoid copying a second time the data onto the gfx card
using one of Apple's extensions if available. While not platform
indepdendent I'd love to see Qt's texture rendering implementation be
as fast as it can be on the target hardware. I mean, why not?
-as a side comment, are there any plans in Qt4.2 or beyond to improve
OpenGL suppose so non-rednering OpenGL calls could be performed in
secondary threads? Case in point, I'd love to have a worker thread
prepare and load textures for me, while the main QGLWidget gui thread
do all the actual rendering. currently it's very easy to get your
OpenGL based GUI to block when loading big textures which is
nonoptimal.
-Will
On 7/20/06, Simon Hausmann <simon.hausmann@xxxxxxxxxxxxx> wrote:
> On Thursday 20 July 2006 12:43, Benoit Jacob wrote:
> > > As a sidenote: In Qt 4.2 the text rendering on an OpenGL widget through
> > > QPainter uses a similar technique (pre-rendered glyph textures), and that
> > > of course works with richtext, too.
> >
> > That's very interesting! I'd like to ask a few questions in order to
> > determine if I should use this new Qt feature instead of my own stuff:
> >
> > - is your implementation unicode-safe ?
>
> Yes, because it maps from glyph index to the glyph texture.
>
> > - the notion of "OpenGL-compatible fonts" has disappeared, right?
>
> Basically ... yes :)
>
> > - how memory efficient is your implementation ? Precisely: do you only
> > render textures for the actually needed characters ? And, how many
> > bits-per-pixel do your textures use? I mean, if I only need one color
> > for text on top of a transparent background, 8 bpp are enough.
>
> There is basically one texture per physical font and inside the texture only
> the actually used glyphs are rendered. Rendering all glyphs that the font
> contains would obviously quickly exceed the available texture memory. The
> texture (and therefore the glyphs) contain only alpha values with 8 bits per
> value, so they can be re-used independent of the resulting text color.
>
> > Out of curiosity, I'd also like to know- did you also use a QHash on
> > QChars? Or something different?
>
> We do not use a mapping from a QChar because that is not unicode safe. In some
> languages you can for example have multiple characters turning into one
> single glyph. That is why the caching of glyphs needs to be done after the
> text shaping, based on the resulting glyph indices in the font.
>
>
> Simon
>
>
>
--
[ signature omitted ]