diff --git a/src/Text/Font.h b/src/Text/Font.h index 743685649..07cc72761 100644 --- a/src/Text/Font.h +++ b/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); diff --git a/src/Text/FontRenderer.h b/src/Text/FontRenderer.h index 0164edf32..6fd72ec57 100644 --- a/src/Text/FontRenderer.h +++ b/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: diff --git a/src/Text/Text.h b/src/Text/Text.h index 982b73d75..56cef3e75 100644 --- a/src/Text/Text.h +++ b/src/Text/Text.h @@ -25,7 +25,10 @@ namespace Magnum { namespace Text { class Font; class FontRenderer; -/* TextRenderer used only statically */ + +template class TextRenderer; +typedef TextRenderer<2> TextRenderer2D; +typedef TextRenderer<3> TextRenderer3D; }} diff --git a/src/Text/TextRenderer.cpp b/src/Text/TextRenderer.cpp index e31d4fe44..8af91b16e 100644 --- a/src/Text/TextRenderer.cpp +++ b/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::tuple TextRenderer TextRenderer::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::Position(), + typename Shaders::AbstractTextShader::TextureCoordinates()); +} + +template void TextRenderer::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), 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(indices, glyphCount); + else if(vertexCount < 65535) + createIndices(indices, glyphCount); + else + createIndices(indices, glyphCount); + CORRADE_INTERNAL_ASSERT_OUTPUT(indexBuffer.unmap()); +} + +template void TextRenderer::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* const vertices = static_cast*>(vertexBuffer.map(0, layouter.glyphCount()*4*sizeof(Vertex), + 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(quadPosition.topLeft()), textureCoordinates.topLeft()}; + vertices[vertex+1] = {point(quadPosition.bottomLeft()), textureCoordinates.bottomLeft()}; + vertices[vertex+2] = {point(quadPosition.topRight()), textureCoordinates.topRight()}; + vertices[vertex+3] = {point(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>; diff --git a/src/Text/TextRenderer.h b/src/Text/TextRenderer.h index 920e8ce58..89328520c 100644 --- a/src/Text/TextRenderer.h +++ b/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 class MAGNUM_TEXT_EXPORT TextRenderer { @@ -66,6 +124,61 @@ template class MAGNUM_TEXT_EXPORT TextRenderer { * subclasses and rectangle spanning the rendered text. */ static std::tuple 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 */