diff --git a/src/font.cpp b/src/font.cpp index 5b4a8d0..b37d5a8 100644 --- a/src/font.cpp +++ b/src/font.cpp @@ -1,11 +1,24 @@ #include "font.hpp" +#include "texture_atlas.hpp" +#include "vulkan_allocator.hpp" +#include "vulkan_instance.hpp" +#include #include +#include +#include -namespace gz { +namespace gz::vlk { - FontManager::FontManager(const std::string&& fontDir) - : fontDir(fontDir) { + FontManager::FontManager(VulkanInstance& instance, const std::string&& fontDir) + : vk(instance), fontDir(fontDir) + { + fLog = Log(LogCreateInfo { + .showLog = true, + .storeLog = false, + .prefix = "FontManager", + .prefixColor = Color::BO_GREEN, + }); FT_Error error = FT_Init_FreeType(&freetype); if (error != FT_Err_Ok) { throw Exception("Could not load freetype library", "FontManager"); @@ -13,16 +26,132 @@ namespace gz { } - - void FontManager::loadFont(const std::string& font) { - FT_Error error = FT_New_Face(freetype, (fontDir + "/" + font).c_str(), 0, &faces[font]); + FontManager::~FontManager() { + FT_Error error = FT_Done_FreeType(freetype); if (error != FT_Err_Ok) { - throw Exception("Could not load font.", "FontManager::loadFont"); - } - FT_Set_Pixel_Sizes(faces[font], 0, 48); - if (FT_Load_Char(faces[font], 'X', FT_LOAD_RENDER)) { - throw Exception("Could not load char.", "FontManager::loadFont"); + fLog.error("~FontManager: FT_Done_FreeType returned", error); } } + + Font FontManager::getFont(const std::string& fontname) { + if (fonts.contains(fontname)) { + return fonts.at(fontname); + } + else { + return loadFont(fontname); + } + } + + Font FontManager::loadFont(const std::string& fontname) { + static constexpr FT_Long faceIndex = 0; + Font font = fontInfos.size(); + + fontInfos.emplace_back(FontInfo(TextureAtlas(vk, 48, 48, 16, 16, vk::Format::eR8Uint))); + FT_Face& face = fontInfos.back().face; + + FT_Error error = FT_New_Face(freetype, (fontDir + "/" + fontname).c_str(), faceIndex, &face); + if (error != FT_Err_Ok) { + throw Exception("Could not load font.", "FontManager::loadFont"); + } + for (FT_Int i = 0; i < face->num_charmaps; i++) { + if (face->charmaps[i]->encoding == ft_encoding_unicode) { + face->charmap = face->charmaps[i]; + fLog("loadFont: loading", font, "with unicode charmap index", i); + goto foundUnicodeMap; + } + fLog.log0("loadFont: Charmap", i, face->charmaps[i]->platform_id, face->charmaps[i]->encoding); + } + throw Exception("Could not find unicode char map for font '" + fontname + "'.", "FontManager::loadFont"); +foundUnicodeMap: + /* FT_Set_Pixel_Sizes(fonts[font], 0, 48); */ + /* fonts[font]->charmap->encoding_idk */ + fLog("Font num_glyphs:", face->num_glyphs); + addChars(font, "abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789üß"); + return font; + } + + void FontManager::addChars(Font font, const std::string& chars) { + FT_Face& face = fontInfos.at(font).face; + TextureAtlas& atlas = fontInfos.at(font).atlas; + auto& charInfoMap = fontInfos.at(font).charInfos; + for (char c : chars) { + if (charInfoMap.contains(c)) { continue; } + FT_UInt charIndex = FT_Get_Char_Index(face, c); + fLog("Char:", c, "index:", charIndex, "glyph index:", face->glyph->glyph_index, "bitmap rows/width:", face->glyph->bitmap.rows, face->glyph->bitmap.width, "bitmap ptr:", reinterpret_cast(face->glyph->bitmap.buffer), "(before load char)"); + if (FT_Load_Char(face, c, FT_LOAD_RENDER)) { + throw Exception(std::string("Could not load char.") + c, "FontManager::loadFont"); + } + if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL)) { + throw Exception(std::string("Could not render char.") + c, "FontManager::loadFont"); + } + /* for (size_t i = 0; i < face->glyph->bitmap_left) */ + FT_Bitmap& bitmap = face->glyph->bitmap; + fLog("Char:", c, + "index:", charIndex, + "glyph index:", face->glyph->glyph_index, + "bitmap rows/width:", bitmap.rows, bitmap.width, + "bitmap pitch:", bitmap.pitch, + "bitmap ptr:", gz::toHexString(reinterpret_cast(bitmap.buffer))); + assert(bitmap.pitch > 0); + CharInfo& charInfo = charInfoMap[c]; + charInfo.atlas = font; + uint8_t bytesPerPixel = bitmap.width / bitmap.pitch; + std::tie(charInfo.texCoordTopLeft, charInfo.texCoordBottomRight) = atlas.addTexture(bitmap.buffer, bitmap.rows, bitmap.width, bytesPerPixel); + } + } + + void FontManager::createDescriptorResources() { + // 1) layout + // combined image sampler + std::vector samplerLayoutBindings(fontInfos.size(), { + .binding = 0, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment, + /* .pImmutableSamplers = nullptr, */ + } + ); + vk.createDescriptorSetLayout(samplerLayoutBindings, descriptorSetLayout); + + // 2) pool + vk::DescriptorPoolSize poolSize { + .type = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = vk.getMaxFramesInFlight(), + }; + vk.createDescriptorPool({ poolSize }, vk.getMaxFramesInFlight(), descriptorPool); + + // 3) Set + vk.createDescriptorSet(descriptorSetLayout, descriptorPool, descriptorSet); + + // 4) configure sets + std::vector imageInfos; + for (auto it = fontInfos.cbegin(); it != fontInfos.cend(); it++) { + imageInfos.emplace_back(); + imageInfos.back().imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + imageInfos.back().imageView = it->atlas.getTextureImageView(); + imageInfos.back().sampler = it->atlas.getTextureSampler(); + } + + vk::WriteDescriptorSet descriptorW { + .dstSet = descriptorSet, + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + }; + descriptorW.setImageInfo(imageInfos); + + // 1 write, no copies + vk.getDevice().updateDescriptorSets(descriptorW, nullptr); + fLog.log0("createDescriptorSets: Created descriptor sets."); + } + + + void FontManager::getTexCoords(Font font, char c, glm::vec2& texCoords) { + CharInfo& charI = fontInfos.at(font).charInfos.at(c); + fLog.log0("getTexCoords", texCoords, "textureInfo: topleft", charI.texCoordTopLeft, "botright", charI.texCoordBottomRight); + texCoords = charI.texCoordTopLeft + texCoords * (charI.texCoordBottomRight - charI.texCoordTopLeft); + } + } diff --git a/src/font.hpp b/src/font.hpp index 5dd7d88..4a20c2f 100644 --- a/src/font.hpp +++ b/src/font.hpp @@ -1,20 +1,80 @@ #pragma once +#include "texture_atlas.hpp" +#include "vulkan_allocator.hpp" + +#include #include #include #include #include FT_FREETYPE_H -namespace gz { +#include + +namespace gz::vlk { + struct CharInfo { + // TODO is atlas member needed? + uint32_t atlas; + glm::vec2 texCoordTopLeft; + glm::vec2 texCoordBottomRight; + }; + /// Handle + using Font = uint16_t; + struct FontInfo { + FontInfo(TextureAtlas&& atlas) : atlas(atlas) {}; + FT_Face face; + TextureAtlas atlas; + using CharInfos = std::unordered_map; + CharInfos charInfos; + }; + + class FontManager { public: - FontManager(const std::string&& fontDir); - void loadFont(const std::string& font); - const std::unordered_map& getFaces() const { return faces; } + FontManager(VulkanInstance& instance, const std::string&& fontDir); + /** + * @brief Load a font + * @details + * -# load face + * -# set the charmap to unicode (throw if not found) + */ + ~FontManager(); + FontManager(const FontManager& other) = delete; + FontManager(FontManager&& other) = delete; + Font getFont(const std::string& fontname); + const std::unordered_map& getFonts() const { return fonts; } + + void getTexCoords(Font font, const char c, glm::vec2& texCoords); + const vk::DescriptorSet& getDescriptorSet() const { return descriptorSet; } + const vk::DescriptorSetLayout& getDescriptorSetLayout() const { return descriptorSetLayout; } private: + Font loadFont(const std::string& fontname); + VulkanInstance& vk; FT_Library freetype; std::string fontDir; - std::unordered_map faces; + Log fLog; + + void addChars(Font font, const std::string& chars); + /** + * @name Font management + */ + /// @{ + /// Font name to handle + std::unordered_map fonts; + /// Font handle as index + std::vector fontInfos; + /// @} + /** + * @name Create desciptors + * @todo Currently the same as in TextureManager -> merge? + * @see fm_descriptors + */ + /// @{ + vk::DescriptorSetLayout descriptorSetLayout; + vk::DescriptorPool descriptorPool; + vk::DescriptorSet descriptorSet; + void createDescriptorResources(); + /// @} }; }