From c81e63a8ff99ceeee1e97fa790b363742ca96ac8 Mon Sep 17 00:00:00 2001 From: ohyzha Date: Tue, 21 Jan 2025 16:24:33 +0200 Subject: [PATCH 1/4] fix wrong charset being used for atlas generation and use namespaces explicitly because of broken intellisense --- .../Scene/SdfFontAtlasGenerator.cpp | 80 ++++++++++--------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/openVulkanoCpp/Scene/SdfFontAtlasGenerator.cpp b/openVulkanoCpp/Scene/SdfFontAtlasGenerator.cpp index cd1c049..6d11046 100644 --- a/openVulkanoCpp/Scene/SdfFontAtlasGenerator.cpp +++ b/openVulkanoCpp/Scene/SdfFontAtlasGenerator.cpp @@ -14,9 +14,6 @@ namespace OpenVulkano::Scene { - using namespace msdfgen; - using namespace msdf_atlas; - SdfFontAtlasGeneratorConfig SdfFontAtlasGeneratorConfig::sdfDefaultConfig = { 42, 1.0, 5 }; SdfFontAtlasGeneratorConfig SdfFontAtlasGeneratorConfig::msdfDefaultConfig = { 32, 1.0, 3 }; @@ -24,11 +21,11 @@ namespace OpenVulkano::Scene void SdfFontAtlasGeneratorGeneric::GenerateAtlas(const std::string& fontFile, const std::set& charset, const std::optional& pngOutput) { - FreetypeHandle* ft; - FontHandle* font; + msdfgen::FreetypeHandle* ft; + msdfgen::FontHandle* font; InitFreetypeFromFile(ft, font, fontFile); - Charset s; - std::for_each(s.begin(), s.end(), [&](uint32_t unicode) { s.add(unicode); }); + msdf_atlas::Charset s; + std::for_each(charset.begin(), charset.end(), [&](uint32_t unicode) { s.add(unicode); }); Generate(ft, font, s, pngOutput); } @@ -42,42 +39,47 @@ namespace OpenVulkano::Scene void SdfFontAtlasGeneratorGeneric::GenerateAtlas(const Array& fontData, const std::set& charset, const std::optional& pngOutput) { - FreetypeHandle* ft; - FontHandle* font; + msdfgen::FreetypeHandle* ft; + msdfgen::FontHandle* font; InitFreetypeFromBuffer(ft, font, (const msdfgen::byte*)(fontData.Data()), fontData.Size()); - Charset s; - std::for_each(s.begin(), s.end(), [&](uint32_t unicode) { s.add(unicode); }); + msdf_atlas::Charset s; + std::for_each(charset.begin(), charset.end(), [&](uint32_t unicode) { s.add(unicode); }); Generate(ft, font, s, pngOutput); } template - void SdfFontAtlasGeneratorGeneric::GenerateAtlas(const std::string& fontFile, const Charset& charset, - const std::optional& pngOutput) + void SdfFontAtlasGeneratorGeneric::GenerateAtlas(const std::string& fontFile, + const msdf_atlas::Charset& charset, + const std::optional& pngOutput) { // TODO: dynamic atlas and add only those symbols which are not present yet in current atlas - FreetypeHandle* ft; - FontHandle* font; + msdfgen::FreetypeHandle* ft; + msdfgen::FontHandle* font; InitFreetypeFromFile(ft, font, fontFile); Generate(ft, font, charset, pngOutput); } template void SdfFontAtlasGeneratorGeneric::GenerateAtlas(const msdfgen::byte* fontData, int length, - const Charset& charset, - const std::optional& pngOutput) + const msdf_atlas::Charset& charset, + const std::optional& pngOutput) { - FreetypeHandle* ft; - FontHandle* font; + msdfgen::FreetypeHandle* ft; + msdfgen::FontHandle* font; InitFreetypeFromBuffer(ft, font, fontData, length); Generate(ft, font, charset, pngOutput); } template - void SdfFontAtlasGeneratorGeneric::InitFreetypeFromFile(FreetypeHandle*& ft, FontHandle*& font, - const std::string& fontFile) + void SdfFontAtlasGeneratorGeneric::InitFreetypeFromFile(msdfgen::FreetypeHandle*& ft, + msdfgen::FontHandle*& font, + const std::string& fontFile) { - ft = initializeFreetype(); - if (!ft) { throw std::runtime_error("Failed to initialize freetype"); } + ft = msdfgen::initializeFreetype(); + if (!ft) + { + throw std::runtime_error("Failed to initialize freetype"); + } font = loadFont(ft, fontFile.data()); if (!font) { @@ -88,11 +90,15 @@ namespace OpenVulkano::Scene } template - void SdfFontAtlasGeneratorGeneric::InitFreetypeFromBuffer(FreetypeHandle*& ft, FontHandle*& font, - const msdfgen::byte* fontData, int length) + void SdfFontAtlasGeneratorGeneric::InitFreetypeFromBuffer(msdfgen::FreetypeHandle*& ft, + msdfgen::FontHandle*& font, + const msdfgen::byte* fontData, int length) { - ft = initializeFreetype(); - if (!ft) { throw std::runtime_error("Failed to initialize freetype"); } + ft = msdfgen::initializeFreetype(); + if (!ft) + { + throw std::runtime_error("Failed to initialize freetype"); + } font = loadFontData(ft, fontData, length); if (!font) { @@ -103,24 +109,24 @@ namespace OpenVulkano::Scene } template - void SdfFontAtlasGeneratorGeneric::Generate(FreetypeHandle* ft, FontHandle* font, const Charset& chset, + void SdfFontAtlasGeneratorGeneric::Generate(msdfgen::FreetypeHandle* ft, msdfgen::FontHandle* font, + const msdf_atlas::Charset& chset, const std::optional& pngOutput) { - - std::vector glyphsGeometry; + std::vector glyphsGeometry; // FontGeometry is a helper class that loads a set of glyphs from a single font. - FontGeometry fontGeometry(&glyphsGeometry); + msdf_atlas::FontGeometry fontGeometry(&glyphsGeometry); fontGeometry.loadCharset(font, 1, chset); if constexpr (Channels == 3) { const double maxCornerAngle = 3.0; - for (GlyphGeometry& glyph: glyphsGeometry) + for (msdf_atlas::GlyphGeometry& glyph : glyphsGeometry) glyph.edgeColoring(&msdfgen::edgeColoringByDistance, maxCornerAngle, 0); } - TightAtlasPacker packer; - packer.setDimensionsConstraint(DimensionsConstraint::SQUARE); + msdf_atlas::TightAtlasPacker packer; + packer.setDimensionsConstraint(msdf_atlas::DimensionsConstraint::SQUARE); int width, height; const int glyphsPerRow = std::sqrt(glyphsGeometry.size()); const int glyphSize = m_config.glyphSize; @@ -135,7 +141,7 @@ namespace OpenVulkano::Scene Generator generator; generator.resize(width, height); - GeneratorAttributes attributes; + msdf_atlas::GeneratorAttributes attributes; generator.setAttributes(attributes); generator.setThreadCount(4); generator.generate(glyphsGeometry.data(), glyphsGeometry.size()); @@ -147,7 +153,7 @@ namespace OpenVulkano::Scene if constexpr (Channels == 3) { // store RGB as RGBA - const BitmapConstRef storage = generator.atlasStorage(); + const msdfgen::BitmapConstRef storage = generator.atlasStorage(); msdfgen::byte* data = static_cast(m_atlasData->GetTexture()->textureBuffer); for (size_t srcPos = 0, dstPos = 0; srcPos < width * height * 3; srcPos += 3, dstPos += 4) { @@ -171,7 +177,7 @@ namespace OpenVulkano::Scene for (const auto& glyph: glyphsGeometry) { GlyphInfo& info = m_atlasData->GetGlyphs()[glyph.getCodepoint()]; - const GlyphBox& glyphBox = generator.getLayout()[idx++]; + const msdf_atlas::GlyphBox& glyphBox = generator.getLayout()[idx++]; Bbox glyphBaselineBbox, glyphAtlasBbox; glyph.getQuadPlaneBounds(glyphBaselineBbox.l, glyphBaselineBbox.b, glyphBaselineBbox.r, From f50bbc798e27ffcab17fc1a4e8551d930b08cf06 Mon Sep 17 00:00:00 2001 From: ohyzha Date: Wed, 22 Jan 2025 17:06:03 +0200 Subject: [PATCH 2/4] add helper functions to FontAtlasType --- openVulkanoCpp/Scene/Text/FontAtlasType.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openVulkanoCpp/Scene/Text/FontAtlasType.hpp b/openVulkanoCpp/Scene/Text/FontAtlasType.hpp index dc95833..33d9323 100644 --- a/openVulkanoCpp/Scene/Text/FontAtlasType.hpp +++ b/openVulkanoCpp/Scene/Text/FontAtlasType.hpp @@ -40,6 +40,10 @@ namespace OpenVulkano::Scene [[nodiscard]] constexpr uint32_t GetChannelCount() const { return CHANNEL_COUNT[static_cast(m_type)]; } + [[nodiscard]] constexpr bool IsSDF() const { return m_type == SDF || m_type == MSDF; } + + [[nodiscard]] constexpr bool IsBitmap() const { return m_type == BITMAP || m_type == BITMAP_SUBPIXEL; } + [[nodiscard]] constexpr operator Type() const { return m_type; } [[nodiscard]] constexpr auto operator<=>(const FontAtlasType rhs) const { return m_type <=> rhs.m_type; } From 8ebf3aa7d21849fe044f18dd1ac9d795c14bc589 Mon Sep 17 00:00:00 2001 From: ohyzha Date: Wed, 22 Jan 2025 17:06:42 +0200 Subject: [PATCH 3/4] apply formatting to file --- .../Scene/SdfFontAtlasGenerator.cpp | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/openVulkanoCpp/Scene/SdfFontAtlasGenerator.cpp b/openVulkanoCpp/Scene/SdfFontAtlasGenerator.cpp index 6d11046..2716975 100644 --- a/openVulkanoCpp/Scene/SdfFontAtlasGenerator.cpp +++ b/openVulkanoCpp/Scene/SdfFontAtlasGenerator.cpp @@ -111,7 +111,7 @@ namespace OpenVulkano::Scene template void SdfFontAtlasGeneratorGeneric::Generate(msdfgen::FreetypeHandle* ft, msdfgen::FontHandle* font, const msdf_atlas::Charset& chset, - const std::optional& pngOutput) + const std::optional& pngOutput) { std::vector glyphsGeometry; // FontGeometry is a helper class that loads a set of glyphs from a single font. @@ -122,7 +122,9 @@ namespace OpenVulkano::Scene { const double maxCornerAngle = 3.0; for (msdf_atlas::GlyphGeometry& glyph : glyphsGeometry) + { glyph.edgeColoring(&msdfgen::edgeColoringByDistance, maxCornerAngle, 0); + } } msdf_atlas::TightAtlasPacker packer; @@ -147,8 +149,10 @@ namespace OpenVulkano::Scene generator.generate(glyphsGeometry.data(), glyphsGeometry.size()); int idx = 0; - m_atlasData = std::make_shared(Math::Vector2ui{ width, height }, fontGeometry.getMetrics().lineHeight, - channelsCount == 1 ? FontAtlasType::SDF : FontAtlasType::MSDF, m_channelsCount == 1 ? DataFormat::R8_UNORM : DataFormat::R8G8B8A8_UNORM); + m_atlasData = + std::make_shared(Math::Vector2ui { width, height }, fontGeometry.getMetrics().lineHeight, + channelsCount == 1 ? FontAtlasType::SDF : FontAtlasType::MSDF, + m_channelsCount == 1 ? DataFormat::R8_UNORM : DataFormat::R8G8B8A8_UNORM); if constexpr (Channels == 3) { @@ -164,7 +168,7 @@ namespace OpenVulkano::Scene } } else - { + { const msdfgen::BitmapConstRef& storage = generator.atlasStorage(); memcpy(m_atlasData->GetTexture()->textureBuffer, storage.pixels, width * height); } @@ -174,7 +178,7 @@ namespace OpenVulkano::Scene double l = 0, r = 0, t = 0, b = 0; }; - for (const auto& glyph: glyphsGeometry) + for (const auto& glyph : glyphsGeometry) { GlyphInfo& info = m_atlasData->GetGlyphs()[glyph.getCodepoint()]; const msdf_atlas::GlyphBox& glyphBox = generator.getLayout()[idx++]; @@ -187,6 +191,8 @@ namespace OpenVulkano::Scene double bearingY = glyphBox.bounds.t; double w = glyphBaselineBbox.r - glyphBaselineBbox.l; double h = glyphBaselineBbox.t - glyphBaselineBbox.b; + + // UV mapping double l = glyphAtlasBbox.l; double r = glyphAtlasBbox.r; double t = glyphAtlasBbox.t; @@ -200,7 +206,10 @@ namespace OpenVulkano::Scene SetGlyphData(info, { bearingX, bearingY }, { w, h }, glyphAtlasAABB, glyphBox.advance); } - if (pngOutput && !pngOutput->empty()) { m_atlasData->Save(*pngOutput); } + if (pngOutput && !pngOutput->empty()) + { + m_atlasData->Save(*pngOutput); + } destroyFont(font); deinitializeFreetype(ft); } @@ -208,4 +217,4 @@ namespace OpenVulkano::Scene template class SdfFontAtlasGeneratorGeneric<1>; template class SdfFontAtlasGeneratorGeneric<3>; } -#endif \ No newline at end of file +#endif From a73df1b4f34b3a41987ea0117bdc57340915f393 Mon Sep 17 00:00:00 2001 From: ohyzha Date: Wed, 22 Jan 2025 17:08:14 +0200 Subject: [PATCH 4/4] text with smallest possible spacing between glyphs --- examples/ExampleApps/TextExampleApp.cpp | 24 ++++++++++++------------ openVulkanoCpp/Scene/TextDrawable.cpp | 19 ++++++++++++++----- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/examples/ExampleApps/TextExampleApp.cpp b/examples/ExampleApps/TextExampleApp.cpp index e286b87..0e76db8 100644 --- a/examples/ExampleApps/TextExampleApp.cpp +++ b/examples/ExampleApps/TextExampleApp.cpp @@ -34,7 +34,7 @@ namespace OpenVulkano namespace fs = std::filesystem; constexpr int CREATE_BITMAP_ATLAS = 0; - constexpr int CREATE_NEW_ATLAS = 0; + //#define CREATE_NEW_ATLAS class TextExampleAppImpl final : public TextExampleApp { @@ -53,6 +53,8 @@ namespace OpenVulkano texts.push_back(std::make_pair("Hello, World!", TextConfig())); texts.push_back(std::make_pair("\u0410\u0411\u0412\u041F", TextConfig())); texts.push_back(std::make_pair("Unsupported glyphs \u1E30\u1E31 are coming", TextConfig())); + texts.push_back(std::make_pair("AAAA, ____", TextConfig())); + texts.push_back(std::make_pair("0123456789", TextConfig())); texts.push_back(std::make_pair("This is first line\nSecond gg line\nThird G line", TextConfig())); texts[1].second.backgroundColor.a = 255; @@ -72,12 +74,12 @@ namespace OpenVulkano generator.GetAtlas()->Save("bitmap_atlas_packed.png"); } -#if defined(MSDFGEN_AVAILABLE) && CREATE_NEW_ATLAS - std::set s = SdfFontAtlasGenerator::LoadAllGlyphs(fontPath); - m_atlasGenerator.GenerateAtlas(fontPath, s); - m_msdfAtlasGenerator.GenerateAtlas(fontPath, s); - m_atlasGenerator.GetAtlas()->Save("sdf_atlas_packed.png"); - m_msdfAtlasGenerator.GetAtlas()->Save("msdf_atlas_packed.png"); +#if defined(MSDFGEN_AVAILABLE) && defined(CREATE_NEW_ATLAS) + std::set s = SdfFontAtlasGenerator::LoadAllGlyphs(fontPath); + m_atlasGenerator.GenerateAtlas(fontPath, s); + m_msdfAtlasGenerator.GenerateAtlas(fontPath, s); + m_atlasGenerator.GetAtlas()->Save("sdf_atlas_packed.png"); + m_msdfAtlasGenerator.GetAtlas()->Save("msdf_atlas_packed.png"); #else auto sdfMetadataInfo = resourceLoader.GetResource("sdf_atlas_packed.png"); auto msdfMetadataInfo = resourceLoader.GetResource("msdf_atlas_packed.png"); @@ -90,16 +92,14 @@ namespace OpenVulkano for (int j = 0; j < texts.size(); j++) { TextDrawable* t = nullptr; -#if defined(MSDFGEN_AVAILABLE) && CREATE_NEW_ATLAS +#if defined(MSDFGEN_AVAILABLE) && defined(CREATE_NEW_ATLAS) if (i < texts.size()) { - t = new TextDrawable(m_atlasGenerator.GetAtlasData(), texts[j].second); - t->SetShader(&TextDrawable::GetSdfDefaultShader()); + t = new TextDrawable(m_atlasGenerator.GetAtlas(), texts[j].second); } else { - t = new TextDrawable(m_msdfAtlasGenerator.GetAtlasData(), texts[j].second); - t->SetShader(&TextDrawable::GetMsdfDefaultShader()); + t = new TextDrawable(m_msdfAtlasGenerator.GetAtlas(), texts[j].second); } #else if (i == 0) diff --git a/openVulkanoCpp/Scene/TextDrawable.cpp b/openVulkanoCpp/Scene/TextDrawable.cpp index ae5e2c1..a140294 100644 --- a/openVulkanoCpp/Scene/TextDrawable.cpp +++ b/openVulkanoCpp/Scene/TextDrawable.cpp @@ -96,12 +96,14 @@ namespace OpenVulkano::Scene const double lineHeight = m_atlasData->GetLineHeight(); double posY = pos.y; Math::Vector2f bmin(pos), bmax(pos); + float prevGlyphXBound = -INFINITY; for (auto begin = text.begin(), end = text.end(); begin != end;) { uint32_t c = utf8::next(begin, end); if (c == '\n') { posY -= lineHeight; + prevGlyphXBound = -INFINITY; cursorX = pos.x; continue; } @@ -115,19 +117,26 @@ namespace OpenVulkano::Scene const GlyphInfo& info = symbols.at(c); + float offset = 0; + const bool isZeroLenGlyph = info.pos[1].x == 0; + const bool isBitmap = m_atlasData->GetAtlasType().IsBitmap(); + if (prevGlyphXBound != -INFINITY && !isZeroLenGlyph && !isBitmap) + { + offset = prevGlyphXBound - (info.pos[0].x + cursorX); + } + for (int i = 0; i < 4; i++) { - vertices->position[i].x = info.pos[i].x + cursorX; + vertices->position[i].x = info.pos[i].x + cursorX + offset; vertices->uv[i] = info.uv[i]; if (i < 2) vertices->position[i].y = posY - info.pos[i].y; else vertices->position[i].y = posY + info.pos[i].y; vertices->color = m_cfg.textColor; vertices->background = m_cfg.backgroundColor; } - - // TODO: change to lower value(or ideally remove completely) to avoid overlapping and make less space between symbols - // when setting for depth comparison operator will be available( <= ) - cursorX += info.advance + 0.08; + // slight offset for bitmap atlas since it's tightly packed without any extra padding, while sdf has additional padding + cursorX += info.advance + (isBitmap ? 0.05 : 0); + prevGlyphXBound = isZeroLenGlyph ? cursorX : vertices->position[2].x; if (!m_symbolCount) bmin.x = vertices->position[0].x; bmax.x = std::max(bmax.x, vertices->position[1].x);