Rendering Engine 0.2.9
Modular Graphics Rendering Engine | v0.2.9
font_resources.cpp
Go to the documentation of this file.
1#include "font_resources.hpp"
2#include "image_data.hpp"
4#include "texture_cache.hpp"
5#include "material_types.hpp"
6#include "material_cache.hpp"
7#include "material.hpp"
8#include "utility.hpp"
9#include "text_renderer.hpp"
10
11#include FT_FREETYPE_H
12#include FT_TRUETYPE_TABLES_H
13
14namespace rendering_engine
15{
16std::uint32_t FontResources::sMaxGlyphsPerFontAtlas = 1024;
18
19FontResources::FontResources(RenderResourceContext rrc, TextRenderer* textRenderer, std::string filepath, unsigned int const fontSize)
20 :
21 mRenderResourceContext(rrc),
22 mTextRenderer(textRenderer),
23 mFontSize(fontSize),
24 mErrorResult(FT_Err_Ok),
25 mFace(0)
26{
27 mFontName = boost::filesystem::path(filepath).stem().string();
28
29 mErrorResult = FT_New_Face(mTextRenderer->GetFontLibrary(), filepath.c_str(), 0, &mFace);
30 if (mErrorResult)
31 {
32 throw std::runtime_error{ "Failed to create new face!" };
33 }
34
35 mErrorResult = FT_Set_Char_Size(mFace, mFontSize << 6, mFontSize << 6, 90, 90);
36 if (mErrorResult)
37 {
38 throw std::runtime_error{ "Failed to set char size!" };
39 }
40
41 mFontMetrics.lineHeight = mFace->size->metrics.height >> 6;
42 mFontMetrics.ascender = mFace->size->metrics.ascender >> 6;
43 mFontMetrics.descender = mFace->size->metrics.descender >> 6;
44}
45
46FontResources::FontResources(RenderResourceContext rrc, TextRenderer* textRenderer, std::string fontName, std::vector<uint8_t> const& fileBytes, unsigned int const fontSize)
47 :
48 mRenderResourceContext(rrc),
49 mTextRenderer(textRenderer),
50 mErrorResult(FT_Err_Ok),
51 mFace(0),
52 mFontName(fontName),
53 mFontSize(fontSize),
54 mFontFileBytes(fileBytes)
55{
56 mErrorResult = FT_New_Memory_Face(mTextRenderer->GetFontLibrary(), mFontFileBytes.data(), static_cast<FT_Long>(mFontFileBytes.size()), 0, &mFace);
57 if (mErrorResult)
58 {
59 throw std::runtime_error{ "Failed to create new face!" };
60 }
61
62 mErrorResult = FT_Set_Char_Size(mFace, mFontSize << 6, mFontSize << 6, 90, 90);
63 if (mErrorResult)
64 {
65 throw std::runtime_error{ "Failed to set char size!" };
66 }
67
68 mFontMetrics.lineHeight = mFace->size->metrics.height >> 6;
69 mFontMetrics.ascender = mFace->size->metrics.ascender >> 6;
70 mFontMetrics.descender = mFace->size->metrics.descender >> 6;
71}
72
74{
75 if (mFace)
76 {
77 FT_Done_Face(mFace);
78 mFace = nullptr;
79 }
80}
81
82void FontResources::LoadGlyphsFromCodePointRange(std::uint32_t begin, std::uint32_t end)
83{
84 auto distance = end - begin;
85 std::uint32_t start = begin;
86 while (start < end)
87 {
88 std::uint32_t currentEnd = std::min(start + sMaxGlyphsPerFontAtlas - 1, end);
89 CreateFontAtlasFromRange(start, currentEnd);
91 }
92}
93
94void FontResources::EnsureGlyphs(const std::vector<std::uint32_t>& codePoints)
95{
96 if (codePoints.empty())
97 return;
98
99 std::vector<GlyphIndex> glyphs;
100 for (auto codePoint : codePoints)
101 {
102 glyphs.push_back(GetIndexFromCodePoint(codePoint));
103 }
104
105 EnsureGlyphs(glyphs);
106}
107
108void FontResources::EnsureGlyphs(const std::vector<GlyphIndex>& glyphIndexes)
109{
110 if (glyphIndexes.empty())
111 return;
112
113 std::vector<GlyphIndex> lackingGlyphs;
114 for (const auto& glyphIndex : glyphIndexes)
115 {
116 auto found = mGlyphsByIndex.find(glyphIndex.index);
117 if (found == mGlyphsByIndex.end())
118 {
119 GlyphIndex gi;
120 gi.index = glyphIndex.index;
121 lackingGlyphs.push_back(gi);
122 }
123 }
124 if (!lackingGlyphs.empty())
125 {
126 CreateFontAtlasFromList(lackingGlyphs);
127 }
128}
129
131{
133}
134
135std::pair<GlyphMetrics, ImageData> FontResources::CreateGlyphBitmapBy(GlyphIndex glyphIndex)
136{
137 mErrorResult = FT_Load_Glyph(mFace, glyphIndex.index, FT_LOAD_DEFAULT);
138 if (mErrorResult)
139 {
140 throw std::runtime_error{ "Failed to load glyph!" };
141 }
142
143 mErrorResult = FT_Render_Glyph(mFace->glyph, FT_RENDER_MODE_NORMAL);
144 if (mErrorResult)
145 {
146 throw std::runtime_error{ "Failed to render glyph!" };
147 }
148
149 const unsigned padding = sFontAtlasPaddingPx;
150
151 const auto srcW = mFace->glyph->bitmap.width;
152 const auto srcH = mFace->glyph->bitmap.rows;
153
154 const auto dstW = srcW + padding * 2;
155 const auto dstH = srcH + padding * 2;
156
157 const auto bufferSize = dstW * dstH * 4;
158 std::vector<uint8_t> buffer(bufferSize, 0);
159 for (int y = 0; y < srcH; ++y)
160 {
161 for (int x = 0; x < srcW; ++x)
162 {
163 uint8_t coverage =
164 mFace->glyph->bitmap.buffer[y * srcW + x];
165
166 int dstX = x + padding;
167 int dstY = y + padding;
168
169 const size_t idx = (static_cast<size_t>(dstY) * static_cast<size_t>(dstW)
170 + static_cast<size_t>(dstX)) * 4u;
171
172 buffer[idx + 0] = coverage;
173 buffer[idx + 1] = coverage;
174 buffer[idx + 2] = coverage;
175 buffer[idx + 3] = coverage;
176 }
177 }
178 GlyphMetrics glyphMetrics;
179 glyphMetrics.width = srcW;
180 glyphMetrics.height = srcH;
181
182
183 glyphMetrics.bearingX = mFace->glyph->bitmap_left;
184 glyphMetrics.bearingY = mFace->glyph->bitmap_top;
185
186 glyphMetrics.advanceX = mFace->glyph->advance.x >> 6;
187
188 glyphMetrics.padding = padding;
189
190 return std::pair(glyphMetrics, ImageData(dstW, dstH, buffer));
191}
192
193std::pair<GlyphMetrics, ImageData> FontResources::CreateGlyphBitmapBy(std::uint32_t codePoint)
194{
195 GlyphIndex glyphIndex;
196 glyphIndex.index = static_cast<std::uint32_t>(FT_Get_Char_Index(mFace, static_cast<FT_ULong>(codePoint)));
197
198 return CreateGlyphBitmapBy(glyphIndex);
199}
200
201void FontResources::CreateFontAtlasFromRange(std::uint32_t begin, std::uint32_t end)
202{
203 if (begin > end)
204 {
205 // range is not valid
206 return;
207 }
208
209 // 1. Create font atlas in range of [begin; end]
210 std::unordered_map<std::uint32_t, std::pair<GlyphMetrics, ImageData>> glyphs;
211 for (auto codePoint = begin; codePoint <= end; ++codePoint)
212 {
213 if (!HasGlyph(codePoint))
214 {
215 continue;
216 }
217 // Creating bitmaps of glyphs should be considered to dome multythreaded
218 glyphs[codePoint] = CreateGlyphBitmapBy(codePoint);
219 }
220 if (glyphs.empty())
221 return;
222
223 auto fontAtlas = TextureAtlasMaker::CreateTextureAtlas(glyphs);
224
225 // 2. Upload texture via TextureCache
226
227 auto textureCache = mRenderResourceContext.textureCache;
228 const std::string textureName = mFontName + "_" + std::to_string(mFontSize) + "_FontAtlas_" + std::to_string(mFontAtlases.size());
230 {
231 const std::string fileName = textureName + ".png";
232 auto fullPath = Utility::GetContentFolderPath() / fileName;
233 fontAtlas.WritePngFile(fullPath.string().c_str());
234 }
235 textureCache->LoadTexture(textureName, fontAtlas);
236
237 // 3. Create a material via MaterialCache and add texture to it
238 auto materialCache = mRenderResourceContext.materialCache;
239 const std::string materialName = textureName + "_Mat";
240 MaterialSettings materialSettings;
241 materialSettings.parentMaterialName = std::string{ "Font2D" };
242 materialSettings.materialName = materialName;
243 materialSettings.materialDomain = MaterialDomain::Sprite2D;
244 materialSettings.shadingModel = ShadingModel::Unlit;
245 materialSettings.blendMode = BlendMode::Translucent;
246 materialSettings.parameterLayout = &Font2DLayout;
247
248 materialCache->AddMaterial(materialSettings);
249 Material* material = materialCache->GetMaterial(materialName);
250 material->SetVec4("FontColor", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
251 material->SetVec4("OutlineColor", glm::vec4(0, 0, 0, 1));
252 material->SetFloat("OutlineThicknessPx", 0.0); // OFF by default
253 const float invW = 1.0f / static_cast<float>(fontAtlas.GetWidth());
254 const float invH = 1.0f / static_cast<float>(fontAtlas.GetHeight());
255 material->SetFloat("InvAtlasSizeWidth", invW);
256 material->SetFloat("InvAtlasSizeHeight", invH);
257
258 material->AddTexture(textureName);
259
260 // material->SetFloat >> Add custom material parameters for UV manipulation
261 material->InitializeRenderResources();
262
263 // 4 Upload to container mFontAtlases
264 for (auto& it : glyphs)
265 {
266 const std::uint32_t glyphIndex = static_cast<std::uint32_t>(FT_Get_Char_Index(mFace, static_cast<FT_ULong>(it.first)));
267 mGlyphsByIndex[glyphIndex] = std::pair<GlyphMetrics, std::string>(it.second.first, materialName);
268 }
269
270 mFontAtlases[materialName] = textureName;
271}
272
273bool FontResources::HasGlyph(uint32_t codePoint) const
274{
275 return FT_Get_Char_Index(mFace, codePoint) != 0;
276}
277
279{
280 return mFontMetrics;
281}
282
284{
285 auto search = mGlyphsByIndex.find(glyphIndex.index);
286 if (search == mGlyphsByIndex.end())
287 {
288 return GlyphMetrics();
289 }
290 return search->second.first;
291}
292
294{
295 auto matName = GetFontAtlasMaterialName(glyphIndex);
296 if (matName.empty())
297 {
298 return std::string();
299 }
300 auto search = mFontAtlases.find(matName);
301 if(search == mFontAtlases.end())
302 {
303 return std::string();
304 }
305 return search->second;
306}
307
309{
310 auto search = mGlyphsByIndex.find(glyphIndex.index);
311 if (search == mGlyphsByIndex.end())
312 {
313 return std::string();
314 }
315 return search->second.second;
316}
317
319{
320 GlyphIndex result;
321 result.index = FT_Get_Char_Index(mFace, codePoint);
322 return result;
323}
324
326{
327 return mFace;
328}
329
330void FontResources::CreateFontAtlasFromList(const std::vector<GlyphIndex>& glyphIndexes)
331{
332 // 1. Create font atlas from list
333 std::unordered_map<std::uint32_t, std::pair<GlyphMetrics, ImageData>> glyphs;
334 for (auto glyphIndex : glyphIndexes)
335 {
336 auto found = mGlyphsByIndex.find(glyphIndex.index);
337 if (found != mGlyphsByIndex.end())
338 continue;
339 // Creating bitmaps of glyphs should be considered to dome multithreaded
340 glyphs[glyphIndex.index] = CreateGlyphBitmapBy(glyphIndex);
341 }
342 if (glyphs.empty())
343 return;
344
345 auto fontAtlas = TextureAtlasMaker::CreateTextureAtlas(glyphs);
346
347 // 2. Upload texture via TextureCache
348
349 auto textureCache = mRenderResourceContext.textureCache;
350 const std::string textureName = mFontName + "_" + std::to_string(mFontSize) + "_FontAtlas_" + std::to_string(mFontAtlases.size());
352 {
353 const std::string fileName = textureName + ".png";
354 auto fullPath = Utility::GetContentFolderPath() / fileName;
355 fontAtlas.WritePngFile(fullPath.string().c_str());
356 }
357 textureCache->LoadTexture(textureName, fontAtlas);
358
359 // 3. Create a material via MaterialCache and add texture to it
360 auto materialCache = mRenderResourceContext.materialCache;
361 const std::string materialName = textureName + "_Mat";
362 MaterialSettings materialSettings;
363 materialSettings.parentMaterialName = std::string{ "Font2D" };
364 materialSettings.materialName = materialName;
365 materialSettings.materialDomain = MaterialDomain::Sprite2D;
366 materialSettings.shadingModel = ShadingModel::Unlit;
367 materialSettings.blendMode = BlendMode::Translucent;
368 materialSettings.parameterLayout = &Font2DLayout;
369
370 materialCache->AddMaterial(materialSettings);
371 Material* material = materialCache->GetMaterial(materialName);
372 material->SetVec4("FontColor", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
373 material->SetVec4("OutlineColor", glm::vec4(0, 0, 0, 1));
374 material->SetFloat("OutlineThicknessPx", 0.0f); // OFF by default
375 const float invW = 1.0f / static_cast<float>(fontAtlas.GetWidth());
376 const float invH = 1.0f / static_cast<float>(fontAtlas.GetHeight());
377 material->SetFloat("InvAtlasSizeWidth", invW);
378 material->SetFloat("InvAtlasSizeHeight", invH);
379
380 material->AddTexture(textureName);
381
382 // material->SetFloat >> Add custom material parameters for UV manipulation
383 material->InitializeRenderResources();
384
385 // 4 Upload to container mFontAtlases
386 for (auto& it : glyphs)
387 {
388 mGlyphsByIndex[it.first] = std::pair<GlyphMetrics, std::string>(it.second.first, materialName);
389 }
390
391 mFontAtlases[materialName] = textureName;
392}
393
394} // namespace rendering_engine
const FontMetrics & GetFontMetrics() const
Returns global font metrics.
FontResources(RenderResourceContext rrc, TextRenderer *textRenderer, std::string filepath, unsigned int const fontSize)
Constructs font resources from a font file.
GlyphMetrics GetGlyphMetrics(GlyphIndex glyphIndex) const
Returns metrics for a specific glyph.
static std::uint32_t sMaxGlyphsPerFontAtlas
Maximum number of glyphs per font atlas.
~FontResources()
Releases all font-related resources.
std::pair< GlyphMetrics, ImageData > CreateGlyphBitmapBy(GlyphIndex glyphIndex)
Creates a bitmap and metrics for a glyph by index.
void LoadGlyphsFromCodePointRange(std::uint32_t begin, std::uint32_t end)
Preloads glyphs for a continuous Unicode range.
void CreateFontAtlasFromRange(std::uint32_t begin, std::uint32_t end)
Creates a font atlas for a continuous code point range.
FT_Face GetFontFace()
Returns the internal font face handle.
void StoreFontAtlasesInFiles(bool in)
Enables or disables storing generated font atlases on disk.
std::unordered_map< std::uint32_t, std::pair< GlyphMetrics, std::string > > mGlyphsByIndex
std::string GetFontAtlasTextureName(GlyphIndex glyphIndex) const
Returns the texture name of the atlas containing the glyph.
std::string GetFontAtlasMaterialName(GlyphIndex glyphIndex) const
Returns the material name associated with the glyph.
static unsigned int sFontAtlasPaddingPx
Padding in pixels around glyphs inside atlases.
std::vector< uint8_t > mFontFileBytes
RenderResourceContext mRenderResourceContext
void EnsureGlyphs(const std::vector< std::uint32_t > &codePoints)
Ensures glyphs for the given Unicode code points exist.
std::unordered_map< std::string, std::string > mFontAtlases
GlyphIndex GetIndexFromCodePoint(std::uint32_t codePoint) const
Resolves a Unicode code point to a glyph index.
bool HasGlyph(uint32_t codePoint) const
Checks whether a glyph exists for a code point.
void CreateFontAtlasFromList(const std::vector< GlyphIndex > &glyphIndexes)
Creates a font atlas for a list of glyphs.
Represents raw 2D image data stored in memory.
Definition: image_data.hpp:80
Represents a material instance with parameter values, texture bindings, and rendering configuration.
Definition: material.hpp:30
void InitializeRenderResources()
Initializes backend-specific GPU resources associated with this material.
Definition: material.cpp:26
void SetVec4(const std::string &name, glm::vec4 value)
Sets a vec4 parameter for the material.
Definition: material.cpp:107
void AddTexture(const std::string &textureName)
Adds a texture name to the material's list of used textures.
Definition: material.cpp:112
void SetFloat(const std::string &name, float value)
Sets a float parameter for the material.
Definition: material.cpp:92
Central text system manager and font resource registry.
FT_Library & GetFontLibrary()
Returns the internal FreeType library instance.
bool CreateTextureAtlas(std::map< char, std::pair< unsigned int, unsigned int > > &texAtlasData, ImageData &texAtlasImage)
Creates a texture atlas from the stored image collection.
static boost::filesystem::path GetContentFolderPath()
Returns absolute path to Content.
Definition: utility.cpp:239
static const std::vector< MaterialParameterLayoutEntry > Font2DLayout
Global metrics describing font vertical layout.
Opaque glyph identifier within a font.
Metrics describing a single glyph and its atlas placement.
Settings required to define a material instance.
const std::vector< MaterialParameterLayoutEntry > * parameterLayout
Aggregates pointers to global rendering resource managers.