mirror of
https://github.com/rwengine/openrw.git
synced 2024-11-25 11:52:40 +01:00
Improve TextRenderer kerning and performance
This commit is contained in:
parent
a773262328
commit
3801e69e81
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user