Move some more classes

This commit is contained in:
Georg Hagen
2025-01-26 18:16:18 +01:00
parent 48b3c0745b
commit c8d7af3178
17 changed files with 17 additions and 16 deletions

View File

@@ -0,0 +1,226 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "BitmapFontAtlasGenerator.hpp"
#include "Base/Logger.hpp"
#include "FontAtlas.hpp"
#include <freetype/ftlcdfil.h>
namespace OpenVulkano::Scene
{
void BitmapFontAtlasGenerator::GenerateAtlas(const std::string& fontFile, const std::set<uint32_t>& charset,
const std::optional<std::string>& pngOutput)
{
Generate(fontFile, charset, pngOutput);
}
void BitmapFontAtlasGenerator::GenerateAtlas(const Array<char>& fontData, const std::set<uint32_t>& charset,
const std::optional<std::string>& pngOutput)
{
Generate(fontData, charset, pngOutput);
}
void BitmapFontAtlasGenerator::Generate(const std::variant<std::string, Array<char>>& source,
const std::set<uint32_t>& chset,
const std::optional<std::string>& pngOutput)
{
if (chset.empty())
{
Logger::APP->info("Charset is empty. Nothing to generate.");
return;
}
const auto& [lib, face] = FontAtlasGeneratorBase::InitFreetype(source);
FT_Set_Pixel_Sizes(face.get(), 0, m_pixelSizeConfig.CalculatePixelSize());
if (m_subpixelLayout != SubpixelLayout::UNKNOWN)
{
FT_Error error = FT_Library_SetLcdFilter(lib.get(), FT_LCD_FILTER_DEFAULT);
if (error != 0)
{
m_subpixelLayout = SubpixelLayout::UNKNOWN;
m_channelsCount = 1;
Logger::SCENE->error("Failed to set lcd filter for subpixel rendering. {}", GetFreetypeErrorDescription(error));
}
}
auto [allGlyphs, atlasWidth] = InitGlyphsForPacking(chset, face);
std::vector<Shelf> shelves = Shelf::CreateShelves(atlasWidth, allGlyphs, face, m_channelsCount);
uint32_t atlasHeight = 0;
std::for_each(shelves.begin(), shelves.end(), [&](const Shelf& shelf) { atlasHeight += shelf.GetHeight(); });
const Math::Vector2ui atlasResolution = { atlasWidth, atlasHeight };
// same as in msdfgen lib by default. see import-font.h for reference
// TODO: probably also support keeping coordinates as the integer values native to the font file
// but since some algorithms have already been implemented for EM_NORMALIZED mode, currently there is no support for default font metrics (ints)
// The coordinates will be normalized to the em size, i.e. 1 = 1 em
const double scaleFactor = (1. / face->units_per_EM);
m_atlasData = std::make_shared<FontAtlas>(atlasResolution, face->height * scaleFactor,
static_cast<bool>(m_subpixelLayout) ? FontAtlasType::BITMAP_SUBPIXEL :
FontAtlasType::BITMAP,
m_subpixelLayout.GetTextureDataFormat());
FillGlyphsInfo(allGlyphs, face, scaleFactor);
if (pngOutput) m_atlasData->Save(*pngOutput);
}
std::pair<std::vector<GlyphForPacking>, double>
BitmapFontAtlasGenerator::InitGlyphsForPacking(const std::set<uint32_t>& chset, const FtFaceRecPtr& face)
{
FT_Error error = 0;
double area = 0;
std::vector<GlyphForPacking> allGlyphs;
allGlyphs.reserve(chset.size());
for (uint32_t codepoint : chset)
{
error = FT_Load_Char(face.get(), codepoint, GetGlyphRenderMode());
if (error)
{
Logger::SCENE->error("FT_Load_Char for codepoint {} has failed. {}", codepoint,
GetFreetypeErrorDescription(error));
continue;
}
// TODO: Try to reduce resulting texture size in subpixel rendering mode,
// since freetype for some glyphs not only triples width/height by 3, but also adds extra padding and extra(meaningful?) pixels.
// NOTE: looks like it adds 2 pixels to the left and right in FT_LOAD_TARGET_LCD mode, so we should take this into account in FillSubpixelData.
// https://freetype.org/freetype2/docs/reference/ft2-lcd_rendering.html
// So, the possible approach to try is:
// 1) render glyph here with FT_LOAD_RENDER mode;
// 2) render glyph in FillGlyphsInfo with FT_LOAD_RENDER | FT_LOAD_TARGET_LCD mode;
// 3) take into account all mentioned things above for proper mapping.
FT_GlyphSlot slot = face->glyph;
GlyphForPacking& glyph = allGlyphs.emplace_back(codepoint, ScaleGlyphSize(slot->bitmap.width, slot->bitmap.rows));
area += slot->bitmap.rows * slot->bitmap.width;
}
std::sort(allGlyphs.begin(), allGlyphs.end(),
[](const GlyphForPacking& a, const GlyphForPacking& b) { return a.size.y > b.size.y; });
// make atlas in square form, so that atlasWidth +- equals atlasHeight
return { allGlyphs, ceil(sqrt(area / (m_channelsCount == 1 ? 1 : 3))) };
}
Math::Vector2ui BitmapFontAtlasGenerator::ScaleGlyphSize(unsigned int w, unsigned int h) const
{
if (m_subpixelLayout == SubpixelLayout::UNKNOWN || m_channelsCount == 1)
{
return { w, h };
}
if (m_subpixelLayout.IsHorizontalSubpixelLayout())
{
assert(w % 3 == 0);
w /= 3;
}
else
{
assert(h % 3 == 0);
h /= 3;
}
return { w, h };
}
FT_Int32 BitmapFontAtlasGenerator::GetGlyphRenderMode() const
{
if (m_channelsCount == 1)
{
return FT_LOAD_RENDER;
}
FT_Int32 glyphRenderMode = FT_LOAD_RENDER;
if (m_subpixelLayout < SubpixelLayout::RGBV)
{
glyphRenderMode |= FT_LOAD_TARGET_LCD;
}
else if (m_subpixelLayout < SubpixelLayout::UNKNOWN)
{
glyphRenderMode |= FT_LOAD_TARGET_LCD_V;
}
return glyphRenderMode;
}
void BitmapFontAtlasGenerator::FillGlyphsInfo(const std::vector<GlyphForPacking>& allGlyphs, const FtFaceRecPtr& face, double scaleFactor)
{
size_t loadedGlyphs = 0;
for (const GlyphForPacking& glyph : allGlyphs)
{
FT_Error error = FT_Load_Char(face.get(), glyph.code, GetGlyphRenderMode());
if (error)
{
Logger::SCENE->error("FT_Load_Char for codepoint {} has failed. {}", glyph.code,
GetFreetypeErrorDescription(error));
continue;
}
FT_GlyphSlot slot = face->glyph;
if (m_channelsCount == 1)
{
char* baseAddress = static_cast<char*>(m_atlasData->GetTexture()->textureBuffer)
+ glyph.firstGlyphByteInAtlas;
for (int row = 0; row < slot->bitmap.rows; row++)
{
std::memcpy(baseAddress - row * m_atlasData->GetTexture()->resolution.x,
&slot->bitmap.buffer[row * slot->bitmap.pitch], slot->bitmap.width);
}
}
else
{
FillSubpixelData(slot->bitmap, glyph);
}
GlyphInfo& glyphInfo = m_atlasData->GetGlyphs()[glyph.code];
const Math::Vector2d glyphMetrics = { slot->metrics.width * scaleFactor,
slot->metrics.height * scaleFactor };
const Math::Vector2d glyphBearing = { slot->metrics.horiBearingX * scaleFactor,
slot->metrics.horiBearingY * scaleFactor };
const Math::Vector2ui scaledAtlasSize = ScaleGlyphSize(slot->bitmap.width, slot->bitmap.rows);
Math::AABB glyphAtlasAABB(Math::Vector3f(glyph.atlasPos.x, glyph.atlasPos.y, 0), Math::Vector3f(glyph.atlasPos.x + scaledAtlasSize.x, glyph.atlasPos.y + scaledAtlasSize.y, 0));
SetGlyphData(glyphInfo, glyphBearing, glyphMetrics, glyphAtlasAABB, slot->advance.x * scaleFactor);
loadedGlyphs++;
}
Logger::SCENE->debug("Created atlas with {} glyphs, {} glyphs could not be loaded", loadedGlyphs, allGlyphs.size() - loadedGlyphs);
}
void BitmapFontAtlasGenerator::FillSubpixelData(const FT_Bitmap& bitmap, const GlyphForPacking& glyph)
{
Texture* tex = m_atlasData->GetTexture();
char* texBuffer = static_cast<char*>(tex->textureBuffer);
if (m_subpixelLayout.IsHorizontalSubpixelLayout())
{
// RGB RGB RGB
assert(bitmap.width % 3 == 0);
for (int row = 0; row < bitmap.rows; row++)
{
for (int col = 0, atlasPos = 0; col < bitmap.width; col += 3, atlasPos += 4)
{
const size_t bitmapPos = row * bitmap.pitch + col;
const size_t texturePos = (glyph.firstGlyphByteInAtlas - row * tex->resolution.x * m_channelsCount) + atlasPos;
const uint8_t rgb[3] = { bitmap.buffer[bitmapPos], bitmap.buffer[bitmapPos + 1],
bitmap.buffer[bitmapPos + 2] };
std::memcpy(texBuffer + texturePos, rgb, 3);
texBuffer[texturePos + 3] = 255;
}
}
}
else
{
// RRR
// GGG
// BBB
assert(bitmap.rows % 3 == 0);
for (int row = 0; row < bitmap.rows; row += 3)
{
for (int col = 0; col < bitmap.width; col++)
{
const size_t bitmapPos = col + (bitmap.pitch * row);
const size_t texturePos = (glyph.firstGlyphByteInAtlas + col * m_channelsCount)
- ((row / 3) * (tex->resolution.x * m_channelsCount));
const uint8_t rgb[3] = { bitmap.buffer[bitmapPos + 2 * bitmap.pitch],
bitmap.buffer[bitmapPos + bitmap.pitch], bitmap.buffer[bitmapPos] };
std::memcpy(texBuffer + texturePos, rgb, 3);
texBuffer[texturePos + 3] = 255;
}
}
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "FontAtlasGeneratorBase.hpp"
#include "SubpixelLayout.hpp"
#include "Shelf.hpp"
namespace OpenVulkano::Scene
{
class FontPixelSizeConfig
{
public:
FontPixelSizeConfig(float size = 24.f, float dpi = 72.f, bool isPixelSize = true)
: m_size(size), m_dpi(dpi), m_isPixelSize(isPixelSize)
{
}
void SetSize(float size) { m_size = size; }
void SetDpi(float dpi) { m_dpi = dpi; }
void SetIsPixelSize(bool isPixelSize) { m_isPixelSize = isPixelSize; }
[[nodiscard]] float GetSize() const { return m_size; }
[[nodiscard]] float GetDpi() const { return m_dpi; }
[[nodiscard]] bool GetIsPixelSize() const { return m_isPixelSize; }
[[nodiscard]] unsigned CalculatePixelSize() const { return m_isPixelSize ? m_size : (m_size * m_dpi) / 72.0f; }
private:
float m_size;
float m_dpi;
bool m_isPixelSize;
};
class BitmapFontAtlasGenerator : public FontAtlasGeneratorBase
{
public:
BitmapFontAtlasGenerator(FontPixelSizeConfig config = FontPixelSizeConfig(),
std::optional<SubpixelLayout> subpixelLayout = std::nullopt)
: FontAtlasGeneratorBase(subpixelLayout.has_value() && *subpixelLayout < SubpixelLayout::UNKNOWN ? 4 : 1)
, m_pixelSizeConfig(config)
, m_subpixelLayout(subpixelLayout.value_or(SubpixelLayout::UNKNOWN))
{
}
void GenerateAtlas(const std::string& fontFile, const std::set<uint32_t>& charset,
const std::optional<std::string>& pngOutput = std::nullopt) override;
void GenerateAtlas(const Array<char>& fontData, const std::set<uint32_t>& charset,
const std::optional<std::string>& pngOutput = std::nullopt) override;
private:
void Generate(const std::variant<std::string, Array<char>>& source, const std::set<uint32_t>& chset, const std::optional<std::string>& pngOutput);
void FillGlyphsInfo(const std::vector<GlyphForPacking>& allGlyphs, const FtFaceRecPtr& face, double scaleFactor);
void FillSubpixelData(const FT_Bitmap& bitmap, const GlyphForPacking& glyph);
FT_Int32 GetGlyphRenderMode() const;
// tmp function
Math::Vector2ui ScaleGlyphSize(unsigned int w, unsigned int h) const;
std::pair<std::vector<GlyphForPacking>, double> InitGlyphsForPacking(const std::set<uint32_t>& chset, const FtFaceRecPtr& face);
private:
FontPixelSizeConfig m_pixelSizeConfig;
SubpixelLayout m_subpixelLayout;
};
}

View File

@@ -5,8 +5,8 @@
*/
#include "FontAtlasFactory.hpp"
#include "Scene/SdfFontAtlasGenerator.hpp"
#include "Scene/BitmapFontAtlasGenerator.hpp"
#include "SdfFontAtlasGenerator.hpp"
#include "BitmapFontAtlasGenerator.hpp"
#include "Host/SystemFontResolver.hpp"
#include "Base/Logger.hpp"
#include "Host/ResourceLoader.hpp"

View File

@@ -7,7 +7,7 @@
#pragma once
#include "FontAtlas.hpp"
#include "Scene/SubpixelLayout.hpp"
#include "SubpixelLayout.hpp"
#include "Data/Containers/Array.hpp"
namespace OpenVulkano::Scene

View File

@@ -0,0 +1,105 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "FontAtlasGeneratorBase.hpp"
#include "FontAtlas.hpp"
#include "Base/Logger.hpp"
#include <filesystem>
namespace OpenVulkano::Scene
{
std::pair<FtLibraryRecPtr, FtFaceRecPtr>
FontAtlasGeneratorBase::InitFreetype(const std::variant<std::string, Array<char>>& source)
{
FT_Library library;
auto error = FT_Init_FreeType(&library);
if (error)
{
throw std::runtime_error(fmt::format("Could not initalize freetype library. {}", GetFreetypeErrorDescription(error)));
}
FT_Face face;
if (std::holds_alternative<std::string>(source))
{
error = FT_New_Face(library, std::get<0>(source).c_str(), 0, &face);
}
else
{
auto& arr = std::get<1>(source);
error = FT_New_Memory_Face(library, (const FT_Byte*) (arr.Data()), arr.Size(), 0, &face);
}
if (error == FT_Err_Unknown_File_Format)
{
throw std::runtime_error("Unknown font file format\n");
}
else if (error)
{
throw std::runtime_error(fmt::format("Font file could not be opened or read or it's corrupted. {}", GetFreetypeErrorDescription(error)));
}
// some fancy font without unicode charmap
if (face->charmap == nullptr)
{
throw std::runtime_error("Selected font doesn't contain unicode charmap");
}
return { FtLibraryRecPtr(library), FtFaceRecPtr(face) };
}
std::string FontAtlasGeneratorBase::GetFreetypeErrorDescription(FT_Error error)
{
if (const char* s = FT_Error_String(error))
{
return s;
}
return fmt::format("Error code is {}", error);
}
void FontAtlasGeneratorBase::SetGlyphData(GlyphInfo& info, Math::Vector2d bearing, Math::Vector2d size,
const Math::AABB& aabb, double advance)
{
const auto& resolution = m_atlasData->GetTexture()->resolution;
const double l = aabb.min.x;
const double r = aabb.max.x;
const double t = aabb.max.y;
const double b = aabb.min.y;
info.pos[0].x = bearing.x;
info.pos[0].y = size.y - bearing.y;
info.uv[0].x = l / resolution.x;
info.uv[0].y = b / resolution.y;
info.pos[1].x = bearing.x + size.x;
info.pos[1].y = size.y - bearing.y;
info.uv[1].x = r / resolution.x;
info.uv[1].y = b / resolution.y;
info.pos[2].x = bearing.x + size.x;
info.pos[2].y = bearing.y;
info.uv[2].x = r / resolution.x;
info.uv[2].y = t / resolution.y;
info.pos[3].x = bearing.x;
info.pos[3].y = bearing.y;
info.uv[3].x = l / resolution.x;
info.uv[3].y = t / resolution.y;
info.advance = advance;
}
std::set<uint32_t> FontAtlasGeneratorBase::LoadAllGlyphs(const std::variant<std::string, Array<char>>& data)
{
const auto& [lib, face] = InitFreetype(data);
std::set<uint32_t> s;
FT_UInt glyphIndex;
FT_ULong unicode = FT_Get_First_Char(face.get(), &glyphIndex);
while (glyphIndex != 0)
{
s.insert(unicode);
unicode = FT_Get_Next_Char(face.get(), unicode, &glyphIndex);
}
return s;
}
}

View File

@@ -0,0 +1,35 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "IFontAtlasGenerator.hpp"
#include "Math/AABB.hpp"
#include "Extensions/FreetypeHelper.hpp"
#include <variant>
namespace OpenVulkano::Scene
{
class GlyphInfo;
class FontAtlasGeneratorBase : public IFontAtlasGenerator
{
protected:
int m_channelsCount;
std::shared_ptr<FontAtlas> m_atlasData;
public:
FontAtlasGeneratorBase(const int channelsCount) : m_channelsCount(channelsCount) {}
[[nodiscard]] const std::shared_ptr<FontAtlas>& GetAtlas() const final { return m_atlasData; }
[[nodiscard]] int GetAtlasChannelsCount() const { return m_channelsCount; }
[[nodiscard]] static std::set<uint32_t> LoadAllGlyphs(const std::variant<std::string, Array<char>>& data);
protected:
void SetGlyphData(GlyphInfo& info, Math::Vector2d bearing, Math::Vector2d size, const Math::AABB& aabb, double advance);
[[nodiscard]] static std::string GetFreetypeErrorDescription(FT_Error error);
[[nodiscard]] static std::pair<FtLibraryRecPtr, FtFaceRecPtr> InitFreetype(const std::variant<std::string, Array<char>>& source);
};
}

View File

@@ -0,0 +1,29 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <Data/Containers/Array.hpp>
#include <string>
#include <optional>
#include <set>
#include <memory>
namespace OpenVulkano::Scene
{
struct FontAtlas;
class IFontAtlasGenerator
{
public:
virtual ~IFontAtlasGenerator() = default;
virtual void GenerateAtlas(const std::string& fontFile, const std::set<uint32_t>& charset,
const std::optional<std::string>& pngOutput = std::nullopt) = 0;
virtual void GenerateAtlas(const Array<char>& fontData, const std::set<uint32_t>& charset,
const std::optional<std::string>& pngOutput = std::nullopt) = 0;
virtual const std::shared_ptr<FontAtlas>& GetAtlas() const = 0;
};
}

View File

@@ -0,0 +1,220 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#if __has_include("msdfgen.h")
#include "FontAtlas.hpp"
#include "SdfFontAtlasGenerator.hpp"
#include "Base/Logger.hpp"
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include <msdf-atlas-gen/msdf-atlas-gen.h>
namespace OpenVulkano::Scene
{
SdfFontAtlasGeneratorConfig SdfFontAtlasGeneratorConfig::sdfDefaultConfig = { 42, 1.0, 5 };
SdfFontAtlasGeneratorConfig SdfFontAtlasGeneratorConfig::msdfDefaultConfig = { 32, 1.0, 3 };
template<int Channels>
void SdfFontAtlasGeneratorGeneric<Channels>::GenerateAtlas(const std::string& fontFile, const std::set<uint32_t>& charset,
const std::optional<std::string>& pngOutput)
{
msdfgen::FreetypeHandle* ft;
msdfgen::FontHandle* font;
InitFreetypeFromFile(ft, font, fontFile);
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> SdfFontAtlasGeneratorGeneric<Channels>::SdfFontAtlasGeneratorGeneric() : FontAtlasGeneratorBase(Channels)
{
if constexpr (Channels == 1) m_config = SdfFontAtlasGeneratorConfig::sdfDefaultConfig;
else m_config = SdfFontAtlasGeneratorConfig::msdfDefaultConfig;
}
template<int Channels>
void SdfFontAtlasGeneratorGeneric<Channels>::GenerateAtlas(const Array<char>& fontData, const std::set<uint32_t>& charset,
const std::optional<std::string>& pngOutput)
{
msdfgen::FreetypeHandle* ft;
msdfgen::FontHandle* font;
InitFreetypeFromBuffer(ft, font, (const msdfgen::byte*)(fontData.Data()), fontData.Size());
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 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
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 msdf_atlas::Charset& charset,
const std::optional<std::string>& pngOutput)
{
msdfgen::FreetypeHandle* ft;
msdfgen::FontHandle* font;
InitFreetypeFromBuffer(ft, font, fontData, length);
Generate(ft, font, charset, pngOutput);
}
template<int Channels>
void SdfFontAtlasGeneratorGeneric<Channels>::InitFreetypeFromFile(msdfgen::FreetypeHandle*& ft,
msdfgen::FontHandle*& font,
const std::string& fontFile)
{
ft = msdfgen::initializeFreetype();
if (!ft)
{
throw std::runtime_error("Failed to initialize freetype");
}
font = loadFont(ft, fontFile.data());
if (!font)
{
deinitializeFreetype(ft);
ft = nullptr;
throw std::runtime_error(fmt::format("Failed to load font from file {0}", fontFile.data()));
}
}
template<int Channels>
void SdfFontAtlasGeneratorGeneric<Channels>::InitFreetypeFromBuffer(msdfgen::FreetypeHandle*& ft,
msdfgen::FontHandle*& font,
const msdfgen::byte* fontData, int length)
{
ft = msdfgen::initializeFreetype();
if (!ft)
{
throw std::runtime_error("Failed to initialize freetype");
}
font = loadFontData(ft, fontData, length);
if (!font)
{
deinitializeFreetype(ft);
ft = nullptr;
throw std::runtime_error("Failed to load font data from given buffer");
}
}
template<int Channels>
void SdfFontAtlasGeneratorGeneric<Channels>::Generate(msdfgen::FreetypeHandle* ft, msdfgen::FontHandle* font,
const msdf_atlas::Charset& chset,
const std::optional<std::string>& pngOutput)
{
std::vector<msdf_atlas::GlyphGeometry> glyphsGeometry;
// FontGeometry is a helper class that loads a set of glyphs from a single font.
msdf_atlas::FontGeometry fontGeometry(&glyphsGeometry);
fontGeometry.loadCharset(font, 1, chset);
if constexpr (Channels == 3)
{
const double maxCornerAngle = 3.0;
for (msdf_atlas::GlyphGeometry& glyph : glyphsGeometry)
{
glyph.edgeColoring(&msdfgen::edgeColoringByDistance, maxCornerAngle, 0);
}
}
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;
const int rowWidth = glyphSize * glyphsPerRow;
packer.setDimensions(rowWidth, rowWidth);
// something to play with. should not be too high.
// more value - more sdf impact
packer.setPixelRange(m_config.pixelRange);
packer.setMiterLimit(m_config.miterLimit);
packer.pack(glyphsGeometry.data(), glyphsGeometry.size());
packer.getDimensions(width, height);
Generator generator;
generator.resize(width, height);
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);
if constexpr (Channels == 3)
{
// store RGB as RGBA
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)
{
data[dstPos] = storage.pixels[srcPos];
data[dstPos + 1] = storage.pixels[srcPos + 1];
data[dstPos + 2] = storage.pixels[srcPos + 2];
data[dstPos + 3] = 255;
}
}
else
{
const msdfgen::BitmapConstRef<msdfgen::byte, 1>& storage = generator.atlasStorage();
memcpy(m_atlasData->GetTexture()->textureBuffer, storage.pixels, width * height);
}
struct Bbox
{
double l = 0, r = 0, t = 0, b = 0;
};
for (const auto& glyph : glyphsGeometry)
{
GlyphInfo& info = m_atlasData->GetGlyphs()[glyph.getCodepoint()];
const msdf_atlas::GlyphBox& glyphBox = generator.getLayout()[idx++];
Bbox glyphBaselineBbox, glyphAtlasBbox;
glyph.getQuadPlaneBounds(glyphBaselineBbox.l, glyphBaselineBbox.b, glyphBaselineBbox.r,
glyphBaselineBbox.t);
glyph.getQuadAtlasBounds(glyphAtlasBbox.l, glyphAtlasBbox.b, glyphAtlasBbox.r, glyphAtlasBbox.t);
double bearingX = glyphBox.bounds.l;
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;
double b = glyphAtlasBbox.b;
Math::AABB glyphAtlasAABB;
glyphAtlasAABB.min.x = l;
glyphAtlasAABB.min.y = b;
glyphAtlasAABB.max.x = r;
glyphAtlasAABB.max.y = t;
SetGlyphData(info, { bearingX, bearingY }, { w, h }, glyphAtlasAABB, glyphBox.advance);
}
if (pngOutput && !pngOutput->empty())
{
m_atlasData->Save(*pngOutput);
}
destroyFont(font);
deinitializeFreetype(ft);
}
template class SdfFontAtlasGeneratorGeneric<1>;
template class SdfFontAtlasGeneratorGeneric<3>;
}
#endif

View File

@@ -0,0 +1,68 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#if __has_include("msdfgen.h")
#include "FontAtlasGeneratorBase.hpp"
#include <msdfgen.h>
#include <msdf-atlas-gen/msdf-atlas-gen.h>
#include <string>
#include <optional>
#include <map>
#include <variant>
#define MSDFGEN_AVAILABLE 1
namespace OpenVulkano::Scene
{
struct SdfFontAtlasGeneratorConfig
{
static SdfFontAtlasGeneratorConfig sdfDefaultConfig;
static SdfFontAtlasGeneratorConfig msdfDefaultConfig;
int glyphSize;
double miterLimit;
msdfgen::Range pixelRange;
};
template<int Channels>
class SdfFontAtlasGeneratorGeneric final : public FontAtlasGeneratorBase
{
private:
using SdfGenerator = msdf_atlas::ImmediateAtlasGenerator<float, 1, msdf_atlas::sdfGenerator,
msdf_atlas::BitmapAtlasStorage<msdfgen::byte, 1>>;
using MsdfGenerator = msdf_atlas::ImmediateAtlasGenerator<float, 3, msdf_atlas::msdfGenerator,
msdf_atlas::BitmapAtlasStorage<msdfgen::byte, 3>>;
public:
using Generator = std::conditional<Channels == 1, SdfGenerator, MsdfGenerator>::type;
using Config = SdfFontAtlasGeneratorConfig;
static constexpr int channelsCount = (Channels == 1 ? 1 : 4);
SdfFontAtlasGeneratorGeneric();
void GenerateAtlas(const std::string& fontFile, const std::set<uint32_t>& charset,
const std::optional<std::string>& pngOutput = std::nullopt) override;
void GenerateAtlas(const Array<char>& fontData, const std::set<uint32_t>& charset,
const std::optional<std::string>& pngOutput = std::nullopt) override;
void GenerateAtlas(const std::string& fontFile, const msdf_atlas::Charset& charset = msdf_atlas::Charset::ASCII,
const std::optional<std::string>& pngOutput = std::nullopt);
void GenerateAtlas(const msdfgen::byte* fontData, int length,
const msdf_atlas::Charset& charset = msdf_atlas::Charset::ASCII,
const std::optional<std::string>& pngOutput = std::nullopt);
void SetGeneratorConfig(const Config& config) { m_config = config; }
Config& GetGeneratorConfig() { return m_config; }
private:
void InitFreetypeFromFile(msdfgen::FreetypeHandle*& ft, msdfgen::FontHandle*& font, const std::string& file);
void InitFreetypeFromBuffer(msdfgen::FreetypeHandle*& ft, msdfgen::FontHandle*& font,
const msdfgen::byte* fontData, int length);
void Generate(msdfgen::FreetypeHandle* ft, msdfgen::FontHandle* font, const msdf_atlas::Charset& chset,
const std::optional<std::string>& pngOutput);
private:
Config m_config;
};
using SdfFontAtlasGenerator = SdfFontAtlasGeneratorGeneric<1>;
using MsdfFontAtlasGenerator = SdfFontAtlasGeneratorGeneric<3>;
}
#endif

View File

@@ -0,0 +1,109 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "Math/Math.hpp"
#include "Extensions/FreetypeHelper.hpp"
#include <vector>
#include <optional>
namespace OpenVulkano::Scene
{
struct GlyphForPacking
{
uint32_t code;
Math::Vector2i size;
Math::Vector2d atlasPos;
size_t firstGlyphByteInAtlas;
};
struct Shelf
{
inline static std::vector<Shelf> CreateShelves(uint32_t atlasWidth, std::vector<GlyphForPacking>& glyphs,
const FtFaceRecPtr& face, int channelsCount);
Shelf(uint32_t width, uint32_t height, int pixelSize, uint32_t prevShelvesHeight)
: m_width(width), m_height(height), m_remainingWidth(width), m_pixelSize(pixelSize), m_prevShelvesHeight(prevShelvesHeight) {}
bool HasSpaceForGlyph(uint32_t glyphWidth, uint32_t glyphHeight) const
{
return m_remainingWidth >= glyphWidth && m_height >= glyphHeight;
}
uint32_t GetWidth() const { return m_width; }
uint32_t GetHeight() const { return m_height; }
uint32_t GetNextGlyphPos() const { return m_nextGlyphPos; };
uint32_t GetOccupiedSize() const { return ((m_width - m_remainingWidth) * m_height); }
uint32_t GetPrevShelvesHeight() const { return m_prevShelvesHeight; }
int GetPixelSize() const { return m_pixelSize; }
std::optional<std::pair<uint32_t, uint32_t>> AddGlyph(uint32_t glyphWidth, uint32_t glyphHeight)
{
if (!HasSpaceForGlyph(glyphWidth, glyphHeight))
{
return {};
}
uint32_t insertionPos = m_nextGlyphPos;
m_nextGlyphPos += glyphWidth;
m_remainingWidth -= glyphWidth;
uint32_t hOffset = m_height - (m_height - glyphHeight);
uint32_t glyphFirstByte = (insertionPos * m_pixelSize)
+ ((hOffset * m_width * m_pixelSize) - (m_width * m_pixelSize))
+ (m_prevShelvesHeight * m_width * m_pixelSize);
return std::make_pair(insertionPos, glyphFirstByte);
}
private:
uint32_t m_width;
uint32_t m_height;
uint32_t m_remainingWidth;
uint32_t m_nextGlyphPos = 0;
uint32_t m_prevShelvesHeight;
int m_pixelSize;
};
std::vector<Shelf> Shelf::CreateShelves(uint32_t atlasWidth, std::vector<GlyphForPacking>& glyphs,
const FtFaceRecPtr& face, int channelsCount)
{
std::vector<Shelf> shelves;
for (GlyphForPacking& glyph : glyphs)
{
FT_Error error = FT_Load_Char(face.get(), glyph.code, FT_LOAD_RENDER);
if (error)
{
continue;
}
FT_GlyphSlot slot = face->glyph;
bool needNewShelf = true;
uint32_t totalPrevShelvesHeight = 0;
for (Shelf& shelf : shelves)
{
if (std::optional<std::pair<uint32_t, uint32_t>> opt = shelf.AddGlyph(glyph.size.x, glyph.size.y))
{
glyph.firstGlyphByteInAtlas = opt->second;
glyph.atlasPos.x = opt->first;
glyph.atlasPos.y = totalPrevShelvesHeight;
needNewShelf = false;
break;
}
totalPrevShelvesHeight += shelf.GetHeight();
}
if (needNewShelf)
{
shelves.emplace_back(atlasWidth, glyph.size.y, channelsCount, totalPrevShelvesHeight);
Shelf& shelf = shelves.back();
uint32_t firstByte = (*shelf.AddGlyph(glyph.size.x, glyph.size.y)).second;
glyph.firstGlyphByteInAtlas = firstByte;
glyph.atlasPos.x = 0;
glyph.atlasPos.y = totalPrevShelvesHeight;
}
}
return shelves;
}
}

View File

@@ -0,0 +1,75 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "Scene/DataFormat.hpp"
#include <magic_enum.hpp>
#include <cinttypes>
#include <string_view>
namespace OpenVulkano
{
class SubpixelLayout
{
public:
enum Layout : uint32_t
{
RGB,
BGR,
RGBV,
BGRV,
// unknown and auto must be last two
UNKNOWN,
NONE = UNKNOWN,
AUTO = UNKNOWN
};
constexpr SubpixelLayout() = default;
constexpr SubpixelLayout(Layout layout) : m_layout(layout) {}
[[nodiscard]] DataFormat GetTextureDataFormat() const
{
if (m_layout >= SubpixelLayout::UNKNOWN)
{
return DataFormat::R8_UINT;
}
if (m_layout == SubpixelLayout::BGR || m_layout == SubpixelLayout::BGRV)
{
return DataFormat::B8G8R8A8_UINT;
}
return DataFormat::R8G8B8A8_UINT;
}
[[nodiscard]] std::string_view GetName() const { return magic_enum::enum_name(m_layout); }
[[nodiscard]] constexpr bool operator==(Layout rhs) const
{
return m_layout == rhs;
}
[[nodiscard]] constexpr bool operator!=(Layout rhs) const
{
return m_layout != rhs;
}
[[nodiscard]] constexpr operator uint32_t() const
{
return m_layout;
}
[[nodiscard]] bool IsHorizontalSubpixelLayout() const
{
return m_layout == SubpixelLayout::RGB || m_layout == SubpixelLayout::BGR;
}
explicit operator bool() const { return m_layout < Layout::UNKNOWN; }
private:
Layout m_layout = Layout::UNKNOWN;
};
}