make font atlas generator interface more uniform and implement bitmap font atlas rendering and generation
This commit is contained in:
@@ -25,6 +25,7 @@
|
|||||||
#include "Image/ImageLoaderPng.hpp"
|
#include "Image/ImageLoaderPng.hpp"
|
||||||
#include "Scene/FontAtlasGenerator.hpp"
|
#include "Scene/FontAtlasGenerator.hpp"
|
||||||
#include "Scene/IFontAtlasGenerator.hpp"
|
#include "Scene/IFontAtlasGenerator.hpp"
|
||||||
|
#include "Scene/BitmapFontAtlasGenerator.hpp"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -39,6 +40,7 @@ namespace OpenVulkano
|
|||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
//#define CREATE_NEW_ATLAS 1
|
//#define CREATE_NEW_ATLAS 1
|
||||||
|
#define CREATE_BITMAP_ATLAS 0
|
||||||
|
|
||||||
class TextExampleAppImpl final : public TextExampleApp
|
class TextExampleAppImpl final : public TextExampleApp
|
||||||
{
|
{
|
||||||
@@ -66,11 +68,23 @@ namespace OpenVulkano
|
|||||||
const int N = texts.size();
|
const int N = texts.size();
|
||||||
auto& resourceLoader = ResourceLoader::GetInstance();
|
auto& resourceLoader = ResourceLoader::GetInstance();
|
||||||
const std::string fontPath = resourceLoader.GetResourcePath("Roboto-Regular.ttf");
|
const std::string fontPath = resourceLoader.GetResourcePath("Roboto-Regular.ttf");
|
||||||
m_nodesPool.resize(N * 2);
|
m_nodesPool.resize(N * 3);
|
||||||
m_drawablesPool.resize(N * 2);
|
m_drawablesPool.resize(N * 3);
|
||||||
|
|
||||||
|
#if CREATE_BITMAP_ATLAS
|
||||||
|
std::set<uint32_t> s = BitmapFontAtlasGenerator::LoadAllGlyphs(fontPath);
|
||||||
|
BitmapFontAtlasGenerator generator;
|
||||||
|
generator.GenerateAtlas(fontPath, s);
|
||||||
|
generator.SaveAtlasMetadataInfo("bitmap_atlas");
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(MSDFGEN_AVAILABLE) && defined(CREATE_NEW_ATLAS)
|
#if defined(MSDFGEN_AVAILABLE) && defined(CREATE_NEW_ATLAS)
|
||||||
msdf_atlas::Charset charset = SdfFontAtlasGenerator::LoadAllGlyphs(fontPath);
|
std::set<uint32_t> s = SdfFontAtlasGenerator::LoadAllGlyphs(fontPath);
|
||||||
|
msdf_atlas::Charset charset;
|
||||||
|
for (uint32_t c : s)
|
||||||
|
{
|
||||||
|
charset.add(c);
|
||||||
|
}
|
||||||
m_atlasGenerator.GenerateAtlas(fontPath, charset);
|
m_atlasGenerator.GenerateAtlas(fontPath, charset);
|
||||||
m_msdfAtlasGenerator.GenerateAtlas(fontPath, charset);
|
m_msdfAtlasGenerator.GenerateAtlas(fontPath, charset);
|
||||||
m_atlasGenerator.SaveAtlasMetadataInfo("sdf_atlas.png");
|
m_atlasGenerator.SaveAtlasMetadataInfo("sdf_atlas.png");
|
||||||
@@ -78,9 +92,10 @@ namespace OpenVulkano
|
|||||||
#else
|
#else
|
||||||
auto sdfMetadataInfo = resourceLoader.GetResource("sdf_atlas_packed.png");
|
auto sdfMetadataInfo = resourceLoader.GetResource("sdf_atlas_packed.png");
|
||||||
auto msdfMetadataInfo = resourceLoader.GetResource("msdf_atlas_packed.png");
|
auto msdfMetadataInfo = resourceLoader.GetResource("msdf_atlas_packed.png");
|
||||||
|
auto bitmapMetadataInfo = resourceLoader.GetResource("bitmap_atlas_packed.png");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (int i = 0; i < texts.size() * 2; i++)
|
for (int i = 0; i < texts.size() * 3; i++)
|
||||||
{
|
{
|
||||||
int textIdx = i % texts.size();
|
int textIdx = i % texts.size();
|
||||||
TextDrawable* t = nullptr;
|
TextDrawable* t = nullptr;
|
||||||
@@ -96,15 +111,24 @@ namespace OpenVulkano
|
|||||||
t->SetShader(&TextDrawable::GetMsdfDefaultShader());
|
t->SetShader(&TextDrawable::GetMsdfDefaultShader());
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (i < texts.size())
|
int xOffset = 0;
|
||||||
|
if (i < N)
|
||||||
{
|
{
|
||||||
t = new TextDrawable(sdfMetadataInfo, texts[textIdx].second);
|
t = new TextDrawable(sdfMetadataInfo, texts[textIdx].second);
|
||||||
t->SetShader(&TextDrawable::GetSdfDefaultShader());
|
t->SetShader(&TextDrawable::GetSdfDefaultShader());
|
||||||
|
xOffset = -5;
|
||||||
}
|
}
|
||||||
else
|
else if (i >= N && i < N * 2)
|
||||||
{
|
{
|
||||||
t = new TextDrawable(msdfMetadataInfo, texts[textIdx].second);
|
t = new TextDrawable(msdfMetadataInfo, texts[textIdx].second);
|
||||||
t->SetShader(&TextDrawable::GetMsdfDefaultShader());
|
t->SetShader(&TextDrawable::GetMsdfDefaultShader());
|
||||||
|
xOffset = 15;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
t = new TextDrawable(bitmapMetadataInfo, texts[textIdx].second);
|
||||||
|
t->SetShader(&TextDrawable::GetBitmapDefaultShader());
|
||||||
|
xOffset = 35;
|
||||||
}
|
}
|
||||||
// OR use separate texture + metadata file
|
// OR use separate texture + metadata file
|
||||||
//auto metadataInfo = resourceLoader.GetResource("atlas_metadata");
|
//auto metadataInfo = resourceLoader.GetResource("atlas_metadata");
|
||||||
@@ -121,7 +145,7 @@ namespace OpenVulkano
|
|||||||
t->GenerateText(texts[textIdx].first);
|
t->GenerateText(texts[textIdx].first);
|
||||||
m_drawablesPool[i] = t;
|
m_drawablesPool[i] = t;
|
||||||
m_nodesPool[i].Init();
|
m_nodesPool[i].Init();
|
||||||
m_nodesPool[i].SetMatrix(Math::Utils::translate(glm::mat4x4(1.f), Vector3f((i < texts.size() ? -5 : 15), 2 - textIdx * 2, 0)));
|
m_nodesPool[i].SetMatrix(Math::Utils::translate(glm::mat4x4(1.f), Vector3f(xOffset, 2 - textIdx * 2, 0)));
|
||||||
m_nodesPool[i].AddDrawable(m_drawablesPool[i]);
|
m_nodesPool[i].AddDrawable(m_drawablesPool[i]);
|
||||||
m_scene.GetRoot()->AddChild(&m_nodesPool[i]);
|
m_scene.GetRoot()->AddChild(&m_nodesPool[i]);
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
examples/ExampleSources/bitmap_atlas_packed.png
Normal file
BIN
examples/ExampleSources/bitmap_atlas_packed.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 161 KiB |
@@ -31,6 +31,7 @@ namespace OpenVulkano::Scene
|
|||||||
{
|
{
|
||||||
SDF = 0,
|
SDF = 0,
|
||||||
MSDF,
|
MSDF,
|
||||||
|
BITMAP,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
};
|
};
|
||||||
static constexpr std::string_view DEFAULT_FG_SHADERS[] = { "Shader/text", "Shader/msdfText" };
|
static constexpr std::string_view DEFAULT_FG_SHADERS[] = { "Shader/text", "Shader/msdfText" };
|
||||||
|
|||||||
117
openVulkanoCpp/Scene/BitmapFontAtlasGenerator.cpp
Normal file
117
openVulkanoCpp/Scene/BitmapFontAtlasGenerator.cpp
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* 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"
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_atlasData.reset(new AtlasData);
|
||||||
|
const std::string sourceName = (std::holds_alternative<std::string>(source) ? std::get<0>(source) : "Binary array");
|
||||||
|
const auto& [lib, face] = FontAtlasGeneratorBase::InitFreetype(source);
|
||||||
|
FT_FaceRec* pFace = face.get();
|
||||||
|
|
||||||
|
// TODO: add flexibility to set your own size
|
||||||
|
const Math::Vector2ui cellSize = { 24, 24 };
|
||||||
|
// set pixel width/height lower than glyph size above, otherwise some glyphs will be cropped or some overlapping will be present
|
||||||
|
FT_Set_Pixel_Sizes(pFace, 0, cellSize.y - cellSize.y / 3);
|
||||||
|
const double sq = std::sqrt(chset.size());
|
||||||
|
const size_t glyphsPerRow = static_cast<size_t>(sq) + (sq - static_cast<size_t>(sq) != 0);
|
||||||
|
const size_t rows = (chset.size() / glyphsPerRow) + (chset.size() % glyphsPerRow != 0);
|
||||||
|
const Math::Vector2ui atlasResolution = { glyphsPerRow * cellSize.x, rows * cellSize.y };
|
||||||
|
|
||||||
|
// 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. / pFace->units_per_EM);
|
||||||
|
SetupAtlasData(atlasResolution, pFace->height * scaleFactor, FontAtlasType::BITMAP);
|
||||||
|
|
||||||
|
size_t loadedGlyphs = 0;
|
||||||
|
FT_Error error = 0;
|
||||||
|
int currentPosX = 0;
|
||||||
|
int currentPosY = 0;
|
||||||
|
Math::Vector2ui gridPos = { 0, 0 };
|
||||||
|
for (uint32_t codepoint : chset)
|
||||||
|
{
|
||||||
|
error = FT_Load_Char(pFace, codepoint, FT_LOAD_RENDER);
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
Logger::APP->error("FT_Load_Char for codepoint {} failed while reading from source {}", codepoint, sourceName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FT_GlyphSlot slot = face->glyph;
|
||||||
|
if (slot->bitmap.width > cellSize.x || slot->bitmap.rows > cellSize.y)
|
||||||
|
{
|
||||||
|
Logger::APP->warn("Glyph size exceeds grid cell size: {}x{} exceeds {}x{}", slot->bitmap.width, slot->bitmap.rows, cellSize.x, cellSize.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t firstGlyphByte = (gridPos.y * cellSize.x + gridPos.x * atlasResolution.x * cellSize.y);
|
||||||
|
for (int row = 0; row < slot->bitmap.rows; row++)
|
||||||
|
{
|
||||||
|
for (int col = 0; col < slot->bitmap.width; col++)
|
||||||
|
{
|
||||||
|
m_atlasData->img->data[firstGlyphByte + row * atlasResolution.x + col] = slot->bitmap.buffer[(slot->bitmap.rows - 1 - row) * slot->bitmap.pitch + col];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GlyphInfo& glyphInfo = m_atlasData->glyphs[codepoint];
|
||||||
|
const Math::Vector2d glyphMetrics = { slot->metrics.width * scaleFactor, slot->metrics.height * scaleFactor };
|
||||||
|
const Math::Vector2d glyphBearing = { slot->metrics.horiBearingX * scaleFactor, slot->metrics.horiBearingY * scaleFactor };
|
||||||
|
// metrics are 1/64 of a pixel
|
||||||
|
constexpr double toPixelScaler = 1. / 64;
|
||||||
|
const Math::Vector2d whPixel = { static_cast<double>(slot->metrics.width * toPixelScaler),
|
||||||
|
static_cast<double>(slot->metrics.height * toPixelScaler) };
|
||||||
|
Math::AABB glyphAtlasAABB(Math::Vector3f(currentPosX, currentPosY, 0), Math::Vector3f(currentPosX + whPixel.x, currentPosY + whPixel.y, 0));
|
||||||
|
SetGlyphData(glyphInfo, glyphBearing, glyphMetrics, glyphAtlasAABB, slot->advance.x * scaleFactor);
|
||||||
|
|
||||||
|
currentPosX += cellSize.x;
|
||||||
|
loadedGlyphs++;
|
||||||
|
|
||||||
|
if (currentPosX + cellSize.x > atlasResolution.x)
|
||||||
|
{
|
||||||
|
currentPosX = 0;
|
||||||
|
currentPosY += cellSize.y;
|
||||||
|
gridPos.y = 0;
|
||||||
|
gridPos.x++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gridPos.y++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pngOutput)
|
||||||
|
{
|
||||||
|
SavePng(*pngOutput);
|
||||||
|
}
|
||||||
|
Logger::APP->debug("Created atlas with {} glyphs, {} glyphs could not be loaded", loadedGlyphs, chset.size() - loadedGlyphs);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
25
openVulkanoCpp/Scene/BitmapFontAtlasGenerator.hpp
Normal file
25
openVulkanoCpp/Scene/BitmapFontAtlasGenerator.hpp
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* 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"
|
||||||
|
|
||||||
|
namespace OpenVulkano::Scene
|
||||||
|
{
|
||||||
|
|
||||||
|
class BitmapFontAtlasGenerator : public FontAtlasGeneratorBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BitmapFontAtlasGenerator() : FontAtlasGeneratorBase(1) {}
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -8,17 +8,9 @@
|
|||||||
|
|
||||||
#include "FontAtlasGenerator.hpp"
|
#include "FontAtlasGenerator.hpp"
|
||||||
#include "Base/Logger.hpp"
|
#include "Base/Logger.hpp"
|
||||||
#include "Scene/AtlasData.hpp"
|
|
||||||
#include <msdfgen.h>
|
#include <msdfgen.h>
|
||||||
#include <msdfgen-ext.h>
|
#include <msdfgen-ext.h>
|
||||||
#include <msdf-atlas-gen/msdf-atlas-gen.h>
|
#include <msdf-atlas-gen/msdf-atlas-gen.h>
|
||||||
#define STBI_MSC_SECURE_CRT
|
|
||||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
|
||||||
#include <stb_image_write.h>
|
|
||||||
#include <ft2build.h>
|
|
||||||
#include FT_FREETYPE_H
|
|
||||||
#include <fstream>
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
namespace OpenVulkano::Scene
|
namespace OpenVulkano::Scene
|
||||||
{
|
{
|
||||||
@@ -28,40 +20,6 @@ namespace OpenVulkano::Scene
|
|||||||
FontAtlasGeneratorConfig FontAtlasGeneratorConfig::sdfDefaultConfig = { 42, 1.0, 5 };
|
FontAtlasGeneratorConfig FontAtlasGeneratorConfig::sdfDefaultConfig = { 42, 1.0, 5 };
|
||||||
FontAtlasGeneratorConfig FontAtlasGeneratorConfig::msdfDefaultConfig = { 32, 1.0, 3 };
|
FontAtlasGeneratorConfig FontAtlasGeneratorConfig::msdfDefaultConfig = { 32, 1.0, 3 };
|
||||||
|
|
||||||
template<int Channels>
|
|
||||||
Charset FontAtlasGenerator<Channels>::LoadAllGlyphs(const std::variant<std::string, Array<char>>& data)
|
|
||||||
{
|
|
||||||
FT_Library library;
|
|
||||||
auto error = FT_Init_FreeType(&library);
|
|
||||||
if (error) { throw std::runtime_error("Could not initalize freetype library\n"); }
|
|
||||||
FT_Face face;
|
|
||||||
if (std::holds_alternative<std::string>(data))
|
|
||||||
{
|
|
||||||
error = FT_New_Face(library, std::get<0>(data).c_str(), 0, &face);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto& arr = std::get<1>(data);
|
|
||||||
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("Font file could not be opened or read or it's corrupted\n"); }
|
|
||||||
|
|
||||||
// some fancy font without unicode charmap
|
|
||||||
if (face->charmap == nullptr) { throw std::runtime_error("Selected font doesn't contain unicode charmap"); }
|
|
||||||
Charset s;
|
|
||||||
FT_UInt glyphIndex;
|
|
||||||
FT_ULong unicode = FT_Get_First_Char(face, &glyphIndex);
|
|
||||||
while (glyphIndex != 0)
|
|
||||||
{
|
|
||||||
s.add(unicode);
|
|
||||||
unicode = FT_Get_Next_Char(face, unicode, &glyphIndex);
|
|
||||||
}
|
|
||||||
FT_Done_Face(face);
|
|
||||||
FT_Done_FreeType(library);
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<int Channels>
|
template<int Channels>
|
||||||
void FontAtlasGenerator<Channels>::GenerateAtlas(const std::string& fontFile, const std::set<uint32_t>& charset,
|
void FontAtlasGenerator<Channels>::GenerateAtlas(const std::string& fontFile, const std::set<uint32_t>& charset,
|
||||||
const std::optional<std::string>& pngOutput)
|
const std::optional<std::string>& pngOutput)
|
||||||
@@ -74,21 +32,19 @@ namespace OpenVulkano::Scene
|
|||||||
Generate(ft, font, s, pngOutput);
|
Generate(ft, font, s, pngOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int Channels>
|
template<int Channels> FontAtlasGenerator<Channels>::FontAtlasGenerator() : FontAtlasGeneratorBase(Channels)
|
||||||
FontAtlasGenerator<Channels>::FontAtlasGenerator()
|
|
||||||
{
|
{
|
||||||
if constexpr (Channels == 1) m_config = FontAtlasGeneratorConfig::sdfDefaultConfig;
|
if constexpr (Channels == 1) m_config = FontAtlasGeneratorConfig::sdfDefaultConfig;
|
||||||
else m_config = FontAtlasGeneratorConfig::msdfDefaultConfig;
|
else m_config = FontAtlasGeneratorConfig::msdfDefaultConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int Channels>
|
template<int Channels>
|
||||||
void FontAtlasGenerator<Channels>::GenerateAtlas(const Array<char>& fontData, int length,
|
void FontAtlasGenerator<Channels>::GenerateAtlas(const Array<char>& fontData, const std::set<uint32_t>& charset,
|
||||||
const std::set<uint32_t>& charset,
|
|
||||||
const std::optional<std::string>& pngOutput)
|
const std::optional<std::string>& pngOutput)
|
||||||
{
|
{
|
||||||
FreetypeHandle* ft;
|
FreetypeHandle* ft;
|
||||||
FontHandle* font;
|
FontHandle* font;
|
||||||
InitFreetypeFromBuffer(ft, font, (const msdfgen::byte*)(fontData.Data()), length);
|
InitFreetypeFromBuffer(ft, font, (const msdfgen::byte*)(fontData.Data()), fontData.Size());
|
||||||
Charset s;
|
Charset s;
|
||||||
std::for_each(s.begin(), s.end(), [&](uint32_t unicode) { s.add(unicode); });
|
std::for_each(s.begin(), s.end(), [&](uint32_t unicode) { s.add(unicode); });
|
||||||
Generate(ft, font, s, pngOutput);
|
Generate(ft, font, s, pngOutput);
|
||||||
@@ -116,37 +72,6 @@ namespace OpenVulkano::Scene
|
|||||||
Generate(ft, font, charset, pngOutput);
|
Generate(ft, font, charset, pngOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int Channels>
|
|
||||||
void FontAtlasGenerator<Channels>::SaveAtlasMetadataInfo(const std::string& outputFile,
|
|
||||||
bool packIntoSingleFile) const
|
|
||||||
{
|
|
||||||
if (m_atlasData->glyphs.empty())
|
|
||||||
{
|
|
||||||
Logger::DATA->info("No glyphs loaded. Nothing to save.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::string fileName = outputFile;
|
|
||||||
uint32_t packedFlag = packIntoSingleFile;
|
|
||||||
if (packIntoSingleFile)
|
|
||||||
{
|
|
||||||
std::filesystem::path fPath(fileName);
|
|
||||||
fileName = (fPath.parent_path() / fPath.stem()).string() + "_packed.png";
|
|
||||||
SavePng(fileName);
|
|
||||||
}
|
|
||||||
std::fstream fs(fileName.c_str(), std::ios_base::out | std::ios_base::binary | (packedFlag ? std::ios_base::app : std::ios_base::trunc));
|
|
||||||
fs.write(reinterpret_cast<const char*>(&m_atlasData->meta), sizeof(AtlasMetadata));
|
|
||||||
uint64_t metadataBytes = sizeof(AtlasMetadata);
|
|
||||||
for (const auto& [key, val]: m_atlasData->glyphs)
|
|
||||||
{
|
|
||||||
fs.write(reinterpret_cast<const char*>(&key), sizeof(uint32_t));
|
|
||||||
fs.write(reinterpret_cast<const char*>(&val), sizeof(GlyphInfo));
|
|
||||||
metadataBytes += sizeof(uint32_t);
|
|
||||||
metadataBytes += sizeof(GlyphInfo);
|
|
||||||
}
|
|
||||||
fs.write(reinterpret_cast<const char*>(&metadataBytes), sizeof(uint64_t));
|
|
||||||
fs.write(reinterpret_cast<const char*>(&packedFlag), sizeof(uint32_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<int Channels>
|
template<int Channels>
|
||||||
void FontAtlasGenerator<Channels>::InitFreetypeFromFile(FreetypeHandle*& ft, FontHandle*& font,
|
void FontAtlasGenerator<Channels>::InitFreetypeFromFile(FreetypeHandle*& ft, FontHandle*& font,
|
||||||
const std::string& fontFile)
|
const std::string& fontFile)
|
||||||
@@ -217,13 +142,12 @@ namespace OpenVulkano::Scene
|
|||||||
generator.generate(glyphsGeometry.data(), glyphsGeometry.size());
|
generator.generate(glyphsGeometry.data(), glyphsGeometry.size());
|
||||||
|
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
|
SetupAtlasData(Math::Vector3ui(width, height, 1), fontGeometry.getMetrics().lineHeight,
|
||||||
|
channelsCount == 1 ? FontAtlasType::SDF : FontAtlasType::MSDF);
|
||||||
|
|
||||||
if constexpr (Channels == 3)
|
if constexpr (Channels == 3)
|
||||||
{
|
{
|
||||||
// store RGB as RGBA
|
// store RGB as RGBA
|
||||||
m_atlasData->img = std::make_unique<Image::Image>();
|
|
||||||
m_atlasData->img->data = Array<uint8_t>(width * height * 4);
|
|
||||||
m_atlasData->img->resolution = Math::Vector3ui(width, height, 1);
|
|
||||||
m_atlasData->img->dataFormat = OpenVulkano::DataFormat::R8G8B8A8_UNORM;
|
|
||||||
const BitmapConstRef<msdfgen::byte, 3> storage = generator.atlasStorage();
|
const BitmapConstRef<msdfgen::byte, 3> storage = generator.atlasStorage();
|
||||||
msdfgen::byte* data = static_cast<msdfgen::byte*>(m_atlasData->img->data.Data());
|
msdfgen::byte* data = static_cast<msdfgen::byte*>(m_atlasData->img->data.Data());
|
||||||
for (size_t srcPos = 0, dstPos = 0; srcPos < width * height * 3; srcPos += 3, dstPos += 4)
|
for (size_t srcPos = 0, dstPos = 0; srcPos < width * height * 3; srcPos += 3, dstPos += 4)
|
||||||
@@ -236,21 +160,10 @@ namespace OpenVulkano::Scene
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_atlasData->img = std::make_unique<Image::Image>();
|
|
||||||
m_atlasData->img->data = Array<uint8_t>(width * height);
|
|
||||||
m_atlasData->img->resolution = Math::Vector3ui(width, height, 1);
|
|
||||||
m_atlasData->img->dataFormat = OpenVulkano::DataFormat::R8_UNORM;
|
|
||||||
const msdfgen::BitmapConstRef<msdfgen::byte, 1>& storage = generator.atlasStorage();
|
const msdfgen::BitmapConstRef<msdfgen::byte, 1>& storage = generator.atlasStorage();
|
||||||
memcpy(m_atlasData->img->data.Data(), storage.pixels, width * height);
|
memcpy(m_atlasData->img->data.Data(), storage.pixels, width * height);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_atlasData->texture.resolution = m_atlasData->img->resolution;
|
|
||||||
m_atlasData->texture.textureBuffer = m_atlasData->img->data.Data();
|
|
||||||
m_atlasData->texture.format = m_atlasData->img->dataFormat;
|
|
||||||
m_atlasData->texture.size = width * height * channelsCount;
|
|
||||||
m_atlasData->meta.lineHeight = fontGeometry.getMetrics().lineHeight;
|
|
||||||
m_atlasData->meta.atlasType = channelsCount == 1 ? FontAtlasType::SDF : FontAtlasType::MSDF;
|
|
||||||
|
|
||||||
struct Bbox
|
struct Bbox
|
||||||
{
|
{
|
||||||
double l = 0, r = 0, t = 0, b = 0;
|
double l = 0, r = 0, t = 0, b = 0;
|
||||||
@@ -274,31 +187,12 @@ namespace OpenVulkano::Scene
|
|||||||
double t = glyphAtlasBbox.t;
|
double t = glyphAtlasBbox.t;
|
||||||
double b = glyphAtlasBbox.b;
|
double b = glyphAtlasBbox.b;
|
||||||
|
|
||||||
info.xyz[0].x = bearingX;
|
Math::AABB glyphAtlasAABB;
|
||||||
info.xyz[0].y = h - bearingY;
|
glyphAtlasAABB.min.x = l;
|
||||||
info.xyz[0].z = 0;
|
glyphAtlasAABB.min.y = b;
|
||||||
info.uv[0].x = l / m_atlasData->texture.resolution.x;
|
glyphAtlasAABB.max.x = r;
|
||||||
info.uv[0].y = b / m_atlasData->texture.resolution.y;
|
glyphAtlasAABB.max.y = t;
|
||||||
|
SetGlyphData(info, { bearingX, bearingY }, { w, h }, glyphAtlasAABB, glyphBox.advance);
|
||||||
info.xyz[1].x = bearingX + w;
|
|
||||||
info.xyz[1].y = h - bearingY;
|
|
||||||
info.xyz[1].z = 0;
|
|
||||||
info.uv[1].x = r / m_atlasData->texture.resolution.x;
|
|
||||||
info.uv[1].y = b / m_atlasData->texture.resolution.y;
|
|
||||||
|
|
||||||
info.xyz[2].x = bearingX + w;
|
|
||||||
info.xyz[2].y = bearingY; //h - bearingY + h;
|
|
||||||
info.xyz[2].z = 0;
|
|
||||||
info.uv[2].x = r / m_atlasData->texture.resolution.x;
|
|
||||||
info.uv[2].y = t / m_atlasData->texture.resolution.y;
|
|
||||||
|
|
||||||
info.xyz[3].x = bearingX;
|
|
||||||
info.xyz[3].y = bearingY;
|
|
||||||
info.xyz[3].z = 0;
|
|
||||||
info.uv[3].x = l / m_atlasData->texture.resolution.x;
|
|
||||||
info.uv[3].y = t / m_atlasData->texture.resolution.y;
|
|
||||||
|
|
||||||
info.advance = glyphBox.advance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pngOutput && !pngOutput->empty()) { SavePng(pngOutput.value()); }
|
if (pngOutput && !pngOutput->empty()) { SavePng(pngOutput.value()); }
|
||||||
@@ -306,21 +200,6 @@ namespace OpenVulkano::Scene
|
|||||||
deinitializeFreetype(ft);
|
deinitializeFreetype(ft);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int Channels>
|
|
||||||
void FontAtlasGenerator<Channels>::SavePng(const std::string& output) const
|
|
||||||
{
|
|
||||||
stbi_flip_vertically_on_write(1);
|
|
||||||
if (std::filesystem::path(output).extension() == ".png")
|
|
||||||
{
|
|
||||||
stbi_write_png(output.c_str(), m_atlasData->img->resolution.x, m_atlasData->img->resolution.y,
|
|
||||||
channelsCount, m_atlasData->img->data.Data(), channelsCount * m_atlasData->img->resolution.x);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
stbi_write_png((output + ".png").c_str(), m_atlasData->img->resolution.x, m_atlasData->img->resolution.y,
|
|
||||||
channelsCount, m_atlasData->img->data.Data(), channelsCount * m_atlasData->img->resolution.x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
template class FontAtlasGenerator<1>;
|
template class FontAtlasGenerator<1>;
|
||||||
template class FontAtlasGenerator<3>;
|
template class FontAtlasGenerator<3>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,7 @@
|
|||||||
|
|
||||||
#if __has_include("msdfgen.h")
|
#if __has_include("msdfgen.h")
|
||||||
|
|
||||||
#include "Scene/AtlasData.hpp"
|
#include "FontAtlasGeneratorBase.hpp"
|
||||||
#include "IFontAtlasGenerator.hpp"
|
|
||||||
#include "Scene/Texture.hpp"
|
|
||||||
#include <msdfgen.h>
|
#include <msdfgen.h>
|
||||||
#include <msdf-atlas-gen/msdf-atlas-gen.h>
|
#include <msdf-atlas-gen/msdf-atlas-gen.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -32,7 +30,7 @@ namespace OpenVulkano::Scene
|
|||||||
};
|
};
|
||||||
|
|
||||||
template<int Channels>
|
template<int Channels>
|
||||||
class FontAtlasGenerator : public IFontAtlasGenerator
|
class FontAtlasGenerator : public FontAtlasGeneratorBase
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
using SdfGenerator = msdf_atlas::ImmediateAtlasGenerator<float, 1, msdf_atlas::sdfGenerator,
|
using SdfGenerator = msdf_atlas::ImmediateAtlasGenerator<float, 1, msdf_atlas::sdfGenerator,
|
||||||
@@ -43,20 +41,17 @@ namespace OpenVulkano::Scene
|
|||||||
using Generator = std::conditional<Channels == 1, SdfGenerator, MsdfGenerator>::type;
|
using Generator = std::conditional<Channels == 1, SdfGenerator, MsdfGenerator>::type;
|
||||||
using Config = FontAtlasGeneratorConfig;
|
using Config = FontAtlasGeneratorConfig;
|
||||||
static constexpr int channelsCount = (Channels == 1 ? 1 : 4);
|
static constexpr int channelsCount = (Channels == 1 ? 1 : 4);
|
||||||
static msdf_atlas::Charset LoadAllGlyphs(const std::variant<std::string, Array<char>>& data);
|
|
||||||
FontAtlasGenerator();
|
FontAtlasGenerator();
|
||||||
void GenerateAtlas(const std::string& fontFile, const std::set<uint32_t>& charset,
|
void GenerateAtlas(const std::string& fontFile, const std::set<uint32_t>& charset,
|
||||||
const std::optional<std::string>& pngOutput = std::nullopt) override;
|
const std::optional<std::string>& pngOutput = std::nullopt) override;
|
||||||
void GenerateAtlas(const Array<char>& fontData, int length, const std::set<uint32_t>& charset,
|
void GenerateAtlas(const Array<char>& fontData, const std::set<uint32_t>& charset,
|
||||||
const std::optional<std::string>& pngOutput = std::nullopt) override;
|
const std::optional<std::string>& pngOutput = std::nullopt) override;
|
||||||
void GenerateAtlas(const std::string& fontFile, const msdf_atlas::Charset& charset = msdf_atlas::Charset::ASCII,
|
void GenerateAtlas(const std::string& fontFile, const msdf_atlas::Charset& charset = msdf_atlas::Charset::ASCII,
|
||||||
const std::optional<std::string>& pngOutput = std::nullopt);
|
const std::optional<std::string>& pngOutput = std::nullopt);
|
||||||
void GenerateAtlas(const msdfgen::byte* fontData, int length,
|
void GenerateAtlas(const msdfgen::byte* fontData, int length,
|
||||||
const msdf_atlas::Charset& charset = msdf_atlas::Charset::ASCII,
|
const msdf_atlas::Charset& charset = msdf_atlas::Charset::ASCII,
|
||||||
const std::optional<std::string>& pngOutput = std::nullopt);
|
const std::optional<std::string>& pngOutput = std::nullopt);
|
||||||
void SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile = true) const override;
|
|
||||||
void SetGeneratorConfig(const Config& config) { m_config = config; }
|
void SetGeneratorConfig(const Config& config) { m_config = config; }
|
||||||
std::shared_ptr<AtlasData> GetAtlasData() const { return m_atlasData; }
|
|
||||||
Config& GetGeneratorConfig() { return m_config; }
|
Config& GetGeneratorConfig() { return m_config; }
|
||||||
private:
|
private:
|
||||||
void InitFreetypeFromFile(msdfgen::FreetypeHandle*& ft, msdfgen::FontHandle*& font, const std::string& file);
|
void InitFreetypeFromFile(msdfgen::FreetypeHandle*& ft, msdfgen::FontHandle*& font, const std::string& file);
|
||||||
@@ -64,11 +59,8 @@ namespace OpenVulkano::Scene
|
|||||||
const msdfgen::byte* fontData, int length);
|
const msdfgen::byte* fontData, int length);
|
||||||
void Generate(msdfgen::FreetypeHandle* ft, msdfgen::FontHandle* font, const msdf_atlas::Charset& chset,
|
void Generate(msdfgen::FreetypeHandle* ft, msdfgen::FontHandle* font, const msdf_atlas::Charset& chset,
|
||||||
const std::optional<std::string>& pngOutput);
|
const std::optional<std::string>& pngOutput);
|
||||||
void SavePng(const std::string& output) const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Config m_config;
|
Config m_config;
|
||||||
std::shared_ptr<AtlasData> m_atlasData;
|
|
||||||
};
|
};
|
||||||
using SdfFontAtlasGenerator = FontAtlasGenerator<1>;
|
using SdfFontAtlasGenerator = FontAtlasGenerator<1>;
|
||||||
using MsdfFontAtlasGenerator = FontAtlasGenerator<3>;
|
using MsdfFontAtlasGenerator = FontAtlasGenerator<3>;
|
||||||
|
|||||||
180
openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp
Normal file
180
openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
* 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 "Base/Logger.hpp"
|
||||||
|
#define STBI_MSC_SECURE_CRT
|
||||||
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
#include <stb_image_write.h>
|
||||||
|
#include <ft2build.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace OpenVulkano::Scene
|
||||||
|
{
|
||||||
|
std::pair<FontAtlasGeneratorBase::FT_LIB_REC, FontAtlasGeneratorBase::FT_FACE_REC>
|
||||||
|
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("Could not initalize freetype library\n");
|
||||||
|
}
|
||||||
|
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("Font file could not be opened or read or it's corrupted\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// some fancy font without unicode charmap
|
||||||
|
if (face->charmap == nullptr)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Selected font doesn't contain unicode charmap");
|
||||||
|
}
|
||||||
|
return std::make_pair(FT_LIB_REC(library), FT_FACE_REC(face));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FontAtlasGeneratorBase::SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile) const
|
||||||
|
{
|
||||||
|
if (m_atlasData->glyphs.empty())
|
||||||
|
{
|
||||||
|
Logger::DATA->info("No glyphs loaded. Nothing to save.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string fileName = outputFile;
|
||||||
|
uint32_t packedFlag = packIntoSingleFile;
|
||||||
|
if (packIntoSingleFile)
|
||||||
|
{
|
||||||
|
std::filesystem::path fPath(fileName);
|
||||||
|
fileName = (fPath.parent_path() / fPath.stem()).string() + "_packed.png";
|
||||||
|
SavePng(fileName);
|
||||||
|
}
|
||||||
|
std::fstream fs(fileName.c_str(), std::ios_base::out | std::ios_base::binary | (packedFlag ? std::ios_base::app : std::ios_base::trunc));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&m_atlasData->meta), sizeof(AtlasMetadata));
|
||||||
|
uint64_t metadataBytes = sizeof(AtlasMetadata);
|
||||||
|
for (const auto& [key, val] : m_atlasData->glyphs)
|
||||||
|
{
|
||||||
|
fs.write(reinterpret_cast<const char*>(&key), sizeof(uint32_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&val), sizeof(GlyphInfo));
|
||||||
|
metadataBytes += sizeof(uint32_t);
|
||||||
|
metadataBytes += sizeof(GlyphInfo);
|
||||||
|
}
|
||||||
|
fs.write(reinterpret_cast<const char*>(&metadataBytes), sizeof(uint64_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&packedFlag), sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontAtlasGeneratorBase::SavePng(const std::string& output) const
|
||||||
|
{
|
||||||
|
stbi_flip_vertically_on_write(1);
|
||||||
|
if (std::filesystem::path(output).extension() == ".png")
|
||||||
|
{
|
||||||
|
stbi_write_png(output.c_str(), m_atlasData->img->resolution.x, m_atlasData->img->resolution.y, m_channelsCount, m_atlasData->img->data.Data(),
|
||||||
|
m_channelsCount * m_atlasData->img->resolution.x);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stbi_write_png((output + ".png").c_str(), m_atlasData->img->resolution.x, m_atlasData->img->resolution.y,
|
||||||
|
m_channelsCount, m_atlasData->img->data.Data(), m_channelsCount * m_atlasData->img->resolution.x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontAtlasGeneratorBase::SetupAtlasData(Math::Vector2ui textureResolution, double lineHeight,
|
||||||
|
FontAtlasType::Type atlasType)
|
||||||
|
{
|
||||||
|
// generate texture
|
||||||
|
if (m_channelsCount == 1)
|
||||||
|
{
|
||||||
|
m_atlasData->img = std::make_unique<Image::Image>();
|
||||||
|
m_atlasData->img->data = Array<uint8_t>(textureResolution.x * textureResolution.y);
|
||||||
|
m_atlasData->img->resolution = Math::Vector3ui(textureResolution, 1);
|
||||||
|
m_atlasData->img->dataFormat = OpenVulkano::DataFormat::R8_UNORM;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_atlasData->img = std::make_unique<Image::Image>();
|
||||||
|
// RGBA
|
||||||
|
m_atlasData->img->data = Array<uint8_t>(textureResolution.x * textureResolution.y * 4);
|
||||||
|
m_atlasData->img->resolution = Math::Vector3ui(textureResolution, 1);
|
||||||
|
m_atlasData->img->dataFormat = OpenVulkano::DataFormat::R8G8B8A8_UNORM;
|
||||||
|
}
|
||||||
|
m_atlasData->texture.resolution = m_atlasData->img->resolution;
|
||||||
|
m_atlasData->texture.textureBuffer = m_atlasData->img->data.Data();
|
||||||
|
m_atlasData->texture.format = m_atlasData->img->dataFormat;
|
||||||
|
m_atlasData->texture.size = m_atlasData->img->data.Size();
|
||||||
|
m_atlasData->texture.m_samplerConfig = &SamplerConfig::NEAREST;
|
||||||
|
m_atlasData->meta.atlasType = atlasType;
|
||||||
|
m_atlasData->meta.lineHeight = lineHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontAtlasGeneratorBase::SetGlyphData(GlyphInfo& info, Math::Vector2d bearing, Math::Vector2d size,
|
||||||
|
const Math::AABB& aabb, double advance)
|
||||||
|
{
|
||||||
|
double bearingX = bearing.x;
|
||||||
|
double bearingY = bearing.y;
|
||||||
|
double w = size.x;
|
||||||
|
double h = size.y;
|
||||||
|
double l = aabb.min.x;
|
||||||
|
double r = aabb.max.x;
|
||||||
|
double t = aabb.max.y;
|
||||||
|
double b = aabb.min.y;
|
||||||
|
|
||||||
|
info.xyz[0].x = bearingX;
|
||||||
|
info.xyz[0].y = h - bearingY;
|
||||||
|
info.xyz[0].z = 0;
|
||||||
|
info.uv[0].x = l / m_atlasData->texture.resolution.x;
|
||||||
|
info.uv[0].y = b / m_atlasData->texture.resolution.y;
|
||||||
|
|
||||||
|
info.xyz[1].x = bearingX + w;
|
||||||
|
info.xyz[1].y = h - bearingY;
|
||||||
|
info.xyz[1].z = 0;
|
||||||
|
info.uv[1].x = r / m_atlasData->texture.resolution.x;
|
||||||
|
info.uv[1].y = b / m_atlasData->texture.resolution.y;
|
||||||
|
|
||||||
|
info.xyz[2].x = bearingX + w;
|
||||||
|
info.xyz[2].y = bearingY; //h - bearingY + h;
|
||||||
|
info.xyz[2].z = 0;
|
||||||
|
info.uv[2].x = r / m_atlasData->texture.resolution.x;
|
||||||
|
info.uv[2].y = t / m_atlasData->texture.resolution.y;
|
||||||
|
|
||||||
|
info.xyz[3].x = bearingX;
|
||||||
|
info.xyz[3].y = bearingY;
|
||||||
|
info.xyz[3].z = 0;
|
||||||
|
info.uv[3].x = l / m_atlasData->texture.resolution.x;
|
||||||
|
info.uv[3].y = t / m_atlasData->texture.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
56
openVulkanoCpp/Scene/FontAtlasGeneratorBase.hpp
Normal file
56
openVulkanoCpp/Scene/FontAtlasGeneratorBase.hpp
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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 <ft2build.h>
|
||||||
|
#include FT_FREETYPE_H
|
||||||
|
#include <variant>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
namespace OpenVulkano::Scene
|
||||||
|
{
|
||||||
|
class FontAtlasGeneratorBase : public IFontAtlasGenerator
|
||||||
|
{
|
||||||
|
struct LibDeleter
|
||||||
|
{
|
||||||
|
void operator()(FT_Library lib)
|
||||||
|
{
|
||||||
|
FT_Done_FreeType(lib);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FaceDeleter
|
||||||
|
{
|
||||||
|
void operator()(FT_Face face)
|
||||||
|
{
|
||||||
|
FT_Done_Face(face);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
using FT_LIB_REC = std::unique_ptr<FT_LibraryRec_, LibDeleter>;
|
||||||
|
using FT_FACE_REC = std::unique_ptr<FT_FaceRec_, FaceDeleter>;
|
||||||
|
|
||||||
|
FontAtlasGeneratorBase(int channelsCount) : m_channelsCount(channelsCount) {}
|
||||||
|
void SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile = true) const override;
|
||||||
|
std::shared_ptr<AtlasData> GetAtlasData() const { return m_atlasData; }
|
||||||
|
int GetAtlasChannelsCount() const { return m_channelsCount; }
|
||||||
|
|
||||||
|
static std::set<uint32_t> LoadAllGlyphs(const std::variant<std::string, Array<char>>& data);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void SavePng(const std::string& output) const;
|
||||||
|
void SetupAtlasData(Math::Vector2ui textureResolution, double lineHeight, FontAtlasType::Type atlasType);
|
||||||
|
void SetGlyphData(GlyphInfo& info, Math::Vector2d bearing, Math::Vector2d size, const Math::AABB& aabb, double advance);
|
||||||
|
static std::pair<FT_LIB_REC, FT_FACE_REC> InitFreetype(const std::variant<std::string, Array<char>>& source);
|
||||||
|
protected:
|
||||||
|
int m_channelsCount;
|
||||||
|
std::shared_ptr<AtlasData> m_atlasData;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ namespace OpenVulkano::Scene
|
|||||||
public:
|
public:
|
||||||
virtual void GenerateAtlas(const std::string& fontFile, const std::set<uint32_t>& charset,
|
virtual void GenerateAtlas(const std::string& fontFile, const std::set<uint32_t>& charset,
|
||||||
const std::optional<std::string>& pngOutput = std::nullopt) = 0;
|
const std::optional<std::string>& pngOutput = std::nullopt) = 0;
|
||||||
virtual void GenerateAtlas(const Array<char>& fontData, int length, const std::set<uint32_t>& charset,
|
virtual void GenerateAtlas(const Array<char>& fontData, const std::set<uint32_t>& charset,
|
||||||
const std::optional<std::string>& pngOutput = std::nullopt) = 0;
|
const std::optional<std::string>& pngOutput = std::nullopt) = 0;
|
||||||
virtual void SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile = true) const = 0;
|
virtual void SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile = true) const = 0;
|
||||||
virtual std::shared_ptr<AtlasData> GetAtlasData() const = 0;
|
virtual std::shared_ptr<AtlasData> GetAtlasData() const = 0;
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ namespace OpenVulkano::Scene
|
|||||||
static Shader sdfDefaultShader;
|
static Shader sdfDefaultShader;
|
||||||
if (once)
|
if (once)
|
||||||
{
|
{
|
||||||
sdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/text");
|
sdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/sdfText");
|
||||||
sdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/text");
|
sdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/sdfText");
|
||||||
sdfDefaultShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription());
|
sdfDefaultShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription());
|
||||||
sdfDefaultShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING);
|
sdfDefaultShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING);
|
||||||
DescriptorSetLayoutBinding desc = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING;
|
DescriptorSetLayoutBinding desc = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING;
|
||||||
@@ -46,7 +46,7 @@ namespace OpenVulkano::Scene
|
|||||||
static Shader msdfDefaultShader;
|
static Shader msdfDefaultShader;
|
||||||
if (once)
|
if (once)
|
||||||
{
|
{
|
||||||
msdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/text");
|
msdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/sdfText");
|
||||||
msdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/msdfText");
|
msdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/msdfText");
|
||||||
msdfDefaultShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription());
|
msdfDefaultShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription());
|
||||||
msdfDefaultShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING);
|
msdfDefaultShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING);
|
||||||
@@ -60,6 +60,26 @@ namespace OpenVulkano::Scene
|
|||||||
return msdfDefaultShader;
|
return msdfDefaultShader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Shader& TextDrawable::GetBitmapDefaultShader()
|
||||||
|
{
|
||||||
|
static bool once = true;
|
||||||
|
static Shader bitmapDefaultShader;
|
||||||
|
if (once)
|
||||||
|
{
|
||||||
|
bitmapDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/text");
|
||||||
|
bitmapDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/text");
|
||||||
|
bitmapDefaultShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription());
|
||||||
|
bitmapDefaultShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING);
|
||||||
|
DescriptorSetLayoutBinding desc = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING;
|
||||||
|
desc.stageFlags = ShaderProgramType::FRAGMENT;
|
||||||
|
bitmapDefaultShader.AddDescriptorSetLayoutBinding(desc);
|
||||||
|
bitmapDefaultShader.alphaBlend = true;
|
||||||
|
bitmapDefaultShader.cullMode = CullMode::NONE;
|
||||||
|
once = false;
|
||||||
|
}
|
||||||
|
return bitmapDefaultShader;
|
||||||
|
}
|
||||||
|
|
||||||
TextDrawable::TextDrawable(const TextConfig& config)
|
TextDrawable::TextDrawable(const TextConfig& config)
|
||||||
{
|
{
|
||||||
m_cfg = config;
|
m_cfg = config;
|
||||||
@@ -248,6 +268,10 @@ namespace OpenVulkano::Scene
|
|||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
m_bbox.Init(bmin, bmax);
|
m_bbox.Init(bmin, bmax);
|
||||||
|
if (m_atlasData->meta.atlasType == FontAtlasType::BITMAP)
|
||||||
|
{
|
||||||
|
m_material.texture->m_samplerConfig = &SamplerConfig::NEAREST;
|
||||||
|
}
|
||||||
SimpleDrawable::Init(m_shader, &m_geometry, &m_material, &m_uniBuffer);
|
SimpleDrawable::Init(m_shader, &m_geometry, &m_material, &m_uniBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ namespace OpenVulkano::Scene
|
|||||||
public:
|
public:
|
||||||
static Shader& GetSdfDefaultShader();
|
static Shader& GetSdfDefaultShader();
|
||||||
static Shader& GetMsdfDefaultShader();
|
static Shader& GetMsdfDefaultShader();
|
||||||
|
static Shader& GetBitmapDefaultShader();
|
||||||
TextDrawable(const TextConfig& config = TextConfig());
|
TextDrawable(const TextConfig& config = TextConfig());
|
||||||
TextDrawable(const Array<char>& atlasMetadata, const TextConfig& config = TextConfig());
|
TextDrawable(const Array<char>& atlasMetadata, const TextConfig& config = TextConfig());
|
||||||
TextDrawable(const std::string& atlasMetadataFile, const TextConfig& config = TextConfig());
|
TextDrawable(const std::string& atlasMetadataFile, const TextConfig& config = TextConfig());
|
||||||
|
|||||||
39
openVulkanoCpp/Shader/sdfText.frag
Normal file
39
openVulkanoCpp/Shader/sdfText.frag
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 1) in vec2 texCoord;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D texSampler;
|
||||||
|
|
||||||
|
layout(set = 3, binding = 0) uniform TextConfig
|
||||||
|
{
|
||||||
|
vec4 textColor;
|
||||||
|
vec4 borderColor;
|
||||||
|
vec4 backgroundColor;
|
||||||
|
float threshold;
|
||||||
|
float borderSize;
|
||||||
|
float smoothing;
|
||||||
|
bool applyBorder;
|
||||||
|
} textConfig;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
float distance = texture(texSampler, texCoord).r;
|
||||||
|
float alpha = smoothstep(textConfig.threshold - textConfig.smoothing, textConfig.threshold + textConfig.smoothing, distance);
|
||||||
|
if (textConfig.applyBorder)
|
||||||
|
{
|
||||||
|
float border = smoothstep(textConfig.threshold + textConfig.borderSize - textConfig.smoothing,
|
||||||
|
textConfig.threshold + textConfig.borderSize + textConfig.smoothing, distance);
|
||||||
|
outColor = mix(textConfig.borderColor, textConfig.textColor, border) * alpha;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outColor = vec4(textConfig.textColor) * alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textConfig.backgroundColor.a != 0)
|
||||||
|
{
|
||||||
|
outColor = mix(textConfig.backgroundColor, outColor, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
openVulkanoCpp/Shader/sdfText.vert
Normal file
26
openVulkanoCpp/Shader/sdfText.vert
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#version 450
|
||||||
|
layout(location = 0) in vec3 position;
|
||||||
|
layout(location = 1) in vec3 normal;
|
||||||
|
layout(location = 2) in vec3 tangent;
|
||||||
|
layout(location = 3) in vec3 biTangent;
|
||||||
|
layout(location = 4) in vec3 textureCoordinates;
|
||||||
|
layout(location = 5) in vec4 color;
|
||||||
|
layout(location = 1) out vec2 fragTextureCoordinates;
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0) uniform NodeData
|
||||||
|
{
|
||||||
|
mat4 world;
|
||||||
|
} node;
|
||||||
|
|
||||||
|
layout(set = 1, binding = 0) uniform CameraData
|
||||||
|
{
|
||||||
|
mat4 viewProjection;
|
||||||
|
mat4 view;
|
||||||
|
mat4 projection;
|
||||||
|
vec4 camPos;
|
||||||
|
} cam;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = cam.viewProjection * node.world * vec4(position, 1.0);
|
||||||
|
fragTextureCoordinates.xy = textureCoordinates.xy;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#version 450
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec4 color;
|
||||||
layout(location = 1) in vec2 texCoord;
|
layout(location = 1) in vec2 texCoord;
|
||||||
|
|
||||||
layout(location = 0) out vec4 outColor;
|
layout(location = 0) out vec4 outColor;
|
||||||
@@ -19,21 +20,12 @@ layout(set = 3, binding = 0) uniform TextConfig
|
|||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
float distance = texture(texSampler, texCoord).r;
|
// interesting results
|
||||||
float alpha = smoothstep(textConfig.threshold - textConfig.smoothing, textConfig.threshold + textConfig.smoothing, distance);
|
//float distance = texture(texSampler, texCoord).r;
|
||||||
if (textConfig.applyBorder)
|
//float alpha = smoothstep(textConfig.threshold - textConfig.smoothing, textConfig.threshold + textConfig.smoothing, distance);
|
||||||
{
|
//outColor = vec4(textConfig.textColor) * alpha;
|
||||||
float border = smoothstep(textConfig.threshold + textConfig.borderSize - textConfig.smoothing,
|
|
||||||
textConfig.threshold + textConfig.borderSize + textConfig.smoothing, distance);
|
|
||||||
outColor = mix(textConfig.borderColor, textConfig.textColor, border) * alpha;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
outColor = vec4(textConfig.textColor) * alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textConfig.backgroundColor.a != 0)
|
|
||||||
{
|
vec4 sampled = vec4(1.0, 1.0, 1.0, texture(texSampler, texCoord).r);
|
||||||
outColor = mix(textConfig.backgroundColor, outColor, alpha);
|
outColor = vec4(textConfig.textColor) * sampled;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ layout(location = 2) in vec3 tangent;
|
|||||||
layout(location = 3) in vec3 biTangent;
|
layout(location = 3) in vec3 biTangent;
|
||||||
layout(location = 4) in vec3 textureCoordinates;
|
layout(location = 4) in vec3 textureCoordinates;
|
||||||
layout(location = 5) in vec4 color;
|
layout(location = 5) in vec4 color;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
layout(location = 1) out vec2 fragTextureCoordinates;
|
layout(location = 1) out vec2 fragTextureCoordinates;
|
||||||
|
|
||||||
layout(set = 0, binding = 0) uniform NodeData
|
layout(set = 0, binding = 0) uniform NodeData
|
||||||
@@ -23,4 +25,5 @@ layout(set = 1, binding = 0) uniform CameraData
|
|||||||
void main() {
|
void main() {
|
||||||
gl_Position = cam.viewProjection * node.world * vec4(position, 1.0);
|
gl_Position = cam.viewProjection * node.world * vec4(position, 1.0);
|
||||||
fragTextureCoordinates.xy = textureCoordinates.xy;
|
fragTextureCoordinates.xy = textureCoordinates.xy;
|
||||||
|
outColor = color;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user