addditional font atlas implementation

This commit is contained in:
Jack Andersen 2015-11-24 15:46:30 -10:00
parent 99d85ba60c
commit b6a5655067
8 changed files with 437 additions and 14 deletions

3
specter/.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "freetype2"]
path = freetype2
url = https://github.com/AxioDL/freetype2
[submodule "MathLib"]
path = MathLib
url = https://github.com/AxioDL/MathLib.git

View File

@ -1,4 +1,6 @@
add_subdirectory(freetype2)
add_subdirectory(MathLib)
set(MATHLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/MathLib/include)
if(NOT DEFINED HECL_INCLUDE_DIR)
message(FATAL_ERROR "Specter may only be built as a sub-project containing hecl with
@ -24,7 +26,8 @@ add_subdirectory(resources/fonts)
include_directories(include ${HECL_INCLUDE_DIR} ${BOO_INCLUDE_DIR}
${LOG_VISOR_INCLUDE_DIR} ${ATHENA_INCLUDE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/freetype2/include)
${CMAKE_CURRENT_SOURCE_DIR}/freetype2/include
${MATHLIB_INCLUDE_DIR})
list(APPEND SPECTER_HEADERS
include/Specter/Specter.hpp

1
specter/MathLib Submodule

@ -0,0 +1 @@
Subproject commit 8d54a3c7b5d76e32d9fb3ad57619a680b32909b7

View File

@ -42,6 +42,8 @@ class FreeTypeGZipMemFace
FT_Face m_face = nullptr;
public:
FreeTypeGZipMemFace(FT_Library lib, const uint8_t* data, size_t sz);
FreeTypeGZipMemFace(const FreeTypeGZipMemFace& other) = delete;
FreeTypeGZipMemFace& operator=(const FreeTypeGZipMemFace& other) = delete;
~FreeTypeGZipMemFace() {close();}
void open();
void close();
@ -53,7 +55,9 @@ class FontAtlas
friend class FontCache;
FT_Face m_face;
boo::ITextureS* m_tex;
uint32_t m_dpi;
public:
struct Glyph
{
atUint32 m_unicodePoint;
@ -65,14 +69,30 @@ class FontAtlas
atUint8 m_width;
atUint8 m_height;
atInt8 m_verticalOffset;
atUint16 m_kernIdx;
atInt16 m_kernIdx = -1;
};
struct KernAdj
{
atUint32 a;
atUint32 b;
atInt8 adj;
};
private:
std::vector<Glyph> m_glyphs;
std::map<atUint32, size_t> m_glyphLookup;
std::vector<KernAdj> m_kernAdjs;
std::unordered_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);
FontAtlas(boo::IGraphicsDataFactory* gf, FT_Face face, uint32_t dpi,
bool subpixel, Athena::io::FileWriter& writer);
FontAtlas(boo::IGraphicsDataFactory* gf, FT_Face face, uint32_t dpi,
bool subpixel, Athena::io::FileReader& reader);
FontAtlas(const FontAtlas& other) = delete;
FontAtlas& operator=(const FontAtlas& other) = delete;
uint32_t dpi() const {return m_dpi;}
};
class FontCache
@ -92,6 +112,8 @@ class FontCache
std::unordered_map<FontTag, std::unique_ptr<FontAtlas>> m_cachedAtlases;
public:
FontCache(const HECL::Runtime::FileStoreManager& fileMgr);
FontCache(const FontCache& other) = delete;
FontCache& operator=(const FontCache& other) = delete;
FontTag prepCustomFont(boo::IGraphicsDataFactory* gf,
const std::string& name, FT_Face face, bool subpixel=false,
@ -106,6 +128,8 @@ public:
{return prepCustomFont(gf, "bmonofont", m_monoFace, subpixel, points, dpi);}
void closeBuiltinFonts() {m_regFace.close(); m_monoFace.close();}
const FontAtlas& lookupAtlas(FontTag tag) const;
};
}

View File

