1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-09-15 15:02:34 +02:00

Improve TextRenderer kerning and performance

This commit is contained in:
Daniel Evans 2015-04-03 02:56:37 +01:00
parent a773262328
commit 3801e69e81
3 changed files with 212 additions and 149 deletions

View File

@ -3,6 +3,7 @@
#include "OpenGLRenderer.hpp"
#define GAME_FONTS 3
#define GAME_GLYPHS 192
class GameWorld;
class GameRenderer;
@ -44,6 +45,14 @@ public:
TextInfo();
};
/**
* Stores the information for kerning a glyph
*/
struct GlyphInfo
{
float widthFrac;
};
TextRenderer(GameWorld* engine, GameRenderer* renderer);
~TextRenderer();
@ -53,6 +62,8 @@ public:
private:
std::string fonts[GAME_FONTS];
GlyphInfo glyphData[GAME_GLYPHS];
GameWorld* engine;
GameRenderer* renderer;
Renderer::ShaderProgram* textShader;

View File

@ -1,83 +1,7 @@
#include "render/TextRenderer.hpp"
#include <render/GameRenderer.hpp>
#include <engine/GameWorld.hpp>
const char* TextVertexShader = R"(
#version 130
#extension GL_ARB_explicit_attrib_location : enable
#extension GL_ARB_uniform_buffer_object : enable
layout(location = 0) in vec2 position;
layout(location = 3) in vec2 texcoord;
layout(location = 2) in vec3 colour;
out vec2 TexCoord;
out vec3 Colour;
uniform mat4 proj;
void main()
{
gl_Position = proj * vec4(position, 0.0, 1.0);
TexCoord = texcoord;
Colour = colour;
})";
const char* TextFragmentShader = R"(
#version 130
in vec2 TexCoord;
in vec3 Colour;
uniform vec4 colour;
uniform sampler2D fontTexture;
out vec4 outColour;
void main()
{
float a = texture(fontTexture, TexCoord).a;
outColour = vec4(Colour, a);
})";
struct TextVertex
{
glm::vec2 position;
glm::vec2 texcoord;
glm::vec3 colour;
static const AttributeList vertex_attributes() {
return {
{ATRS_Position, 2, sizeof(TextVertex), 0ul},
{ATRS_TexCoord, 2, sizeof(TextVertex), 0ul + sizeof(glm::vec2)},
{ATRS_Colour, 3, sizeof(TextVertex), 0ul + sizeof(glm::vec2) * 2},
};
}
};
TextRenderer::TextInfo::TextInfo()
: font(0), size(1.f), baseColour({1.f, 1.f, 1.f}), align(Left)
{
}
TextRenderer::TextRenderer(GameWorld* engine, GameRenderer* renderer)
: fonts({}), engine(engine), renderer(renderer)
{
textShader = renderer->getRenderer()->createShader(
TextVertexShader, TextFragmentShader );
}
TextRenderer::~TextRenderer()
{
}
void TextRenderer::setFontTexture(int index, const std::string& texture)
{
if( index < GAME_FONTS )
{
fonts[index] = texture;
}
}
#include <boost/concept_check.hpp>
/// @todo This is very rough
int charToIndex(char g)
@ -124,6 +48,118 @@ glm::vec4 indexToCoord(int font, int index)
glm::vec4( gsize, gsize ); // + glm::vec4( 0.0001f, 0.0001f,-0.0001f,-0.0001f);
}
const char* TextVertexShader = R"(
#version 130
#extension GL_ARB_explicit_attrib_location : enable
#extension GL_ARB_uniform_buffer_object : enable
layout(location = 0) in vec2 position;
layout(location = 3) in vec2 texcoord;
layout(location = 2) in vec3 colour;
out vec2 TexCoord;
out vec3 Colour;
uniform mat4 proj;
uniform vec2 alignment;
void main()
{
gl_Position = proj * vec4(alignment + position, 0.0, 1.0);
TexCoord = texcoord;
Colour = colour;
})";
const char* TextFragmentShader = R"(
#version 130
in vec2 TexCoord;
in vec3 Colour;
uniform vec4 colour;
uniform sampler2D fontTexture;
out vec4 outColour;
void main()
{
float a = texture(fontTexture, TexCoord).a;
outColour = vec4(Colour, a);
})";
struct TextVertex
{
glm::vec2 position;
glm::vec2 texcoord;
glm::vec3 colour;
static const AttributeList vertex_attributes() {
return {
{ATRS_Position, 2, sizeof(TextVertex), 0ul},
{ATRS_TexCoord, 2, sizeof(TextVertex), 0ul + sizeof(glm::vec2)},
{ATRS_Colour, 3, sizeof(TextVertex), 0ul + sizeof(glm::vec2) * 2},
};
}
};
TextRenderer::TextInfo::TextInfo()
: font(0), size(1.f), baseColour({1.f, 1.f, 1.f}), align(Left)
{
}
TextRenderer::TextRenderer(GameWorld* engine, GameRenderer* renderer)
: fonts({}), engine(engine), renderer(renderer)
{
textShader = renderer->getRenderer()->createShader(
TextVertexShader, TextFragmentShader );
for( int g = 0; g < GAME_GLYPHS; g++ )
{
glyphData[g] = { 1.f };
}
glyphData[charToIndex(' ')].widthFrac = 0.4f;
glyphData[charToIndex('\'')].widthFrac = 0.5f;
// Assumes contigious a-z character encoding
for(char g = 0; g <= ('z'-'a'); g++)
{
switch( ('a' + g) )
{
case 'i':
glyphData[charToIndex('a' + g)].widthFrac = 0.4f;
glyphData[charToIndex('A' + g)].widthFrac = 0.4f;
break;
case 'l':
glyphData[charToIndex('a' + g)].widthFrac = 0.5f;
glyphData[charToIndex('A' + g)].widthFrac = 0.5f;
break;
case 'm':
glyphData[charToIndex('a' + g)].widthFrac = 1.0f;
glyphData[charToIndex('A' + g)].widthFrac = 1.0f;
break;
case 'w':
glyphData[charToIndex('a' + g)].widthFrac = 1.0f;
glyphData[charToIndex('A' + g)].widthFrac = 1.0f;
break;
default:
glyphData[charToIndex('a' + g)].widthFrac = 0.7f;
glyphData[charToIndex('A' + g)].widthFrac = 0.7f;
break;
}
}
}
TextRenderer::~TextRenderer()
{
}
void TextRenderer::setFontTexture(int index, const std::string& texture)
{
if( index < GAME_FONTS )
{
fonts[index] = texture;
}
}
void TextRenderer::renderText(const TextRenderer::TextInfo& ti)
{
renderer->getRenderer()->useProgram(textShader);
@ -132,33 +168,30 @@ void TextRenderer::renderText(const TextRenderer::TextInfo& ti)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glActiveTexture(GL_TEXTURE0);
renderer->getRenderer()->setUniform(textShader, "proj", renderer->getRenderer()->get2DProjection());
renderer->getRenderer()->setUniformTexture(textShader, "fontTexture", 0);
glm::vec2 coord( 0.f, 0.f );
glm::vec2 alignment = ti.screenPosition;
glm::vec2 coord = ti.screenPosition;
Renderer::DrawParameters dp;
dp.start = 0;
dp.count = gb.getCount();
auto ftexture = engine->gameData.findTexture(fonts[ti.font]);
dp.texture = ftexture->getName();
glm::vec2 ss( ti.size );
/// @todo smarter alignment
if ( ti.align == TextInfo::Right )
{
coord.x -= ss.x * ti.text.length();
}
else if ( ti.align == TextInfo::Center )
{
coord.x -= glm::floor(ss.x * ti.text.length() * 0.5f);
}
glm::vec3 colour = ti.baseColour;
std::vector<TextVertex> geo;
float maxWidth = 0.f;
/// @todo make this less wastefull
for( const char& c : ti.text )
{
int glyph = charToIndex(c);
if( glyph >= GAME_GLYPHS )
{
continue;
}
auto& data = glyphData[glyph];
auto tex = indexToCoord(ti.font, glyph);
ss.x = ti.size * data.widthFrac;
tex.z = tex.x + (tex.z - tex.x) * data.widthFrac;
// Handle special chars.
if( c == '\n' )
{
@ -167,23 +200,42 @@ void TextRenderer::renderText(const TextRenderer::TextInfo& ti)
continue;
}
int glyph = charToIndex(c);
auto tex = indexToCoord(ti.font, glyph);
glm::vec2 p = coord;
coord.x += ss.x;
maxWidth = std::max(coord.x, maxWidth);
std::vector<TextVertex> geo = {
{ { p.x, p.y + ss.y }, {tex.x, tex.w}, colour },
{ { p.x + ss.x, p.y + ss.y }, {tex.z, tex.w}, colour },
{ { p.x, p.y }, {tex.x, tex.y}, colour },
{ { p.x + ss.x, p.y }, {tex.z, tex.y}, colour },
};
geo.push_back({ { p.x, p.y + ss.y }, {tex.x, tex.w}, colour });
geo.push_back({ { p.x + ss.x, p.y + ss.y }, {tex.z, tex.w}, colour });
geo.push_back({ { p.x, p.y }, {tex.x, tex.y}, colour });
gb.uploadVertices(geo);
db.addGeometry(&gb);
db.setFaceType(GL_TRIANGLE_STRIP);
renderer->getRenderer()->drawArrays(glm::mat4(), &db, dp);
geo.push_back({ { p.x + ss.x, p.y }, {tex.z, tex.y}, colour });
geo.push_back({ { p.x, p.y }, {tex.x, tex.y}, colour });
geo.push_back({ { p.x + ss.x, p.y + ss.y }, {tex.z, tex.w}, colour });
}
if ( ti.align == TextInfo::Right )
{
alignment.x -= maxWidth;
}
else if ( ti.align == TextInfo::Center )
{
alignment.x -= (maxWidth / 2.f);
}
renderer->getRenderer()->setUniform(textShader, "proj", renderer->getRenderer()->get2DProjection());
renderer->getRenderer()->setUniformTexture(textShader, "fontTexture", 0);
renderer->getRenderer()->setUniform(textShader, "alignment", alignment);
gb.uploadVertices(geo);
db.addGeometry(&gb);
db.setFaceType(GL_TRIANGLES);
Renderer::DrawParameters dp;
dp.start = 0;
dp.count = gb.getCount();
auto ftexture = engine->gameData.findTexture(fonts[ti.font]);
dp.texture = ftexture->getName();
renderer->getRenderer()->drawArrays(glm::mat4(), &db, dp);
}

