Recall that updating the VBO at every frame and
computing texture coordinates was a bit of a pain; it turns out the iPhone
supports an easier way to render pixel rectangles when you’re using OpenGL
ES 1.1.
Warning:
This extension is not supported under OpenGL
ES 2.0.
The GL_OES_draw_texture
extension (supported on all iPhone models at the time of this writing)
adds two new functions to OpenGL’s repertoire:
void glDrawTexfOES(GLfloat x, GLfloat y, GLfloat z,
GLfloat width, GLfloat height);
void glDrawTexfvOES(const GLfloat *destRectangle);
These two functions are basically equivalent;
either can be used to render a rectangle. The second function takes a
pointer to the same five floats described in the first function.
Note:
The GL_OES_draw_texture
extension actually introduces eight functions in all because of the
variants for GLshort, GLint, and
GLfixed. I tend to use the GLfloat
variants.
This extension also introduces a new texture
parameter called GL_TEXTURE_CROP_RECT_OES, which can be
used liked this:
int sourceRectangle[] = { x, y, width, height };
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, sourceRectangle);
When used together, the new
glDrawTex* functions and the new texture parameter make
it easy to draw rectangles of pixels; there’s no need to mess with
cumbersome VBOs and triangles.
To summarize, use glDrawTex*
to set the destination rectangle on the screen; use the new crop rectangle
parameter to set up the rectangle in the source texture.
Let’s walk through the process of converting
the FpsRenderer sample to use the
draw_texture extension. First we can remove several
fields from the class, including m_vbo,
m_windowSize, and all constants except
MaxNumDigits.
We can also replace the cumbersome
WriteGlyphVertex method with a new streamlined method
called RenderGlyph. See Example 1. For brevity, sections of code
that remain unchanged are replaced with ellipses.
Example 1. Simplified FpsRenderer skeleton
...
class FpsRenderer {
public:
FpsRenderer(vec2 windowSize)
{
...
}
void RenderFps()
{
uint64_t deltaTime = GetElapsedNanoseconds();
double fps = 1000000000.0 / deltaTime;
double alpha = m_filterConstant;
m_fps = fps * alpha + m_fps * (1.0 - alpha);
fps = round(m_fps);
glBindTexture(GL_TEXTURE_2D, m_textureHandle);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
glColor4f(1, 1, 1, 1);
char digits[MaxNumDigits + 1] = {0};
sprintf(digits, "%d", (int) fps);
vec2 pos(5, 10);
for (char* digit = &digits[0]; *digit; ++digit) {
int glyphIndex = *digit - '0';
const Glyph& glyph = NumeralGlyphs[glyphIndex];
RenderGlyph(glyph, pos);
pos.x += glyph.Metrics.XAdvance;
}
glDisable(GL_BLEND);
}
private:
static const int MaxNumDigits = 3;
uint64_t GetElapsedNanoseconds()
{
...
}
void RenderGlyph(const Glyph& glyph, vec2 position)
{
position.y -= glyph.Metrics.Height + glyph.Metrics.YBearing;
int box[] = { glyph.Position.X,
m_textureSize.y - 1
+ glyph.Position.Y - glyph.Metrics.Height,
glyph.Metrics.Width + 1,
glyph.Metrics.Height + 1 };
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, box);
glDrawTexfOES(position.x, position.y, 0,
glyph.Metrics.Width + 1, glyph.Metrics.Height + 1);
}
double m_filterConstant;
double m_fps;
uint64_t m_previousTime;
vec2 m_textureSize;
GLuint m_textureHandle;
};