From 503e31947f83d37b05f3970368671c6a4735a7c1 Mon Sep 17 00:00:00 2001 From: ohyzha Date: Fri, 27 Dec 2024 16:51:26 +0200 Subject: [PATCH] make font atlas generator interface more uniform and implement bitmap font atlas rendering and generation --- examples/ExampleApps/TextExampleApp.cpp | 48 +++-- .../ExampleSources/bitmap_atlas_packed.png | Bin 0 -> 164748 bytes openVulkanoCpp/Scene/AtlasData.hpp | 1 + .../Scene/BitmapFontAtlasGenerator.cpp | 117 ++++++++++++ .../Scene/BitmapFontAtlasGenerator.hpp | 25 +++ openVulkanoCpp/Scene/FontAtlasGenerator.cpp | 149 ++------------- openVulkanoCpp/Scene/FontAtlasGenerator.hpp | 14 +- .../Scene/FontAtlasGeneratorBase.cpp | 180 ++++++++++++++++++ .../Scene/FontAtlasGeneratorBase.hpp | 56 ++++++ openVulkanoCpp/Scene/IFontAtlasGenerator.hpp | 2 +- openVulkanoCpp/Scene/TextDrawable.cpp | 30 ++- openVulkanoCpp/Scene/TextDrawable.hpp | 1 + openVulkanoCpp/Shader/sdfText.frag | 39 ++++ openVulkanoCpp/Shader/sdfText.vert | 26 +++ openVulkanoCpp/Shader/text.frag | 32 ++-- openVulkanoCpp/Shader/text.vert | 3 + 16 files changed, 541 insertions(+), 182 deletions(-) create mode 100644 examples/ExampleSources/bitmap_atlas_packed.png create mode 100644 openVulkanoCpp/Scene/BitmapFontAtlasGenerator.cpp create mode 100644 openVulkanoCpp/Scene/BitmapFontAtlasGenerator.hpp create mode 100644 openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp create mode 100644 openVulkanoCpp/Scene/FontAtlasGeneratorBase.hpp create mode 100644 openVulkanoCpp/Shader/sdfText.frag create mode 100644 openVulkanoCpp/Shader/sdfText.vert diff --git a/examples/ExampleApps/TextExampleApp.cpp b/examples/ExampleApps/TextExampleApp.cpp index 3436a83..f6d15c4 100644 --- a/examples/ExampleApps/TextExampleApp.cpp +++ b/examples/ExampleApps/TextExampleApp.cpp @@ -25,6 +25,7 @@ #include "Image/ImageLoaderPng.hpp" #include "Scene/FontAtlasGenerator.hpp" #include "Scene/IFontAtlasGenerator.hpp" +#include "Scene/BitmapFontAtlasGenerator.hpp" #include #ifdef _WIN32 @@ -39,6 +40,7 @@ namespace OpenVulkano namespace fs = std::filesystem; //#define CREATE_NEW_ATLAS 1 + #define CREATE_BITMAP_ATLAS 0 class TextExampleAppImpl final : public TextExampleApp { @@ -66,21 +68,34 @@ namespace OpenVulkano const int N = texts.size(); auto& resourceLoader = ResourceLoader::GetInstance(); const std::string fontPath = resourceLoader.GetResourcePath("Roboto-Regular.ttf"); - m_nodesPool.resize(N * 2); - m_drawablesPool.resize(N * 2); - + m_nodesPool.resize(N * 3); + m_drawablesPool.resize(N * 3); + +#if CREATE_BITMAP_ATLAS + std::set s = BitmapFontAtlasGenerator::LoadAllGlyphs(fontPath); + BitmapFontAtlasGenerator generator; + generator.GenerateAtlas(fontPath, s); + generator.SaveAtlasMetadataInfo("bitmap_atlas"); +#endif + #if defined(MSDFGEN_AVAILABLE) && defined(CREATE_NEW_ATLAS) - msdf_atlas::Charset charset = SdfFontAtlasGenerator::LoadAllGlyphs(fontPath); - m_atlasGenerator.GenerateAtlas(fontPath, charset); - m_msdfAtlasGenerator.GenerateAtlas(fontPath, charset); - m_atlasGenerator.SaveAtlasMetadataInfo("sdf_atlas.png"); - m_msdfAtlasGenerator.SaveAtlasMetadataInfo("msdf_atlas"); + std::set s = SdfFontAtlasGenerator::LoadAllGlyphs(fontPath); + msdf_atlas::Charset charset; + for (uint32_t c : s) + { + charset.add(c); + } + m_atlasGenerator.GenerateAtlas(fontPath, charset); + m_msdfAtlasGenerator.GenerateAtlas(fontPath, charset); + m_atlasGenerator.SaveAtlasMetadataInfo("sdf_atlas.png"); + m_msdfAtlasGenerator.SaveAtlasMetadataInfo("msdf_atlas"); #else auto sdfMetadataInfo = resourceLoader.GetResource("sdf_atlas_packed.png"); auto msdfMetadataInfo = resourceLoader.GetResource("msdf_atlas_packed.png"); + auto bitmapMetadataInfo = resourceLoader.GetResource("bitmap_atlas_packed.png"); #endif - for (int i = 0; i < texts.size() * 2; i++) + for (int i = 0; i < texts.size() * 3; i++) { int textIdx = i % texts.size(); TextDrawable* t = nullptr; @@ -96,15 +111,24 @@ namespace OpenVulkano t->SetShader(&TextDrawable::GetMsdfDefaultShader()); } #else - if (i < texts.size()) + int xOffset = 0; + if (i < N) { t = new TextDrawable(sdfMetadataInfo, texts[textIdx].second); t->SetShader(&TextDrawable::GetSdfDefaultShader()); + xOffset = -5; } - else + else if (i >= N && i < N * 2) { t = new TextDrawable(msdfMetadataInfo, texts[textIdx].second); 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 //auto metadataInfo = resourceLoader.GetResource("atlas_metadata"); @@ -121,7 +145,7 @@ namespace OpenVulkano t->GenerateText(texts[textIdx].first); m_drawablesPool[i] = t; 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_scene.GetRoot()->AddChild(&m_nodesPool[i]); } diff --git a/examples/ExampleSources/bitmap_atlas_packed.png b/examples/ExampleSources/bitmap_atlas_packed.png new file mode 100644 index 0000000000000000000000000000000000000000..d8071d65b01f280cd0c0d300d48ed963d940ddcb GIT binary patch literal 164748 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 {fnXpXPT15B54fllS3)@@|a4#kTXmsqwj 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 literal 0 HcmV?d00001 diff --git a/openVulkanoCpp/Scene/AtlasData.hpp b/openVulkanoCpp/Scene/AtlasData.hpp index 08dc6a6..ed75309 100644 --- a/openVulkanoCpp/Scene/AtlasData.hpp +++ b/openVulkanoCpp/Scene/AtlasData.hpp @@ -31,6 +31,7 @@ namespace OpenVulkano::Scene { SDF = 0, MSDF, + BITMAP, UNKNOWN }; static constexpr std::string_view DEFAULT_FG_SHADERS[] = { "Shader/text", "Shader/msdfText" }; diff --git a/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.cpp b/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.cpp new file mode 100644 index 0000000..533dd80 --- /dev/null +++ b/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.cpp @@ -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& charset, + const std::optional& pngOutput) + { + Generate(fontFile, charset, pngOutput); + } + + void BitmapFontAtlasGenerator::GenerateAtlas(const Array& fontData, const std::set& charset, + const std::optional& pngOutput) + { + Generate(fontData, charset, pngOutput); + } + + void BitmapFontAtlasGenerator::Generate(const std::variant>& source, + const std::set& chset, + const std::optional& 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(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(sq) + (sq - static_cast(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(slot->metrics.width * toPixelScaler), + static_cast(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); + + } +} diff --git a/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.hpp b/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.hpp new file mode 100644 index 0000000..949b323 --- /dev/null +++ b/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.hpp @@ -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& charset, + const std::optional& pngOutput = std::nullopt) override; + void GenerateAtlas(const Array& fontData, const std::set& charset, + const std::optional& pngOutput = std::nullopt) override; + private: + void Generate(const std::variant>& source, const std::set& chset, const std::optional& pngOutput); + }; +} diff --git a/openVulkanoCpp/Scene/FontAtlasGenerator.cpp b/openVulkanoCpp/Scene/FontAtlasGenerator.cpp index dd0bdb9..179bfcf 100644 --- a/openVulkanoCpp/Scene/FontAtlasGenerator.cpp +++ b/openVulkanoCpp/Scene/FontAtlasGenerator.cpp @@ -8,17 +8,9 @@ #include "FontAtlasGenerator.hpp" #include "Base/Logger.hpp" -#include "Scene/AtlasData.hpp" #include #include #include -#define STBI_MSC_SECURE_CRT -#define STB_IMAGE_WRITE_IMPLEMENTATION -#include -#include -#include FT_FREETYPE_H -#include -#include namespace OpenVulkano::Scene { @@ -28,43 +20,9 @@ namespace OpenVulkano::Scene FontAtlasGeneratorConfig FontAtlasGeneratorConfig::sdfDefaultConfig = { 42, 1.0, 5 }; FontAtlasGeneratorConfig FontAtlasGeneratorConfig::msdfDefaultConfig = { 32, 1.0, 3 }; - template - Charset FontAtlasGenerator::LoadAllGlyphs(const std::variant>& 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(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 void FontAtlasGenerator::GenerateAtlas(const std::string& fontFile, const std::set& charset, - const std::optional& pngOutput) + const std::optional& pngOutput) { FreetypeHandle* ft; FontHandle* font; @@ -74,21 +32,19 @@ namespace OpenVulkano::Scene Generate(ft, font, s, pngOutput); } - template - FontAtlasGenerator::FontAtlasGenerator() + template FontAtlasGenerator::FontAtlasGenerator() : FontAtlasGeneratorBase(Channels) { if constexpr (Channels == 1) m_config = FontAtlasGeneratorConfig::sdfDefaultConfig; else m_config = FontAtlasGeneratorConfig::msdfDefaultConfig; } template - void FontAtlasGenerator::GenerateAtlas(const Array& fontData, int length, - const std::set& charset, - const std::optional& pngOutput) + void FontAtlasGenerator::GenerateAtlas(const Array& fontData, const std::set& charset, + const std::optional& pngOutput) { FreetypeHandle* ft; FontHandle* font; - InitFreetypeFromBuffer(ft, font, (const msdfgen::byte*)(fontData.Data()), length); + InitFreetypeFromBuffer(ft, font, (const msdfgen::byte*)(fontData.Data()), fontData.Size()); Charset s; std::for_each(s.begin(), s.end(), [&](uint32_t unicode) { s.add(unicode); }); Generate(ft, font, s, pngOutput); @@ -116,37 +72,6 @@ namespace OpenVulkano::Scene Generate(ft, font, charset, pngOutput); } - template - void FontAtlasGenerator::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(&m_atlasData->meta), sizeof(AtlasMetadata)); - uint64_t metadataBytes = sizeof(AtlasMetadata); - for (const auto& [key, val]: m_atlasData->glyphs) - { - fs.write(reinterpret_cast(&key), sizeof(uint32_t)); - fs.write(reinterpret_cast(&val), sizeof(GlyphInfo)); - metadataBytes += sizeof(uint32_t); - metadataBytes += sizeof(GlyphInfo); - } - fs.write(reinterpret_cast(&metadataBytes), sizeof(uint64_t)); - fs.write(reinterpret_cast(&packedFlag), sizeof(uint32_t)); - } - template void FontAtlasGenerator::InitFreetypeFromFile(FreetypeHandle*& ft, FontHandle*& font, const std::string& fontFile) @@ -217,13 +142,12 @@ namespace OpenVulkano::Scene generator.generate(glyphsGeometry.data(), glyphsGeometry.size()); int idx = 0; + SetupAtlasData(Math::Vector3ui(width, height, 1), fontGeometry.getMetrics().lineHeight, + channelsCount == 1 ? FontAtlasType::SDF : FontAtlasType::MSDF); + if constexpr (Channels == 3) { // store RGB as RGBA - m_atlasData->img = std::make_unique(); - m_atlasData->img->data = Array(width * height * 4); - m_atlasData->img->resolution = Math::Vector3ui(width, height, 1); - m_atlasData->img->dataFormat = OpenVulkano::DataFormat::R8G8B8A8_UNORM; const BitmapConstRef storage = generator.atlasStorage(); msdfgen::byte* data = static_cast(m_atlasData->img->data.Data()); for (size_t srcPos = 0, dstPos = 0; srcPos < width * height * 3; srcPos += 3, dstPos += 4) @@ -236,21 +160,10 @@ namespace OpenVulkano::Scene } else { - m_atlasData->img = std::make_unique(); - m_atlasData->img->data = Array(width * height); - m_atlasData->img->resolution = Math::Vector3ui(width, height, 1); - m_atlasData->img->dataFormat = OpenVulkano::DataFormat::R8_UNORM; const msdfgen::BitmapConstRef& storage = generator.atlasStorage(); 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 { double l = 0, r = 0, t = 0, b = 0; @@ -274,31 +187,12 @@ namespace OpenVulkano::Scene double t = glyphAtlasBbox.t; double b = glyphAtlasBbox.b; - 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 = glyphBox.advance; + 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()) { SavePng(pngOutput.value()); } @@ -306,21 +200,6 @@ namespace OpenVulkano::Scene deinitializeFreetype(ft); } - template - void FontAtlasGenerator::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<3>; } diff --git a/openVulkanoCpp/Scene/FontAtlasGenerator.hpp b/openVulkanoCpp/Scene/FontAtlasGenerator.hpp index bac326c..1ae7863 100644 --- a/openVulkanoCpp/Scene/FontAtlasGenerator.hpp +++ b/openVulkanoCpp/Scene/FontAtlasGenerator.hpp @@ -8,9 +8,7 @@ #if __has_include("msdfgen.h") -#include "Scene/AtlasData.hpp" -#include "IFontAtlasGenerator.hpp" -#include "Scene/Texture.hpp" +#include "FontAtlasGeneratorBase.hpp" #include #include #include @@ -32,7 +30,7 @@ namespace OpenVulkano::Scene }; template - class FontAtlasGenerator : public IFontAtlasGenerator + class FontAtlasGenerator : public FontAtlasGeneratorBase { private: using SdfGenerator = msdf_atlas::ImmediateAtlasGenerator::type; using Config = FontAtlasGeneratorConfig; static constexpr int channelsCount = (Channels == 1 ? 1 : 4); - static msdf_atlas::Charset LoadAllGlyphs(const std::variant>& data); FontAtlasGenerator(); void GenerateAtlas(const std::string& fontFile, const std::set& charset, const std::optional& pngOutput = std::nullopt) override; - void GenerateAtlas(const Array& fontData, int length, const std::set& charset, + void GenerateAtlas(const Array& fontData, const std::set& charset, const std::optional& pngOutput = std::nullopt) override; void GenerateAtlas(const std::string& fontFile, const msdf_atlas::Charset& charset = msdf_atlas::Charset::ASCII, const std::optional& pngOutput = std::nullopt); void GenerateAtlas(const msdfgen::byte* fontData, int length, const msdf_atlas::Charset& charset = msdf_atlas::Charset::ASCII, const std::optional& pngOutput = std::nullopt); - void SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile = true) const override; void SetGeneratorConfig(const Config& config) { m_config = config; } - std::shared_ptr GetAtlasData() const { return m_atlasData; } Config& GetGeneratorConfig() { return m_config; } private: 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); void Generate(msdfgen::FreetypeHandle* ft, msdfgen::FontHandle* font, const msdf_atlas::Charset& chset, const std::optional& pngOutput); - void SavePng(const std::string& output) const; - private: Config m_config; - std::shared_ptr m_atlasData; }; using SdfFontAtlasGenerator = FontAtlasGenerator<1>; using MsdfFontAtlasGenerator = FontAtlasGenerator<3>; diff --git a/openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp b/openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp new file mode 100644 index 0000000..e9528fd --- /dev/null +++ b/openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp @@ -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 +#include +#include +#include + +namespace OpenVulkano::Scene +{ + std::pair + FontAtlasGeneratorBase::InitFreetype(const std::variant>& 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(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(&m_atlasData->meta), sizeof(AtlasMetadata)); + uint64_t metadataBytes = sizeof(AtlasMetadata); + for (const auto& [key, val] : m_atlasData->glyphs) + { + fs.write(reinterpret_cast(&key), sizeof(uint32_t)); + fs.write(reinterpret_cast(&val), sizeof(GlyphInfo)); + metadataBytes += sizeof(uint32_t); + metadataBytes += sizeof(GlyphInfo); + } + fs.write(reinterpret_cast(&metadataBytes), sizeof(uint64_t)); + fs.write(reinterpret_cast(&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(); + m_atlasData->img->data = Array(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(); + // RGBA + m_atlasData->img->data = Array(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 FontAtlasGeneratorBase::LoadAllGlyphs(const std::variant>& data) + { + const auto& [lib, face] = InitFreetype(data); + std::set 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; + } + +} diff --git a/openVulkanoCpp/Scene/FontAtlasGeneratorBase.hpp b/openVulkanoCpp/Scene/FontAtlasGeneratorBase.hpp new file mode 100644 index 0000000..e1e1ea7 --- /dev/null +++ b/openVulkanoCpp/Scene/FontAtlasGeneratorBase.hpp @@ -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 +#include FT_FREETYPE_H +#include +#include + +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; + using FT_FACE_REC = std::unique_ptr; + + FontAtlasGeneratorBase(int channelsCount) : m_channelsCount(channelsCount) {} + void SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile = true) const override; + std::shared_ptr GetAtlasData() const { return m_atlasData; } + int GetAtlasChannelsCount() const { return m_channelsCount; } + + static std::set LoadAllGlyphs(const std::variant>& 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 InitFreetype(const std::variant>& source); + protected: + int m_channelsCount; + std::shared_ptr m_atlasData; + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp b/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp index bd641c1..cdb4094 100644 --- a/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp +++ b/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp @@ -21,7 +21,7 @@ namespace OpenVulkano::Scene public: virtual void GenerateAtlas(const std::string& fontFile, const std::set& charset, const std::optional& pngOutput = std::nullopt) = 0; - virtual void GenerateAtlas(const Array& fontData, int length, const std::set& charset, + virtual void GenerateAtlas(const Array& fontData, const std::set& charset, const std::optional& pngOutput = std::nullopt) = 0; virtual void SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile = true) const = 0; virtual std::shared_ptr GetAtlasData() const = 0; diff --git a/openVulkanoCpp/Scene/TextDrawable.cpp b/openVulkanoCpp/Scene/TextDrawable.cpp index be09db9..2f083aa 100644 --- a/openVulkanoCpp/Scene/TextDrawable.cpp +++ b/openVulkanoCpp/Scene/TextDrawable.cpp @@ -26,8 +26,8 @@ namespace OpenVulkano::Scene static Shader sdfDefaultShader; if (once) { - sdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/text"); - sdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/text"); + sdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/sdfText"); + sdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/sdfText"); sdfDefaultShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); sdfDefaultShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING); DescriptorSetLayoutBinding desc = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING; @@ -46,7 +46,7 @@ namespace OpenVulkano::Scene static Shader msdfDefaultShader; if (once) { - msdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/text"); + msdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/sdfText"); msdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/msdfText"); msdfDefaultShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); msdfDefaultShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING); @@ -60,6 +60,26 @@ namespace OpenVulkano::Scene 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) { m_cfg = config; @@ -248,6 +268,10 @@ namespace OpenVulkano::Scene ++i; } 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); } diff --git a/openVulkanoCpp/Scene/TextDrawable.hpp b/openVulkanoCpp/Scene/TextDrawable.hpp index 3896651..f8d6e3f 100644 --- a/openVulkanoCpp/Scene/TextDrawable.hpp +++ b/openVulkanoCpp/Scene/TextDrawable.hpp @@ -38,6 +38,7 @@ namespace OpenVulkano::Scene public: static Shader& GetSdfDefaultShader(); static Shader& GetMsdfDefaultShader(); + static Shader& GetBitmapDefaultShader(); TextDrawable(const TextConfig& config = TextConfig()); TextDrawable(const Array& atlasMetadata, const TextConfig& config = TextConfig()); TextDrawable(const std::string& atlasMetadataFile, const TextConfig& config = TextConfig()); diff --git a/openVulkanoCpp/Shader/sdfText.frag b/openVulkanoCpp/Shader/sdfText.frag new file mode 100644 index 0000000..579d9c7 --- /dev/null +++ b/openVulkanoCpp/Shader/sdfText.frag @@ -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); + } +} diff --git a/openVulkanoCpp/Shader/sdfText.vert b/openVulkanoCpp/Shader/sdfText.vert new file mode 100644 index 0000000..c259c65 --- /dev/null +++ b/openVulkanoCpp/Shader/sdfText.vert @@ -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; +} diff --git a/openVulkanoCpp/Shader/text.frag b/openVulkanoCpp/Shader/text.frag index 5333c6f..c960875 100644 --- a/openVulkanoCpp/Shader/text.frag +++ b/openVulkanoCpp/Shader/text.frag @@ -1,5 +1,6 @@ #version 450 +layout(location = 0) in vec4 color; layout(location = 1) in vec2 texCoord; layout(location = 0) out vec4 outColor; @@ -12,28 +13,19 @@ layout(set = 3, binding = 0) uniform TextConfig vec4 borderColor; vec4 backgroundColor; float threshold; - float borderSize; - float smoothing; - bool applyBorder; + 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); - } + // interesting results + //float distance = texture(texSampler, texCoord).r; + //float alpha = smoothstep(textConfig.threshold - textConfig.smoothing, textConfig.threshold + textConfig.smoothing, distance); + //outColor = vec4(textConfig.textColor) * alpha; + + + vec4 sampled = vec4(1.0, 1.0, 1.0, texture(texSampler, texCoord).r); + outColor = vec4(textConfig.textColor) * sampled; } diff --git a/openVulkanoCpp/Shader/text.vert b/openVulkanoCpp/Shader/text.vert index 1e9ef24..47f4b61 100644 --- a/openVulkanoCpp/Shader/text.vert +++ b/openVulkanoCpp/Shader/text.vert @@ -5,6 +5,8 @@ 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 = 0) out vec4 outColor; layout(location = 1) out vec2 fragTextureCoordinates; layout(set = 0, binding = 0) uniform NodeData @@ -23,4 +25,5 @@ layout(set = 1, binding = 0) uniform CameraData void main() { gl_Position = cam.viewProjection * node.world * vec4(position, 1.0); fragTextureCoordinates.xy = textureCoordinates.xy; + outColor = color; }