Browse Source

Text: mutable TextRenderer implementation.

Allows fast text updates (using buffer mapping). Also fully documented
the workflow.
pull/7/head
Vladimír Vondruš 13 years ago
parent
commit
9a1f1e1a16
  1. 24
      src/Text/Font.h
  2. 5
      src/Text/FontRenderer.h
  3. 5
      src/Text/Text.h
  4. 90
      src/Text/TextRenderer.cpp
  5. 119
      src/Text/TextRenderer.h

24
src/Text/Font.h

@ -39,6 +39,28 @@ namespace Magnum { namespace Text {
@brief %Font
Contains font with characters prerendered into texture atlas.
@section Font-usage Usage
You need to maintain instance of FontRenderer during the lifetime of all Font
instances. The font can be created either from file or from memory location of
format supported by [FreeType](http://www.freetype.org/) library. Next step is
to prerender all the glyphs which will be used in text rendering later.
@code
Text::FontRenderer fontRenderer;
Text::Font font(fontRenderer, "MyFont.ttf", 48.0f);
font.prerender("abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789 ", Vector2i(512));
@endcode
See TextRenderer for information about text rendering.
@section Font-extensions Required OpenGL functionality
%Font texture uses one-component internal format, which requires
@extension{ARB,texture_rg} (also part of OpenGL ES 3.0 or available as
@es_extension{EXT,texture_rg} in ES 2.0).
*/
class MAGNUM_TEXT_EXPORT Font {
Font(const Font&) = delete;
@ -69,6 +91,8 @@ class MAGNUM_TEXT_EXPORT Font {
*
* Creates new atlas with prerendered characters, replacing the
* previous one (if any).
* @attention @p atlasSize must be large enough to contain all
* rendered glyphs.
*/
void prerender(const std::string& characters, const Vector2i& atlasSize);

5
src/Text/FontRenderer.h

@ -29,9 +29,10 @@ typedef FT_LibraryRec_* FT_Library;
namespace Magnum { namespace Text {
/**
@brief Font renderer
@brief %Font renderer
Contains global instance of font renderer used by Font class.
Contains global instance of font renderer. See Font class documentation for
more information.
*/
class MAGNUM_TEXT_EXPORT FontRenderer {
public:

5
src/Text/Text.h

@ -25,7 +25,10 @@ namespace Magnum { namespace Text {
class Font;
class FontRenderer;
/* TextRenderer used only statically */
template<std::uint8_t> class TextRenderer;
typedef TextRenderer<2> TextRenderer2D;
typedef TextRenderer<3> TextRenderer3D;
}}

90
src/Text/TextRenderer.cpp

@ -20,6 +20,8 @@
#include "Math/Point2D.h"
#include "Math/Point3D.h"
#include "Context.h"
#include "Extensions.h"
#include "Mesh.h"
#include "Swizzle.h"
#include "Shaders/AbstractTextShader.h"
@ -242,6 +244,94 @@ template<std::uint8_t dimensions> std::tuple<Mesh, Rectangle> TextRenderer<dimen
return std::make_tuple(std::move(mesh), rectangle);
}
template<std::uint8_t dimensions> TextRenderer<dimensions>::TextRenderer(Font& font, const GLfloat size): font(font), size(size), _capacity(0), vertexBuffer(Buffer::Target::Array), indexBuffer(Buffer::Target::ElementArray) {
#ifndef MAGNUM_TARGET_GLES
MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::map_buffer_range);
#else
#ifdef MAGNUM_TARGET_GLES2
MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::EXT::map_buffer_range);
#endif
#endif
_mesh.setPrimitive(Mesh::Primitive::Triangles)
->addInterleavedVertexBuffer(&vertexBuffer, 0,
typename Shaders::AbstractTextShader<dimensions>::Position(),
typename Shaders::AbstractTextShader<dimensions>::TextureCoordinates());
}
template<std::uint8_t dimensions> void TextRenderer<dimensions>::reserve(const uint32_t glyphCount, const Buffer::Usage vertexBufferUsage, const Buffer::Usage indexBufferUsage) {
_capacity = glyphCount;
const std::uint32_t vertexCount = glyphCount*4;
const std::uint32_t indexCount = glyphCount*6;
/* Allocate vertex buffer, reset vertex count */
vertexBuffer.setData(vertexCount*sizeof(Vertex<dimensions>), nullptr, vertexBufferUsage);
_mesh.setVertexCount(0);
/* Allocate index buffer, reset index count and reconfigure buffer binding */
Mesh::IndexType indexType;
std::size_t indicesSize;
if(vertexCount < 255) {
indexType = Mesh::IndexType::UnsignedByte;
indicesSize = indexCount*sizeof(GLushort);
} else if(vertexCount < 65535) {
indexType = Mesh::IndexType::UnsignedShort;
indicesSize = indexCount*sizeof(GLushort);
} else {
indexType = Mesh::IndexType::UnsignedInt;
indicesSize = indexCount*sizeof(GLuint);
}
indexBuffer.setData(indicesSize, nullptr, indexBufferUsage);
_mesh.setIndexCount(0)
->setIndexBuffer(&indexBuffer, 0, indexType, 0, vertexCount);
/* Prefill index buffer */
void* indices = indexBuffer.map(0, indicesSize, Buffer::MapFlag::InvalidateBuffer|Buffer::MapFlag::Write);
if(vertexCount < 255)
createIndices<GLubyte>(indices, glyphCount);
else if(vertexCount < 65535)
createIndices<GLushort>(indices, glyphCount);
else
createIndices<GLuint>(indices, glyphCount);
CORRADE_INTERNAL_ASSERT_OUTPUT(indexBuffer.unmap());
}
template<std::uint8_t dimensions> void TextRenderer<dimensions>::render(const std::string& text) {
TextLayouter layouter(font, size, text);
CORRADE_ASSERT(layouter.glyphCount() <= _capacity, "Text::TextRenderer::render(): capacity" << _capacity << "too small to render" << layouter.glyphCount() << "glyphs", );
/* Render all glyphs */
Vertex<dimensions>* const vertices = static_cast<Vertex<dimensions>*>(vertexBuffer.map(0, layouter.glyphCount()*4*sizeof(Vertex<dimensions>),
Buffer::MapFlag::InvalidateBuffer|Buffer::MapFlag::Write));
Vector2 cursorPosition;
for(std::uint32_t i = 0; i != layouter.glyphCount(); ++i) {
/* Position of the texture in the resulting glyph, texture coordinates */
Rectangle quadPosition, textureCoordinates;
Vector2 advance;
std::tie(quadPosition, textureCoordinates, advance) = layouter.renderGlyph(cursorPosition, i);
if(i == 0)
_rectangle.bottomLeft() = quadPosition.bottomLeft();
else if(i == layouter.glyphCount()-1)
_rectangle.topRight() = quadPosition.topRight();
const std::size_t vertex = i*4;
vertices[vertex] = {point<dimensions>(quadPosition.topLeft()), textureCoordinates.topLeft()};
vertices[vertex+1] = {point<dimensions>(quadPosition.bottomLeft()), textureCoordinates.bottomLeft()};
vertices[vertex+2] = {point<dimensions>(quadPosition.topRight()), textureCoordinates.topRight()};
vertices[vertex+3] = {point<dimensions>(quadPosition.bottomRight()), textureCoordinates.bottomRight()};
/* Advance cursor position to next character */
cursorPosition += advance;
}
CORRADE_INTERNAL_ASSERT_OUTPUT(vertexBuffer.unmap());
/* Update index count */
_mesh.setIndexCount(layouter.glyphCount()*6);
}
template class TextRenderer<2>;
template class TextRenderer<3>;

119
src/Text/TextRenderer.h

@ -19,9 +19,10 @@
* @brief Class Magnum::Text::TextRenderer
*/
#include "Math/Vector2.h"
#include "Math/Geometry/Rectangle.h"
#include "Buffer.h"
#include "DimensionTraits.h"
#include "Mesh.h"
#include "Text/Text.h"
#include "magnumTextVisibility.h"
@ -36,8 +37,65 @@ namespace Magnum { namespace Text {
@brief %Text renderer
Lays out the text into mesh using [HarfBuzz](http://www.freedesktop.org/wiki/Software/HarfBuzz)
library. Use of ligatures, kerning etc. depends on features supported by the
particular font.
library. Use of ligatures, kerning etc. depends on features supported by
particular font. See also Font.
@section TextRenderer-usage Usage
Immutable text (e.g. menu items, credits) can be simply rendered using static
methods, returning result either as data arrays or as fully configured mesh.
The text can be then drawn by configuring text shader, binding font texture
and drawing the mesh:
@code
Text::Font font;
Shaders::TextShader2D shader;
Buffer vertexBuffer, indexBuffer;
Mesh mesh;
// Render the text
Rectangle rectangle;
std::tie(mesh, rectangle) = Text::TextRenderer2D::render(font, 0.15f,
"Hello World!", &vertexBuffer, &indexBuffer, Buffer::Usage::StaticDraw);
// Draw white text centered on the screen
shader.setTransformationProjectionMatrix(projection*Matrix3::translation(-rectangle.width()/2.0f))
->setColor(Color3<>(1.0f));
->use();
font.texture()->bind(Shaders::TextShader2D::FontTextureLayer);
mesh.draw();
@endcode
See render(Font&, GLfloat, const std::string&) and
render(Font&, GLfloat, const std::string&, Buffer*, Buffer*, Buffer::Usage)
for more information.
While this method is sufficient for one-shot rendering of static texts, for
mutable texts (e.g. FPS counters, chat messages) there is another approach
that doesn't recreate everything on each text change:
@code
Text::Font font;
Shaders::TextShader2D shader;
// Initialize renderer and reserve memory for enough glyphs
Text::TextRenderer2D renderer(font, 0.15f);
renderer.reserve(32, Buffer::Usage::DynamicDraw, Buffer::Usage::StaticDraw);
// Update the text occasionally
renderer.render("Hello World Countdown: 10");
// Draw the text centered on the screen
shader.setTransformationProjectionMatrix(projection*Matrix3::translation(-renderer.rectangle().width()/2.0f))
->setColor(Color3<>(1.0f));
->use();
font.texture()->bind(Shaders::TextShader2D::FontTextureLayer);
renderer.mesh().draw();
@endcode
@section TextRenderer-extensions Required OpenGL functionality
Mutable text rendering requires @extension{ARB,map_buffer_range} (also part of
OpenGL ES 3.0 or available as @es_extension{EXT,map_buffer_range} in ES 2.0)
for asynchronous buffer updates.
@see TextRenderer2D, TextRenderer3D, Font, Shaders::AbstractTextShader
*/
template<std::uint8_t dimensions> class MAGNUM_TEXT_EXPORT TextRenderer {
@ -66,6 +124,61 @@ template<std::uint8_t dimensions> class MAGNUM_TEXT_EXPORT TextRenderer {
* subclasses and rectangle spanning the rendered text.
*/
static std::tuple<Mesh, Rectangle> render(Font& font, GLfloat size, const std::string& text, Buffer* vertexBuffer, Buffer* indexBuffer, Buffer::Usage usage);
/**
* @brief Constructor
* @param font %Font to use
* @param size %Font size
*/
TextRenderer(Font& font, GLfloat size);
/**
* @brief Capacity for rendered glyphs
*
* @see reserve()
*/
inline std::uint32_t capacity() const { return _capacity; }
/** @brief Rectangle spanning the rendered text */
inline Rectangle rectangle() const { return _rectangle; }
/** @brief Text mesh */
inline Mesh* mesh() { return &_mesh; }
/**
* @brief Reserve capacity for rendered glyphs
*
* Reallocates memory in buffers to hold @p glyphCount glyphs and
* prefills index buffer. Consider using appropriate @p vertexBufferUsage
* if the text will be changed frequently. Index buffer is changed
* only by calling this function, thus @p indexBufferUsage generally
* doesn't need to be so dynamic if the capacity won't be changed much.
*
* Initially zero capacity is reserved.
* @see capacity()
*/
void reserve(const std::uint32_t glyphCount, const Buffer::Usage vertexBufferUsage, const Buffer::Usage indexBufferUsage);
/**
* @brief Render text
*
* Renders the text to vertex buffer, reusing index buffer already
* filled with reserve(). Rectangle spanning the rendered text is
* available through rectangle().
*
* Initially no text is rendered.
* @attention The capacity must be large enough to contain all glyphs,
* see reserve() for more information.
*/
void render(const std::string& text);
private:
Font& font;
GLfloat size;
std::uint32_t _capacity;
Rectangle _rectangle;
Buffer vertexBuffer, indexBuffer;
Mesh _mesh;
};
/** @brief Two-dimensional text renderer */

Loading…
Cancel
Save