mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2025-01-17 22:55:38 +00:00
Implemented dynamic texture allocation to the font handling.
This commit is contained in:
parent
4cedd9119f
commit
610ac9adb3
|
@ -22,12 +22,12 @@ Font::Font(int size, const std::string& path)
|
||||||
, mFontSize(size)
|
, mFontSize(size)
|
||||||
, mMaxGlyphHeight {0}
|
, mMaxGlyphHeight {0}
|
||||||
{
|
{
|
||||||
if (mFontSize < 9) {
|
if (mFontSize < 3) {
|
||||||
mFontSize = 9;
|
mFontSize = 3;
|
||||||
LOG(LogWarning) << "Requested font size too small, changing to minimum supported size";
|
LOG(LogWarning) << "Requested font size too small, changing to minimum supported size";
|
||||||
}
|
}
|
||||||
else if (mFontSize > Renderer::getScreenHeight()) {
|
else if (mFontSize > Renderer::getScreenHeight() * 1.5f) {
|
||||||
mFontSize = static_cast<int>(Renderer::getScreenHeight());
|
mFontSize = static_cast<int>(Renderer::getScreenHeight() * 1.5f);
|
||||||
LOG(LogWarning) << "Requested font size too large, changing to maximum supported size";
|
LOG(LogWarning) << "Requested font size too large, changing to maximum supported size";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,12 +205,12 @@ TextCache* Font::buildTextCache(const std::string& text,
|
||||||
cache->vertexLists.resize(vertMap.size());
|
cache->vertexLists.resize(vertMap.size());
|
||||||
cache->metrics = {sizeText(text, lineSpacing)};
|
cache->metrics = {sizeText(text, lineSpacing)};
|
||||||
|
|
||||||
unsigned int i {0};
|
size_t i {0};
|
||||||
for (auto it = vertMap.cbegin(); it != vertMap.cend(); ++it) {
|
for (auto it = vertMap.cbegin(); it != vertMap.cend(); ++it) {
|
||||||
TextCache::VertexList& vertList = cache->vertexLists.at(i);
|
TextCache::VertexList& vertList = cache->vertexLists.at(i);
|
||||||
|
|
||||||
vertList.textureIdPtr = &it->first->textureId;
|
vertList.textureIdPtr = &it->first->textureId;
|
||||||
vertList.verts = it->second;
|
vertList.verts = it->second;
|
||||||
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearFaceCache();
|
clearFaceCache();
|
||||||
|
@ -402,23 +402,24 @@ float Font::getLetterHeight()
|
||||||
|
|
||||||
std::shared_ptr<Font> Font::getFromTheme(const ThemeData::ThemeElement* elem,
|
std::shared_ptr<Font> Font::getFromTheme(const ThemeData::ThemeElement* elem,
|
||||||
unsigned int properties,
|
unsigned int properties,
|
||||||
const std::shared_ptr<Font>& orig)
|
const std::shared_ptr<Font>& orig,
|
||||||
|
const float maxHeight)
|
||||||
{
|
{
|
||||||
using namespace ThemeFlags;
|
using namespace ThemeFlags;
|
||||||
if (!(properties & FONT_PATH) && !(properties & FONT_SIZE))
|
if (!(properties & FONT_PATH) && !(properties & FONT_SIZE))
|
||||||
return orig;
|
return orig;
|
||||||
|
|
||||||
std::shared_ptr<Font> font;
|
|
||||||
int size {static_cast<int>(orig ? orig->mFontSize : FONT_SIZE_MEDIUM)};
|
int size {static_cast<int>(orig ? orig->mFontSize : FONT_SIZE_MEDIUM)};
|
||||||
std::string path {orig ? orig->mPath : getDefaultPath()};
|
std::string path {orig ? orig->mPath : getDefaultPath()};
|
||||||
|
|
||||||
float sh {static_cast<float>(Renderer::getScreenHeight())};
|
float screenHeight {static_cast<float>(Renderer::getScreenHeight())};
|
||||||
|
|
||||||
// Make sure the size is not unreasonably large (which may be caused by a mistake in the
|
|
||||||
// theme configuration).
|
|
||||||
if (properties & FONT_SIZE && elem->has("fontSize"))
|
if (properties & FONT_SIZE && elem->has("fontSize"))
|
||||||
size = glm::clamp(static_cast<int>(sh * elem->get<float>("fontSize")), 0,
|
size = static_cast<int>(glm::clamp(screenHeight * elem->get<float>("fontSize"),
|
||||||
static_cast<int>(Renderer::getInstance()->getScreenHeight()));
|
screenHeight * 0.001f, screenHeight * 1.5f));
|
||||||
|
|
||||||
|
if (maxHeight != 0.0f && static_cast<float>(size) > maxHeight)
|
||||||
|
size = static_cast<int>(maxHeight);
|
||||||
|
|
||||||
if (properties & FONT_PATH && elem->has("fontPath"))
|
if (properties & FONT_PATH && elem->has("fontPath"))
|
||||||
path = elem->get<std::string>("fontPath");
|
path = elem->get<std::string>("fontPath");
|
||||||
|
@ -438,7 +439,7 @@ size_t Font::getMemUsage() const
|
||||||
{
|
{
|
||||||
size_t memUsage {0};
|
size_t memUsage {0};
|
||||||
for (auto it = mTextures.cbegin(); it != mTextures.cend(); ++it)
|
for (auto it = mTextures.cbegin(); it != mTextures.cend(); ++it)
|
||||||
memUsage += it->textureSize.x * it->textureSize.y * 4;
|
memUsage += (*it)->textureSize.x * (*it)->textureSize.y * 4;
|
||||||
|
|
||||||
for (auto it = mFaceCache.cbegin(); it != mFaceCache.cend(); ++it)
|
for (auto it = mFaceCache.cbegin(); it != mFaceCache.cend(); ++it)
|
||||||
memUsage += it->second->data.length;
|
memUsage += it->second->data.length;
|
||||||
|
@ -495,36 +496,12 @@ std::vector<std::string> Font::getFallbackFontPaths()
|
||||||
Font::FontTexture::FontTexture(const int mFontSize)
|
Font::FontTexture::FontTexture(const int mFontSize)
|
||||||
{
|
{
|
||||||
textureId = 0;
|
textureId = 0;
|
||||||
|
|
||||||
// This is a hack to add some extra texture size when running at very low resolutions. If not
|
|
||||||
// doing this, the use of fallback fonts (such as Japanese characters) could result in the
|
|
||||||
// texture not fitting the glyphs.
|
|
||||||
int extraTextureSize {0};
|
|
||||||
const float screenSizeModifier {
|
|
||||||
std::min(Renderer::getScreenWidthModifier(), Renderer::getScreenHeightModifier())};
|
|
||||||
|
|
||||||
if (screenSizeModifier < 0.2f)
|
|
||||||
extraTextureSize += 6;
|
|
||||||
if (screenSizeModifier < 0.45f)
|
|
||||||
extraTextureSize += 4;
|
|
||||||
|
|
||||||
// It's not entirely clear if the 20 and 22 constants are correct, but they seem to provide
|
|
||||||
// a texture buffer large enough to hold the fonts. This logic is obviously a hack though
|
|
||||||
// and needs to be properly reviewed and improved.
|
|
||||||
textureSize =
|
|
||||||
glm::ivec2 {mFontSize * (20 + extraTextureSize), mFontSize * (22 + extraTextureSize / 2)};
|
|
||||||
|
|
||||||
// Make sure the size is not unreasonably large (which may be caused by a mistake in the
|
|
||||||
// theme configuration).
|
|
||||||
if (textureSize.x > static_cast<int>(Renderer::getScreenWidth()) * 10)
|
|
||||||
textureSize.x =
|
|
||||||
glm::clamp(textureSize.x, 0, static_cast<int>(Renderer::getScreenWidth()) * 10);
|
|
||||||
if (textureSize.y > static_cast<int>(Renderer::getScreenHeight()) * 10)
|
|
||||||
textureSize.y =
|
|
||||||
glm::clamp(textureSize.y, 0, static_cast<int>(Renderer::getScreenHeight()) * 10);
|
|
||||||
|
|
||||||
writePos = glm::ivec2 {0, 0};
|
|
||||||
rowHeight = 0;
|
rowHeight = 0;
|
||||||
|
writePos = glm::ivec2 {0, 0};
|
||||||
|
|
||||||
|
// Set the texture to a reasonable size, if we run out of space for adding glyphs then
|
||||||
|
// more textures will be created dynamically.
|
||||||
|
textureSize = glm::ivec2 {mFontSize * 6, mFontSize * 6};
|
||||||
}
|
}
|
||||||
|
|
||||||
Font::FontTexture::~FontTexture()
|
Font::FontTexture::~FontTexture()
|
||||||
|
@ -540,16 +517,14 @@ bool Font::FontTexture::findEmpty(const glm::ivec2& size, glm::ivec2& cursor_out
|
||||||
|
|
||||||
if (writePos.x + size.x >= textureSize.x &&
|
if (writePos.x + size.x >= textureSize.x &&
|
||||||
writePos.y + rowHeight + size.y + 1 < textureSize.y) {
|
writePos.y + rowHeight + size.y + 1 < textureSize.y) {
|
||||||
// Row full, but it should fit on the next row so move the cursor there.
|
// Row is full, but the glyph should fit on the next row so move the cursor there.
|
||||||
// Leave 1px of space between glyphs.
|
// Leave 1px of space between glyphs.
|
||||||
writePos = glm::ivec2 {0, writePos.y + rowHeight + 1};
|
writePos = glm::ivec2 {0, writePos.y + rowHeight + 1};
|
||||||
rowHeight = 0;
|
rowHeight = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (writePos.x + size.x >= textureSize.x || writePos.y + size.y >= textureSize.y) {
|
if (writePos.x + size.x >= textureSize.x || writePos.y + size.y >= textureSize.y)
|
||||||
// Nope, still won't fit.
|
return false; // No it still won't fit.
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor_out = writePos;
|
cursor_out = writePos;
|
||||||
// Leave 1px of space between glyphs.
|
// Leave 1px of space between glyphs.
|
||||||
|
@ -608,7 +583,7 @@ void Font::rebuildTextures()
|
||||||
{
|
{
|
||||||
// Recreate OpenGL textures.
|
// Recreate OpenGL textures.
|
||||||
for (auto it = mTextures.begin(); it != mTextures.end(); ++it)
|
for (auto it = mTextures.begin(); it != mTextures.end(); ++it)
|
||||||
it->initTexture();
|
(*it)->initTexture();
|
||||||
|
|
||||||
// Re-upload the texture data.
|
// Re-upload the texture data.
|
||||||
for (auto it = mGlyphMap.cbegin(); it != mGlyphMap.cend(); ++it) {
|
for (auto it = mGlyphMap.cbegin(); it != mGlyphMap.cend(); ++it) {
|
||||||
|
@ -635,7 +610,7 @@ void Font::rebuildTextures()
|
||||||
void Font::unloadTextures()
|
void Font::unloadTextures()
|
||||||
{
|
{
|
||||||
for (auto it = mTextures.begin(); it != mTextures.end(); ++it)
|
for (auto it = mTextures.begin(); it != mTextures.end(); ++it)
|
||||||
it->deinitTexture();
|
(*it)->deinitTexture();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Font::getTextureForNewGlyph(const glm::ivec2& glyphSize,
|
void Font::getTextureForNewGlyph(const glm::ivec2& glyphSize,
|
||||||
|
@ -643,29 +618,19 @@ void Font::getTextureForNewGlyph(const glm::ivec2& glyphSize,
|
||||||
glm::ivec2& cursor_out)
|
glm::ivec2& cursor_out)
|
||||||
{
|
{
|
||||||
if (mTextures.size()) {
|
if (mTextures.size()) {
|
||||||
// Check if the most recent texture has space.
|
// Check if the most recent texture has space available for the glyph.
|
||||||
tex_out = &mTextures.back();
|
tex_out = mTextures.back().get();
|
||||||
|
|
||||||
// Will this one work?
|
// Will this one work?
|
||||||
if (tex_out->findEmpty(glyphSize, cursor_out))
|
if (tex_out->findEmpty(glyphSize, cursor_out))
|
||||||
return; // Yes.
|
return; // Yes.
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should never happen, assuming the texture size is large enough to fit the font,
|
mTextures.emplace_back(std::make_unique<FontTexture>(mFontSize));
|
||||||
// as set in the FontTexture constructor. In the unlikely situation that it still happens,
|
tex_out = mTextures.back().get();
|
||||||
// setting the texture to nullptr makes sure the application doesn't crash and that the
|
|
||||||
// user is clearly notified of the problem by the fact that the glyph/character will be
|
|
||||||
// completely missing.
|
|
||||||
if (mGlyphMap.size() > 0) {
|
|
||||||
tex_out = nullptr;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mTextures.push_back(FontTexture(mFontSize));
|
|
||||||
tex_out = &mTextures.back();
|
|
||||||
tex_out->initTexture();
|
tex_out->initTexture();
|
||||||
|
|
||||||
bool ok = tex_out->findEmpty(glyphSize, cursor_out);
|
bool ok {tex_out->findEmpty(glyphSize, cursor_out)};
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOG(LogError) << "Glyph too big to fit on a new texture (glyph size > "
|
LOG(LogError) << "Glyph too big to fit on a new texture (glyph size > "
|
||||||
<< tex_out->textureSize.x << ", " << tex_out->textureSize.y << ")";
|
<< tex_out->textureSize.x << ", " << tex_out->textureSize.y << ")";
|
||||||
|
@ -677,15 +642,11 @@ FT_Face Font::getFaceForChar(unsigned int id)
|
||||||
{
|
{
|
||||||
static const std::vector<std::string> fallbackFonts {getFallbackFontPaths()};
|
static const std::vector<std::string> fallbackFonts {getFallbackFontPaths()};
|
||||||
|
|
||||||
// Look through our current font + fallback fonts to see if any have the
|
// Look for the glyph in our current font and then in the fallback fonts if needed.
|
||||||
// glyph we're looking for.
|
|
||||||
for (unsigned int i = 0; i < fallbackFonts.size() + 1; ++i) {
|
for (unsigned int i = 0; i < fallbackFonts.size() + 1; ++i) {
|
||||||
auto fit = mFaceCache.find(i);
|
auto fit = mFaceCache.find(i);
|
||||||
|
|
||||||
// Doesn't exist yet.
|
|
||||||
if (fit == mFaceCache.cend()) {
|
if (fit == mFaceCache.cend()) {
|
||||||
// i == 0 -> mPath
|
|
||||||
// Otherwise, take from fallbackFonts.
|
|
||||||
const std::string& path {i == 0 ? mPath : fallbackFonts.at(i - 1)};
|
const std::string& path {i == 0 ? mPath : fallbackFonts.at(i - 1)};
|
||||||
ResourceData data {ResourceManager::getInstance().getFileData(path)};
|
ResourceData data {ResourceManager::getInstance().getFileData(path)};
|
||||||
mFaceCache[i] =
|
mFaceCache[i] =
|
||||||
|
@ -697,18 +658,18 @@ FT_Face Font::getFaceForChar(unsigned int id)
|
||||||
return fit->second->face;
|
return fit->second->face;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing has a valid glyph - return the "real" face so we get a "missing" character.
|
// Couldn't find a valid glyph, return the "real" face so we get a "missing" character.
|
||||||
return mFaceCache.cbegin()->second->face;
|
return mFaceCache.cbegin()->second->face;
|
||||||
}
|
}
|
||||||
|
|
||||||
Font::Glyph* Font::getGlyph(const unsigned int id)
|
Font::Glyph* Font::getGlyph(const unsigned int id)
|
||||||
{
|
{
|
||||||
// Is it already loaded?
|
// Check if the glyph has already been loaded.
|
||||||
auto it = mGlyphMap.find(id);
|
auto it = mGlyphMap.find(id);
|
||||||
if (it != mGlyphMap.cend())
|
if (it != mGlyphMap.cend())
|
||||||
return &it->second;
|
return &it->second;
|
||||||
|
|
||||||
// Nope, need to make a glyph.
|
// We need to create a new entry.
|
||||||
FT_Face face {getFaceForChar(id)};
|
FT_Face face {getFaceForChar(id)};
|
||||||
if (!face) {
|
if (!face) {
|
||||||
LOG(LogError) << "Couldn't find appropriate font face for character " << id << " for font "
|
LOG(LogError) << "Couldn't find appropriate font face for character " << id << " for font "
|
||||||
|
@ -716,22 +677,29 @@ Font::Glyph* Font::getGlyph(const unsigned int id)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
FT_GlyphSlot g {face->glyph};
|
const FT_GlyphSlot glyphSlot {face->glyph};
|
||||||
|
|
||||||
if (FT_Load_Char(face, id, FT_LOAD_RENDER)) {
|
// TODO: Evaluate/test hinting when HarfBuzz has been added.
|
||||||
|
// If the font does not contain hinting information then force the use of the automatic
|
||||||
|
// hinter that is built into FreeType.
|
||||||
|
// const bool hasHinting {static_cast<bool>(glyphSlot->face->face_flags & FT_FACE_FLAG_HINTER)};
|
||||||
|
const bool hasHinting {true};
|
||||||
|
|
||||||
|
if (FT_Load_Char(face, id,
|
||||||
|
(hasHinting ?
|
||||||
|
FT_LOAD_RENDER :
|
||||||
|
FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT))) {
|
||||||
LOG(LogError) << "Couldn't find glyph for character " << id << " for font " << mPath
|
LOG(LogError) << "Couldn't find glyph for character " << id << " for font " << mPath
|
||||||
<< ", size " << mFontSize;
|
<< ", size " << mFontSize;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::ivec2 glyphSize {g->bitmap.width, g->bitmap.rows};
|
|
||||||
|
|
||||||
FontTexture* tex {nullptr};
|
FontTexture* tex {nullptr};
|
||||||
glm::ivec2 cursor {0, 0};
|
glm::ivec2 cursor {0, 0};
|
||||||
|
const glm::ivec2 glyphSize {glyphSlot->bitmap.width, glyphSlot->bitmap.rows};
|
||||||
getTextureForNewGlyph(glyphSize, tex, cursor);
|
getTextureForNewGlyph(glyphSize, tex, cursor);
|
||||||
|
|
||||||
// getTextureForNewGlyph can fail if the glyph is bigger than the max texture
|
// This should (hopefully) never occur as size constraints are enforced earlier on.
|
||||||
// size (absurdly large font size).
|
|
||||||
if (tex == nullptr) {
|
if (tex == nullptr) {
|
||||||
LOG(LogError) << "Couldn't create glyph for character " << id << " for font " << mPath
|
LOG(LogError) << "Couldn't create glyph for character " << id << " for font " << mPath
|
||||||
<< ", size " << mFontSize << " (no suitable texture found)";
|
<< ", size " << mFontSize << " (no suitable texture found)";
|
||||||
|
@ -747,20 +715,18 @@ Font::Glyph* Font::getGlyph(const unsigned int id)
|
||||||
glyph.texSize = glm::vec2 {glyphSize.x / static_cast<float>(tex->textureSize.x),
|
glyph.texSize = glm::vec2 {glyphSize.x / static_cast<float>(tex->textureSize.x),
|
||||||
glyphSize.y / static_cast<float>(tex->textureSize.y)};
|
glyphSize.y / static_cast<float>(tex->textureSize.y)};
|
||||||
|
|
||||||
glyph.advance = glm::vec2 {static_cast<float>(g->metrics.horiAdvance) / 64.0f,
|
glyph.advance = glm::vec2 {static_cast<float>(glyphSlot->metrics.horiAdvance) / 64.0f,
|
||||||
static_cast<float>(g->metrics.vertAdvance) / 64.0f};
|
static_cast<float>(glyphSlot->metrics.vertAdvance) / 64.0f};
|
||||||
glyph.bearing = glm::vec2 {static_cast<float>(g->metrics.horiBearingX) / 64.0f,
|
glyph.bearing = glm::vec2 {static_cast<float>(glyphSlot->metrics.horiBearingX) / 64.0f,
|
||||||
static_cast<float>(g->metrics.horiBearingY) / 64.0f};
|
static_cast<float>(glyphSlot->metrics.horiBearingY) / 64.0f};
|
||||||
|
|
||||||
// Upload glyph bitmap to texture.
|
// Upload glyph bitmap to texture.
|
||||||
mRenderer->updateTexture(tex->textureId, Renderer::TextureType::RED, cursor.x, cursor.y,
|
mRenderer->updateTexture(tex->textureId, Renderer::TextureType::RED, cursor.x, cursor.y,
|
||||||
glyphSize.x, glyphSize.y, g->bitmap.buffer);
|
glyphSize.x, glyphSize.y, glyphSlot->bitmap.buffer);
|
||||||
|
|
||||||
// Update max glyph height.
|
|
||||||
if (glyphSize.y > mMaxGlyphHeight)
|
if (glyphSize.y > mMaxGlyphHeight)
|
||||||
mMaxGlyphHeight = glyphSize.y;
|
mMaxGlyphHeight = glyphSize.y;
|
||||||
|
|
||||||
// Done.
|
|
||||||
return &glyph;
|
return &glyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,8 @@ public:
|
||||||
|
|
||||||
static std::shared_ptr<Font> getFromTheme(const ThemeData::ThemeElement* elem,
|
static std::shared_ptr<Font> getFromTheme(const ThemeData::ThemeElement* elem,
|
||||||
unsigned int properties,
|
unsigned int properties,
|
||||||
const std::shared_ptr<Font>& orig);
|
const std::shared_ptr<Font>& orig,
|
||||||
|
const float maxHeight = 0.0f);
|
||||||
|
|
||||||
// Returns an approximation of VRAM used by this font's texture (in bytes).
|
// Returns an approximation of VRAM used by this font's texture (in bytes).
|
||||||
size_t getMemUsage() const;
|
size_t getMemUsage() const;
|
||||||
|
@ -159,11 +160,10 @@ private:
|
||||||
|
|
||||||
static inline FT_Library sLibrary {nullptr};
|
static inline FT_Library sLibrary {nullptr};
|
||||||
static inline std::map<std::pair<std::string, int>, std::weak_ptr<Font>> sFontMap;
|
static inline std::map<std::pair<std::string, int>, std::weak_ptr<Font>> sFontMap;
|
||||||
static inline std::vector<std::string> mFallbackFonts;
|
|
||||||
|
|
||||||
Renderer* mRenderer;
|
Renderer* mRenderer;
|
||||||
|
std::vector<std::unique_ptr<FontTexture>> mTextures;
|
||||||
std::map<unsigned int, std::unique_ptr<FontFace>> mFaceCache;
|
std::map<unsigned int, std::unique_ptr<FontFace>> mFaceCache;
|
||||||
std::vector<FontTexture> mTextures;
|
|
||||||
std::map<unsigned int, Glyph> mGlyphMap;
|
std::map<unsigned int, Glyph> mGlyphMap;
|
||||||
|
|
||||||
const std::string mPath;
|
const std::string mPath;
|
||||||
|
|
Loading…
Reference in a new issue