Implement optimizations for 2D text rendering (#23)
Some checks failed
Run ReCI Build Test / Explore-Gitea-Actions (push) Failing after 1m51s
Some checks failed
Run ReCI Build Test / Explore-Gitea-Actions (push) Failing after 1m51s
The following patches are included: ## J2D: Rewrite text rendering This patch rewrites text rendering for `J2D::DrawString` to now construct a texture atlas for all ASCII-range glyphs in the FT font face, instead of constructing a texture for every glyph. This improves text rendering performance for several reasons: 1. Binding textures is relatively expensive as the GPU is required to do a context switch for internal data like texture parameters, and also cannot optimize for accesses to the same texture across draw calls. This patch removes the need to call `glBindTexture` more than once per call to `J2D::DrawString`. 2. As a consequence of the above, all glyphs for a given string can now be rendered in a single call to `glDrawArrays`. This is done by storing the cached texture coordinates on `CachedGlyph` and constructing a full array of vertices and texture coordinates for the entire string at once, resulting in only /one/ set of client-to-device attribute uploads and only one draw call, instead of being required to upload attribute data for each glyph separately. ## FontCache: Use map for efficient glyph lookup This patch updates `CachedFont` to now use an `std::map` for cached glyphs, instead of an `std::vector`. `std::map` allows O(log n) lookup, whereas `std::vector` only allows O(n) lookup. Note: `std::unordered_map` technically has better lookup complexity here, with amortized O(1) lookup. However, hashmaps have a higher inherent overhead than red-black trees so this would only be viable when going above around 1000 entries, which should never happen here for ASCII glyphs. Co-authored-by: Ori Sky Farrell <ori.sky.farrell+git@gmail.com> Reviewed-on: #23 Co-authored-by: ori_sky <redacted@ori.mx> Co-committed-by: ori_sky <redacted@ori.mx>
This commit is contained in:
@@ -7,12 +7,11 @@ char CachedGlyph::getCharacter() {
|
||||
return character;
|
||||
}
|
||||
|
||||
const GLuint* CachedGlyph::getTexture() {
|
||||
return &texture;
|
||||
const std::array<GLfloat, 12> CachedGlyph::getTexCoords() const {
|
||||
return texcoords;
|
||||
}
|
||||
|
||||
CachedGlyph::CachedGlyph(GLuint texture_id, char c, float x2offset, float y2offset, float w, float h, float advanceX, float advanceY) {
|
||||
texture = texture_id;
|
||||
CachedGlyph::CachedGlyph(char c, std::array<GLfloat, 12> texcoords, float x2offset, float y2offset, float w, float h, float advanceX, float advanceY) {
|
||||
character = c;
|
||||
this->x2offset = x2offset;
|
||||
this->y2offset = y2offset;
|
||||
@@ -20,13 +19,14 @@ CachedGlyph::CachedGlyph(GLuint texture_id, char c, float x2offset, float y2offs
|
||||
this->h = h;
|
||||
this->advanceX = advanceX;
|
||||
this->advanceY = advanceY;
|
||||
this->texcoords = texcoords;
|
||||
}
|
||||
|
||||
//TODO
|
||||
//Because most things shown would be english characters. We can cut down on the iteration time significantly
|
||||
//by putting each english character at the beginning of the list in order of how often they usually occur in text.
|
||||
void JGL::CachedFont::appendGlyph(JGL::CachedGlyph* glyph) {
|
||||
glyphs.push_back(glyph);
|
||||
glyphs.emplace(glyph->getCharacter(), glyph);
|
||||
}
|
||||
|
||||
unsigned int JGL::CachedFont::getFontSize() {
|
||||
@@ -38,63 +38,48 @@ unsigned int JGL::CachedFont::getFontIndex() {
|
||||
}
|
||||
|
||||
CachedGlyph* JGL::CachedFont::getGlyph(char c) {
|
||||
for (const auto& g : glyphs)
|
||||
if (c == g->getCharacter())
|
||||
return g;
|
||||
auto it = glyphs.find(c);
|
||||
if (it != glyphs.end())
|
||||
return it->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CachedFont::CachedFont(unsigned int font_size, unsigned int font_index) {
|
||||
CachedFont::CachedFont(GLuint texture_id, GLsizei texture_width, GLsizei texture_height, unsigned int font_size, unsigned int font_index) {
|
||||
this->texture = texture_id;
|
||||
this->texture_width = texture_width;
|
||||
this->texture_height = texture_height;
|
||||
this->font_size = font_size;
|
||||
this->font_index = font_index;
|
||||
}
|
||||
|
||||
void CachedFont::eraseGlyph(CachedGlyph* glyph) {
|
||||
if (glyph == nullptr)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < glyphs.size(); i++)
|
||||
if (glyphs[i] == glyph)
|
||||
glDeleteTextures(1, glyphs[i]->getTexture()),
|
||||
delete glyphs[i],
|
||||
glyphs.erase(glyphs.begin() + i);
|
||||
}
|
||||
|
||||
void CachedFont::eraseGlyph(char c) {
|
||||
for (int i = 0; i < glyphs.size(); i++)
|
||||
if (glyphs[i]->getCharacter() == c)
|
||||
glDeleteTextures(1, glyphs[i]->getTexture()),
|
||||
delete glyphs[i],
|
||||
glyphs.erase(glyphs.begin() + i);
|
||||
}
|
||||
|
||||
void CachedFont::eraseGlyph(GLuint texture_id) {
|
||||
for (int i = 0; i < glyphs.size(); i++)
|
||||
if (glyphs[i]->getTexture() == &texture_id)
|
||||
glDeleteTextures(1, glyphs[i]->getTexture()),
|
||||
delete glyphs[i],
|
||||
glyphs.erase(glyphs.begin() + i);
|
||||
}
|
||||
|
||||
std::vector<CachedGlyph*> CachedFont::getGlyphs() {
|
||||
std::map<char, CachedGlyph*> CachedFont::getGlyphs() {
|
||||
return glyphs;
|
||||
}
|
||||
|
||||
const GLuint* CachedFont::getTexture() {
|
||||
return &texture;
|
||||
}
|
||||
|
||||
const GLsizei CachedFont::getTextureWidth() const {
|
||||
return texture_width;
|
||||
}
|
||||
|
||||
const GLsizei CachedFont::getTextureHeight() const {
|
||||
return texture_height;
|
||||
}
|
||||
|
||||
void FontCache::appendFont(CachedFont* font) {
|
||||
cachedFonts.push_back(font);
|
||||
}
|
||||
|
||||
void FontCache::newFont(unsigned int font_size, unsigned int font_index) {
|
||||
auto* font = new CachedFont(font_size, font_index);
|
||||
void FontCache::newFont(GLuint texture_id, GLsizei texture_width, GLsizei texture_height, unsigned int font_size, unsigned int font_index) {
|
||||
auto* font = new CachedFont(texture_id, texture_width, texture_height, font_size, font_index);
|
||||
cachedFonts.push_back(font);
|
||||
}
|
||||
|
||||
void FontCache::eraseFont(CachedFont* font) {
|
||||
for (int i = 0; i < cachedFonts.size(); i++) {
|
||||
if (cachedFonts[i] == font) {
|
||||
for (auto& g: cachedFonts[i]->getGlyphs())
|
||||
cachedFonts[i]->eraseGlyph(g);
|
||||
|
||||
delete cachedFonts[i];
|
||||
cachedFonts.erase(cachedFonts.begin() + i);
|
||||
}
|
||||
|
Reference in New Issue
Block a user