CoriEngine
Loading...
Searching...
No Matches
Font.cpp
Go to the documentation of this file.
1#include "Font.hpp"
2#include "FontData.hpp"
6
7namespace Cori {
8 namespace Graphics {
9 std::shared_ptr<Font> Font::Create(const std::filesystem::path& path, const std::vector<CharsetRange>& charsets, const float minimalScale /*48.0f*/, const float miterLimit /*1.0f*/) {
11 std::shared_ptr<Font> coriFont = std::make_shared<Font>();
12 Core::Application::SubmitWorkerTask([path, charsets, minimalScale, miterLimit, coriFont] {
13 CORI_CORE_INFO_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "Loading Font from: {}", path.string());
14 msdfgen::FreetypeHandle* ft = msdfgen::initializeFreetype();
15 CORI_CORE_ASSERT(ft, "Failed to initialize FreeType when loading Font");
16 msdfgen::FontHandle* font = msdfgen::loadFont(ft, path.string().c_str());
17 if (font) {
18 coriFont->Load(static_cast<void*>(font), charsets, path, minimalScale, miterLimit);
19 CORI_CORE_INFO_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "Loaded Font from '{}' successfully.", path.string());
20 msdfgen::destroyFont(font);
21 } else {
22 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "Failed to load Font from '{}', checking if placeholder is available.", path.string());
24 CORI_CORE_INFO_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "Placeholder is available for <{}>, asset status set to PLACEHOLDER.", CORI_CLEAN_TYPE_NAME(Font));
25 coriFont->m_Status = AssetStatus::PLACEHOLDER;
26 } else {
27 CORI_CORE_FATAL_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "No placeholder is available for <{}>, asset status set to FAILED.", CORI_CLEAN_TYPE_NAME(Font));
28 coriFont->m_Status = AssetStatus::FAILED;
29 }
30 }
31 msdfgen::deinitializeFreetype(ft);
32 });
33 return coriFont;
34 }
35
36 std::shared_ptr<Font> Font::Create(const Descriptor& descriptor) {
37 return Create(descriptor.m_FontPath, descriptor.m_CharsetRanges, descriptor.m_MinimalScale, descriptor.m_MiterLimit);
38 }
39
41 m_Status = AssetStatus::LOADING;
42 }
43
44 void Font::Load(void* font, const std::vector<CharsetRange>& charsets, const std::filesystem::path& fontPath, const float minimalScale, const float miterLimit) {
46 auto font_ = static_cast<msdfgen::FontHandle*>(font);
47
48 m_Data = new Internal::FontData();
49
50 msdf_atlas::Charset charset;
51
52 for (const auto& [m_Start, m_End] : charsets) {
53 for (uint32_t c = m_Start; c <= m_End; ++c) {
54 charset.add(c);
55 }
56 }
57
58 CORI_CORE_DEBUG_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "Trying to load '{}' glyphs.", charset.size());
59
60 int32_t loadedCount = m_Data->m_FontGeometry.loadCharset(font_, 1.0f, charset);
61 if (loadedCount > 0) {
62 CORI_CORE_DEBUG_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "Loaded '{}' glyphs. This number can be lower than the requested glyph count '{}' due to font not having some ones.", loadedCount, charset.size());
63 } else {
64 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "Failed to load charset with '{}' glyphs. loadCharset returned '{}'", charset.size(), loadedCount);
65 }
66
67 constexpr float angleThreshold = 3.0f;
68 for (msdf_atlas::GlyphGeometry& glyph : m_Data->m_Glyphs) {
69 glyph.edgeColoring(&msdfgen::edgeColoringInkTrap, angleThreshold, 0);
70 }
71
72 msdf_atlas::TightAtlasPacker packer;
73 packer.setDimensionsConstraint(msdf_atlas::DimensionsConstraint::SQUARE);
74 packer.setMinimumScale(minimalScale);
75 packer.setSpacing(1);
76 packer.setPixelRange(2.0);
77 packer.setMiterLimit(miterLimit);
78 // this take time
79 int32_t notPackedCount = packer.pack(m_Data->m_Glyphs.data(), m_Data->m_Glyphs.size());
80 if (notPackedCount > 0) {
81 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "Failed to pack '{}' glyphs.", notPackedCount);
82 }
83
84 m_Data->m_FinalScale = packer.getScale();
85
86 std::filesystem::path target = std::filesystem::path("cache") / "fonts" / (fontPath.stem().string() + "Cache.bin");
87
88 std::filesystem::create_directories(target.parent_path());
89
90 size_t fontFileSize = std::filesystem::file_size(fontPath);
91
92 static auto CheckCached = [&]() -> bool {
93 std::ifstream f(target, std::ios::in | std::ios::binary);
94
95 if (!f.is_open()) {
96 CORI_CORE_INFO_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "No cache found for font '{}', generating it now.", fontPath.stem().string());
97 return true;
98 }
99
100 if (!f.good()) {
101 f.close();
102 CORI_CORE_ERROR_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "Failed to read cached for font '{}', cache will be generated from scratch.", fontPath.stem().string());
103 return true;
104 }
105
106 f.seekg(0, std::ios::end);
107 size_t fileSize = f.tellg();
108 f.seekg(0, std::ios::beg);
109
110 std::vector<unsigned char> buffer(fileSize);
111
112 f.read(reinterpret_cast<char*>(buffer.data()), fileSize);
113
114 f.close();
115
116 auto widthLoaded = *reinterpret_cast<int32_t*>(&buffer[0]);
117 auto heightLoaded = *reinterpret_cast<int32_t*>(&buffer[4]);
118 auto finalSizeLoaded = *reinterpret_cast<double*>(&buffer[8]);
119 auto miterLimitLoaded = *reinterpret_cast<float*>(&buffer[16]);
120 auto cachedFontFileSize = *reinterpret_cast<size_t*>(&buffer[20]);
121
122 if (finalSizeLoaded != m_Data->m_FinalScale) {
123 CORI_CORE_WARN_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "Cache for font with name '{}' was generated with different scale, regenerating it now.", fontPath.stem().string());
124 return true;
125 }
126
127 if (miterLimitLoaded != miterLimit) {
128 CORI_CORE_WARN_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "Cache for font with name '{}' was generated with different miter limit, regenerating it now.", fontPath.stem().string());
129 return true;
130 }
131
132 if (cachedFontFileSize != fontFileSize) {
133 CORI_CORE_WARN_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "Cache for font with name '{}' was generated with the font that had different filesize, regenerating it now.", fontPath.stem().string());
134 return true;
135 }
136
137 for (const auto& [m_Start, m_End] : charsets) {
138 bool found = false;
139 for (uint32_t i = 0; i < static_cast<uint32_t>(buffer[28]); ++i) {
140 size_t startPos = 32 + i * 8;
141 auto start = *reinterpret_cast<uint32_t*>(&buffer[startPos]);
142 auto end = *reinterpret_cast<uint32_t*>(&buffer[startPos + 4]);
143
144 if (start == m_Start && end == m_End) {
145 found = true;
146 break;
147 }
148 }
149
150 if (!found) {
151 CORI_CORE_WARN_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "Cache for font with name '{}' was generated with different charsets, regenerating it now.", fontPath.stem().string());
152 return true;
153 }
154 }
155
156 size_t assumedAtlasSize = widthLoaded * 3 * heightLoaded;
157 size_t pixelsOffset = 32 + static_cast<uint32_t>(buffer[28]) * 8;
158 size_t actualAtlasSize = fileSize - pixelsOffset;
159
160 if (assumedAtlasSize != actualAtlasSize) {
161 CORI_CORE_WARN_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "Cache for font with name '{}' has corrupted pixel data, regenerating it now.", fontPath.stem().string());
162 return true;
163 }
164
165 CORI_CORE_INFO_TAGGED({ Logger::Tags::Graphics::Self, Logger::Tags::Graphics::Font }, "Cache found for font '{}', msdf-atlas will be loaded from it.", fontPath.stem().string());
166
167 void* pixels = &buffer[pixelsOffset];
168
169 Texture::Params params2 { .m_PixelFormat = Texture::RGB888, .m_WrapMode = Texture::CLAMP_TO_EDGE, .m_Filter = Texture::LINEAR, .m_UnpackAlignment = 1, .m_HasSemiTransparency = false, };
170
171 void* data = malloc(assumedAtlasSize);
172 memcpy(data, pixels, assumedAtlasSize);
173
174 std::shared_ptr<Font> subject = shared_from_this();
175
176 Core::Application::SubmitMainTask([data, params2, subject, widthLoaded, heightLoaded] {
177 subject->m_Data->m_Atlas = Texture2D::Create(data, widthLoaded, heightLoaded, params2);
178 subject->m_Status = AssetStatus::READY;
179 free(data);
180 });
181
182 //m_Data->m_Atlas = Texture2D::Create(data, widthLoaded, heightLoaded, params2);
183 return false;
184 };
185
186 bool regenerate = CheckCached();
187
188 if (regenerate) {
189 int32_t width = 0;
190 int32_t height = 0;
191 packer.getDimensions(width, height);
192 msdf_atlas::ImmediateAtlasGenerator<float, 3, msdf_atlas::msdfGenerator, msdf_atlas::BitmapAtlasStorage<msdf_atlas::byte, 3>> generator(width, height);
193 msdf_atlas::GeneratorAttributes attributes;
194 attributes.scanlinePass = true;
195 generator.setAttributes(attributes);
196 int32_t n = std::thread::hardware_concurrency();
197 if (n == 1) {
198 generator.setThreadCount(n);
199 }
200 else {
201 if (n > 12) {
202 generator.setThreadCount(n - 4);
203 } else {
204 generator.setThreadCount(n / 2);
205 }
206 }
207
208 generator.generate(m_Data->m_Glyphs.data(), m_Data->m_Glyphs.size());
209 msdfgen::BitmapConstRef<msdf_atlas::byte, 3> storage = generator.atlasStorage();
210
211 std::ofstream out(target, std::ios::out | std::ios::binary);
212
213 uint32_t charsetSize = charsets.size();
214
215 /* Font Cache binary file layout
216 * offset 0 - size 4 bytes (int32_t) - atlas width,
217 * offset 4 - size 4 bytes (int32_t) - atlas width,
218 * offset 8 - size 8 bytes (double) - finalScale,
219 * offset 16 - size 4 bytes (float) - mitterLimit,
220 * offset 20 - size 8 bytes (size_t) - size of the font file the cache was created from,
221 * offset 28 - size 4 bytes (uint32_t) - the amount of charsets the cache was created with, and also the amount of charsets present further in the file,
222 * offset 32: next there is charsets, the amount is described by the previous 4 bytes. Each charset is 8 bytes, first 4 bytes (uint32_t) - start codepoint, last 4 bytes (uint32_t) - end codepoint.
223 * the rest is msdf-atlas in uncompressed RGB888 format, without padding for Alpha channel.
224 */
225
226 out.write(reinterpret_cast<const char*>(&storage.width), sizeof(storage.width));
227 out.write(reinterpret_cast<const char*>(&storage.height), sizeof(storage.height));
228 out.write(reinterpret_cast<const char*>(&m_Data->m_FinalScale), sizeof(m_Data->m_FinalScale));
229 out.write(reinterpret_cast<const char*>(&miterLimit), sizeof(miterLimit));
230 out.write(reinterpret_cast<const char*>(&fontFileSize), sizeof(fontFileSize));
231 out.write(reinterpret_cast<const char*>(&charsetSize), sizeof(charsetSize));
232
233 for (const auto& [m_Start, m_End] : charsets) {
234 out.write(reinterpret_cast<const char*>(&m_Start), sizeof(m_Start));
235 out.write(reinterpret_cast<const char*>(&m_End), sizeof(m_End));
236 }
237
238 out.write(reinterpret_cast<const char*>(storage.pixels), storage.width * 3 * storage.height);
239
240 out.close();
241
242 void* data = malloc(storage.width * 3 * storage.height);
243 memcpy(data, storage.pixels, storage.width * 3 * storage.height);
244
245 Texture::Params params{.m_PixelFormat = Texture::RGB888, .m_WrapMode = Texture::CLAMP_TO_EDGE, .m_Filter = Texture::LINEAR, .m_UnpackAlignment = 1, .m_HasSemiTransparency = false};
246
247 std::shared_ptr<Font> subject = shared_from_this();
248 int32_t w = storage.width;
249 int32_t h = storage.height;
250
251 Core::Application::SubmitMainTask([data, params, subject, w, h]{
252 subject->m_Data->m_Atlas = Texture2D::Create(data, w, h, params);
253 subject->m_Status = AssetStatus::READY;
254 free(data);
255 });
256
257 //m_Data->m_Atlas = Texture2D::Create(storage.pixels, storage.width, storage.height, params);
258 }
259 }
260
261
263 delete m_Data;
264 }
265
267 return m_Status;
268 }
269
270 Internal::FontData* Font::GetData() {
271 return m_Data;
272 }
273 }
274}
#define CORI_CLEAN_TYPE_NAME(tn)
#define CORI_CORE_DEBUG_TAGGED(...)
Definition Logger.hpp:1026
#define CORI_CORE_FATAL_TAGGED(...)
Definition Logger.hpp:1040
#define CORI_CORE_ASSERT(x,...)
Definition Logger.hpp:1029
#define CORI_CORE_ERROR_TAGGED(...)
Definition Logger.hpp:1039
#define CORI_CORE_WARN_TAGGED(...)
Definition Logger.hpp:1038
#define CORI_CORE_INFO_TAGGED(...)
Definition Logger.hpp:1027
#define CORI_PROFILE_FUNCTION()
Definition Profiler.hpp:9
static bool HasPlaceholder()
Checks if an asset type have placeholder registered.
static std::future< std::invoke_result_t< F, Args... > > SubmitMainTask(F &&f, Args &&... args)
Submits a task to be executed on the main thread.
static std::future< std::invoke_result_t< F, Args... > > SubmitWorkerTask(F &&f, Args &&... args)
Submits a task to be executed on the worker thread.
AssetStatus GetStatus() const
Definition Font.cpp:266
static std::shared_ptr< Font > Create(const std::filesystem::path &path, const std::vector< CharsetRange > &charsets, const float minimalScale=48.0f, const float miterLimit=1.0f)
Creates a Font object.
Definition Font.cpp:9
static std::shared_ptr< Texture2D > Create(const std::shared_ptr< Image > &image)
Creates a Texture2D from the Image.
Definition Texture.cpp:7
Almost everything connected to graphics is in this namespace.
Definition Window.hpp:7
Global engine namespace.
static constexpr char Font[]
Definition Logger.hpp:62
static constexpr char Self[]
Definition Logger.hpp:47