mirror of https://github.com/AxioDL/metaforce.git
Initial font caching implementation
This commit is contained in:
parent
02cfa7ec74
commit
73581ffc53
|
@ -44,6 +44,8 @@ list(APPEND SPECTER_HEADERS
|
||||||
include/Specter/NodeSocket.hpp
|
include/Specter/NodeSocket.hpp
|
||||||
include/Specter/FontCache.hpp)
|
include/Specter/FontCache.hpp)
|
||||||
|
|
||||||
|
atdna(atdna_FontCache.cpp include/Specter/FontCache.hpp)
|
||||||
|
|
||||||
list(APPEND SPECTER_SOURCES
|
list(APPEND SPECTER_SOURCES
|
||||||
lib/Specter.cpp
|
lib/Specter.cpp
|
||||||
lib/View.cpp
|
lib/View.cpp
|
||||||
|
@ -60,6 +62,7 @@ list(APPEND SPECTER_SOURCES
|
||||||
lib/Menu.cpp
|
lib/Menu.cpp
|
||||||
lib/Node.cpp
|
lib/Node.cpp
|
||||||
lib/NodeSocket.cpp
|
lib/NodeSocket.cpp
|
||||||
lib/FontCache.cpp)
|
lib/FontCache.cpp
|
||||||
|
atdna_FontCache.cpp)
|
||||||
|
|
||||||
add_library(Specter ${SPECTER_SOURCES} ${SPECTER_HEADERS})
|
add_library(Specter ${SPECTER_SOURCES} ${SPECTER_HEADERS})
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit bddbb5dd8a91591218ba2f50f4ef1c90ed8a4ca7
|
Subproject commit 4998fba725b01da1a659ed09388ea4207ff03b02
|
|
@ -6,6 +6,30 @@
|
||||||
|
|
||||||
#include <boo/boo.hpp>
|
#include <boo/boo.hpp>
|
||||||
#include <HECL/Runtime.hpp>
|
#include <HECL/Runtime.hpp>
|
||||||
|
#include <Athena/FileReader.hpp>
|
||||||
|
#include <Athena/FileWriter.hpp>
|
||||||
|
|
||||||
|
namespace Specter
|
||||||
|
{
|
||||||
|
class FontTag
|
||||||
|
{
|
||||||
|
friend class FontCache;
|
||||||
|
uint64_t m_hash;
|
||||||
|
FontTag(const std::string& name, bool subpixel, float points, unsigned dpi);
|
||||||
|
public:
|
||||||
|
uint64_t hash() const {return m_hash;}
|
||||||
|
bool operator==(const FontTag& other) const {return m_hash == other.m_hash;}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace std
|
||||||
|
{
|
||||||
|
template <> struct hash<Specter::FontTag>
|
||||||
|
{
|
||||||
|
size_t operator() (const Specter::FontTag& handle) const NOEXCEPT
|
||||||
|
{return size_t(handle.hash());}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
namespace Specter
|
namespace Specter
|
||||||
{
|
{
|
||||||
|
@ -21,19 +45,37 @@ public:
|
||||||
operator FT_Face() {return m_face;}
|
operator FT_Face() {return m_face;}
|
||||||
};
|
};
|
||||||
|
|
||||||
class FontHandle
|
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
class FontAtlas
|
class FontAtlas
|
||||||
{
|
{
|
||||||
|
friend class FontCache;
|
||||||
FT_Face m_face;
|
FT_Face m_face;
|
||||||
std::vector<boo::ITextureS*> m_texs;
|
boo::ITextureS* m_tex;
|
||||||
|
|
||||||
|
struct Glyph
|
||||||
|
{
|
||||||
|
atUint32 m_unicodePoint;
|
||||||
|
atUint32 m_layerIdx;
|
||||||
|
float m_uv[4];
|
||||||
|
atInt8 m_leftPadding;
|
||||||
|
atInt8 m_advance;
|
||||||
|
atInt8 m_rightPadding;
|
||||||
|
atUint8 m_width;
|
||||||
|
atUint8 m_height;
|
||||||
|
atInt8 m_verticalOffset;
|
||||||
|
atUint16 m_kernIdx;
|
||||||
|
};
|
||||||
|
std::vector<Glyph> m_glyphs;
|
||||||
|
std::map<atUint32, size_t> m_glyphLookup;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FontAtlas(boo::IGraphicsDataFactory* gf, FT_Face face, bool subpixel, Athena::io::FileWriter& writer);
|
||||||
|
FontAtlas(boo::IGraphicsDataFactory* gf, FT_Face face, bool subpixel, Athena::io::FileReader& reader);
|
||||||
};
|
};
|
||||||
|
|
||||||
class FontCache
|
class FontCache
|
||||||
{
|
{
|
||||||
const HECL::Runtime::FileStoreManager& m_fileMgr;
|
const HECL::Runtime::FileStoreManager& m_fileMgr;
|
||||||
|
HECL::SystemString m_cacheRoot;
|
||||||
struct Library
|
struct Library
|
||||||
{
|
{
|
||||||
FT_Library m_lib;
|
FT_Library m_lib;
|
||||||
|
@ -43,12 +85,20 @@ class FontCache
|
||||||
} m_fontLib;
|
} m_fontLib;
|
||||||
FreeTypeGZipMemFace m_regFace;
|
FreeTypeGZipMemFace m_regFace;
|
||||||
FreeTypeGZipMemFace m_monoFace;
|
FreeTypeGZipMemFace m_monoFace;
|
||||||
|
|
||||||
|
std::unordered_map<FontTag, std::unique_ptr<FontAtlas>> m_cachedAtlases;
|
||||||
public:
|
public:
|
||||||
FontCache(const HECL::Runtime::FileStoreManager& fileMgr);
|
FontCache(const HECL::Runtime::FileStoreManager& fileMgr);
|
||||||
|
|
||||||
FontHandle prepMainFont(float points=10.0);
|
FontTag prepCustomFont(boo::IGraphicsDataFactory* gf,
|
||||||
FontHandle prepMonoFont(float points=10.0);
|
const std::string& name, FT_Face face, bool subpixel=false,
|
||||||
FontHandle prepCustomFont(FT_Face face, float points=10.0);
|
float points=10.0, uint32_t dpi=72);
|
||||||
|
FontTag prepMainFont(boo::IGraphicsDataFactory* gf,
|
||||||
|
bool subpixel=false, float points=10.0, uint32_t dpi=72)
|
||||||
|
{return prepCustomFont(gf, "droidsans-permissive", m_regFace, subpixel, points, dpi);}
|
||||||
|
FontTag prepMonoFont(boo::IGraphicsDataFactory* gf,
|
||||||
|
bool subpixel=false, float points=10.0, uint32_t dpi=72)
|
||||||
|
{return prepCustomFont(gf, "bmonofont", m_monoFace, subpixel, points, dpi);}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,17 @@ namespace Specter
|
||||||
{
|
{
|
||||||
static LogVisor::LogModule Log("Specter::FontCache");
|
static LogVisor::LogModule Log("Specter::FontCache");
|
||||||
|
|
||||||
|
FontTag::FontTag(const std::string& name, bool subpixel, float points, uint32_t dpi)
|
||||||
|
{
|
||||||
|
XXH64_state_t st;
|
||||||
|
XXH64_reset(&st, 0);
|
||||||
|
XXH64_update(&st, name.data(), name.size());
|
||||||
|
XXH64_update(&st, &subpixel, 1);
|
||||||
|
XXH64_update(&st, &points, 4);
|
||||||
|
XXH64_update(&st, &dpi, 4);
|
||||||
|
m_hash = XXH64_digest(&st);
|
||||||
|
}
|
||||||
|
|
||||||
FreeTypeGZipMemFace::FreeTypeGZipMemFace(FT_Library lib, const uint8_t* data, size_t sz)
|
FreeTypeGZipMemFace::FreeTypeGZipMemFace(FT_Library lib, const uint8_t* data, size_t sz)
|
||||||
{
|
{
|
||||||
m_comp.base = (unsigned char*)data;
|
m_comp.base = (unsigned char*)data;
|
||||||
|
@ -43,6 +54,237 @@ FreeTypeGZipMemFace::~FreeTypeGZipMemFace()
|
||||||
FT_Done_Face(m_face);
|
FT_Done_Face(m_face);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define TEXMAP_DIM 1024
|
||||||
|
|
||||||
|
static unsigned RoundUpPow2(unsigned v)
|
||||||
|
{
|
||||||
|
v--;
|
||||||
|
v |= v >> 1;
|
||||||
|
v |= v >> 2;
|
||||||
|
v |= v >> 4;
|
||||||
|
v |= v >> 8;
|
||||||
|
v |= v >> 16;
|
||||||
|
v++;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
using GreyPixel = uint8_t;
|
||||||
|
static void MemcpyRect(GreyPixel* img, const FT_Bitmap* bmp, unsigned slice, unsigned x, unsigned y)
|
||||||
|
{
|
||||||
|
unsigned sy = TEXMAP_DIM * slice + y;
|
||||||
|
for (unsigned i=0 ; i<bmp->rows ; ++i)
|
||||||
|
{
|
||||||
|
const unsigned char* s = &bmp->buffer[bmp->pitch*i];
|
||||||
|
GreyPixel* t = &img[TEXMAP_DIM*sy+x];
|
||||||
|
memcpy(t, s, bmp->width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
union RgbaPixel
|
||||||
|
{
|
||||||
|
uint8_t rgba[4];
|
||||||
|
uint32_t pixel;
|
||||||
|
};
|
||||||
|
static void MemcpyRect(RgbaPixel* img, const FT_Bitmap* bmp, unsigned slice, unsigned x, unsigned y)
|
||||||
|
{
|
||||||
|
unsigned sy = TEXMAP_DIM * slice + y;
|
||||||
|
for (unsigned i=0 ; i<bmp->rows ; ++i)
|
||||||
|
{
|
||||||
|
const unsigned char* s = &bmp->buffer[bmp->pitch*i];
|
||||||
|
RgbaPixel* t = &img[TEXMAP_DIM*sy+x];
|
||||||
|
for (unsigned j=0 ; j<bmp->width/3 ; ++j)
|
||||||
|
{
|
||||||
|
t[j].rgba[0] = s[j*3];
|
||||||
|
t[j].rgba[1] = s[j*3+1];
|
||||||
|
t[j].rgba[2] = s[j*3+2];
|
||||||
|
t[j].rgba[3] = 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FontAtlas::FontAtlas(boo::IGraphicsDataFactory* gf, FT_Face face, bool subpixel, Athena::io::FileWriter& writer)
|
||||||
|
{
|
||||||
|
FT_Int32 baseFlags = FT_LOAD_NO_BITMAP;
|
||||||
|
if (subpixel)
|
||||||
|
baseFlags |= FT_LOAD_TARGET_LCD;
|
||||||
|
else
|
||||||
|
baseFlags |= FT_LOAD_TARGET_NORMAL;
|
||||||
|
|
||||||
|
/* First count glyphs exposed by unicode charmap and tally required area */
|
||||||
|
size_t glyphCount = 0;
|
||||||
|
FT_UInt gindex;
|
||||||
|
FT_ULong charcode = FT_Get_First_Char(face, &gindex);
|
||||||
|
unsigned curLineWidth = 0;
|
||||||
|
unsigned curLineHeight = 0;
|
||||||
|
unsigned totalHeight = 0;
|
||||||
|
unsigned fullTexmap = 0;
|
||||||
|
while (gindex != 0)
|
||||||
|
{
|
||||||
|
++glyphCount;
|
||||||
|
FT_Load_Glyph(face, gindex, baseFlags);
|
||||||
|
unsigned width = face->glyph->bitmap.width;
|
||||||
|
if (subpixel)
|
||||||
|
width /= 3;
|
||||||
|
if (curLineWidth + width > TEXMAP_DIM)
|
||||||
|
{
|
||||||
|
if (totalHeight + curLineHeight > TEXMAP_DIM)
|
||||||
|
{
|
||||||
|
totalHeight = 0;
|
||||||
|
++fullTexmap;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
totalHeight += curLineHeight;
|
||||||
|
curLineHeight = 0;
|
||||||
|
curLineWidth = 0;
|
||||||
|
}
|
||||||
|
curLineHeight = std::max(curLineHeight, face->glyph->bitmap.rows);
|
||||||
|
charcode = FT_Get_Next_Char(face, charcode, &gindex);
|
||||||
|
}
|
||||||
|
m_glyphs.reserve(glyphCount);
|
||||||
|
|
||||||
|
totalHeight = RoundUpPow2(totalHeight);
|
||||||
|
unsigned finalHeight = fullTexmap ? TEXMAP_DIM : totalHeight;
|
||||||
|
writer.writeUint32Big(fullTexmap);
|
||||||
|
writer.writeUint32Big(TEXMAP_DIM);
|
||||||
|
writer.writeUint32Big(finalHeight);
|
||||||
|
|
||||||
|
if (subpixel)
|
||||||
|
{
|
||||||
|
/* Allocate texmap */
|
||||||
|
std::unique_ptr<RgbaPixel[]> texmap;
|
||||||
|
size_t bufSz;
|
||||||
|
if (fullTexmap)
|
||||||
|
{
|
||||||
|
size_t count = TEXMAP_DIM * TEXMAP_DIM * (fullTexmap + 1);
|
||||||
|
texmap.reset(new RgbaPixel[count]);
|
||||||
|
bufSz = count * sizeof(RgbaPixel);
|
||||||
|
memset(texmap.get(), 0, bufSz);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size_t count = TEXMAP_DIM * totalHeight;
|
||||||
|
texmap.reset(new RgbaPixel[TEXMAP_DIM * totalHeight]);
|
||||||
|
bufSz = count * sizeof(RgbaPixel);
|
||||||
|
memset(texmap.get(), 0, bufSz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Assemble glyph texmaps and internal data structures */
|
||||||
|
charcode = FT_Get_First_Char(face, &gindex);
|
||||||
|
curLineWidth = 0;
|
||||||
|
curLineHeight = 0;
|
||||||
|
totalHeight = 0;
|
||||||
|
fullTexmap = 0;
|
||||||
|
while (gindex != 0)
|
||||||
|
{
|
||||||
|
FT_Load_Glyph(face, gindex, FT_LOAD_RENDER | baseFlags);
|
||||||
|
m_glyphs.emplace_back();
|
||||||
|
Glyph& g = m_glyphs.back();
|
||||||
|
g.m_unicodePoint = charcode;
|
||||||
|
g.m_layerIdx = fullTexmap;
|
||||||
|
g.m_width = face->glyph->bitmap.width / 3;
|
||||||
|
g.m_height = face->glyph->bitmap.rows;
|
||||||
|
g.m_uv[0] = curLineWidth / float(TEXMAP_DIM);
|
||||||
|
g.m_uv[1] = totalHeight / float(finalHeight);
|
||||||
|
g.m_uv[2] = g.m_uv[0] + g.m_width / float(TEXMAP_DIM);
|
||||||
|
g.m_uv[3] = g.m_uv[1] + g.m_height / float(finalHeight);
|
||||||
|
g.m_leftPadding = 0;
|
||||||
|
g.m_advance = face->glyph->advance.x;
|
||||||
|
g.m_rightPadding = 0;
|
||||||
|
g.m_verticalOffset = face->glyph->metrics.horiBearingY;
|
||||||
|
g.m_kernIdx = 0;
|
||||||
|
MemcpyRect(texmap.get(), &face->glyph->bitmap, fullTexmap, curLineWidth, totalHeight);
|
||||||
|
if (curLineWidth + g.m_width > TEXMAP_DIM)
|
||||||
|
{
|
||||||
|
if (totalHeight + curLineHeight > TEXMAP_DIM)
|
||||||
|
{
|
||||||
|
totalHeight = 0;
|
||||||
|
++fullTexmap;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
totalHeight += curLineHeight;
|
||||||
|
curLineHeight = 0;
|
||||||
|
curLineWidth = 0;
|
||||||
|
}
|
||||||
|
curLineHeight = std::max(curLineHeight, face->glyph->bitmap.rows);
|
||||||
|
charcode = FT_Get_Next_Char(face, charcode, &gindex);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_tex =
|
||||||
|
gf->newStaticArrayTexture(TEXMAP_DIM, finalHeight, fullTexmap + 1,
|
||||||
|
boo::TextureFormat::RGBA8, texmap.get(), bufSz);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Allocate texmap */
|
||||||
|
std::unique_ptr<GreyPixel[]> texmap;
|
||||||
|
size_t bufSz;
|
||||||
|
if (fullTexmap)
|
||||||
|
{
|
||||||
|
size_t count = TEXMAP_DIM * TEXMAP_DIM * (fullTexmap + 1);
|
||||||
|
texmap.reset(new GreyPixel[count]);
|
||||||
|
bufSz = count * sizeof(GreyPixel);
|
||||||
|
memset(texmap.get(), 0, bufSz);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size_t count = TEXMAP_DIM * totalHeight;
|
||||||
|
texmap.reset(new GreyPixel[TEXMAP_DIM * totalHeight]);
|
||||||
|
bufSz = count * sizeof(GreyPixel);
|
||||||
|
memset(texmap.get(), 0, bufSz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Assemble glyph texmaps and internal data structures */
|
||||||
|
charcode = FT_Get_First_Char(face, &gindex);
|
||||||
|
curLineWidth = 0;
|
||||||
|
curLineHeight = 0;
|
||||||
|
totalHeight = 0;
|
||||||
|
fullTexmap = 0;
|
||||||
|
while (gindex != 0)
|
||||||
|
{
|
||||||
|
FT_Load_Glyph(face, gindex, FT_LOAD_RENDER | baseFlags);
|
||||||
|
m_glyphs.emplace_back();
|
||||||
|
Glyph& g = m_glyphs.back();
|
||||||
|
g.m_unicodePoint = charcode;
|
||||||
|
g.m_layerIdx = fullTexmap;
|
||||||
|
g.m_width = face->glyph->bitmap.width;
|
||||||
|
g.m_height = face->glyph->bitmap.rows;
|
||||||
|
g.m_uv[0] = curLineWidth / float(TEXMAP_DIM);
|
||||||
|
g.m_uv[1] = totalHeight / float(finalHeight);
|
||||||
|
g.m_uv[2] = g.m_uv[0] + g.m_width / float(TEXMAP_DIM);
|
||||||
|
g.m_uv[3] = g.m_uv[1] + g.m_height / float(finalHeight);
|
||||||
|
g.m_leftPadding = 0;
|
||||||
|
g.m_advance = face->glyph->advance.x;
|
||||||
|
g.m_rightPadding = 0;
|
||||||
|
g.m_verticalOffset = face->glyph->metrics.horiBearingY;
|
||||||
|
g.m_kernIdx = 0;
|
||||||
|
MemcpyRect(texmap.get(), &face->glyph->bitmap, fullTexmap, curLineWidth, totalHeight);
|
||||||
|
if (curLineWidth + g.m_width > TEXMAP_DIM)
|
||||||
|
{
|
||||||
|
if (totalHeight + curLineHeight > TEXMAP_DIM)
|
||||||
|
{
|
||||||
|
totalHeight = 0;
|
||||||
|
++fullTexmap;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
totalHeight += curLineHeight;
|
||||||
|
curLineHeight = 0;
|
||||||
|
curLineWidth = 0;
|
||||||
|
}
|
||||||
|
curLineHeight = std::max(curLineHeight, face->glyph->bitmap.rows);
|
||||||
|
charcode = FT_Get_Next_Char(face, charcode, &gindex);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_tex =
|
||||||
|
gf->newStaticArrayTexture(TEXMAP_DIM, finalHeight, fullTexmap + 1,
|
||||||
|
boo::TextureFormat::I8, texmap.get(), bufSz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FontAtlas::FontAtlas(boo::IGraphicsDataFactory* gf, FT_Face face, bool subpixel, Athena::io::FileReader& reader)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
FontCache::Library::Library()
|
FontCache::Library::Library()
|
||||||
{
|
{
|
||||||
FT_Error err = FT_Init_FreeType(&m_lib);
|
FT_Error err = FT_Init_FreeType(&m_lib);
|
||||||
|
@ -66,8 +308,57 @@ static FT_Library InitLib()
|
||||||
|
|
||||||
FontCache::FontCache(const HECL::Runtime::FileStoreManager& fileMgr)
|
FontCache::FontCache(const HECL::Runtime::FileStoreManager& fileMgr)
|
||||||
: m_fileMgr(fileMgr),
|
: m_fileMgr(fileMgr),
|
||||||
|
m_cacheRoot(m_fileMgr.getStoreRoot() + _S("/fontcache")),
|
||||||
m_regFace(m_fontLib, DROIDSANS_PERMISSIVE, DROIDSANS_PERMISSIVE_SZ),
|
m_regFace(m_fontLib, DROIDSANS_PERMISSIVE, DROIDSANS_PERMISSIVE_SZ),
|
||||||
m_monoFace(m_fontLib, BMONOFONT, BMONOFONT_SZ) {}
|
m_monoFace(m_fontLib, BMONOFONT, BMONOFONT_SZ)
|
||||||
|
{HECL::MakeDir(m_cacheRoot.c_str());}
|
||||||
|
|
||||||
|
FontTag FontCache::prepCustomFont(boo::IGraphicsDataFactory* gf,
|
||||||
|
const std::string& name, FT_Face face, bool subpixel,
|
||||||
|
float points, uint32_t dpi)
|
||||||
|
{
|
||||||
|
/* Quick validation */
|
||||||
|
if (!face)
|
||||||
|
Log.report(LogVisor::FatalError, "invalid freetype face");
|
||||||
|
|
||||||
|
if (!face->charmap || face->charmap->encoding != ft_encoding_unicode)
|
||||||
|
Log.report(LogVisor::FatalError, "font does not contain a unicode char map");
|
||||||
|
|
||||||
|
/* Set size with FreeType */
|
||||||
|
FT_Set_Char_Size(face, points * 64.0, 0, dpi, 0);
|
||||||
|
|
||||||
|
/* Make tag and search for cached version */
|
||||||
|
FontTag tag(name, subpixel, points, dpi);
|
||||||
|
auto search = m_cachedAtlases.find(tag);
|
||||||
|
if (search != m_cachedAtlases.end())
|
||||||
|
return tag;
|
||||||
|
|
||||||
|
/* Now check filesystem cache */
|
||||||
|
HECL::SystemString cachePath = m_cacheRoot + _S('/') + HECL::Format("%" PRIx64, tag.hash());
|
||||||
|
#if 0
|
||||||
|
HECL::Sstat st;
|
||||||
|
if (!HECL::Stat(cachePath.c_str(), &st) && S_ISREG(st.st_mode))
|
||||||
|
{
|
||||||
|
Athena::io::FileReader r(cachePath);
|
||||||
|
if (!r.hasError())
|
||||||
|
{
|
||||||
|
atUint32 magic = r.readUint32Big();
|
||||||
|
if (r.position() == 4 && magic == 'FONT')
|
||||||
|
{
|
||||||
|
m_cachedAtlases.emplace(tag, std::make_unique<FontAtlas>(gf, face, subpixel, r));
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Nada, build and cache now */
|
||||||
|
Athena::io::FileWriter w(cachePath);
|
||||||
|
if (w.hasError())
|
||||||
|
Log.report(LogVisor::FatalError, "unable to open '%s' for writing", cachePath.c_str());
|
||||||
|
w.writeUint32Big('FONT');
|
||||||
|
m_cachedAtlases.emplace(tag, std::make_unique<FontAtlas>(gf, face, subpixel, w));
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue