Merge pull request 'Text improvements' (#190) from text_improvements into master

Reviewed-on: https://git.madvoxel.net/OpenVulkano/OpenVulkano/pulls/190
Reviewed-by: Georg Hagen <georg.hagen@madvoxel.com>
This commit is contained in:
Oleksii_Hyzha
2025-01-22 23:10:22 +01:00
4 changed files with 89 additions and 61 deletions

View File

@@ -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,7 +74,7 @@ namespace OpenVulkano
generator.GetAtlas()->Save("bitmap_atlas_packed.png");
}
#if defined(MSDFGEN_AVAILABLE) && CREATE_NEW_ATLAS
#if defined(MSDFGEN_AVAILABLE) && defined(CREATE_NEW_ATLAS)
std::set<uint32_t> s = SdfFontAtlasGenerator::LoadAllGlyphs(fontPath);
m_atlasGenerator.GenerateAtlas(fontPath, s);
m_msdfAtlasGenerator.GenerateAtlas(fontPath, s);
@@ -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)

View File

@@ -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<Channels>::GenerateAtlas(const std::string& fontFile, const std::set<uint32_t>& charset,
const std::optional<std::string>& 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<Channels>::GenerateAtlas(const Array<char>& fontData, const std::set<uint32_t>& charset,
const std::optional<std::string>& 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<int Channels>
void SdfFontAtlasGeneratorGeneric<Channels>::GenerateAtlas(const std::string& fontFile, const Charset& charset,
void SdfFontAtlasGeneratorGeneric<Channels>::GenerateAtlas(const std::string& fontFile,
const msdf_atlas::Charset& charset,
const std::optional<std::string>& 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<int Channels>
void SdfFontAtlasGeneratorGeneric<Channels>::GenerateAtlas(const msdfgen::byte* fontData, int length,
const Charset& charset,
const msdf_atlas::Charset& charset,
const std::optional<std::string>& pngOutput)
{
FreetypeHandle* ft;
FontHandle* font;
msdfgen::FreetypeHandle* ft;
msdfgen::FontHandle* font;
InitFreetypeFromBuffer(ft, font, fontData, length);
Generate(ft, font, charset, pngOutput);
}
template<int Channels>
void SdfFontAtlasGeneratorGeneric<Channels>::InitFreetypeFromFile(FreetypeHandle*& ft, FontHandle*& font,
void SdfFontAtlasGeneratorGeneric<Channels>::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<int Channels>
void SdfFontAtlasGeneratorGeneric<Channels>::InitFreetypeFromBuffer(FreetypeHandle*& ft, FontHandle*& font,
void SdfFontAtlasGeneratorGeneric<Channels>::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,26 @@ namespace OpenVulkano::Scene
}
template<int Channels>
void SdfFontAtlasGeneratorGeneric<Channels>::Generate(FreetypeHandle* ft, FontHandle* font, const Charset& chset,
void SdfFontAtlasGeneratorGeneric<Channels>::Generate(msdfgen::FreetypeHandle* ft, msdfgen::FontHandle* font,
const msdf_atlas::Charset& chset,
const std::optional<std::string>& pngOutput)
{
std::vector<GlyphGeometry> glyphsGeometry;
std::vector<msdf_atlas::GlyphGeometry> 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,19 +143,21 @@ 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());
int idx = 0;
m_atlasData = std::make_shared<FontAtlas>(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<FontAtlas>(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)
{
// store RGB as RGBA
const BitmapConstRef<msdfgen::byte, 3> storage = generator.atlasStorage();
const msdfgen::BitmapConstRef<msdfgen::byte, 3> storage = generator.atlasStorage();
msdfgen::byte* data = static_cast<msdfgen::byte*>(m_atlasData->GetTexture()->textureBuffer);
for (size_t srcPos = 0, dstPos = 0; srcPos < width * height * 3; srcPos += 3, dstPos += 4)
{
@@ -168,10 +178,10 @@ 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 GlyphBox& glyphBox = generator.getLayout()[idx++];
const msdf_atlas::GlyphBox& glyphBox = generator.getLayout()[idx++];
Bbox glyphBaselineBbox, glyphAtlasBbox;
glyph.getQuadPlaneBounds(glyphBaselineBbox.l, glyphBaselineBbox.b, glyphBaselineBbox.r,
@@ -181,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;
@@ -194,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);
}

View File

@@ -40,6 +40,10 @@ namespace OpenVulkano::Scene
[[nodiscard]] constexpr uint32_t GetChannelCount() const { return CHANNEL_COUNT[static_cast<int>(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; }

View File

@@ -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);