diff --git a/examples/ExampleApps/TextExampleApp.cpp b/examples/ExampleApps/TextExampleApp.cpp index 879de78..4a06706 100644 --- a/examples/ExampleApps/TextExampleApp.cpp +++ b/examples/ExampleApps/TextExampleApp.cpp @@ -24,7 +24,7 @@ #include "Controller/FreeCamCameraController.hpp" #include "Image/ImageLoaderPng.hpp" #include "Scene/FontAtlasGenerator.hpp" -#include "Scene/MsdfFontAtlasGenerator.hpp" +#include "Scene/IFontAtlasGenerator.hpp" #include #ifdef _WIN32 @@ -57,7 +57,7 @@ namespace OpenVulkano texts.push_back(std::make_pair("ABab?.^{}_cdFGETG123)(", TextConfig())); texts.push_back(std::make_pair("Hello, World!", TextConfig())); texts.push_back(std::make_pair("\u0410\u0411\u0412\u041F", TextConfig())); - texts.push_back(std::make_pair("Text with unsupported glyphs \u1E30\u1E31 is coming", TextConfig())); + texts.push_back(std::make_pair("Unsupported glyphs \u1E30\u1E31 are coming", TextConfig())); texts.push_back(std::make_pair("This is first line\nSecond gg line\nThird G line", TextConfig())); texts[0].second.applyBorder = true; texts[1].second.backgroundColor.a = 1; @@ -65,27 +65,44 @@ namespace OpenVulkano const int N = texts.size(); auto& resourceLoader = ResourceLoader::GetInstance(); const std::string fontPath = resourceLoader.GetResourcePath("Roboto-Regular.ttf"); - m_nodesPool.resize(N); - m_drawablesPool.resize(N); + m_nodesPool.resize(N * 2); + m_drawablesPool.resize(N * 2); #ifdef MSDFGEN_AVAILABLE - msdf_atlas::Charset charset = MsdfFontAtlasGenerator::LoadAllGlyphs(fontPath); - //msdf_atlas::Charset charset = msdf_atlas::Charset::ASCII; - //for (msdf_atlas::unicode_t c = 0x0410; c <= 0x041F; c++) - //{ - // // some unicode values - // charset.add(c); - //} + msdf_atlas::Charset charset = SdfFontAtlasGenerator::LoadAllGlyphs(fontPath); m_atlasGenerator.GenerateAtlas(fontPath, charset); + m_msdfAtlasGenerator.GenerateAtlas(fontPath, charset); +#else + auto sdfMetadataInfo = resourceLoader.GetResource("sdf_atlas_packed"); + auto msdfMetadataInfo = resourceLoader.GetResource("msdf_atlas_packed.png"); #endif - for (int i = 0; i < texts.size(); i++) + for (int i = 0; i < texts.size() * 2; i++) { + int textIdx = i % texts.size(); + TextDrawable* t = nullptr; #ifdef MSDFGEN_AVAILABLE - TextDrawable* t = new TextDrawable(&m_atlasGenerator, texts[i].second); + if (i < texts.size()) + { + t = new TextDrawable(&m_atlasGenerator, texts[textIdx].second); + t->SetShader(&TextDrawable::GetSdfDefaultShader()); + } + else + { + t = new TextDrawable(&m_msdfAtlasGenerator, texts[textIdx].second); + t->SetShader(&TextDrawable::GetMsdfDefaultShader()); + } #else - auto metadataInfo = resourceLoader.GetResource("full_atlas_metadata_packed"); - TextDrawable* t = new TextDrawable(metadataInfo, texts[i].second); + if (i < texts.size()) + { + t = new TextDrawable(sdfMetadataInfo, texts[textIdx].second); + t->SetShader(&TextDrawable::GetSdfDefaultShader()); + } + else + { + t = new TextDrawable(msdfMetadataInfo, texts[textIdx].second); + t->SetShader(&TextDrawable::GetMsdfDefaultShader()); + } // OR use separate texture + metadata file //auto metadataInfo = resourceLoader.GetResource("atlas_metadata"); //auto data = resourceLoader.GetResource("roboto-regular-atlas.png"); @@ -98,17 +115,17 @@ namespace OpenVulkano //tex.size = image->data.Size(); // 1 channel //TextDrawable* t = new TextDrawable(metadataInfo, &tex, texts[i].second); #endif // MSDFGEN_AVAILABLE - t->GenerateText(texts[i].first); + 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(-5, 2 - i * 2, 0))); + m_nodesPool[i].SetMatrix(Math::Utils::translate(glm::mat4x4(1.f), Vector3f((i < texts.size() ? -5 : 15), 2 - textIdx * 2, 0))); m_nodesPool[i].AddDrawable(m_drawablesPool[i]); m_scene.GetRoot()->AddChild(&m_nodesPool[i]); } GetGraphicsAppManager()->GetRenderer()->SetScene(&m_scene); m_camController.Init(&m_cam); m_camController.SetDefaultKeybindings(); - m_camController.SetPosition({ 0, 0, 10 }); + m_camController.SetPosition({ 10, 0, 15 }); m_camController.SetBoostFactor(5); std::shared_ptr m_perfInfo = @@ -126,6 +143,7 @@ namespace OpenVulkano { for (SimpleDrawable* d: m_drawablesPool) { + d->Close(); delete d; } } @@ -135,7 +153,8 @@ namespace OpenVulkano PerspectiveCamera m_cam; OpenVulkano::FreeCamCameraController m_camController; #ifdef MSDFGEN_AVAILABLE - MsdfFontAtlasGenerator m_atlasGenerator; + SdfFontAtlasGenerator m_atlasGenerator; + MsdfFontAtlasGenerator m_msdfAtlasGenerator; #endif std::vector m_drawablesPool; std::vector m_nodesPool; diff --git a/examples/ExampleSources/atlas_metadata b/examples/ExampleSources/atlas_metadata deleted file mode 100644 index ec51427..0000000 Binary files a/examples/ExampleSources/atlas_metadata and /dev/null differ diff --git a/examples/ExampleSources/msdf_atlas_packed.png b/examples/ExampleSources/msdf_atlas_packed.png new file mode 100644 index 0000000..abfafdf Binary files /dev/null and b/examples/ExampleSources/msdf_atlas_packed.png differ diff --git a/examples/ExampleSources/roboto-regular-atlas.png b/examples/ExampleSources/roboto-regular-atlas.png deleted file mode 100644 index 4faa297..0000000 Binary files a/examples/ExampleSources/roboto-regular-atlas.png and /dev/null differ diff --git a/examples/ExampleSources/full_atlas_metadata_packed b/examples/ExampleSources/sdf_atlas_packed similarity index 100% rename from examples/ExampleSources/full_atlas_metadata_packed rename to examples/ExampleSources/sdf_atlas_packed diff --git a/openVulkanoCpp/Image/ImageLoader.cpp b/openVulkanoCpp/Image/ImageLoader.cpp index d05d4ec..624818e 100644 --- a/openVulkanoCpp/Image/ImageLoader.cpp +++ b/openVulkanoCpp/Image/ImageLoader.cpp @@ -25,18 +25,15 @@ namespace OpenVulkano::Image "Stbi load image channels mismatch. Desired channels = {}, actual amount of channels in image = {}", desiredChannels, channels); } - result.data = OpenVulkano::Array(cols * rows * channels); switch (channels) { case 1: result.dataFormat = OpenVulkano::DataFormat::R8_UNORM; break; case 2: - result.dataFormat = OpenVulkano::DataFormat::R8G8_UNORM; + result.dataFormat = OpenVulkano::DataFormat::R8G8_UNORM; break; case 3: - result.dataFormat = OpenVulkano::DataFormat::R8G8B8_UNORM; - break; case 4: result.dataFormat = OpenVulkano::DataFormat::R8G8B8A8_UNORM; break; @@ -44,7 +41,22 @@ namespace OpenVulkano::Image result.resolution.x = cols; result.resolution.y = rows; result.resolution.z = 1; - std::memcpy(result.data.Data(), pixelData, result.data.Size()); + if (channels == 3) + { + result.data = OpenVulkano::Array(cols * rows * 4); + for (size_t srcPos = 0, dstPos = 0; srcPos < cols * rows * 3; srcPos += 3, dstPos += 4) + { + result.data[dstPos] = pixelData[srcPos]; + result.data[dstPos + 1] = pixelData[srcPos + 1]; + result.data[dstPos + 2] = pixelData[srcPos + 2]; + result.data[dstPos + 3] = 255; + } + } + else + { + result.data = OpenVulkano::Array(cols * rows * channels); + std::memcpy(result.data.Data(), pixelData, result.data.Size()); + } stbi_image_free(pixelData); return std::make_unique(std::move(result)); } diff --git a/openVulkanoCpp/Scene/MsdfFontAtlasGenerator.cpp b/openVulkanoCpp/Scene/FontAtlasGenerator.cpp similarity index 63% rename from openVulkanoCpp/Scene/MsdfFontAtlasGenerator.cpp rename to openVulkanoCpp/Scene/FontAtlasGenerator.cpp index 2179491..42d355a 100644 --- a/openVulkanoCpp/Scene/MsdfFontAtlasGenerator.cpp +++ b/openVulkanoCpp/Scene/FontAtlasGenerator.cpp @@ -6,7 +6,7 @@ #if __has_include("msdfgen.h") -#include "MsdfFontAtlasGenerator.hpp" +#include "FontAtlasGenerator.hpp" #include "Base/Logger.hpp" #include "Scene/AtlasMetadata.hpp" #include @@ -24,8 +24,12 @@ namespace OpenVulkano::Scene { using namespace msdfgen; using namespace msdf_atlas; + + FontAtlasGeneratorConfig FontAtlasGeneratorConfig::sdfDefaultConfig = { 42, 1.0, 5 }; + FontAtlasGeneratorConfig FontAtlasGeneratorConfig::msdfDefaultConfig = { 32, 1.0, 3 }; - Charset MsdfFontAtlasGenerator::LoadAllGlyphs(const std::variant>& data) + template + Charset FontAtlasGenerator::LoadAllGlyphs(const std::variant>& data) { FT_Library library; auto error = FT_Init_FreeType(&library); @@ -58,7 +62,8 @@ namespace OpenVulkano::Scene return s; } - void MsdfFontAtlasGenerator::GenerateAtlas(const std::string& fontFile, const std::set& charset, + template + void FontAtlasGenerator::GenerateAtlas(const std::string& fontFile, const std::set& charset, const std::optional& pngOutput) { FreetypeHandle* ft; @@ -69,7 +74,15 @@ namespace OpenVulkano::Scene Generate(ft, font, s, pngOutput); } - void MsdfFontAtlasGenerator::GenerateAtlas(const Array& fontData, int length, + template + FontAtlasGenerator::FontAtlasGenerator() + { + 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) { @@ -81,7 +94,8 @@ namespace OpenVulkano::Scene Generate(ft, font, s, pngOutput); } - void MsdfFontAtlasGenerator::GenerateAtlas(const std::string& fontFile, const Charset& charset, + template + void FontAtlasGenerator::GenerateAtlas(const std::string& fontFile, const Charset& charset, const std::optional& pngOutput) { // TODO: dynamic atlas and add only those symbols which are not present yet in current atlas @@ -91,7 +105,9 @@ namespace OpenVulkano::Scene Generate(ft, font, charset, pngOutput); } - void MsdfFontAtlasGenerator::GenerateAtlas(const msdfgen::byte* fontData, int length, const Charset& charset, + template + void FontAtlasGenerator::GenerateAtlas(const msdfgen::byte* fontData, int length, + const Charset& charset, const std::optional& pngOutput) { FreetypeHandle* ft; @@ -100,7 +116,9 @@ namespace OpenVulkano::Scene Generate(ft, font, charset, pngOutput); } - void MsdfFontAtlasGenerator::SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile) const + template + void FontAtlasGenerator::SaveAtlasMetadataInfo(const std::string& outputFile, + bool packIntoSingleFile) const { if (m_symbols.empty()) { @@ -111,17 +129,16 @@ namespace OpenVulkano::Scene uint32_t packedFlag = packIntoSingleFile; if (packIntoSingleFile) { - size_t ext = outputFile.find_last_of('.'); - if (ext == std::string::npos) + bool isPng = std::filesystem::path(fileName).extension() == ".png"; + if (!isPng) { - fileName += "_packed"; + fileName += "_packed.png"; } else { - fileName.insert(ext - 1, "_packed"); + fileName.insert(fileName.size() - 4, "_packed"); } - const BitmapConstRef& storage = m_generator.atlasStorage(); - SavePng(m_generator.atlasStorage(), fileName, 1); + 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_meta), sizeof(AtlasMetadata)); @@ -137,7 +154,9 @@ namespace OpenVulkano::Scene fs.write(reinterpret_cast(&packedFlag), sizeof(uint32_t)); } - void MsdfFontAtlasGenerator::InitFreetypeFromFile(FreetypeHandle*& ft, FontHandle*& font, const std::string& fontFile) + template + void FontAtlasGenerator::InitFreetypeFromFile(FreetypeHandle*& ft, FontHandle*& font, + const std::string& fontFile) { ft = initializeFreetype(); if (!ft) { throw std::runtime_error("Failed to initialize freetype"); } @@ -150,7 +169,8 @@ namespace OpenVulkano::Scene } } - void MsdfFontAtlasGenerator::InitFreetypeFromBuffer(FreetypeHandle*& ft, FontHandle*& font, + template + void FontAtlasGenerator::InitFreetypeFromBuffer(FreetypeHandle*& ft, FontHandle*& font, const msdfgen::byte* fontData, int length) { ft = initializeFreetype(); @@ -164,7 +184,8 @@ namespace OpenVulkano::Scene } } - void MsdfFontAtlasGenerator::Generate(FreetypeHandle* ft, FontHandle* font, const Charset& chset, + template + void FontAtlasGenerator::Generate(FreetypeHandle* ft, FontHandle* font, const Charset& chset, const std::optional& pngOutput) { m_symbols.clear(); @@ -173,6 +194,13 @@ namespace OpenVulkano::Scene FontGeometry fontGeometry(&glyphsGeometry); fontGeometry.loadCharset(font, 1, chset); + if constexpr (Channels == 3) + { + const double maxCornerAngle = 3.0; + for (GlyphGeometry& glyph: glyphsGeometry) + glyph.edgeColoring(&msdfgen::edgeColoringByDistance, maxCornerAngle, 0); + } + TightAtlasPacker packer; packer.setDimensionsConstraint(DimensionsConstraint::SQUARE); int width, height; @@ -187,18 +215,39 @@ namespace OpenVulkano::Scene packer.pack(glyphsGeometry.data(), glyphsGeometry.size()); packer.getDimensions(width, height); - m_generator.resize(width, height); + Generator generator; + generator.resize(width, height); GeneratorAttributes attributes; - m_generator.setAttributes(attributes); - m_generator.setThreadCount(4); - m_generator.generate(glyphsGeometry.data(), glyphsGeometry.size()); + generator.setAttributes(attributes); + generator.setThreadCount(4); + generator.generate(glyphsGeometry.data(), glyphsGeometry.size()); + //const auto& storage = generator.atlasStorage(); int idx = 0; - const BitmapConstRef& storage = m_generator.atlasStorage(); - m_atlasTex.resolution = Math::Vector3ui(storage.width, storage.height, 1); - m_atlasTex.textureBuffer = (msdfgen::byte*) storage.pixels; - m_atlasTex.format = OpenVulkano::DataFormat::R8_UNORM; - m_atlasTex.size = storage.width * storage.height * 1; // 1 channel + if constexpr (Channels == 3) + { + // store RGB as RGBA + const BitmapConstRef storage = generator.atlasStorage(); + msdfgen::Bitmap bitmap(width, height); + msdfgen::byte* data = static_cast(bitmap); + for (size_t srcPos = 0, dstPos = 0; srcPos < width * height * 3; srcPos += 3, dstPos += 4) + { + data[dstPos] = storage.pixels[srcPos]; + data[dstPos + 1] = storage.pixels[srcPos + 1]; + data[dstPos + 2] = storage.pixels[srcPos + 2]; + data[dstPos + 3] = 255; + } + m_atlasStorage = std::move(bitmap); + } + else + { + m_atlasStorage = generator.atlasStorage(); + } + + m_atlasTex.resolution = Math::Vector3ui(m_atlasStorage.width(), m_atlasStorage.height(), 1); + m_atlasTex.textureBuffer = (msdfgen::byte*) m_atlasStorage; + m_atlasTex.format = (channelsCount == 1 ? OpenVulkano::DataFormat::R8_UNORM : OpenVulkano::DataFormat::R8G8B8A8_UNORM); + m_atlasTex.size = m_atlasStorage.width() * m_atlasStorage.height() * channelsCount; m_meta.lineHeight = fontGeometry.getMetrics().lineHeight; @@ -210,7 +259,7 @@ namespace OpenVulkano::Scene for (const auto& glyph: glyphsGeometry) { GlyphInfo& info = m_symbols[glyph.getCodepoint()]; - const GlyphBox& glyphBox = m_generator.getLayout()[idx++]; + const GlyphBox& glyphBox = generator.getLayout()[idx++]; Bbox glyphBaselineBbox, glyphAtlasBbox; glyph.getQuadPlaneBounds(glyphBaselineBbox.l, glyphBaselineBbox.b, glyphBaselineBbox.r, @@ -252,25 +301,28 @@ namespace OpenVulkano::Scene info.advance = glyphBox.advance; } - if (pngOutput && !pngOutput->empty()) { SavePng(storage, pngOutput.value(), 1); } + if (pngOutput && !pngOutput->empty()) { SavePng(pngOutput.value()); } destroyFont(font); deinitializeFreetype(ft); } - void MsdfFontAtlasGenerator::SavePng(const BitmapConstRef& storage, const std::string& output, - int channels) const + template + void FontAtlasGenerator::SavePng(const std::string& output) const { + // rework here. do not pass storage . use m_atlasStorage stbi_flip_vertically_on_write(1); if (std::filesystem::path(output).extension() == ".png") { - stbi_write_png(output.c_str(), storage.width, storage.height, channels, storage.pixels, - channels * storage.width); + stbi_write_png(output.c_str(), m_atlasStorage.width(), m_atlasStorage.height(), channelsCount, + m_atlasStorage.operator const msdfgen::byte*(), channelsCount * m_atlasStorage.width()); } else { - stbi_write_png((output + ".png").c_str(), storage.width, storage.height, channels, storage.pixels, - channels * storage.width); + stbi_write_png((output + ".png").c_str(), m_atlasStorage.width(), m_atlasStorage.height(), channelsCount, + m_atlasStorage.operator const msdfgen::byte*(), channelsCount * m_atlasStorage.width()); } } + template class FontAtlasGenerator<1>; + template class FontAtlasGenerator<3>; } #endif \ No newline at end of file diff --git a/openVulkanoCpp/Scene/FontAtlasGenerator.hpp b/openVulkanoCpp/Scene/FontAtlasGenerator.hpp index 301dc9d..deebd5b 100644 --- a/openVulkanoCpp/Scene/FontAtlasGenerator.hpp +++ b/openVulkanoCpp/Scene/FontAtlasGenerator.hpp @@ -6,27 +6,79 @@ #pragma once +#if __has_include("msdfgen.h") + #include "Scene/AtlasMetadata.hpp" +#include "IFontAtlasGenerator.hpp" #include "Scene/Texture.hpp" +#include +#include #include #include #include #include -#include -#include +#define MSDFGEN_AVAILABLE 1 namespace OpenVulkano::Scene { - class FontAtlasGenerator + + struct FontAtlasGeneratorConfig { - 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, - const std::optional& pngOutput = std::nullopt) = 0; - virtual void SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile = true) const = 0; - virtual const Texture& GetAtlas() const = 0; - virtual std::map& GetGlyphsInfo() = 0; - virtual AtlasMetadata& GetAtlasMetadata() = 0; + static FontAtlasGeneratorConfig sdfDefaultConfig; + static FontAtlasGeneratorConfig msdfDefaultConfig; + int glyphSize; + double miterLimit; + msdfgen::Range pixelRange; }; + + template + class FontAtlasGenerator : public IFontAtlasGenerator + { + private: + using SdfGenerator = msdf_atlas::ImmediateAtlasGenerator>; + using MsdfGenerator = msdf_atlas::ImmediateAtlasGenerator>; + public: + using Generator = std::conditional::type; + using AtlasData = std::conditional, msdfgen::Bitmap>::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, + 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; } + const Texture& GetAtlas() const override { return m_atlasTex; } + std::map& GetGlyphsInfo() override { return m_symbols; } + AtlasMetadata& GetAtlasMetadata() override { return m_meta; } + //Generator& GetFontAtlasGenerator() { return m_generator; } + Config& GetGeneratorConfig() { return m_config; } + private: + void InitFreetypeFromFile(msdfgen::FreetypeHandle*& ft, msdfgen::FontHandle*& font, const std::string& file); + void InitFreetypeFromBuffer(msdfgen::FreetypeHandle*& ft, msdfgen::FontHandle*& font, + const msdfgen::byte* fontData, int length); + void Generate(msdfgen::FreetypeHandle* ft, msdfgen::FontHandle* font, const msdf_atlas::Charset& chset, + const std::optional& pngOutput); + void SavePng(const std::string& output) const; + + private: + //Generator m_generator; + Texture m_atlasTex; + AtlasMetadata m_meta; + Config m_config; + std::map m_symbols; + AtlasData m_atlasStorage; + }; + using SdfFontAtlasGenerator = FontAtlasGenerator<1>; + using MsdfFontAtlasGenerator = FontAtlasGenerator<3>; } +#endif diff --git a/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp b/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp new file mode 100644 index 0000000..aeae92d --- /dev/null +++ b/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp @@ -0,0 +1,32 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include "Scene/AtlasMetadata.hpp" +#include "Scene/Texture.hpp" +#include +#include +#include +#include +#include +#include + +namespace OpenVulkano::Scene +{ + class IFontAtlasGenerator + { + 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, + const std::optional& pngOutput = std::nullopt) = 0; + virtual void SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile = true) const = 0; + virtual const Texture& GetAtlas() const = 0; + virtual std::map& GetGlyphsInfo() = 0; + virtual AtlasMetadata& GetAtlasMetadata() = 0; + }; +} diff --git a/openVulkanoCpp/Scene/MsdfFontAtlasGenerator.hpp b/openVulkanoCpp/Scene/MsdfFontAtlasGenerator.hpp deleted file mode 100644 index c1e7411..0000000 --- a/openVulkanoCpp/Scene/MsdfFontAtlasGenerator.hpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -#pragma once - -#if __has_include("msdfgen.h") - -#include "Scene/AtlasMetadata.hpp" -#include "FontAtlasGenerator.hpp" -#include "Scene/Texture.hpp" -#include -#include -#include -#include -#include -#include -#define MSDFGEN_AVAILABLE 1 - -namespace OpenVulkano::Scene -{ - - struct MsdfFontAtlasGeneratorConfig - { - int glyphSize = 42; - double miterLimit = 1.0; - msdfgen::Range pixelRange = 5; - }; - - class MsdfFontAtlasGenerator : public FontAtlasGenerator - { - public: - using SdfGenerator = msdf_atlas::ImmediateAtlasGenerator>; - using Config = MsdfFontAtlasGeneratorConfig; - static msdf_atlas::Charset LoadAllGlyphs(const std::variant>& data); - 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, - 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; } - const Texture& GetAtlas() const override { return m_atlasTex; } - std::map& GetGlyphsInfo() override { return m_symbols; } - AtlasMetadata& GetAtlasMetadata() override { return m_meta; } - SdfGenerator& GetFontAtlasGenerator() { return m_generator; } - Config& GetGeneratorConfig() { return m_config; } - private: - void InitFreetypeFromFile(msdfgen::FreetypeHandle*& ft, msdfgen::FontHandle*& font, const std::string& file); - void InitFreetypeFromBuffer(msdfgen::FreetypeHandle*& ft, msdfgen::FontHandle*& font, - const msdfgen::byte* fontData, int length); - void Generate(msdfgen::FreetypeHandle* ft, msdfgen::FontHandle* font, const msdf_atlas::Charset& chset, - const std::optional& pngOutput); - void SavePng(const msdfgen::BitmapConstRef& storage, const std::string& output, - int channels) const; - - private: - SdfGenerator m_generator; - Texture m_atlasTex; - AtlasMetadata m_meta; - Config m_config; - std::map m_symbols; - }; -} -#endif diff --git a/openVulkanoCpp/Scene/TextDrawable.cpp b/openVulkanoCpp/Scene/TextDrawable.cpp index cdad027..39f2a71 100644 --- a/openVulkanoCpp/Scene/TextDrawable.cpp +++ b/openVulkanoCpp/Scene/TextDrawable.cpp @@ -9,7 +9,7 @@ #include "Scene/Material.hpp" #include "Scene/Vertex.hpp" #include "Scene/UniformBuffer.hpp" -#include "Scene/FontAtlasGenerator.hpp" +#include "Scene/IFontAtlasGenerator.hpp" #include "Base/Logger.hpp" #include "Host/ResourceLoader.hpp" #include "Image/ImageLoader.hpp" @@ -20,22 +20,40 @@ namespace OpenVulkano::Scene { - Shader& TextDrawable::GetDefaultShader() + Shader& TextDrawable::GetSdfDefaultShader() { static bool once = true; - static Shader textDefaultShader; + static Shader sdfDefaultShader; if (once) { - textDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/text"); - textDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/text"); - textDefaultShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); - textDefaultShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING); - textDefaultShader.AddDescriptorSetLayoutBinding(UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING); - textDefaultShader.alphaBlend = true; - textDefaultShader.cullMode = CullMode::NONE; + sdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/text"); + sdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/text"); + sdfDefaultShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); + sdfDefaultShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING); + sdfDefaultShader.AddDescriptorSetLayoutBinding(UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING); + sdfDefaultShader.alphaBlend = true; + sdfDefaultShader.cullMode = CullMode::NONE; once = false; } - return textDefaultShader; + return sdfDefaultShader; + } + + Shader& TextDrawable::GetMsdfDefaultShader() + { + static bool once = true; + static Shader msdfDefaultShader; + if (once) + { + msdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/text"); + msdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/msdfText"); + msdfDefaultShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); + msdfDefaultShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING); + msdfDefaultShader.AddDescriptorSetLayoutBinding(UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING); + msdfDefaultShader.alphaBlend = true; + msdfDefaultShader.cullMode = CullMode::NONE; + once = false; + } + return msdfDefaultShader; } TextDrawable::TextDrawable(const Array& atlasMetadata, const TextConfig& config) @@ -63,12 +81,13 @@ namespace OpenVulkano::Scene uint64_t offsetToMetadata = atlasMetadata.Size() - metadataBytes - sizeof(uint32_t) - sizeof(uint64_t); if (isPacked) { - m_material.texture = new Texture(); + m_texture = Texture(); + m_material.texture = &m_texture.value(); m_img = Image::IImageLoader::loadData((const uint8_t*) atlasMetadata.Data(), offsetToMetadata); m_material.texture->format = m_img->dataFormat; m_material.texture->resolution = m_img->resolution; - m_material.texture->size = m_img->data.Size(); // 1 channel + m_material.texture->size = m_img->data.Size(); m_material.texture->textureBuffer = m_img->data.Data(); } else @@ -110,7 +129,7 @@ namespace OpenVulkano::Scene m_uniBuffer.Init(sizeof(TextConfig), &m_cfg, 3); } - TextDrawable::TextDrawable(FontAtlasGenerator* fontAtlasGenerator, const TextConfig& config) + TextDrawable::TextDrawable(IFontAtlasGenerator* fontAtlasGenerator, const TextConfig& config) { if (!fontAtlasGenerator) { throw std::runtime_error("FontAtlasGenerator is nullptr"); } if (fontAtlasGenerator->GetGlyphsInfo().empty()) { throw std::runtime_error("Glyphs are not loaded"); } @@ -237,7 +256,7 @@ namespace OpenVulkano::Scene SimpleDrawable::Init(m_shader, &m_geometry, &m_material, &m_uniBuffer); } - void TextDrawable::SetFontAtlasGenerator(FontAtlasGenerator* fontAtlasGenerator) + void TextDrawable::SetFontAtlasGenerator(IFontAtlasGenerator* fontAtlasGenerator) { if (!fontAtlasGenerator || fontAtlasGenerator->GetGlyphsInfo().empty()) { diff --git a/openVulkanoCpp/Scene/TextDrawable.hpp b/openVulkanoCpp/Scene/TextDrawable.hpp index 8f26bea..1cc99b4 100644 --- a/openVulkanoCpp/Scene/TextDrawable.hpp +++ b/openVulkanoCpp/Scene/TextDrawable.hpp @@ -14,11 +14,12 @@ #include "AtlasMetadata.hpp" #include "Image/Image.hpp" #include +#include namespace OpenVulkano::Scene { - class FontAtlasGenerator; + class IFontAtlasGenerator; struct TextConfig { @@ -35,20 +36,21 @@ namespace OpenVulkano::Scene class TextDrawable : public SimpleDrawable { public: - static Shader& GetDefaultShader(); + static Shader& GetSdfDefaultShader(); + static Shader& GetMsdfDefaultShader(); TextDrawable(const Array& atlasMetadata, const TextConfig& config = TextConfig()); TextDrawable(const std::string& atlasMetadataFile, const TextConfig& config = TextConfig()); TextDrawable(const std::string& atlasMetadataFile, Texture* atlasTex, const TextConfig& config = TextConfig()); TextDrawable(const Array& atlasMetadata, Texture* atlasTex, const TextConfig& config = TextConfig()); TextDrawable(const std::map& glyphData, Texture* atlasTex, const TextConfig& config = TextConfig()); - TextDrawable(FontAtlasGenerator* fontAtlasGenerator, const TextConfig& config = TextConfig()); + TextDrawable(IFontAtlasGenerator* fontAtlasGenerator, const TextConfig& config = TextConfig()); void GenerateText(const std::string& text, const Math::Vector3f& pos = Math::Vector3f(0.f)); void SetConfig(const TextConfig& cfg) { m_cfg = cfg; } void SetShader(Shader* shader) { m_shader = shader; } TextConfig& GetConfig() { return m_cfg; } Shader* GetShader() { return m_shader; } - void SetFontAtlasGenerator(FontAtlasGenerator* fontAtlasGenerator); - FontAtlasGenerator* GetFontAtlasGenerator() { return m_fontAtlasGenerator; } + void SetFontAtlasGenerator(IFontAtlasGenerator* fontAtlasGenerator); + IFontAtlasGenerator* GetFontAtlasGenerator() { return m_fontAtlasGenerator; } private: Geometry m_geometry; Material m_material; @@ -56,8 +58,9 @@ namespace OpenVulkano::Scene std::map m_glyphs; AtlasMetadata m_meta; std::unique_ptr m_img; - FontAtlasGenerator* m_fontAtlasGenerator = nullptr; - Shader* m_shader = &GetDefaultShader(); + std::optional m_texture; + IFontAtlasGenerator* m_fontAtlasGenerator = nullptr; + Shader* m_shader = nullptr; TextConfig m_cfg; }; } diff --git a/openVulkanoCpp/Shader/msdfText.frag b/openVulkanoCpp/Shader/msdfText.frag new file mode 100644 index 0000000..a6c3c78 --- /dev/null +++ b/openVulkanoCpp/Shader/msdfText.frag @@ -0,0 +1,47 @@ +#version 450 + +layout(location = 0) 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; + +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + +// this parameter should be same as FontAtlasGeneratorConfig::pixelRange +const float pxRange = 3; + +float screenPxRange() { + vec2 unitRange = vec2(pxRange)/vec2(textureSize(texSampler, 0)); + vec2 screenTexSize = vec2(1.0)/fwidth(texCoord); + return max(0.5*dot(unitRange, screenTexSize), 1.0); +} + +void main() +{ + vec3 msd = texture(texSampler, texCoord).rgb; + float sd = median(msd.r, msd.g, msd.b); + float screenPxDistance = screenPxRange()*(sd - 0.5); + float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0); + if (textConfig.backgroundColor.a != 0) + { + outColor = mix(textConfig.backgroundColor, textConfig.textColor, opacity); + } + else + { + outColor = vec4(vec3(textConfig.textColor), opacity); + } +}