From f58064d724b469189e8ed5f29483a0ea6c5cb75d Mon Sep 17 00:00:00 2001 From: Georg Hagen Date: Fri, 10 Jan 2025 18:09:38 +0100 Subject: [PATCH 01/11] Shrink GlyphInfo struct --- openVulkanoCpp/Scene/AtlasData.hpp | 2 +- .../Scene/FontAtlasGeneratorBase.cpp | 26 +++++++++---------- openVulkanoCpp/Scene/TextDrawable.cpp | 19 +++++++++----- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/openVulkanoCpp/Scene/AtlasData.hpp b/openVulkanoCpp/Scene/AtlasData.hpp index be784ec..2fc74ef 100644 --- a/openVulkanoCpp/Scene/AtlasData.hpp +++ b/openVulkanoCpp/Scene/AtlasData.hpp @@ -20,7 +20,7 @@ namespace OpenVulkano::Scene { //GlyphGeometry geometry; //GlyphBox glyphBox; - Math::Vector3f_SIMD xyz[4] = {}; + Math::Vector2f_SIMD pos[4] = {}; Math::Vector2f_SIMD uv[4] = {}; double advance = 0; }; diff --git a/openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp b/openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp index b1218af..3db5aba 100644 --- a/openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp +++ b/openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp @@ -82,7 +82,11 @@ namespace OpenVulkano::Scene for (const auto& [key, val] : m_atlasData->glyphs) { fs.write(reinterpret_cast(&key), sizeof(uint32_t)); - fs.write(reinterpret_cast(&val), sizeof(GlyphInfo)); + fs.write(reinterpret_cast(&val.pos), sizeof(GlyphInfo::pos)); + fs.write(reinterpret_cast(&val.pos), sizeof(GlyphInfo::pos)); // TODO remove this after cleaning up the atlas writing code + fs.write(reinterpret_cast(&val.uv), sizeof(GlyphInfo::uv)); + fs.write(reinterpret_cast(&val.advance), sizeof(GlyphInfo::advance)); + fs.write(reinterpret_cast(&val.advance), sizeof(GlyphInfo::advance)); // TODO remove this afer cleaning up the atlas writing code metadataBytes += sizeof(uint32_t); metadataBytes += sizeof(GlyphInfo); } @@ -125,32 +129,28 @@ namespace OpenVulkano::Scene void FontAtlasGeneratorBase::SetGlyphData(GlyphInfo& info, Math::Vector2d bearing, Math::Vector2d size, const Math::AABB& aabb, double advance) { - const double bearingX = bearing.x; - const double bearingY = bearing.y; - const double w = size.x; - const double h = size.y; const double l = aabb.min.x; const double r = aabb.max.x; const double t = aabb.max.y; const double b = aabb.min.y; - info.xyz[0].x = bearingX; - info.xyz[0].y = h - bearingY; + info.pos[0].x = bearing.x; + info.pos[0].y = size.y - bearing.y; 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.pos[1].x = bearing.x + size.x; + info.pos[1].y = size.y - bearing.y; 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.pos[2].x = bearing.x + size.x; + info.pos[2].y = bearing.y; 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.pos[3].x = bearing.x; + info.pos[3].y = bearing.y; info.uv[3].x = l / m_atlasData->texture.resolution.x; info.uv[3].y = t / m_atlasData->texture.resolution.y; diff --git a/openVulkanoCpp/Scene/TextDrawable.cpp b/openVulkanoCpp/Scene/TextDrawable.cpp index 88acca2..b17b60c 100644 --- a/openVulkanoCpp/Scene/TextDrawable.cpp +++ b/openVulkanoCpp/Scene/TextDrawable.cpp @@ -107,9 +107,16 @@ namespace OpenVulkano::Scene read_bytes += sizeof(uint32_t); readMetadataBytes += sizeof(uint32_t); GlyphInfo& info = m_atlasData->glyphs[unicode]; - std::memcpy(&info, atlasMetadata.Data() + read_bytes, sizeof(GlyphInfo)); - read_bytes += sizeof(GlyphInfo); - readMetadataBytes += sizeof(GlyphInfo); + for (int i = 0; i < 4; i++) + { + std::memcpy(&info.pos[i], atlasMetadata.Data() + read_bytes, sizeof(GlyphInfo::pos) / 4); + read_bytes += sizeof(GlyphInfo::pos) / 2; + } + std::memcpy(&info.uv, atlasMetadata.Data() + read_bytes, sizeof(GlyphInfo::uv)); + read_bytes += sizeof(GlyphInfo::uv); + std::memcpy(&info.advance, atlasMetadata.Data() + read_bytes, sizeof(GlyphInfo::advance)); + read_bytes += sizeof(GlyphInfo::advance) * 2; + readMetadataBytes += sizeof(GlyphInfo) + sizeof(GlyphInfo::pos) + sizeof(double); } if (m_atlasData->meta.atlasType == FontAtlasType::BITMAP) { @@ -172,10 +179,10 @@ namespace OpenVulkano::Scene for (int i = 0; i < 4; i++) { - vertices->position[i].x = info.xyz[i].x + cursorX; + vertices->position[i].x = info.pos[i].x + cursorX; vertices->uv[i] = info.uv[i]; - if (i < 2) vertices->position[i].y = posY - info.xyz[i].y; - else vertices->position[i].y = posY + info.xyz[i].y; + if (i < 2) vertices->position[i].y = posY - info.pos[i].y; + else vertices->position[i].y = posY + info.pos[i].y; vertices->color = m_cfg.textColor; vertices->background = m_cfg.backgroundColor; } From c2152e6b3ce513432b3d19cec3df8338ce5fb626 Mon Sep 17 00:00:00 2001 From: Georg Hagen Date: Fri, 10 Jan 2025 18:31:34 +0100 Subject: [PATCH 02/11] Move FontAtlasType into its own file --- openVulkanoCpp/Scene/AtlasData.hpp | 31 +-------------- openVulkanoCpp/Scene/Text/FontAtlasType.hpp | 42 +++++++++++++++++++++ 2 files changed, 43 insertions(+), 30 deletions(-) create mode 100644 openVulkanoCpp/Scene/Text/FontAtlasType.hpp diff --git a/openVulkanoCpp/Scene/AtlasData.hpp b/openVulkanoCpp/Scene/AtlasData.hpp index 2fc74ef..1c75272 100644 --- a/openVulkanoCpp/Scene/AtlasData.hpp +++ b/openVulkanoCpp/Scene/AtlasData.hpp @@ -10,6 +10,7 @@ #include "Math/Math.hpp" #include "Image/Image.hpp" #include "Scene/Texture.hpp" +#include "Scene/Text/FontAtlasType.hpp" #include #include #include @@ -25,36 +26,6 @@ namespace OpenVulkano::Scene double advance = 0; }; - class FontAtlasType - { - public: - enum Type : int16_t - { - SDF = 0, - MSDF, - BITMAP, - UNKNOWN - }; - - static constexpr std::string_view DEFAULT_FG_SHADERS[] = { "Shader/sdfText", "Shader/msdfText", "Shader/text" }; - - constexpr FontAtlasType(Type type) : m_type(type) {} - - [[nodiscard]] constexpr Type GetType() const { return m_type; } - - [[nodiscard]] constexpr auto GetName() const { return magic_enum::enum_name(m_type); } - - [[nodiscard]] constexpr const std::string_view& GetDefaultFragmentShader() const - { - return DEFAULT_FG_SHADERS[static_cast(m_type)]; - } - - [[nodiscard]] constexpr operator Type() const { return m_type; } - - private: - Type m_type; - }; - struct AtlasMetadata { // vertical difference between baselines diff --git a/openVulkanoCpp/Scene/Text/FontAtlasType.hpp b/openVulkanoCpp/Scene/Text/FontAtlasType.hpp new file mode 100644 index 0000000..c7614c9 --- /dev/null +++ b/openVulkanoCpp/Scene/Text/FontAtlasType.hpp @@ -0,0 +1,42 @@ +/* + * 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 + +namespace OpenVulkano::Scene +{ + class FontAtlasType + { + public: + enum Type : int16_t + { + SDF = 0, + MSDF, + BITMAP, + UNKNOWN + }; + + static constexpr std::string_view DEFAULT_FG_SHADERS[] = { "Shader/sdfText", "Shader/msdfText", "Shader/text" }; + + constexpr FontAtlasType(Type type) : m_type(type) {} + + [[nodiscard]] constexpr Type GetType() const { return m_type; } + + [[nodiscard]] constexpr auto GetName() const { return magic_enum::enum_name(m_type); } + + [[nodiscard]] constexpr const std::string_view& GetDefaultFragmentShader() const + { + return DEFAULT_FG_SHADERS[static_cast(m_type)]; + } + + [[nodiscard]] constexpr operator Type() const { return m_type; } + + private: + Type m_type; + }; +} \ No newline at end of file From 0eda1964e3cbe369c6234b0ae0ab57ad47e9f4d1 Mon Sep 17 00:00:00 2001 From: Georg Hagen Date: Sat, 11 Jan 2025 01:20:26 +0100 Subject: [PATCH 03/11] Extend Array to make it convertable to std::span --- openVulkanoCpp/Data/Containers/Array.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openVulkanoCpp/Data/Containers/Array.hpp b/openVulkanoCpp/Data/Containers/Array.hpp index 25ffc0c..8de41fd 100644 --- a/openVulkanoCpp/Data/Containers/Array.hpp +++ b/openVulkanoCpp/Data/Containers/Array.hpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace OpenVulkano { @@ -171,6 +172,11 @@ namespace OpenVulkano return data; } + operator std::span() const noexcept + { + return { data, size }; + } + iterator Begin() noexcept { return data; From 6a3c31346f5662740fb00dbe6b1e041ca60bd81b Mon Sep 17 00:00:00 2001 From: Georg Hagen Date: Sat, 11 Jan 2025 01:21:08 +0100 Subject: [PATCH 04/11] Cleanup DataFormat --- openVulkanoCpp/Scene/DataFormat.cpp | 6 +++--- openVulkanoCpp/Scene/DataFormat.hpp | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/openVulkanoCpp/Scene/DataFormat.cpp b/openVulkanoCpp/Scene/DataFormat.cpp index 2dcceb8..723a69c 100644 --- a/openVulkanoCpp/Scene/DataFormat.cpp +++ b/openVulkanoCpp/Scene/DataFormat.cpp @@ -225,7 +225,7 @@ namespace OpenVulkano return { UNDEFINED }; } - uint32_t DataFormat::GetBytesPerPixel() + uint32_t DataFormat::GetBytesPerPixel() const { auto size = FORMAT_SIZE_MAP.find(m_format); if (size == FORMAT_SIZE_MAP.end() || size->second == 0) [[unlikely]] @@ -236,7 +236,7 @@ namespace OpenVulkano return size->second; } - bool DataFormat::IsBlockCompressed() + bool DataFormat::IsBlockCompressed() const { for (auto format: magic_enum::enum_values()) { @@ -249,7 +249,7 @@ namespace OpenVulkano return false; } - size_t DataFormat::CalculatedSize(uint32_t& width, uint32_t& height) + size_t DataFormat::CalculatedSize(uint32_t& width, uint32_t& height) const { if (IsBlockCompressed()) { diff --git a/openVulkanoCpp/Scene/DataFormat.hpp b/openVulkanoCpp/Scene/DataFormat.hpp index e39a72a..b7fefff 100644 --- a/openVulkanoCpp/Scene/DataFormat.hpp +++ b/openVulkanoCpp/Scene/DataFormat.hpp @@ -336,7 +336,7 @@ namespace OpenVulkano constexpr DataFormat() : DataFormat(UNDEFINED) {} - constexpr DataFormat(Format format) : m_format(format) {} + constexpr DataFormat(const Format format) : m_format(format) {} [[nodiscard]] std::string_view GetName() const; @@ -351,12 +351,12 @@ namespace OpenVulkano (m_format >= PVRTC1_2BPP_SRGB_BLOCK_IMG && m_format <= PVRTC2_4BPP_SRGB_BLOCK_IMG); } - [[nodiscard]] constexpr bool operator ==(Format rhs) + [[nodiscard]] constexpr bool operator ==(Format rhs) const { return m_format == rhs; } - [[nodiscard]] constexpr bool operator !=(Format rhs) + [[nodiscard]] constexpr bool operator !=(Format rhs) const { return m_format != rhs; } @@ -377,11 +377,11 @@ namespace OpenVulkano #pragma clang diagnostic pop } - uint32_t GetBytesPerPixel(); + uint32_t GetBytesPerPixel() const; - bool IsBlockCompressed(); + bool IsBlockCompressed() const; - size_t CalculatedSize(uint32_t& width, uint32_t& height); + size_t CalculatedSize(uint32_t& width, uint32_t& height) const; static DataFormat GetFromName(std::string_view name); From 9cb3d4de85cbe6b5ee4ddd6348784dbfa0227e31 Mon Sep 17 00:00:00 2001 From: Georg Hagen Date: Sat, 11 Jan 2025 01:25:52 +0100 Subject: [PATCH 05/11] Refactor FontAtlas class --- examples/ExampleApps/TextExampleApp.cpp | 8 +- openVulkanoCpp/Scene/AtlasData.hpp | 46 ------- .../Scene/BitmapFontAtlasGenerator.cpp | 14 +-- .../Scene/FontAtlasGeneratorBase.cpp | 89 ++------------ .../Scene/FontAtlasGeneratorBase.hpp | 10 +- openVulkanoCpp/Scene/IFontAtlasGenerator.hpp | 5 +- .../Scene/Prefabs/LabelDrawable.cpp | 14 +-- .../Scene/Prefabs/LabelDrawable.hpp | 4 +- .../Scene/SdfFontAtlasGenerator.cpp | 15 ++- openVulkanoCpp/Scene/Text/FontAtlas.cpp | 115 ++++++++++++++++++ openVulkanoCpp/Scene/Text/FontAtlas.hpp | 62 ++++++++++ openVulkanoCpp/Scene/Text/FontAtlasType.hpp | 7 ++ openVulkanoCpp/Scene/TextDrawable.cpp | 87 ++----------- openVulkanoCpp/Scene/TextDrawable.hpp | 17 ++- 14 files changed, 246 insertions(+), 247 deletions(-) delete mode 100644 openVulkanoCpp/Scene/AtlasData.hpp create mode 100644 openVulkanoCpp/Scene/Text/FontAtlas.cpp create mode 100644 openVulkanoCpp/Scene/Text/FontAtlas.hpp diff --git a/examples/ExampleApps/TextExampleApp.cpp b/examples/ExampleApps/TextExampleApp.cpp index f5b11ee..871b57c 100644 --- a/examples/ExampleApps/TextExampleApp.cpp +++ b/examples/ExampleApps/TextExampleApp.cpp @@ -7,10 +7,10 @@ #include "TextExampleApp.hpp" #include "Scene/Scene.hpp" #include "Scene/Shader/Shader.hpp" -#include "Scene/Geometry.hpp" #include "Scene/TextDrawable.hpp" #include "Scene/Vertex.hpp" #include "Scene/UI/PerformanceInfo.hpp" +#include "Scene/Text/FontAtlas.hpp" #include "Input/InputManager.hpp" #include "Host/GraphicsAppManager.hpp" #include "Host/GLFW/WindowGLFW.hpp" @@ -68,15 +68,15 @@ namespace OpenVulkano std::set s = BitmapFontAtlasGenerator::LoadAllGlyphs(fontPath); BitmapFontAtlasGenerator generator; generator.GenerateAtlas(fontPath, s); - generator.SaveAtlasMetadataInfo("bitmap_atlas"); + generator.GetAtlas()->Save("bitmap_atlas_packed.png"); } #if defined(MSDFGEN_AVAILABLE) && CREATE_NEW_ATLAS std::set s = SdfFontAtlasGenerator::LoadAllGlyphs(fontPath); m_atlasGenerator.GenerateAtlas(fontPath, s); m_msdfAtlasGenerator.GenerateAtlas(fontPath, s); - m_atlasGenerator.SaveAtlasMetadataInfo("sdf_atlas.png"); - m_msdfAtlasGenerator.SaveAtlasMetadataInfo("msdf_atlas"); + m_atlasGenerator.GetAtlas()->Save("sdf_atlas_packed.png"); + m_msdfAtlasGenerator.GetAtlas()->Save("msdf_atlas_packed.png"); #else auto sdfMetadataInfo = resourceLoader.GetResource("sdf_atlas_packed.png"); auto msdfMetadataInfo = resourceLoader.GetResource("msdf_atlas_packed.png"); diff --git a/openVulkanoCpp/Scene/AtlasData.hpp b/openVulkanoCpp/Scene/AtlasData.hpp deleted file mode 100644 index 1c75272..0000000 --- a/openVulkanoCpp/Scene/AtlasData.hpp +++ /dev/null @@ -1,46 +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 - -#include "Base/Wrapper.hpp" -#include "Math/Math.hpp" -#include "Image/Image.hpp" -#include "Scene/Texture.hpp" -#include "Scene/Text/FontAtlasType.hpp" -#include -#include -#include - -namespace OpenVulkano::Scene -{ - struct GlyphInfo - { - //GlyphGeometry geometry; - //GlyphBox glyphBox; - Math::Vector2f_SIMD pos[4] = {}; - Math::Vector2f_SIMD uv[4] = {}; - double advance = 0; - }; - - struct AtlasMetadata - { - // vertical difference between baselines - double lineHeight = 0; - FontAtlasType atlasType = FontAtlasType::UNKNOWN; - }; - - struct AtlasData - { - std::map glyphs; - AtlasMetadata meta; - Unique img; - Texture texture; - - operator bool() const { return !glyphs.empty() && texture.textureBuffer; } - }; - -} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.cpp b/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.cpp index 119bd7b..b75cf4e 100644 --- a/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.cpp +++ b/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.cpp @@ -6,6 +6,7 @@ #include "BitmapFontAtlasGenerator.hpp" #include "Base/Logger.hpp" +#include "Text/FontAtlas.hpp" namespace OpenVulkano::Scene { @@ -31,7 +32,6 @@ namespace OpenVulkano::Scene return; } - m_atlasData = std::make_shared(); const auto& [lib, face] = FontAtlasGeneratorBase::InitFreetype(source); FT_Set_Pixel_Sizes(face.get(), 0, m_pixelSizeConfig.CalculatePixelSize()); @@ -47,12 +47,9 @@ namespace OpenVulkano::Scene // but since some algorithms have already been implemented for EM_NORMALIZED mode, currently there is no support for default font metrics (ints) // The coordinates will be normalized to the em size, i.e. 1 = 1 em const double scaleFactor = (1. / face->units_per_EM); - SetupAtlasData(atlasResolution, face->height * scaleFactor, FontAtlasType::BITMAP); + m_atlasData = std::make_shared(atlasResolution, face->height * scaleFactor, FontAtlasType::BITMAP); FillGlyphsInfo(allGlyphs, face, scaleFactor); - if (pngOutput) - { - SavePng(*pngOutput); - } + if (pngOutput) m_atlasData->Save(*pngOutput); } std::pair, double> @@ -94,14 +91,15 @@ namespace OpenVulkano::Scene } FT_GlyphSlot slot = face->glyph; + char* baseAddress = static_cast(m_atlasData->GetTexture()->textureBuffer) + glyph.firstGlyphByteInAtlas; for (int row = 0; row < slot->bitmap.rows; row++) { - std::memcpy(&m_atlasData->img->data[glyph.firstGlyphByteInAtlas + row * m_atlasData->img->resolution.x], + std::memcpy(baseAddress + row * m_atlasData->GetTexture()->resolution.x, &slot->bitmap.buffer[(slot->bitmap.rows - 1 - row) * slot->bitmap.pitch], slot->bitmap.width); } - GlyphInfo& glyphInfo = m_atlasData->glyphs[glyph.code]; + GlyphInfo& glyphInfo = m_atlasData->GetGlyphs()[glyph.code]; const Math::Vector2d glyphMetrics = { slot->metrics.width * scaleFactor, slot->metrics.height * scaleFactor }; const Math::Vector2d glyphBearing = { slot->metrics.horiBearingX * scaleFactor, diff --git a/openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp b/openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp index 3db5aba..7bddba4 100644 --- a/openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp +++ b/openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp @@ -5,14 +5,9 @@ */ #include "FontAtlasGeneratorBase.hpp" +#include "Text/FontAtlas.hpp" #include "Base/Logger.hpp" -#include "Extensions/STBZlibCompressor.hpp" #include -#include -#define STBI_MSC_SECURE_CRT -#define STB_IMAGE_WRITE_IMPLEMENTATION -#define STBIW_ZLIB_COMPRESS Extensions::STBZlibCompressor -#include namespace OpenVulkano::Scene { @@ -61,74 +56,10 @@ namespace OpenVulkano::Scene return fmt::format("Error code is {}", error); } - 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.pos), sizeof(GlyphInfo::pos)); - fs.write(reinterpret_cast(&val.pos), sizeof(GlyphInfo::pos)); // TODO remove this after cleaning up the atlas writing code - fs.write(reinterpret_cast(&val.uv), sizeof(GlyphInfo::uv)); - fs.write(reinterpret_cast(&val.advance), sizeof(GlyphInfo::advance)); - fs.write(reinterpret_cast(&val.advance), sizeof(GlyphInfo::advance)); // TODO remove this afer cleaning up the atlas writing code - metadataBytes += sizeof(uint32_t); - metadataBytes += sizeof(GlyphInfo); - } - fs.write(reinterpret_cast(&metadataBytes), sizeof(uint64_t)); - fs.write(reinterpret_cast(&packedFlag), sizeof(uint32_t)); - fs.close(); - } - - void FontAtlasGeneratorBase::SavePng(std::string output) const - { - stbi_flip_vertically_on_write(1); - if (std::filesystem::path(output).extension() != ".png") - { - output += ".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); - } - - void FontAtlasGeneratorBase::SetupAtlasData(Math::Vector2ui textureResolution, double lineHeight, - FontAtlasType::Type atlasType) - { - // generate texture - m_atlasData->img = std::make_unique(); - m_atlasData->img->resolution = Math::Vector3ui(textureResolution, 1); - m_atlasData->img->dataFormat = m_channelsCount == 1 ? DataFormat::R8_UNORM : DataFormat::R8G8B8A8_UNORM; - m_atlasData->img->data = Array(m_atlasData->img->dataFormat.CalculatedSize(textureResolution.x, textureResolution.y)); - 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->meta.atlasType = atlasType; - m_atlasData->meta.lineHeight = lineHeight; - if (atlasType == FontAtlasType::BITMAP) - { - m_atlasData->texture.m_samplerConfig = &SamplerConfig::NEAREST; - } - } - void FontAtlasGeneratorBase::SetGlyphData(GlyphInfo& info, Math::Vector2d bearing, Math::Vector2d size, const Math::AABB& aabb, double advance) { + const auto& resolution = m_atlasData->GetTexture()->resolution; const double l = aabb.min.x; const double r = aabb.max.x; const double t = aabb.max.y; @@ -136,23 +67,23 @@ namespace OpenVulkano::Scene info.pos[0].x = bearing.x; info.pos[0].y = size.y - bearing.y; - info.uv[0].x = l / m_atlasData->texture.resolution.x; - info.uv[0].y = b / m_atlasData->texture.resolution.y; + info.uv[0].x = l / resolution.x; + info.uv[0].y = b / resolution.y; info.pos[1].x = bearing.x + size.x; info.pos[1].y = size.y - bearing.y; - info.uv[1].x = r / m_atlasData->texture.resolution.x; - info.uv[1].y = b / m_atlasData->texture.resolution.y; + info.uv[1].x = r / resolution.x; + info.uv[1].y = b / resolution.y; info.pos[2].x = bearing.x + size.x; info.pos[2].y = bearing.y; - info.uv[2].x = r / m_atlasData->texture.resolution.x; - info.uv[2].y = t / m_atlasData->texture.resolution.y; + info.uv[2].x = r / resolution.x; + info.uv[2].y = t / resolution.y; info.pos[3].x = bearing.x; info.pos[3].y = bearing.y; - info.uv[3].x = l / m_atlasData->texture.resolution.x; - info.uv[3].y = t / m_atlasData->texture.resolution.y; + info.uv[3].x = l / resolution.x; + info.uv[3].y = t / resolution.y; info.advance = advance; } diff --git a/openVulkanoCpp/Scene/FontAtlasGeneratorBase.hpp b/openVulkanoCpp/Scene/FontAtlasGeneratorBase.hpp index 0c4ff78..090ca0e 100644 --- a/openVulkanoCpp/Scene/FontAtlasGeneratorBase.hpp +++ b/openVulkanoCpp/Scene/FontAtlasGeneratorBase.hpp @@ -7,29 +7,27 @@ #pragma once #include "IFontAtlasGenerator.hpp" -#include "AtlasData.hpp" #include "Math/AABB.hpp" #include "Extensions/FreetypeHelper.hpp" #include namespace OpenVulkano::Scene { + class GlyphInfo; + class FontAtlasGeneratorBase : public IFontAtlasGenerator { protected: int m_channelsCount; - std::shared_ptr m_atlasData; + std::shared_ptr m_atlasData; public: FontAtlasGeneratorBase(const int channelsCount) : m_channelsCount(channelsCount) {} - void SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile = true) const override; - [[nodiscard]] const std::shared_ptr& GetAtlasData() const override { return m_atlasData; } + [[nodiscard]] const std::shared_ptr& GetAtlas() const final { return m_atlasData; } [[nodiscard]] int GetAtlasChannelsCount() const { return m_channelsCount; } [[nodiscard]] static std::set LoadAllGlyphs(const std::variant>& data); protected: - void SavePng(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); [[nodiscard]] static std::string GetFreetypeErrorDescription(FT_Error error); [[nodiscard]] static std::pair InitFreetype(const std::variant>& source); diff --git a/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp b/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp index 979f723..930c6f1 100644 --- a/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp +++ b/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp @@ -14,7 +14,7 @@ namespace OpenVulkano::Scene { - struct AtlasData; + struct FontAtlas; class IFontAtlasGenerator { @@ -24,7 +24,6 @@ namespace OpenVulkano::Scene const std::optional& pngOutput = std::nullopt) = 0; 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 const std::shared_ptr& GetAtlasData() const = 0; + virtual const std::shared_ptr& GetAtlas() const = 0; }; } diff --git a/openVulkanoCpp/Scene/Prefabs/LabelDrawable.cpp b/openVulkanoCpp/Scene/Prefabs/LabelDrawable.cpp index 16040bd..794ea73 100644 --- a/openVulkanoCpp/Scene/Prefabs/LabelDrawable.cpp +++ b/openVulkanoCpp/Scene/Prefabs/LabelDrawable.cpp @@ -10,9 +10,10 @@ #include "Scene/Vertex.hpp" #include "Scene/Shader/Shader.hpp" #include "Scene/IFontAtlasGenerator.hpp" -#include "Base/Logger.hpp" +#include "Scene/Text/FontAtlasType.hpp" #include + namespace OpenVulkano::Scene { namespace @@ -68,14 +69,11 @@ namespace OpenVulkano::Scene } } - LabelDrawable::LabelDrawable(const std::shared_ptr& atlasData, const LabelDrawableSettings& settings) + LabelDrawable::LabelDrawable(const std::shared_ptr& atlasData, const LabelDrawableSettings& settings) : Drawable(DrawEncoder::GetDrawEncoder(), DrawPhase::MAIN), m_atlasData(atlasData) , m_labelBuffer(sizeof(LabelUniformData), &m_labelData, 4) { - if (atlasData->glyphs.empty() || !atlasData->texture.size) - { - throw std::runtime_error("Can't create label drawable. Either glyphs or texture is empty"); - } + if (!atlasData || !*atlasData) throw std::runtime_error("Can't create label drawable. Either glyphs or texture is empty"); SetLabelSettings(settings); SetShader(IsBillboard() ? &BACKGROUND_BILLBOARD_SHADER : &BACKGROUND_SHADER); } @@ -97,9 +95,9 @@ namespace OpenVulkano::Scene TextDrawable& textDrawable = m_texts.emplace_back(m_atlasData, config); textDrawable.GetConfig().backgroundColor.a = 0; // do not render glyph's background - double lineHeight = m_atlasData->meta.lineHeight; + double lineHeight = m_atlasData->GetLineHeight(); textDrawable.GenerateText(text, m_position); - textDrawable.SetShader(GetTextShader(m_atlasData->meta.atlasType, IsBillboard())); + textDrawable.SetShader(GetTextShader(m_atlasData->GetAtlasType(), IsBillboard())); m_bbox.Grow(textDrawable.GetBoundingBox()); // update position for next text entry m_position.y = m_bbox.GetMin().y - lineHeight; diff --git a/openVulkanoCpp/Scene/Prefabs/LabelDrawable.hpp b/openVulkanoCpp/Scene/Prefabs/LabelDrawable.hpp index 03eff25..6cf4c45 100644 --- a/openVulkanoCpp/Scene/Prefabs/LabelDrawable.hpp +++ b/openVulkanoCpp/Scene/Prefabs/LabelDrawable.hpp @@ -44,7 +44,7 @@ namespace OpenVulkano::Scene class LabelDrawable final : public Drawable { public: - LabelDrawable(const std::shared_ptr& atlasData, const LabelDrawableSettings& settings = LabelDrawableSettings()); + LabelDrawable(const std::shared_ptr& atlasData, const LabelDrawableSettings& settings = LabelDrawableSettings()); void AddText(const std::string& text, const TextConfig& config = TextConfig()); void SetLabelSettings(const LabelDrawableSettings& settings); @@ -61,7 +61,7 @@ namespace OpenVulkano::Scene private: LabelDrawableSettings m_settings; - std::shared_ptr m_atlasData; + std::shared_ptr m_atlasData; UniformBuffer m_labelBuffer; LabelUniformData m_labelData; std::list m_texts; // Using list instead of vector for stable iterators diff --git a/openVulkanoCpp/Scene/SdfFontAtlasGenerator.cpp b/openVulkanoCpp/Scene/SdfFontAtlasGenerator.cpp index 4ca0e97..d72a1a5 100644 --- a/openVulkanoCpp/Scene/SdfFontAtlasGenerator.cpp +++ b/openVulkanoCpp/Scene/SdfFontAtlasGenerator.cpp @@ -5,7 +5,7 @@ */ #if __has_include("msdfgen.h") - +#include "Text/FontAtlas.hpp" #include "SdfFontAtlasGenerator.hpp" #include "Base/Logger.hpp" #include @@ -106,7 +106,6 @@ namespace OpenVulkano::Scene void SdfFontAtlasGeneratorGeneric::Generate(FreetypeHandle* ft, FontHandle* font, const Charset& chset, const std::optional& pngOutput) { - m_atlasData.reset(new AtlasData); std::vector glyphsGeometry; // FontGeometry is a helper class that loads a set of glyphs from a single font. @@ -142,14 +141,14 @@ 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); + m_atlasData = std::make_shared(Math::Vector2ui{ width, height }, fontGeometry.getMetrics().lineHeight, + channelsCount == 1 ? FontAtlasType::SDF : FontAtlasType::MSDF); if constexpr (Channels == 3) { // store RGB as RGBA const BitmapConstRef storage = generator.atlasStorage(); - msdfgen::byte* data = static_cast(m_atlasData->img->data.Data()); + msdfgen::byte* data = static_cast(m_atlasData->GetTexture()->textureBuffer); for (size_t srcPos = 0, dstPos = 0; srcPos < width * height * 3; srcPos += 3, dstPos += 4) { data[dstPos] = storage.pixels[srcPos]; @@ -161,7 +160,7 @@ namespace OpenVulkano::Scene else { const msdfgen::BitmapConstRef& storage = generator.atlasStorage(); - memcpy(m_atlasData->img->data.Data(), storage.pixels, width * height); + memcpy(m_atlasData->GetTexture()->textureBuffer, storage.pixels, width * height); } struct Bbox @@ -171,7 +170,7 @@ namespace OpenVulkano::Scene for (const auto& glyph: glyphsGeometry) { - GlyphInfo& info = m_atlasData->glyphs[glyph.getCodepoint()]; + GlyphInfo& info = m_atlasData->GetGlyphs()[glyph.getCodepoint()]; const GlyphBox& glyphBox = generator.getLayout()[idx++]; Bbox glyphBaselineBbox, glyphAtlasBbox; @@ -195,7 +194,7 @@ namespace OpenVulkano::Scene SetGlyphData(info, { bearingX, bearingY }, { w, h }, glyphAtlasAABB, glyphBox.advance); } - if (pngOutput && !pngOutput->empty()) { SavePng(pngOutput.value()); } + if (pngOutput && !pngOutput->empty()) { m_atlasData->Save(*pngOutput); } destroyFont(font); deinitializeFreetype(ft); } diff --git a/openVulkanoCpp/Scene/Text/FontAtlas.cpp b/openVulkanoCpp/Scene/Text/FontAtlas.cpp new file mode 100644 index 0000000..6e3e2d5 --- /dev/null +++ b/openVulkanoCpp/Scene/Text/FontAtlas.cpp @@ -0,0 +1,115 @@ +/* + * 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 "FontAtlas.hpp" +#include "Base/Logger.hpp" +#include "Image/ImageLoader.hpp" +#include "Extensions/STBZlibCompressor.hpp" +#include +#define STBI_MSC_SECURE_CRT +#define STB_IMAGE_WRITE_IMPLEMENTATION +#define STBIW_ZLIB_COMPRESS Extensions::STBZlibCompressor +#include + +namespace OpenVulkano::Scene +{ + FontAtlas::FontAtlas(const std::filesystem::path& path) + { + auto content = Utils::ReadFile(path); + Load(content); + } + + void FontAtlas::Save(const std::filesystem::path& path) const + { + if (!*this) { Logger::DATA->warn("Can't save empty font atlas!"); return; }; + + stbi_flip_vertically_on_write(1); + stbi_write_png(path.c_str(), m_texture.resolution.x, m_texture.resolution.y, m_texture.format.GetBytesPerPixel(), + m_texture.textureBuffer, m_texture.format.GetBytesPerPixel() * m_texture.resolution.x); + + std::fstream fs(path, std::ios_base::out | std::ios_base::binary | std::ios_base::app); + fs.write(reinterpret_cast(&m_metadata), sizeof(Metadata)); + uint64_t metadataBytes = sizeof(Metadata); + for (const auto& [key, val] : m_glyphs) + { + fs.write(reinterpret_cast(&key), sizeof(uint32_t)); + fs.write(reinterpret_cast(&val), sizeof(GlyphInfo)); + metadataBytes += sizeof(uint32_t) + sizeof(GlyphInfo); + } + fs.write(reinterpret_cast(&metadataBytes), sizeof(uint64_t)); + std::array flags = { 1, 1, 0, 0 }; + fs.write(reinterpret_cast(&flags), sizeof(std::array)); + fs.close(); + } + + void FontAtlas::Load(const std::span data) + { + if (data.size() < 16) { Logger::DATA->warn("Font atlas file is invalid!"); return; }; + uint8_t flags[4] = { 0, 0, 0, 0 }; + std::memcpy(&flags, data.data() + data.size() - sizeof(flags), sizeof(flags)); + size_t headerSize = sizeof(flags) + sizeof(uint64_t); + uint64_t metadataSize = *reinterpret_cast(data.data() + data.size() - headerSize); + char* metadata = data.data() + data.size() - headerSize - metadataSize; + + if (flags[0] == 0) throw std::runtime_error("No longer support loading of unpacked font atlas!"); + LoadImage({ data.data(), data.size() - headerSize - metadataSize }); + + std::span metadataSpan(metadata, metadataSize); + if (flags[1] == 0) LoadLegacy(metadataSpan); + else LoadNew(metadataSpan); + if (GetAtlasType() >= FontAtlasType::BITMAP) m_texture.m_samplerConfig = &SamplerConfig::NEAREST; + } + + void FontAtlas::LoadLegacy(const std::span data) + { + const char* d = data.data(); + const char* end = data.data() + data.size(); + uint32_t unicode = 0; + + std::memcpy(&m_metadata, d, sizeof(Metadata)); + d += sizeof(Metadata); + while (d < end) + { + std::memcpy(&unicode, d, sizeof(uint32_t)); + d += sizeof(uint32_t); + GlyphInfo& info = m_glyphs[unicode]; + for (int i = 0; i < 4; i++) + { + std::memcpy(&info.pos[i], d, sizeof(GlyphInfo::pos) / 4); + d += sizeof(GlyphInfo::pos) / 2; + } + std::memcpy(&info.uv, d, sizeof(GlyphInfo::uv)); + d += sizeof(GlyphInfo::uv); + std::memcpy(&info.advance, d, sizeof(GlyphInfo::advance)); + d += sizeof(GlyphInfo::advance) * 2; + } + } + + void FontAtlas::LoadNew(const std::span data) + { + } + + void FontAtlas::LoadImage(const std::span data) + { + auto img = Image::IImageLoader::loadData(reinterpret_cast(data.data()), data.size()); + m_imgData = std::move(img->data); + m_texture.format = img->dataFormat; + m_texture.resolution = img->resolution; + m_texture.size = m_imgData.Size(); + m_texture.textureBuffer = m_imgData.Data(); + } + + void FontAtlas::Init(const Math::Vector2ui textureResolution, const double lineHeight, const FontAtlasType atlasType) + { + m_metadata = { lineHeight, atlasType }; + m_texture.format = atlasType.GetChannelCount() == 1 ? DataFormat::R8_UNORM : DataFormat::R8G8B8A8_UNORM; + m_texture.resolution = { textureResolution, 1 }; + m_imgData = Array(m_texture.format.CalculatedSize(m_texture.resolution.x, m_texture.resolution.y)); + m_texture.textureBuffer = m_imgData.Data(); + m_texture.size = m_imgData.Size(); + if (atlasType >= FontAtlasType::BITMAP) m_texture.m_samplerConfig = &SamplerConfig::NEAREST; + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/Text/FontAtlas.hpp b/openVulkanoCpp/Scene/Text/FontAtlas.hpp new file mode 100644 index 0000000..1cd1020 --- /dev/null +++ b/openVulkanoCpp/Scene/Text/FontAtlas.hpp @@ -0,0 +1,62 @@ +/* + * 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 "FontAtlasType.hpp" +#include "Math/Math.hpp" +#include "Data/Containers/Array.hpp" +#include "Scene/Texture.hpp" +#include +#include + +namespace OpenVulkano::Scene +{ + struct GlyphInfo + { + //GlyphGeometry geometry; + //GlyphBox glyphBox; + Math::Vector2f_SIMD pos[4] = {}; + Math::Vector2f_SIMD uv[4] = {}; + double advance = 0; + }; + + class FontAtlas + { + struct Metadata + { + double lineHeight = 0; + FontAtlasType atlasType = FontAtlasType::UNKNOWN; + }; + + std::map m_glyphs; + Metadata m_metadata; + Array m_imgData; + Texture m_texture; + + void LoadLegacy(std::span data); + void LoadNew(std::span data); + void LoadImage(std::span data); + + public: + FontAtlas() = default; + FontAtlas(const Math::Vector2ui textureResolution, const double lineHeight, const FontAtlasType atlasType) { Init(textureResolution, lineHeight, atlasType); } + FontAtlas(const std::filesystem::path& path); + FontAtlas(const std::span data) { Load(data); } + + void Init(Math::Vector2ui textureResolution, double lineHeight, FontAtlasType atlasType); + + void Save(const std::filesystem::path& path) const; + void Load(std::span data); + + [[nodiscard]] operator bool() const { return !m_glyphs.empty() && m_texture.textureBuffer; } + + [[nodiscard]] Texture* GetTexture() { return &m_texture; } + [[nodiscard]] decltype(m_glyphs)& GetGlyphs() { return m_glyphs; } + [[nodiscard]] decltype(Metadata::lineHeight) GetLineHeight() const { return m_metadata.lineHeight;} + [[nodiscard]] FontAtlasType GetAtlasType() const { return m_metadata.atlasType;} + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/Text/FontAtlasType.hpp b/openVulkanoCpp/Scene/Text/FontAtlasType.hpp index c7614c9..ba6e31d 100644 --- a/openVulkanoCpp/Scene/Text/FontAtlasType.hpp +++ b/openVulkanoCpp/Scene/Text/FontAtlasType.hpp @@ -23,6 +23,8 @@ namespace OpenVulkano::Scene static constexpr std::string_view DEFAULT_FG_SHADERS[] = { "Shader/sdfText", "Shader/msdfText", "Shader/text" }; + static constexpr uint32_t CHANNEL_COUNT[] = { 1, 4, 1, 4, 0 }; + constexpr FontAtlasType(Type type) : m_type(type) {} [[nodiscard]] constexpr Type GetType() const { return m_type; } @@ -34,8 +36,13 @@ namespace OpenVulkano::Scene return DEFAULT_FG_SHADERS[static_cast(m_type)]; } + [[nodiscard]] constexpr uint32_t GetChannelCount() const { return CHANNEL_COUNT[static_cast(m_type)]; } + [[nodiscard]] constexpr operator Type() const { return m_type; } + [[nodiscard]] constexpr auto operator<=>(const FontAtlasType rhs) const { return m_type <=> rhs.m_type; } + [[nodiscard]] constexpr auto operator<=>(const Type rhs) const { return m_type <=> rhs; } + private: Type m_type; }; diff --git a/openVulkanoCpp/Scene/TextDrawable.cpp b/openVulkanoCpp/Scene/TextDrawable.cpp index b17b60c..113f3e0 100644 --- a/openVulkanoCpp/Scene/TextDrawable.cpp +++ b/openVulkanoCpp/Scene/TextDrawable.cpp @@ -9,10 +9,8 @@ #include "Shader/Shader.hpp" #include "Scene/IFontAtlasGenerator.hpp" #include "Base/Logger.hpp" -#include "Host/ResourceLoader.hpp" #include "Image/ImageLoader.hpp" #include "DataFormat.hpp" -#include "Base/Logger.hpp" #include #include @@ -56,88 +54,30 @@ namespace OpenVulkano::Scene : Drawable(DrawEncoder::GetDrawEncoder()), m_cfg(config) {} - TextDrawable::TextDrawable(const Array& atlasMetadata, const TextConfig& config) - : TextDrawable(atlasMetadata, nullptr, config) - {} - TextDrawable::TextDrawable(const std::string& atlasMetadataFile, const TextConfig& config) - : TextDrawable(Utils::ReadFile(atlasMetadataFile), nullptr, config) + : TextDrawable(Utils::ReadFile(atlasMetadataFile), config) {} - TextDrawable::TextDrawable(const std::string& atlasMetadataFile, Texture* atlasTex, const TextConfig& config) - : TextDrawable(Utils::ReadFile(atlasMetadataFile), atlasTex, config) - {} - - TextDrawable::TextDrawable(const Array& atlasMetadata, Texture* atlasTex, const TextConfig& config) + TextDrawable::TextDrawable(const Array& atlasMetadata, const TextConfig& config) : Drawable(DrawEncoder::GetDrawEncoder()), m_cfg(config) { - uint32_t isPacked; - std::memcpy(&isPacked, atlasMetadata.Data() + (atlasMetadata.Size() - sizeof(uint32_t)), sizeof(uint32_t)); - uint64_t metadataBytes; - std::memcpy(&metadataBytes, atlasMetadata.Data() + (atlasMetadata.Size() - sizeof(uint32_t) - sizeof(uint64_t)), - sizeof(uint64_t)); - uint64_t offsetToMetadata = atlasMetadata.Size() - metadataBytes - sizeof(uint32_t) - sizeof(uint64_t); - m_atlasData = std::make_shared(); - if (isPacked) - { - Texture* texture = &m_atlasData->texture; - m_atlasData->img = Image::IImageLoader::loadData(reinterpret_cast(atlasMetadata.Data()), offsetToMetadata); - texture->format = m_atlasData->img->dataFormat; - texture->resolution = m_atlasData->img->resolution; - texture->size = m_atlasData->img->data.Size(); - texture->textureBuffer = m_atlasData->img->data.Data(); - } - else - { - if (atlasTex == nullptr) { throw std::runtime_error("Atlas texture cannot be null with non-packed atlas metadata"); } - m_atlasData->texture = *atlasTex; - } - - // metadata info - size_t read_bytes = offsetToMetadata; - size_t readMetadataBytes = 0; - uint32_t unicode = 0; - - std::memcpy(&m_atlasData->meta, atlasMetadata.Data() + read_bytes, sizeof(AtlasMetadata)); - read_bytes += sizeof(AtlasMetadata); - readMetadataBytes += sizeof(AtlasMetadata); - while (readMetadataBytes < metadataBytes) - { - std::memcpy(&unicode, atlasMetadata.Data() + read_bytes, sizeof(uint32_t)); - read_bytes += sizeof(uint32_t); - readMetadataBytes += sizeof(uint32_t); - GlyphInfo& info = m_atlasData->glyphs[unicode]; - for (int i = 0; i < 4; i++) - { - std::memcpy(&info.pos[i], atlasMetadata.Data() + read_bytes, sizeof(GlyphInfo::pos) / 4); - read_bytes += sizeof(GlyphInfo::pos) / 2; - } - std::memcpy(&info.uv, atlasMetadata.Data() + read_bytes, sizeof(GlyphInfo::uv)); - read_bytes += sizeof(GlyphInfo::uv); - std::memcpy(&info.advance, atlasMetadata.Data() + read_bytes, sizeof(GlyphInfo::advance)); - read_bytes += sizeof(GlyphInfo::advance) * 2; - readMetadataBytes += sizeof(GlyphInfo) + sizeof(GlyphInfo::pos) + sizeof(double); - } - if (m_atlasData->meta.atlasType == FontAtlasType::BITMAP) - { - m_atlasData->texture.m_samplerConfig = &SamplerConfig::NEAREST; - } + m_atlasData = std::make_shared(atlasMetadata); } - TextDrawable::TextDrawable(const std::shared_ptr& atlasData, const TextConfig& config) + TextDrawable::TextDrawable(const std::shared_ptr& atlasData, const TextConfig& config) : Drawable(DrawEncoder::GetDrawEncoder()), m_atlasData(atlasData), m_cfg(config) { if (!atlasData || !*atlasData) throw std::runtime_error("Cannot initialize text drawable with empty atlas data"); } uint32_t TextDrawable::GetFallbackGlyph() const - { - if (m_atlasData->glyphs.find(MISSING_GLYPH_SYMBOL) != m_atlasData->glyphs.end()) + { //TODO move into FontAtlas + if (m_atlasData->GetGlyphs().contains(MISSING_GLYPH_SYMBOL)) { return MISSING_GLYPH_SYMBOL; } Logger::RENDER->warn("Could not find glyph for character ? to use as fallback. Using first glyph instead"); - return m_atlasData->glyphs.begin()->first; + return m_atlasData->GetGlyphs().begin()->first; } void TextDrawable::GenerateText(const std::string& text, const Math::Vector2f& pos) @@ -151,11 +91,10 @@ namespace OpenVulkano::Scene const size_t len = utf8::distance(text.begin(), text.end()); m_vertexBuffer.Close(); TextGlyphVertex* vertices = m_vertexBuffer.Init(len); - std::map* symbols = &m_atlasData->glyphs; - AtlasMetadata* meta = &m_atlasData->meta; + const std::map& symbols = m_atlasData->GetGlyphs(); double cursorX = pos.x; - const double lineHeight = meta->lineHeight; + const double lineHeight = m_atlasData->GetLineHeight(); double posY = pos.y; Math::Vector3f bmin(pos, 0), bmax(pos, 0); for (auto begin = text.begin(), end = text.end(); begin != end;) @@ -169,13 +108,13 @@ namespace OpenVulkano::Scene } // TODO handle special chars - if (!symbols->contains(c)) + if (!symbols.contains(c)) { Logger::RENDER->warn("Could not find glyph for character {}, using fallback", c); c = fallbackGlyph; } - GlyphInfo& info = symbols->at(c); + const GlyphInfo& info = symbols.at(c); for (int i = 0; i < 4; i++) { @@ -200,10 +139,10 @@ namespace OpenVulkano::Scene bmin.x = vertices->position[0].x; m_bbox.Init(bmin, bmax); - if (!GetShader()) SetShader(GetDefaultShader(m_atlasData->meta.atlasType)); + if (!GetShader()) SetShader(GetDefaultShader(m_atlasData->GetAtlasType())); } - void TextDrawable::SetAtlasData(const std::shared_ptr& atlasData) + void TextDrawable::SetAtlasData(const std::shared_ptr& atlasData) { if (!atlasData || !*atlasData) throw std::runtime_error("Cannot initialize text drawable with empty atlas data"); m_atlasData = atlasData; diff --git a/openVulkanoCpp/Scene/TextDrawable.hpp b/openVulkanoCpp/Scene/TextDrawable.hpp index 2dac8d8..a57dc18 100644 --- a/openVulkanoCpp/Scene/TextDrawable.hpp +++ b/openVulkanoCpp/Scene/TextDrawable.hpp @@ -9,11 +9,12 @@ #include "Drawable.hpp" #include "Texture.hpp" #include "VertexBuffer.hpp" -#include "AtlasData.hpp" #include "Image/Image.hpp" +#include "Text/FontAtlas.hpp" namespace OpenVulkano::Scene { + class FontAtlas; class IFontAtlasGenerator; struct TextConfig @@ -33,7 +34,7 @@ namespace OpenVulkano::Scene class TextDrawable : public Drawable { VertexBuffer m_vertexBuffer; - std::shared_ptr m_atlasData; + std::shared_ptr m_atlasData; Math::AABB2f m_bbox; std::string m_text; size_t m_symbolCount = 0; @@ -43,23 +44,21 @@ namespace OpenVulkano::Scene public: TextDrawable(const TextConfig& config = TextConfig()); - 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::shared_ptr& atlasData, const TextConfig& config = TextConfig()); + TextDrawable(const Array& atlasMetadata, const TextConfig& config = TextConfig()); + TextDrawable(const std::shared_ptr& atlasData, const TextConfig& config = TextConfig()); void GenerateText(const std::string& text, const Math::Vector2f& pos = Math::Vector2f(0.f)); void SetConfig(const TextConfig& cfg) { m_cfg = cfg; } - void SetAtlasData(const std::shared_ptr& atlasData); + void SetAtlasData(const std::shared_ptr& atlasData); [[nodiscard]] Math::AABB2f& GetBoundingBox() { return m_bbox; } [[nodiscard]] TextConfig& GetConfig() { return m_cfg; } [[nodiscard]] const std::string& GetText() const { return m_text; } - [[nodiscard]] const std::shared_ptr& GetAtlasData() { return m_atlasData; } + [[nodiscard]] const std::shared_ptr& GetAtlasData() { return m_atlasData; } [[nodiscard]] VertexBuffer* GetVertexBuffer() { return &m_vertexBuffer; } - [[nodiscard]] Texture* GetTexture() { return &m_atlasData->texture; } + [[nodiscard]] Texture* GetTexture() { return m_atlasData->GetTexture(); } [[nodiscard]] size_t GetSymbolCount() const { return m_symbolCount; } [[nodiscard]] static Shader MakeDefaultShader(FontAtlasType type); From 2b936696560dd505543012a59aeccccde23612b3 Mon Sep 17 00:00:00 2001 From: Georg Hagen Date: Sat, 11 Jan 2025 11:55:12 +0100 Subject: [PATCH 06/11] Fix windows build issue --- openVulkanoCpp/Scene/Text/FontAtlas.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openVulkanoCpp/Scene/Text/FontAtlas.cpp b/openVulkanoCpp/Scene/Text/FontAtlas.cpp index 6e3e2d5..200f15d 100644 --- a/openVulkanoCpp/Scene/Text/FontAtlas.cpp +++ b/openVulkanoCpp/Scene/Text/FontAtlas.cpp @@ -27,7 +27,8 @@ namespace OpenVulkano::Scene if (!*this) { Logger::DATA->warn("Can't save empty font atlas!"); return; }; stbi_flip_vertically_on_write(1); - stbi_write_png(path.c_str(), m_texture.resolution.x, m_texture.resolution.y, m_texture.format.GetBytesPerPixel(), + std::string p = path.string(); + stbi_write_png(p.c_str(), m_texture.resolution.x, m_texture.resolution.y, m_texture.format.GetBytesPerPixel(), m_texture.textureBuffer, m_texture.format.GetBytesPerPixel() * m_texture.resolution.x); std::fstream fs(path, std::ios_base::out | std::ios_base::binary | std::ios_base::app); From 4c5e64ff1821429aece2e16016ee84db743436e2 Mon Sep 17 00:00:00 2001 From: Georg Hagen Date: Sat, 11 Jan 2025 12:01:01 +0100 Subject: [PATCH 07/11] Disable boost downloading --- 3rdParty/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdParty/CMakeLists.txt b/3rdParty/CMakeLists.txt index 1a8e201..0d36cfb 100644 --- a/3rdParty/CMakeLists.txt +++ b/3rdParty/CMakeLists.txt @@ -28,7 +28,7 @@ add_subdirectory(tracy) add_subdirectory(libstud-uuid) add_subdirectory(rapidyaml) add_subdirectory(libarchive) -add_subdirectory(boost) +#add_subdirectory(boost) add_subdirectory(units) add_subdirectory(libjpeg-turbo) add_subdirectory(msdf) From 98db9f79fed01dbb26efc29765ba3d1a87405e56 Mon Sep 17 00:00:00 2001 From: Georg Hagen Date: Sat, 11 Jan 2025 12:35:24 +0100 Subject: [PATCH 08/11] Add flags struct --- openVulkanoCpp/Scene/Text/FontAtlas.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/openVulkanoCpp/Scene/Text/FontAtlas.cpp b/openVulkanoCpp/Scene/Text/FontAtlas.cpp index 200f15d..5432244 100644 --- a/openVulkanoCpp/Scene/Text/FontAtlas.cpp +++ b/openVulkanoCpp/Scene/Text/FontAtlas.cpp @@ -16,6 +16,16 @@ namespace OpenVulkano::Scene { + namespace + { + struct FontAtlasFileFlags + { + uint8_t packed = 1; + uint8_t version = 1; + uint16_t reserved = 0; + }; + } + FontAtlas::FontAtlas(const std::filesystem::path& path) { auto content = Utils::ReadFile(path); @@ -41,25 +51,25 @@ namespace OpenVulkano::Scene metadataBytes += sizeof(uint32_t) + sizeof(GlyphInfo); } fs.write(reinterpret_cast(&metadataBytes), sizeof(uint64_t)); - std::array flags = { 1, 1, 0, 0 }; - fs.write(reinterpret_cast(&flags), sizeof(std::array)); + FontAtlasFileFlags flags; + fs.write(reinterpret_cast(&flags), sizeof(FontAtlasFileFlags)); fs.close(); } void FontAtlas::Load(const std::span data) { if (data.size() < 16) { Logger::DATA->warn("Font atlas file is invalid!"); return; }; - uint8_t flags[4] = { 0, 0, 0, 0 }; + FontAtlasFileFlags flags; std::memcpy(&flags, data.data() + data.size() - sizeof(flags), sizeof(flags)); - size_t headerSize = sizeof(flags) + sizeof(uint64_t); + size_t headerSize = sizeof(FontAtlasFileFlags) + sizeof(uint64_t); uint64_t metadataSize = *reinterpret_cast(data.data() + data.size() - headerSize); char* metadata = data.data() + data.size() - headerSize - metadataSize; - if (flags[0] == 0) throw std::runtime_error("No longer support loading of unpacked font atlas!"); + if (flags.packed == 0) throw std::runtime_error("No longer support loading of unpacked font atlas!"); LoadImage({ data.data(), data.size() - headerSize - metadataSize }); std::span metadataSpan(metadata, metadataSize); - if (flags[1] == 0) LoadLegacy(metadataSpan); + if (flags.version == 0) LoadLegacy(metadataSpan); else LoadNew(metadataSpan); if (GetAtlasType() >= FontAtlasType::BITMAP) m_texture.m_samplerConfig = &SamplerConfig::NEAREST; } From 9167bb82d04adb99c44dd2fbe07f0966e393f326 Mon Sep 17 00:00:00 2001 From: Georg Hagen Date: Sat, 11 Jan 2025 13:22:16 +0100 Subject: [PATCH 09/11] Update atlas metadata encoding, now with compression --- openVulkanoCpp/Base/Wrapper.hpp | 8 +++ openVulkanoCpp/Scene/Text/FontAtlas.cpp | 75 +++++++++++++++++++++---- openVulkanoCpp/Scene/Text/FontAtlas.hpp | 3 + 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/openVulkanoCpp/Base/Wrapper.hpp b/openVulkanoCpp/Base/Wrapper.hpp index b37939f..dff0be9 100644 --- a/openVulkanoCpp/Base/Wrapper.hpp +++ b/openVulkanoCpp/Base/Wrapper.hpp @@ -13,4 +13,12 @@ namespace OpenVulkano template using Ptr = std::shared_ptr; template using Weak = std::weak_ptr; template using Unique = std::unique_ptr; + + template + struct FreeDelete + { + void operator()(T* x) const { free(x); } + }; + + template using UniqueMalloc = std::unique_ptr>; } diff --git a/openVulkanoCpp/Scene/Text/FontAtlas.cpp b/openVulkanoCpp/Scene/Text/FontAtlas.cpp index 5432244..a946f29 100644 --- a/openVulkanoCpp/Scene/Text/FontAtlas.cpp +++ b/openVulkanoCpp/Scene/Text/FontAtlas.cpp @@ -6,9 +6,11 @@ #include "FontAtlas.hpp" #include "Base/Logger.hpp" +#include "Base/Wrapper.hpp" #include "Image/ImageLoader.hpp" #include "Extensions/STBZlibCompressor.hpp" #include +#include #define STBI_MSC_SECURE_CRT #define STB_IMAGE_WRITE_IMPLEMENTATION #define STBIW_ZLIB_COMPRESS Extensions::STBZlibCompressor @@ -36,26 +38,66 @@ namespace OpenVulkano::Scene { if (!*this) { Logger::DATA->warn("Can't save empty font atlas!"); return; }; - stbi_flip_vertically_on_write(1); - std::string p = path.string(); - stbi_write_png(p.c_str(), m_texture.resolution.x, m_texture.resolution.y, m_texture.format.GetBytesPerPixel(), - m_texture.textureBuffer, m_texture.format.GetBytesPerPixel() * m_texture.resolution.x); + std::fstream fs(path, std::ios_base::out | std::ios_base::binary); - std::fstream fs(path, std::ios_base::out | std::ios_base::binary | std::ios_base::app); - fs.write(reinterpret_cast(&m_metadata), sizeof(Metadata)); - uint64_t metadataBytes = sizeof(Metadata); - for (const auto& [key, val] : m_glyphs) + // Save image { - fs.write(reinterpret_cast(&key), sizeof(uint32_t)); - fs.write(reinterpret_cast(&val), sizeof(GlyphInfo)); - metadataBytes += sizeof(uint32_t) + sizeof(GlyphInfo); + stbi_flip_vertically_on_write(1); + int pngSize; + UniqueMalloc png(stbi_write_png_to_mem(static_cast(m_texture.textureBuffer), m_texture.format.GetBytesPerPixel() * m_texture.resolution.x, m_texture.resolution.x, m_texture.resolution.y, m_texture.format.GetBytesPerPixel(), &pngSize)); + fs.write(reinterpret_cast(png.get()), pngSize); } - fs.write(reinterpret_cast(&metadataBytes), sizeof(uint64_t)); + + // Save metadata + { + const Array metadata = SerializeMetadata(); + Array compressed(ZSTD_compressBound(metadata.Size())); + size_t compressedSize = ZSTD_compress(compressed.Data(), compressed.Size(), metadata.Data(), metadata.Size(), 5); + if (ZSTD_isError(compressedSize)) throw std::runtime_error(std::string("Compression failed: ") + ZSTD_getErrorName(compressedSize)); + fs.write(compressed.Data(), compressedSize); + fs.write(reinterpret_cast(&compressedSize), sizeof(uint64_t)); + } + FontAtlasFileFlags flags; fs.write(reinterpret_cast(&flags), sizeof(FontAtlasFileFlags)); fs.close(); } + Array FontAtlas::SerializeMetadata() const + { + Array data(sizeof(Metadata) + m_glyphs.size() * (sizeof(GlyphInfo) + sizeof(uint32_t))); + char* ptr = data.Data(); + memcpy(ptr, &m_metadata, sizeof(Metadata)); + ptr += sizeof(Metadata); + for (const auto& [key, val] : m_glyphs) + { + memcpy(ptr, &key, sizeof(uint32_t)); + ptr += sizeof(uint32_t); + memcpy(ptr, &val, sizeof(GlyphInfo)); + ptr += sizeof(GlyphInfo); + } + return data; + } + + void FontAtlas::DeserializeMetadata(const std::span& data) + { + const char* d = data.data(); + const char* end = data.data() + data.size(); + uint32_t unicode = 0; + + std::memcpy(&m_metadata, d, sizeof(Metadata)); + d += sizeof(Metadata); + while (d < end) + { + std::memcpy(&unicode, d, sizeof(uint32_t)); + d += sizeof(uint32_t); + GlyphInfo& info = m_glyphs[unicode]; + std::memcpy(&info, d, sizeof(GlyphInfo)); + d += sizeof(GlyphInfo); + } + } + + void FontAtlas::Load(const std::span data) { if (data.size() < 16) { Logger::DATA->warn("Font atlas file is invalid!"); return; }; @@ -101,6 +143,15 @@ namespace OpenVulkano::Scene void FontAtlas::LoadNew(const std::span data) { + size_t originalSize = ZSTD_getFrameContentSize(data.data(), data.size()); + if (originalSize == ZSTD_CONTENTSIZE_ERROR) throw std::runtime_error("Not compressed by zstd"); + if (originalSize == ZSTD_CONTENTSIZE_UNKNOWN) throw std::runtime_error("Original size unknown"); + + Array metadata(originalSize); + size_t decompressedBytes = ZSTD_decompress(metadata.Data(), metadata.Size(), data.data(), data.size()); + if (ZSTD_isError(decompressedBytes)) throw std::runtime_error(std::string("Decompression failed: ") + ZSTD_getErrorName(decompressedBytes)); + if (decompressedBytes != originalSize) throw std::runtime_error("Decompressed size does not match expected size"); + DeserializeMetadata(metadata); } void FontAtlas::LoadImage(const std::span data) diff --git a/openVulkanoCpp/Scene/Text/FontAtlas.hpp b/openVulkanoCpp/Scene/Text/FontAtlas.hpp index 1cd1020..9249eb7 100644 --- a/openVulkanoCpp/Scene/Text/FontAtlas.hpp +++ b/openVulkanoCpp/Scene/Text/FontAtlas.hpp @@ -41,6 +41,9 @@ namespace OpenVulkano::Scene void LoadNew(std::span data); void LoadImage(std::span data); + Array SerializeMetadata() const; + void DeserializeMetadata(const std::span& data); + public: FontAtlas() = default; FontAtlas(const Math::Vector2ui textureResolution, const double lineHeight, const FontAtlasType atlasType) { Init(textureResolution, lineHeight, atlasType); } From be5a37fd9f286568c448c48c40222a5e03472f3c Mon Sep 17 00:00:00 2001 From: Georg Hagen Date: Sat, 11 Jan 2025 13:27:49 +0100 Subject: [PATCH 10/11] Update min data size --- openVulkanoCpp/Scene/Text/FontAtlas.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openVulkanoCpp/Scene/Text/FontAtlas.cpp b/openVulkanoCpp/Scene/Text/FontAtlas.cpp index a946f29..19ead41 100644 --- a/openVulkanoCpp/Scene/Text/FontAtlas.cpp +++ b/openVulkanoCpp/Scene/Text/FontAtlas.cpp @@ -24,7 +24,7 @@ namespace OpenVulkano::Scene { uint8_t packed = 1; uint8_t version = 1; - uint16_t reserved = 0; + [[maybe_unused]] uint16_t reserved = 0; }; } @@ -100,7 +100,7 @@ namespace OpenVulkano::Scene void FontAtlas::Load(const std::span data) { - if (data.size() < 16) { Logger::DATA->warn("Font atlas file is invalid!"); return; }; + if (data.size() < 28) { Logger::DATA->warn("Font atlas file is invalid!"); return; }; FontAtlasFileFlags flags; std::memcpy(&flags, data.data() + data.size() - sizeof(flags), sizeof(flags)); size_t headerSize = sizeof(FontAtlasFileFlags) + sizeof(uint64_t); From 006968fb4c3eb4ef3f7c7d3256cb9ae588b8de47 Mon Sep 17 00:00:00 2001 From: Georg Hagen Date: Sat, 11 Jan 2025 13:40:34 +0100 Subject: [PATCH 11/11] Add helper struct --- openVulkanoCpp/Scene/Text/FontAtlas.cpp | 29 +++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/openVulkanoCpp/Scene/Text/FontAtlas.cpp b/openVulkanoCpp/Scene/Text/FontAtlas.cpp index 19ead41..0ec4eba 100644 --- a/openVulkanoCpp/Scene/Text/FontAtlas.cpp +++ b/openVulkanoCpp/Scene/Text/FontAtlas.cpp @@ -26,6 +26,16 @@ namespace OpenVulkano::Scene uint8_t version = 1; [[maybe_unused]] uint16_t reserved = 0; }; + + #pragma pack(push, 1) + struct EndOfFileHeader + { + uint64_t metadataSize; + FontAtlasFileFlags flags; + }; + #pragma pack(pop) + + static_assert(sizeof(EndOfFileHeader) == 12); } FontAtlas::FontAtlas(const std::filesystem::path& path) @@ -97,21 +107,18 @@ namespace OpenVulkano::Scene } } - void FontAtlas::Load(const std::span data) { - if (data.size() < 28) { Logger::DATA->warn("Font atlas file is invalid!"); return; }; - FontAtlasFileFlags flags; - std::memcpy(&flags, data.data() + data.size() - sizeof(flags), sizeof(flags)); - size_t headerSize = sizeof(FontAtlasFileFlags) + sizeof(uint64_t); - uint64_t metadataSize = *reinterpret_cast(data.data() + data.size() - headerSize); - char* metadata = data.data() + data.size() - headerSize - metadataSize; + if (data.size() < sizeof(EndOfFileHeader) + sizeof(Metadata)) { Logger::DATA->warn("Font atlas file is invalid!"); return; }; + EndOfFileHeader eofHeader; + std::memcpy(&eofHeader, data.data() + data.size() - sizeof(EndOfFileHeader), sizeof(EndOfFileHeader)); - if (flags.packed == 0) throw std::runtime_error("No longer support loading of unpacked font atlas!"); - LoadImage({ data.data(), data.size() - headerSize - metadataSize }); + if (eofHeader.flags.packed == 0) throw std::runtime_error("No longer support loading of unpacked font atlas!"); + LoadImage(data); - std::span metadataSpan(metadata, metadataSize); - if (flags.version == 0) LoadLegacy(metadataSpan); + char* metadata = data.data() + data.size() - sizeof(EndOfFileHeader) - eofHeader.metadataSize; + const std::span metadataSpan(metadata, eofHeader.metadataSize); + if (eofHeader.flags.version == 0) LoadLegacy(metadataSpan); else LoadNew(metadataSpan); if (GetAtlasType() >= FontAtlasType::BITMAP) m_texture.m_samplerConfig = &SamplerConfig::NEAREST; }