Skip to content

Commit

Permalink
Use msdfgen for fonts
Browse files Browse the repository at this point in the history
  • Loading branch information
Pursche committed Oct 19, 2024
1 parent 491414c commit 1b07163
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 110 deletions.
2 changes: 1 addition & 1 deletion Source/Renderer/Renderer.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local mod = Solution.Util.CreateModuleTable("Renderer", { "base", "shadercooker", "tracyprofiler", "imgui", "gli", "typesafe", "utfcpp" })
local mod = Solution.Util.CreateModuleTable("Renderer", { "base", "shadercooker", "tracyprofiler", "imgui", "gli", "typesafe", "utfcpp", "msdf-atlas-gen" })

Solution.Util.CreateStaticLib(mod.Name, Solution.Projects.Current.BinDir, mod.Dependencies, function()
local defines = { "_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS", "_SILENCE_ALL_MS_EXT_DEPRECATION_WARNINGS" }
Expand Down
214 changes: 138 additions & 76 deletions Source/Renderer/Renderer/Font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,49 +10,47 @@

#include <filesystem>
#include <utfcpp/utf8.h>
#include <msdf-atlas-gen/msdf-atlas-gen.h>

#undef CreateFont

namespace Renderer
{
msdfgen::FreetypeHandle* Font::_ftHandle = nullptr;
robin_hood::unordered_map<u64, Font*> Font::_fonts;

FontChar& Font::GetChar(u32 character)
const Glyph& Font::GetGlyph(u32 codepoint)
{
auto it = _chars.find(character);
if (it == _chars.end())
auto it = _codepointToGlyphIndex.find(codepoint);
if (it == _codepointToGlyphIndex.end())
{
FontChar fontChar;
if (!InitChar(character, fontChar))
{
NC_LOG_CRITICAL("The font does not support this character");
}

_chars[character] = fontChar;
NC_LOG_CRITICAL("The font does not support this codepoint");
}

return _chars[character];
u32 glyphIndex = it->second;
return _glyphs[glyphIndex];
}

Font* Font::GetDefaultFont(Renderer* renderer, f32 fontSize)
Font* Font::GetDefaultFont(Renderer* renderer)
{
// Hash and add together the fontPath and fontSize
u64 hash = XXHash64::hash("DEFAULT", 8, 42) + XXHash64::hash(&fontSize, sizeof(f32), 42);
// Hash the fontPath
u64 hash = XXHash64::hash("DEFAULT", 8, 42);

auto it = _fonts.find(hash);
if (it == _fonts.end())
{
Font* font = CreateFont(renderer, UBUNTU_REGULAR.data(), fontSize);
Font* font = InitFont(renderer, "DEFAULT_FONT", UBUNTU_REGULAR.data(), UBUNTU_REGULAR.size());
font->desc.path = "DEFAULT_FONT";
_fonts[hash] = font;
}

return _fonts[hash];
}

Font* Font::GetFont(Renderer* renderer, const std::string& fontPath, f32 fontSize)
Font* Font::GetFont(Renderer* renderer, const std::string& fontPath)
{
// Hash and add together the fontPath and fontSize
u64 hash = XXHash64::hash(fontPath.data(), fontPath.size(), 42) + XXHash64::hash(&fontSize, sizeof(f32), 42);
// Hash the fontPath
u64 hash = XXHash64::hash(fontPath.data(), fontPath.size(), 42);

auto it = _fonts.find(hash);
if (it == _fonts.end())
Expand All @@ -76,102 +74,166 @@ namespace Renderer
std::shared_ptr<Bytebuffer> buffer = Bytebuffer::Borrow<209715200>();
file.Read(buffer.get(), file.Length());

Font* font = CreateFont(renderer, buffer->GetDataPointer(), fontSize);
Font* font = InitFont(renderer, fontPath, buffer->GetDataPointer(), file.Length());
font->desc.path = fontPath;
_fonts[hash] = font;
}

return _fonts[hash];
}

bool Font::InitChar(u32 character, FontChar& fontChar)
{
// We need to investigate the on edge value (192) and the pixel distance scale (32.0f) further
fontChar.data = stbtt_GetCodepointSDF(fontInfo, scale, character, desc.padding, 192, 32.0f, &fontChar.width, &fontChar.height, &fontChar.xOffset, &fontChar.yOffset);

if (fontChar.width == 0 && fontChar.height == 0)
return false;

int advance;
int leftSideBearing;
stbtt_GetCodepointHMetrics(fontInfo, character, &advance, &leftSideBearing);
fontChar.advance = advance * scale;
fontChar.leftSideBearing = leftSideBearing * scale;

DataTextureDesc textureDesc;
textureDesc.width = fontChar.width;
textureDesc.height = fontChar.height;
textureDesc.format = ImageFormat::R8_UNORM;
textureDesc.data = fontChar.data;
textureDesc.debugName = desc.path + " " + std::to_string(character);

_renderer->CreateDataTextureIntoArray(textureDesc, _textureArray, fontChar.textureIndex);

delete[] textureDesc.data;

return true;
}

Font* Font::CreateFont(Renderer* renderer, const u8* data, f32 fontSize)
Font* Font::InitFont(Renderer* renderer, std::string_view debugName, const u8* data, size_t dataSize)
{
Font* font = new Font();
font->_renderer = renderer;

font->fontInfo = new stbtt_fontinfo();
stbtt_InitFont(font->fontInfo, data, 0);
// Initialize freetype if it isn't already
if (_ftHandle == nullptr)
{
_ftHandle = msdfgen::initializeFreetype();
}

font->scale = stbtt_ScaleForPixelHeight(font->fontInfo, fontSize);
// Generate atlas
bool success = true;

stbtt_GetFontVMetrics(font->fontInfo, &font->ascent, &font->descent, &font->lineGap);
// Load font file
msdfgen::FontHandle* fontHandle = msdfgen::loadFontData(_ftHandle, data, static_cast<i32>(dataSize));

// Create texture array
TextureArrayDesc desc;
desc.size = 4096;
if (fontHandle == nullptr)
{
NC_LOG_ERROR("Failed to load font");
return nullptr;
}

font->_textureArray = renderer->CreateTextureArray(desc);
// FontGeometry is a helper class that loads a set of glyphs from a single font.
// It can also be used to get additional font metrics, kerning information, etc.
msdf_atlas::FontGeometry fontGeometry(&font->_glyphs);
fontGeometry.loadCharset(fontHandle, 1.0f, msdf_atlas::Charset::ASCII); // TODO: Loop over and load all characters

// Preload char 32 to 127 (commonly used ASCII characters)
for (u32 i = 32; i < 127; i++)
// Apply MSDF edge coloring. See edge-coloring.h for other coloring strategies.
const f64 maxCornerAngle = 3.0;
for (msdf_atlas::GlyphGeometry& glyph : font->_glyphs)
{
FontChar fontChar;
if (font->InitChar(i, fontChar))
{
font->_chars[i] = fontChar;
}
glyph.edgeColoring(&msdfgen::edgeColoringInkTrap, maxCornerAngle, 0);
}

// TightAtlasPacker class computes the layout of the atlas.
msdf_atlas::TightAtlasPacker packer;
// Set atlas parameters:
// setDimensions or setDimensionsConstraint to find the best value
//packer.setDimensionsConstraint(msdf_atlas::DimensionsConstraint::SQUARE);
packer.setDimensions(512, 512);

// setScale for a fixed size or setMinimumScale to use the largest that fits
packer.setMinimumScale(1.0);
// setPixelRange or setUnitRange
//packer.setPixelRange(2.0);
packer.setUnitRange(0.5);
packer.setMiterLimit(1.0);

// Compute atlas layout - pack glyphs
packer.pack(font->_glyphs.data(), static_cast<i32>(font->_glyphs.size()));

// Get final atlas dimensions
int width = 0, height = 0;
packer.getDimensions(width, height);

// The ImmediateAtlasGenerator class facilitates the generation of the atlas bitmap.
msdf_atlas::ImmediateAtlasGenerator<
float, // pixel type of buffer for individual glyphs depends on generator function
4, // number of atlas color channels
msdf_atlas::mtsdfGenerator, // function to generate bitmaps for individual glyphs
msdf_atlas::BitmapAtlasStorage<msdf_atlas::byte, 4> // class that stores the atlas bitmap
// For example, a custom atlas storage class that stores it in VRAM can be used.
> generator(width, height);

// GeneratorAttributes can be modified to change the generator's default settings.
msdf_atlas::GeneratorAttributes attributes;
generator.setAttributes(attributes);
generator.setThreadCount(4);

// Generate atlas bitmap
generator.generate(font->_glyphs.data(), static_cast<i32>(font->_glyphs.size()));

// The atlas bitmap can now be retrieved via atlasStorage as a BitmapConstRef.
DataTextureDesc fontTextureDesc;
fontTextureDesc.debugName = debugName;
fontTextureDesc.format = ImageFormat::R8G8B8A8_UNORM;
fontTextureDesc.width = width;
fontTextureDesc.height = height;

msdfgen::BitmapConstRef<msdf_atlas::byte, 4> textureData = generator.atlasStorage();
fontTextureDesc.data = const_cast<msdf_atlas::byte*>(textureData.pixels);

font->_texture = renderer->CreateDataTexture(fontTextureDesc);


for(u32 i = 0; i < font->_glyphs.size(); i++)
{
u32 codepoint = font->_glyphs[i].getCodepoint();
font->_codepointToGlyphIndex[codepoint] = i;
}

// Get the space gap
int advance;
stbtt_GetCodepointHMetrics(font->fontInfo, ' ', &advance, NULL);
font->spaceGap = advance * font->scale;
font->metrics = fontGeometry.getMetrics();
font->width = static_cast<f32>(width);
font->height = static_cast<f32>(height);

auto range = packer.getPixelRange();
font->lowerPixelRange = static_cast<f32>(range.lower);
font->upperPixelRange = static_cast<f32>(range.upper);

return font;
}

TextureArrayID Font::GetTextureArray()
TextureID Font::GetTextureID()
{
return _textureArray;
return _texture;
}

vec2 Font::CalculateTextSize(const std::string& text)
vec2 Font::CalculateTextSize(const std::string& text, f32 fontSize, f32 borderSize)
{
utf8::iterator it(text.begin(), text.begin(), text.end());
utf8::iterator endIt(text.end(), text.begin(), text.end());

vec2 size = vec2(0, 0);
vec2 size = vec2(borderSize * 2.0f, 0);
for (; it != endIt; it++)
{
u32 c = *it;

if (c == ' ')
// Skip carriage return characters
if (c == '\r')
{
size.x += spaceGap;
continue;
}

FontChar& fontChar = GetChar(c);
// Handle newline characters
if (c == '\n')
{
size.y += fontSize * static_cast<f32>(metrics.lineHeight);
continue;
}

const Glyph& glyph = GetGlyph(c);
if (!glyph.isWhitespace())
{
f64 planeLeft, planeBottom, planeRight, planeTop;

// Get the glyph's quad bounds in plane space (vertex positions)
glyph.getQuadPlaneBounds(planeLeft, planeBottom, planeRight, planeTop);

// Scale the plane coordinates by the font scale
planeBottom *= fontSize;
planeTop *= fontSize;

f32 height = static_cast<f32>(planeTop - planeBottom);
height += metrics.descenderY * fontSize + borderSize;
size.y = height;
}

f32 advance = static_cast<f32>(glyph.getAdvance());
//advance = font->GetFontAdvance(advance, c, *(it+1)); // TODO: Kerning

size.x += fontChar.advance;
size.y = std::max(size.y, static_cast<f32>(fontChar.height));
size.x += (fontSize * advance) + borderSize;
}

return size;
Expand Down
64 changes: 31 additions & 33 deletions Source/Renderer/Renderer/Font.h
Original file line number Diff line number Diff line change
@@ -1,65 +1,63 @@
#pragma once
#include "Descriptors/FontDesc.h"
#include "Descriptors/TextureArrayDesc.h"
#include "Descriptors/TextureDesc.h"

#include <Base/Types.h>

#include <robinhood/robinhood.h>
#include <msdf-atlas-gen/GlyphGeometry.h>
#include <msdf-atlas-gen/FontGeometry.h>

struct stbtt_fontinfo;

namespace msdfgen
{
class FreetypeHandle;
}

namespace Renderer
{
class Renderer;
using Glyph = msdf_atlas::GlyphGeometry;
using FontMetrics = msdfgen::FontMetrics;

struct FontChar
{
f32 advance;
f32 leftSideBearing;
i32 xOffset;
i32 yOffset;
i32 width = 0;
i32 height = 0;
u8* data;

u32 textureIndex;
};
class Renderer;

struct Font
{
static constexpr char* FALLBACK_FONT_PATH = (char*)"Data/Fonts/Ubuntu-Regular.ttf";

FontDesc desc;

stbtt_fontinfo* fontInfo;
FontMetrics metrics;

f32 scale;
i32 ascent;
i32 descent;
i32 lineGap;
f32 spaceGap;

FontChar& GetChar(u32 character);
TextureArrayID GetTextureArray();
vec2 CalculateTextSize(const std::string& text);

static Font* GetDefaultFont(Renderer* renderer, f32 fontSize);
static Font* GetFont(Renderer* renderer, const std::string& fontPath, f32 fontSize);
f32 width;
f32 height;
f32 lowerPixelRange;
f32 upperPixelRange;

const Glyph& GetGlyph(u32 codepoint);
TextureID GetTextureID();

vec2 CalculateTextSize(const std::string& text, f32 fontSize, f32 borderSize);

static Font* GetDefaultFont(Renderer* renderer);
static Font* GetFont(Renderer* renderer, const std::string& fontPath);

private:
Font() = default;

bool InitChar(u32 character, FontChar& fontChar);

static Font* CreateFont(Renderer* renderer, const u8* data, f32 fontSize);
static Font* InitFont(Renderer* renderer, std::string_view debugName, const u8* data, size_t dataSize);

private:
static msdfgen::FreetypeHandle* _ftHandle;
static robin_hood::unordered_map<u64, Font*> _fonts;
robin_hood::unordered_map<u32, FontChar> _chars;

TextureArrayID _textureArray = TextureArrayID::Invalid();
std::vector<Glyph> _glyphs;
robin_hood::unordered_map<u32, u32> _codepointToGlyphIndex;

TextureID _texture = TextureID::Invalid();

Renderer* _renderer;


friend class Renderer;
};
Expand Down

0 comments on commit 1b07163

Please sign in to comment.