View File

@ -52,44 +52,44 @@ void drawOnScreenText(GameWorld* world)
switch(t.osTextStyle)
{
default:
ti.size = 15.f;
ti.font = 0;
ti.align = TextRenderer::TextInfo::Left;
ti.baseColour = glm::vec3(1.f);
ti.screenPosition = vp / 2;
break;
case OnscreenText::HighPriority:
ti.size = 20.f;
ti.font = 2;
ti.baseColour = glm::vec3(1.f);
ti.screenPosition = glm::vec2(vp.x * 0.5f, vp.y - ti.size * 2.f);
ti.align = TextRenderer::TextInfo::Center;
break;
case OnscreenText::CenterBig:
ti.size = 30.f;
ti.font = 1;
ti.baseColour = glm::vec3(82, 114, 128) / 255.f;
ti.align = TextRenderer::TextInfo::Center;
ti.screenPosition = vp / 2;
ti.screenPosition += glm::vec2(0.f, ti.size / 2.f);
shadowOffset = glm::vec2(2.f, 0.f);
break;
case OnscreenText::MissionName:
ti.size = 30.f;
ti.font = 1;
ti.baseColour = glm::vec3(205, 162, 7)/255.f;
ti.screenPosition = glm::vec2(vp.x - 10.f, vp.y * 0.79f);
ti.align = TextRenderer::TextInfo::Right;
shadowOffset = glm::vec2(2.f, 2.f);
break;
case OnscreenText::Help:
ti.screenPosition = glm::vec2(20.f, 20.f);
ti.font = 2;
ti.size = 20.f;
ti.baseColour = glm::vec3(1.f);
ti.align = TextRenderer::TextInfo::Left;
break;
default:
ti.size = 15.f;
ti.font = 0;
ti.align = TextRenderer::TextInfo::Left;
ti.baseColour = glm::vec3(1.f);
ti.screenPosition = vp / 2;
break;
case OnscreenText::HighPriority:
ti.size = 16.f;
ti.font = 2;
ti.baseColour = glm::vec3(1.f);
ti.screenPosition = glm::vec2(vp.x * 0.5f, vp.y * 0.9f);
ti.align = TextRenderer::TextInfo::Center;
break;
case OnscreenText::CenterBig:
ti.size = 30.f;
ti.font = 1;
ti.baseColour = glm::vec3(82, 114, 128) / 255.f;
ti.align = TextRenderer::TextInfo::Center;
ti.screenPosition = vp / 2;
ti.screenPosition += glm::vec2(0.f, ti.size / 2.f);
shadowOffset = glm::vec2(2.f, 0.f);
break;
case OnscreenText::MissionName:
ti.size = 30.f;
ti.font = 1;
ti.baseColour = glm::vec3(205, 162, 7)/255.f;
ti.screenPosition = glm::vec2(vp.x - 10.f, vp.y * 0.79f);
ti.align = TextRenderer::TextInfo::Right;
shadowOffset = glm::vec2(2.f, 2.f);
break;
case OnscreenText::Help:
ti.screenPosition = glm::vec2(20.f, 20.f);
ti.font = 2;
ti.size = 20.f;
ti.baseColour = glm::vec3(1.f);
ti.align = TextRenderer::TextInfo::Left;
break;
}
ti.text = t.osTextString;