@ -2,14 +2,68 @@
#define SPECTER_TEXTVIEW_HPP
#include "View.hpp"
#include <boo/graphicsdev/GL.hpp>
#include <boo/graphicsdev/D3D.hpp>
#include <boo/graphicsdev/Metal.hpp>
#include "FontCache.hpp"
namespace Specter
{
class TextView : public View
{
boo::IGraphicsBufferD* m_glyphBuf;
int m_validDynamicSlots = 0;
size_t m_curGlyphCapacity;
const FontAtlas& m_fontAtlas;
boo::IVertexFormat* m_vtxFmt = nullptr; /* OpenGL only */
public:
class System
{
friend class TextView;
boo::IGraphicsDataFactory* m_factory;
FontCache& m_fcache;
boo::IShaderPipeline* m_regular;
boo::IShaderPipeline* m_subpixel;
boo::IGraphicsBufferS* m_quadVBO;
boo::IVertexFormat* m_vtxFmt = nullptr; /* Not OpenGL */
System(boo::IGraphicsDataFactory* factory, FontCache& fcache)
: m_factory(factory), m_fcache(fcache) {}
};
static System BuildTextSystem(boo::GLDataFactory* factory, FontCache& fcache);
#if _WIN32
static Shaders BuildTextSystem(boo::ID3DDataFactory* factory, FontCache& fcache);
#elif BOO_HAS_METAL
static Shaders BuildTextSystem(boo::MetalDataFactory* factory, FontCache& fcache);
#endif
TextView(System& system, FontTag font, size_t initGlyphCapacity=256);
struct RenderGlyph
{
Zeus::CMatrix4f m_mvp;
Zeus::CVector3f m_uv[4];
Zeus::CColor m_color;
};
std::vector<RenderGlyph>& accessGlyphs() {return m_glyphs;}
void updateGlyphs() {m_validDynamicSlots = 0;}
void typesetGlyphs(const std::string& str,
Zeus::CColor defaultColor=Zeus::CColor::skWhite);
void typesetGlyphs(const std::wstring& str,
Zeus::CColor defaultColor=Zeus::CColor::skWhite);
void colorGlyphs(Zeus::CColor newColor);
void colorGlyphsTypeOn(Zeus::CColor newColor, float startInterval=0.2, float fadeTime=0.5);
void think();
void draw(boo::IGraphicsCommandQueue* gfxQ);
private:
std::vector<RenderGlyph> m_glyphs;
};
}

View File

