Rendering Engine 0.2.17
Modular Graphics Rendering Engine | v0.2.17
Loading...
Searching...
No Matches
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 <limits>
12#include <stdexcept>
13
14#include FT_FREETYPE_H
15#include FT_TRUETYPE_TABLES_H
16
17namespace rendering_engine
18{
19std::uint32_t FontResources::sMaxGlyphsPerFontAtlas = 1024;
21
22FontResources::FontResources(RenderResourceContext rrc, TextRenderer* textRenderer, std::string filepath, unsigned int const fontSize)
23 :
24 mRenderResourceContext(rrc),
25 mTextRenderer(textRenderer),
26 mFontName(std::filesystem::path(filepath).stem().string()),
27 mFontSize(fontSize),
28 mErrorResult(FT_Err_Ok),
29 mFace(0)
30{
31 if (mFontSize >
32 static_cast<unsigned int>(
33 std::numeric_limits<FT_F26Dot6>::max() >> 6))
34 {
35 throw std::out_of_range("Font size exceeds FreeType supported range.");
36 }
37
38 const FT_F26Dot6 fontSize26Dot6 =
39 static_cast<FT_F26Dot6>(mFontSize) << 6;
40
41 mErrorResult = FT_New_Face(mTextRenderer->GetFontLibrary(), filepath.c_str(), 0, &mFace);
42 if (mErrorResult)
43 {
44 throw std::runtime_error{ "Failed to create new face!" };
45 }
46
47 mErrorResult = FT_Set_Char_Size(mFace, fontSize26Dot6, fontSize26Dot6, 90, 90);
48 if (mErrorResult)
49 {
50 throw std::runtime_error{ "Failed to set char size!" };
51 }
52
53 mFontMetrics.lineHeight = FreeType26Dot6ToPixelInt(mFace->size->metrics.height);
54 mFontMetrics.ascender = FreeType26Dot6ToPixelInt(mFace->size->metrics.ascender);
55 mFontMetrics.descender = FreeType26Dot6ToPixelInt(mFace->size->metrics.descender);
56}
57
58FontResources::FontResources(RenderResourceContext rrc, TextRenderer* textRenderer, std::string fontName, std::vector<uint8_t> const& fileBytes, unsigned int const fontSize)
59 :
60 mRenderResourceContext(rrc),
61 mTextRenderer(textRenderer),
62 mFontName(fontName),
63 mFontSize(fontSize),
64 mErrorResult(FT_Err_Ok),
65 mFace(0),
66 mFontFileBytes(fileBytes)
67{
68 if (mFontSize >
69 static_cast<unsigned int>(
70 std::numeric_limits<FT_F26Dot6>::max() >> 6))
71 {
72 throw std::out_of_range("Font size exceeds FreeType supported range.");
73 }
74
75 const FT_F26Dot6 fontSize26Dot6 =
76 static_cast<FT_F26Dot6>(mFontSize) << 6;
77
78 mErrorResult = FT_New_Memory_Face(mTextRenderer->GetFontLibrary(), mFontFileBytes.data(), static_cast<FT_Long>(mFontFileBytes.size()), 0, &mFace);
79 if (mErrorResult)
80 {
81 throw std::runtime_error{ "Failed to create new face!" };
82 }
83
84 mErrorResult = FT_Set_Char_Size(mFace, fontSize26Dot6, fontSize26Dot6, 90, 90);
85 if (mErrorResult)
86 {
87 throw std::runtime_error{ "Failed to set char size!" };
88 }
89
90 mFontMetrics.lineHeight = FreeType26Dot6ToPixelInt(mFace->size->metrics.height);
91 mFontMetrics.ascender = FreeType26Dot6ToPixelInt(mFace->size->metrics.ascender);
92 mFontMetrics.descender = FreeType26Dot6ToPixelInt(mFace->size->metrics.descender);
93}
94
96{
97 if (mFace)
98 {
99 FT_Done_Face(mFace);
100 mFace = nullptr;
101 }
102}
103
104void FontResources::LoadGlyphsFromCodePointRange(std::uint32_t begin, std::uint32_t end)
105{
106 std::uint32_t start = begin;
107 while (start < end)
108 {
109 std::uint32_t currentEnd = std::min(start + sMaxGlyphsPerFontAtlas - 1, end);
110 CreateFontAtlasFromRange(start, currentEnd);
111 start += sMaxGlyphsPerFontAtlas;
112 }
113}
114
115void FontResources::EnsureGlyphs(const std::vector<std::uint32_t>& codePoints)
116{
117 if (codePoints.empty())
118 return;
119
120 std::vector<GlyphIndex> glyphs;
121 for (auto codePoint : codePoints)
122 {
123 glyphs.push_back(GetIndexFromCodePoint(codePoint));
124 }
125
126 EnsureGlyphs(glyphs);
127}
128
129void FontResources::EnsureGlyphs(const std::vector<GlyphIndex>& glyphIndexes)
130{
131 if (glyphIndexes.empty())
132 return;
133
134 std::vector<GlyphIndex> lackingGlyphs;
135 for (const auto& glyphIndex : glyphIndexes)
136 {
137 auto found = mGlyphsByIndex.find(glyphIndex.index);
138 if (found == mGlyphsByIndex.end())
139 {
140 GlyphIndex gi;
141 gi.index = glyphIndex.index;
142 lackingGlyphs.push_back(gi);
143 }
144 }
145 if (!lackingGlyphs.empty())
146 {
147 CreateFontAtlasFromList(lackingGlyphs);
148 }
149}
150
155
156std::pair<GlyphMetrics, ImageData> FontResources::CreateGlyphBitmapBy(GlyphIndex glyphIndex)
157{
158 mErrorResult = FT_Load_Glyph(mFace, glyphIndex.index, FT_LOAD_DEFAULT);
159 if (mErrorResult)
160 {
161 throw std::runtime_error{ "Failed to load glyph!" };
162 }
163
164 mErrorResult = FT_Render_Glyph(mFace->glyph, FT_RENDER_MODE_NORMAL);
165 if (mErrorResult)
166 {
167 throw std::runtime_error{ "Failed to render glyph!" };
168 }
169
170 const unsigned padding = sFontAtlasPaddingPx;
171
172 const auto srcW = mFace->glyph->bitmap.width;
173 const auto srcH = mFace->glyph->bitmap.rows;
174
175 const auto dstW = srcW + padding * 2;
176 const auto dstH = srcH + padding * 2;
177
178 const auto bufferSize = dstW * dstH * 4;
179 std::vector<uint8_t> buffer(bufferSize, 0);
180 for (unsigned int y = 0; y < srcH; ++y)
181 {
182 for (unsigned int x = 0; x < srcW; ++x)
183 {
184 const size_t srcIndex =
185 static_cast<size_t>(y) * static_cast<size_t>(srcW) +
186 static_cast<size_t>(x);
187
188 const uint8_t coverage =
189 mFace->glyph->bitmap.buffer[srcIndex];
190
191 const size_t dstX =
192 static_cast<size_t>(x) + static_cast<size_t>(padding);
193
194 const size_t dstY =
195 static_cast<size_t>(y) + static_cast<size_t>(padding);
196
197 const size_t idx =
198 (dstY * static_cast<size_t>(dstW) + dstX) * 4U;
199
200 buffer[idx + 0] = coverage;
201 buffer[idx + 1] = coverage;
202 buffer[idx + 2] = coverage;
203 buffer[idx + 3] = coverage;
204 }
205 }
206 GlyphMetrics glyphMetrics;
207 glyphMetrics.width = srcW;
208 glyphMetrics.height = srcH;
209
210
211 glyphMetrics.bearingX = mFace->glyph->bitmap_left;
212 glyphMetrics.bearingY = mFace->glyph->bitmap_top;
213
214 glyphMetrics.advanceX = FreeType26Dot6ToPixelInt(mFace->glyph->advance.x);
215
216 glyphMetrics.padding = padding;
217
218 return std::pair(glyphMetrics, ImageData(dstW, dstH, buffer));
219}
220
221std::pair<GlyphMetrics, ImageData> FontResources::CreateGlyphBitmapBy(std::uint32_t codePoint)
222{
223 GlyphIndex glyphIndex;
224 glyphIndex.index = static_cast<std::uint32_t>(FT_Get_Char_Index(mFace, static_cast<FT_ULong>(codePoint)));
225
226 return CreateGlyphBitmapBy(glyphIndex);
227}
228
229void FontResources::CreateFontAtlasFromRange(std::uint32_t begin, std::uint32_t end)
230{
231 if (begin > end)
232 {
233 // range is not valid
234 return;
235 }
236
237 // 1. Create font atlas in range of [begin; end]
238 std::unordered_map<std::uint32_t, std::pair<GlyphMetrics, ImageData>> glyphs;
239 for (auto codePoint = begin; codePoint <= end; ++codePoint)
240 {
241 if (!HasGlyph(codePoint))
242 {
243 continue;
244 }
245 // Creating bitmaps of glyphs should be considered to dome multythreaded
246 glyphs[codePoint] = CreateGlyphBitmapBy(codePoint);
247 }
248 if (glyphs.empty())
249 return;
250
251 auto fontAtlas = TextureAtlasMaker::CreateTextureAtlas(glyphs);
252
253 // 2. Upload texture via TextureCache
254
255 auto textureCache = mRenderResourceContext.textureCache;
256 const std::string textureName = mFontName + "_" + std::to_string(mFontSize) + "_FontAtlas_" + std::to_string(mFontAtlases.size());
258 {
259 const std::string fileName = textureName + ".png";
260 auto fullPath = Utility::GetContentFolderPath() / fileName;
261 fontAtlas.WritePngFile(fullPath.string().c_str());
262 }
263 textureCache->LoadTexture(textureName, fontAtlas);
264
265 // 3. Create a material via MaterialCache and add texture to it
266 auto materialCache = mRenderResourceContext.materialCache;
267 const std::string materialName = textureName + "_Mat";
268 MaterialSettings materialSettings;
269 materialSettings.parentMaterialName = std::string{ "Font2D" };
270 materialSettings.materialName = materialName;
271 materialSettings.materialDomain = MaterialDomain::Sprite2D;
272 materialSettings.shadingModel = ShadingModel::Unlit;
273 materialSettings.blendMode = BlendMode::Translucent;
274 materialSettings.parameterLayout = &Font2DLayout;
275
276 materialCache->AddMaterial(materialSettings);
277 Material* material = materialCache->GetMaterial(materialName);
278 material->SetVec4("FontColor", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
279 material->SetVec4("OutlineColor", glm::vec4(0, 0, 0, 1));
280 material->SetFloat("OutlineThicknessPx", 0.0); // OFF by default
281 const float invW = 1.0f / static_cast<float>(fontAtlas.GetWidth());
282 const float invH = 1.0f / static_cast<float>(fontAtlas.GetHeight());
283 material->SetFloat("InvAtlasSizeWidth", invW);
284 material->SetFloat("InvAtlasSizeHeight", invH);
285
286 material->AddTexture(textureName);
287
288 // material->SetFloat >> Add custom material parameters for UV manipulation
289 material->InitializeRenderResources();
290
291 // 4 Upload to container mFontAtlases
292 for (auto& it : glyphs)
293 {
294 const std::uint32_t glyphIndex = static_cast<std::uint32_t>(FT_Get_Char_Index(mFace, static_cast<FT_ULong>(it.first)));
295 mGlyphsByIndex[glyphIndex] = std::pair<GlyphMetrics, std::string>(it.second.first, materialName);
296 }
297
298 mFontAtlases[materialName] = textureName;
299}
300
301bool FontResources::HasGlyph(uint32_t codePoint) const
302{
303 return FT_Get_Char_Index(mFace, codePoint) != 0;
304}
305
307{
308 return mFontMetrics;
309}
310
312{
313 auto search = mGlyphsByIndex.find(glyphIndex.index);
314 if (search == mGlyphsByIndex.end())
315 {
316 return GlyphMetrics();
317 }
318 return search->second.first;
319}
320
322{
323 auto matName = GetFontAtlasMaterialName(glyphIndex);
324 if (matName.empty())
325 {
326 return std::string();
327 }
328 auto search = mFontAtlases.find(matName);
329 if(search == mFontAtlases.end())
330 {
331 return std::string();
332 }
333 return search->second;
334}
335
337{
338 auto search = mGlyphsByIndex.find(glyphIndex.index);
339 if (search == mGlyphsByIndex.end())
340 {
341 return std::string();
342 }
343 return search->second.second;
344}
345
347{
348 GlyphIndex result;
349 result.index = FT_Get_Char_Index(mFace, codePoint);
350 return result;
351}
352
354{
355 return mFace;
356}
357
358void FontResources::CreateFontAtlasFromList(const std::vector<GlyphIndex>& glyphIndexes)
359{
360 // 1. Create font atlas from list
361 std::unordered_map<std::uint32_t, std::pair<GlyphMetrics, ImageData>> glyphs;
362 for (auto glyphIndex : glyphIndexes)
363 {
364 auto found = mGlyphsByIndex.find(glyphIndex.index);
365 if (found != mGlyphsByIndex.end())
366 continue;
367 // Creating bitmaps of glyphs should be considered to dome multithreaded
368 glyphs[glyphIndex.index] = CreateGlyphBitmapBy(glyphIndex);
369 }
370 if (glyphs.empty())
371 return;
372
373 auto fontAtlas = TextureAtlasMaker::CreateTextureAtlas(glyphs);
374
375 // 2. Upload texture via TextureCache
376
377 auto textureCache = mRenderResourceContext.textureCache;
378 const std::string textureName = mFontName + "_" + std::to_string(mFontSize) + "_FontAtlas_" + std::to_string(mFontAtlases.size());
380 {
381 const std::string fileName = textureName + ".png";
382 auto fullPath = Utility::GetContentFolderPath() / fileName;
383 fontAtlas.WritePngFile(fullPath.string().c_str());
384 }
385 textureCache->LoadTexture(textureName, fontAtlas);
386
387 // 3. Create a material via MaterialCache and add texture to it
388 auto materialCache = mRenderResourceContext.materialCache;
389 const std::string materialName = textureName + "_Mat";
390 MaterialSettings materialSettings;
391 materialSettings.parentMaterialName = std::string{ "Font2D" };
392 materialSettings.materialName = materialName;
393 materialSettings.materialDomain = MaterialDomain::Sprite2D;
394 materialSettings.shadingModel = ShadingModel::Unlit;
395 materialSettings.blendMode = BlendMode::Translucent;
396 materialSettings.parameterLayout = &Font2DLayout;
397
398 materialCache->AddMaterial(materialSettings);
399 Material* material = materialCache->GetMaterial(materialName);
400 material->SetVec4("FontColor", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
401 material->SetVec4("OutlineColor", glm::vec4(0, 0, 0, 1));
402 material->SetFloat("OutlineThicknessPx", 0.0f); // OFF by default
403 const float invW = 1.0f / static_cast<float>(fontAtlas.GetWidth());
404 const float invH = 1.0f / static_cast<float>(fontAtlas.GetHeight());
405 material->SetFloat("InvAtlasSizeWidth", invW);
406 material->SetFloat("InvAtlasSizeHeight", invH);
407
408 material->AddTexture(textureName);
409
410 // material->SetFloat >> Add custom material parameters for UV manipulation
411 material->InitializeRenderResources();
412
413 // 4 Upload to container mFontAtlases
414 for (auto& it : glyphs)
415 {
416 mGlyphsByIndex[it.first] = std::pair<GlyphMetrics, std::string>(it.second.first, materialName);
417 }
418
419 mFontAtlases[materialName] = textureName;
420}
421
422std::int32_t FontResources::FreeType26Dot6ToPixelInt(FT_Pos value)
423{
424 const FT_Pos pixelValue = value >> 6;
425
426 if (pixelValue < static_cast<FT_Pos>(std::numeric_limits<std::int32_t>::min()) ||
427 pixelValue > static_cast<FT_Pos>(std::numeric_limits<std::int32_t>::max()))
428 {
429 throw std::out_of_range("FreeType metric exceeds int32_t range.");
430 }
431
432 return static_cast<std::int32_t>(pixelValue);
433}
434
435} // 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.
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 std::filesystem::path GetContentFolderPath()
Returns absolute path to Content.
Definition utility.cpp:248
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.