@ -2,18 +2,20 @@
#define SPECTER_VIEW_HPP
#include <boo/boo.hpp>
#include "CVector3f.hpp"
#include "CMatrix4f.hpp"
#include "CTransform.hpp"
#include "CColor.hpp"
namespace Specter
{
class View
{
boo::SWindowRect m_rect;
protected:
boo::SWindowRect m_viewport;
void bindViewport(boo::IGraphicsCommandQueue* gfxQ) {gfxQ->setViewport(m_viewport);}
public:
void bindViewport(boo::IGraphicsCommandQueue* gfxQ)
{
gfxQ->setViewport(m_rect);
}
virtual void draw(boo::IGraphicsCommandQueue* gfxQ)=0;
};

View File

@ -121,8 +121,9 @@ static inline void GridFitGlyph(FT_GlyphSlot slot, FT_UInt& width, FT_UInt& heig
height = slot->metrics.height >> 6;
}
FontAtlas::FontAtlas(boo::IGraphicsDataFactory* gf, FT_Face face,
FontAtlas::FontAtlas(boo::IGraphicsDataFactory* gf, FT_Face face, uint32_t dpi,
bool subpixel, Athena::io::FileWriter& writer)
: m_dpi(dpi)
{
FT_Int32 baseFlags = FT_LOAD_NO_BITMAP;
if (subpixel)
@ -163,6 +164,7 @@ FontAtlas::FontAtlas(boo::IGraphicsDataFactory* gf, FT_Face face,
charcode = FT_Get_Next_Char(face, charcode, &gindex);
}
m_glyphs.reserve(glyphCount);
m_glyphLookup.reserve(glyphCount);
totalHeight = RoundUpPow2(totalHeight);
unsigned finalHeight = fullTexmapLayers ? TEXMAP_DIM : totalHeight;
@ -200,6 +202,7 @@ FontAtlas::FontAtlas(boo::IGraphicsDataFactory* gf, FT_Face face,
while (gindex != 0)
{
FT_Load_Glyph(face, gindex, FT_LOAD_RENDER | baseFlags);
m_glyphLookup[charcode] = m_glyphs.size();
m_glyphs.emplace_back();
Glyph& g = m_glyphs.back();
g.m_unicodePoint = charcode;
@ -270,6 +273,7 @@ FontAtlas::FontAtlas(boo::IGraphicsDataFactory* gf, FT_Face face,
while (gindex != 0)
{
FT_Load_Glyph(face, gindex, FT_LOAD_RENDER | baseFlags);
m_glyphLookup[charcode] = m_glyphs.size();
m_glyphs.emplace_back();
Glyph& g = m_glyphs.back();
g.m_unicodePoint = charcode;
@ -312,8 +316,174 @@ FontAtlas::FontAtlas(boo::IGraphicsDataFactory* gf, FT_Face face,
}
}
FontAtlas::FontAtlas(boo::IGraphicsDataFactory* gf, FT_Face face, bool subpixel, Athena::io::FileReader& reader)
FontAtlas::FontAtlas(boo::IGraphicsDataFactory* gf, FT_Face face, uint32_t dpi,
bool subpixel, Athena::io::FileReader& reader)
: m_dpi(dpi)
{
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 */
size_t glyphCount = 0;
FT_UInt gindex;
FT_ULong charcode = FT_Get_First_Char(face, &gindex);
while (gindex != 0)
{
++glyphCount;
charcode = FT_Get_Next_Char(face, charcode, &gindex);
}
m_glyphs.reserve(glyphCount);
m_glyphLookup.reserve(glyphCount);
unsigned fullTexmapLayers = reader.readUint32Big() - 1;
reader.readUint32Big();
unsigned finalHeight = reader.readUint32Big();
if (subpixel)
{
/* Allocate texmap */
std::unique_ptr<RgbaPixel[]> texmap;
size_t bufSz;
if (fullTexmapLayers)
{
//printf("ALLOC: %u\n", fullTexmapLayers + 1);
size_t count = TEXMAP_DIM * TEXMAP_DIM * (fullTexmapLayers + 1);
texmap.reset(new RgbaPixel[count]);
bufSz = count * sizeof(RgbaPixel);
memset(texmap.get(), 0, bufSz);
}
else
{
size_t count = TEXMAP_DIM * finalHeight;
texmap.reset(new RgbaPixel[TEXMAP_DIM * finalHeight]);
bufSz = count * sizeof(RgbaPixel);
memset(texmap.get(), 0, bufSz);
}
/* Assemble glyph texmaps and internal data structures */
charcode = FT_Get_First_Char(face, &gindex);
unsigned curLineWidth = 1;
unsigned curLineHeight = 0;
unsigned totalHeight = 1;
fullTexmapLayers = 0;
while (gindex != 0)
{
FT_Load_Glyph(face, gindex, baseFlags);
FT_UInt width, height;
GridFitGlyph(face->glyph, width, height);
m_glyphs.emplace_back();
Glyph& g = m_glyphs.back();
g.m_unicodePoint = charcode;
g.m_layerIdx = fullTexmapLayers;
g.m_width = width;
g.m_height = height;
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 / 64;
g.m_kernIdx = 0;
if (curLineWidth + g.m_width + 1 > TEXMAP_DIM)
{
totalHeight += curLineHeight + 1;
curLineHeight = 0;
curLineWidth = 1;
}
curLineHeight = std::max(curLineHeight, height);
if (totalHeight + curLineHeight + 1 > TEXMAP_DIM)
{
totalHeight = 1;
++fullTexmapLayers;
//printf("RealB: %u\n", gindex);
curLineHeight = 0;
curLineWidth = 1;
}
curLineWidth += g.m_width + 1;
charcode = FT_Get_Next_Char(face, charcode, &gindex);
}
reader.readUBytesToBuf((atUint8*)texmap.get(), bufSz);
m_tex =
gf->newStaticArrayTexture(TEXMAP_DIM, finalHeight, fullTexmapLayers + 1,
boo::TextureFormat::RGBA8, texmap.get(), bufSz);
}
else
{
/* Allocate texmap */
std::unique_ptr<GreyPixel[]> texmap;
size_t bufSz;
if (fullTexmapLayers)
{
//printf("ALLOC: %u\n", fullTexmapLayers + 1);
size_t count = TEXMAP_DIM * TEXMAP_DIM * (fullTexmapLayers + 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);
unsigned curLineWidth = 1;
unsigned curLineHeight = 0;
unsigned totalHeight = 1;
fullTexmapLayers = 0;
while (gindex != 0)
{
FT_Load_Glyph(face, gindex, baseFlags);
FT_UInt width, height;
GridFitGlyph(face->glyph, width, height);
m_glyphs.emplace_back();
Glyph& g = m_glyphs.back();
g.m_unicodePoint = charcode;
g.m_layerIdx = fullTexmapLayers;
g.m_width = width;
g.m_height = height;
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 >> 6;
g.m_kernIdx = 0;
if (curLineWidth + g.m_width + 1 > TEXMAP_DIM)
{
totalHeight += curLineHeight + 1;
curLineHeight = 0;
curLineWidth = 1;
}
curLineHeight = std::max(curLineHeight, height);
if (totalHeight + curLineHeight + 1 > TEXMAP_DIM)
{
totalHeight = 1;
++fullTexmapLayers;
//printf("RealB: %u\n", gindex);
curLineHeight = 0;
curLineWidth = 1;
}
curLineWidth += g.m_width + 1;
charcode = FT_Get_Next_Char(face, charcode, &gindex);
}
reader.readUBytesToBuf((atUint8*)texmap.get(), bufSz);
m_tex =
gf->newStaticArrayTexture(TEXMAP_DIM, finalHeight, fullTexmapLayers + 1,
boo::TextureFormat::I8, texmap.get(), bufSz);
}
}
FontCache::Library::Library()
@ -367,7 +537,7 @@ FontTag FontCache::prepCustomFont(boo::IGraphicsDataFactory* gf,
atUint32 magic = r.readUint32Big();
if (r.position() == 4 && magic == 'FONT')
{
m_cachedAtlases.emplace(tag, std::make_unique<FontAtlas>(gf, face, subpixel, r));
m_cachedAtlases.emplace(tag, std::make_unique<FontAtlas>(gf, face, dpi, subpixel, r));
return tag;
}
}
@ -379,8 +549,16 @@ FontTag FontCache::prepCustomFont(boo::IGraphicsDataFactory* gf,
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));
m_cachedAtlases.emplace(tag, std::make_unique<FontAtlas>(gf, face, dpi, subpixel, w));
return tag;
}
const FontAtlas& FontCache::lookupAtlas(FontTag tag) const
{
auto search = m_cachedAtlases.find(tag);
if (search == m_cachedAtlases.cend())
Log.report(LogVisor::FatalError, "invalid font");
return *search->second.get();
}
}

View File

@ -1,6 +1,164 @@
#include "Specter/TextView.hpp"
#include "utf8proc.h"
namespace Specter
{
static LogVisor::LogModule Log("Specter::TextView");
TextView::System TextView::BuildTextSystem(boo::GLDataFactory* factory, FontCache& fcache)
{
static const char* VS =
"#version 330\n"
"layout(location=0) in vec3 posIn;\n"
"layout(location=1) in mat4 mvMtx;\n"
"layout(location=5) in vec3 uvIn[4];\n"
"layout(location=9) in vec4 colorIn;\n"
"struct VertToFrag\n"
"{\n"
" vec3 uv;\n"
" vec4 color;\n"
"};\n"
"out VertToFrag vtf;\n"
"void main()\n"
"{\n"
" vtf.uv = uvIn[gl_VertexID];\n"
" vtf.color = colorIn;\n"
" gl_Position = mvMtx * vec4(posIn, 1.0);\n"
"}\n";
static const char* FSReg =
"#version 330\n"
"uniform sampler2DArray fontTex;\n"
"struct VertToFrag\n"
"{\n"
" vec3 uv;\n"
" vec4 color;\n"
"};\n"
"in VertToFrag vtf;\n"
"layout(location=0) out vec4 colorOut;\n"
"void main()\n"
"{\n"
" colorOut = vtf.color;\n"
" colorOut.a = texture(fontTex, vtf.uv).r;\n"
"}\n";
static const char* FSSubpixel =
"#version 330\n"
"uniform sampler2DArray fontTex;\n"
"struct VertToFrag\n"
"{\n"
" vec3 uv;\n"
" vec4 color;\n"
"};\n"
"in VertToFrag vtf;\n"
"layout(location=0, index=0) out vec4 colorOut;\n"
"layout(location=0, index=1) out vec4 blendOut;\n"
"void main()\n"
"{\n"
" colorOut = vtf.color;\n"
" blendOut = texture(fontTex, vtf.uv);\n"
"}\n";
static float Quad[] =
{
0.0, 0.0, 0.0,
0.0, 1.0, 0.0,
1.0, 1.0, 0.0,
1.0, 0.0, 0.0,
};
System ret(factory, fcache);
ret.m_regular =
factory->newShaderPipeline(VS, FSReg, 1, "fontTex", 0, nullptr,
boo::BlendFactor::SrcAlpha, boo::BlendFactor::InvSrcAlpha,
true, true, false);
ret.m_subpixel =
factory->newShaderPipeline(VS, FSSubpixel, 1, "fontTex", 0, nullptr,
boo::BlendFactor::SrcColor1, boo::BlendFactor::InvSrcColor1,
true, true, false);
ret.m_quadVBO =
factory->newStaticBuffer(boo::BufferUse::Vertex, Quad, sizeof(Quad), 1);
return ret;
}
#if _WIN32
TextView::Shaders TextView::BuildTextSystem(boo::ID3DDataFactory* factory, FontCache& fcache)
{
}
#elif BOO_HAS_METAL
TextView::Shaders TextView::BuildTextSystem(boo::MetalDataFactory* factory, FontCache& fcache)
{
}
#endif
TextView::TextView(System& system, FontTag font, size_t initGlyphCapacity)
: m_curGlyphCapacity(initGlyphCapacity),
m_fontAtlas(system.m_fcache.lookupAtlas(font))
{
m_glyphBuf =
system.m_factory->newDynamicBuffer(boo::BufferUse::Vertex,
sizeof(RenderGlyph), initGlyphCapacity);
if (!system.m_vtxFmt)
{
boo::VertexElementDescriptor vdescs[] =
{
{system.m_quadVBO, nullptr, boo::VertexSemantic::Position},
{m_glyphBuf, nullptr, boo::VertexSemantic::ModelView | boo::VertexSemantic::Instanced, 0},
{m_glyphBuf, nullptr, boo::VertexSemantic::ModelView | boo::VertexSemantic::Instanced, 1},
{m_glyphBuf, nullptr, boo::VertexSemantic::ModelView | boo::VertexSemantic::Instanced, 2},
{m_glyphBuf, nullptr, boo::VertexSemantic::ModelView | boo::VertexSemantic::Instanced, 3},
{m_glyphBuf, nullptr, boo::VertexSemantic::UV4 | boo::VertexSemantic::Instanced, 0},
{m_glyphBuf, nullptr, boo::VertexSemantic::UV4 | boo::VertexSemantic::Instanced, 1},
{m_glyphBuf, nullptr, boo::VertexSemantic::UV4 | boo::VertexSemantic::Instanced, 2},
{m_glyphBuf, nullptr, boo::VertexSemantic::UV4 | boo::VertexSemantic::Instanced, 3},
{m_glyphBuf, nullptr, boo::VertexSemantic::Color | boo::VertexSemantic::Instanced}
};
m_vtxFmt = system.m_factory->newVertexFormat(10, vdescs);
}
m_glyphs.reserve(initGlyphCapacity);
}
void TextView::typesetGlyphs(const std::string& str, Zeus::CColor defaultColor)
{
size_t rem = str.size();
const utf8proc_uint8_t* it = str.data();
while (rem)
{
utf8proc_int32_t ch;
utf8proc_ssize_t sz = utf8proc_iterate(it, -1, &ch);
if (sz < 0)
Log.report(LogVisor::FatalError, "invalid UTF-8 char");
m_fontAtlas
rem -= sz;
it += sz;
}
}
void TextView::typesetGlyphs(const std::wstring& str, Zeus::CColor defaultColor)
{
}
void TextView::colorGlyphs(Zeus::CColor newColor)
{
}
void TextView::colorGlyphsTypeOn(Zeus::CColor newColor, float startInterval, float fadeTime)
{
}
void TextView::think()
{
}
void TextView::draw(boo::IGraphicsCommandQueue* gfxQ)
{
}
}