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) 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/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/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; 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/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); diff --git a/openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp b/openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp index b1218af..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,98 +56,34 @@ 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), 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)); - 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 double bearingX = bearing.x; - const double bearingY = bearing.y; - const double w = size.x; - const double h = size.y; + 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; const double b = aabb.min.y; - info.xyz[0].x = bearingX; - info.xyz[0].y = h - bearingY; - info.uv[0].x = l / m_atlasData->texture.resolution.x; - info.uv[0].y = b / m_atlasData->texture.resolution.y; + info.pos[0].x = bearing.x; + info.pos[0].y = size.y - bearing.y; + info.uv[0].x = l / resolution.x; + info.uv[0].y = b / resolution.y; - info.xyz[1].x = bearingX + w; - info.xyz[1].y = h - bearingY; - info.uv[1].x = r / m_atlasData->texture.resolution.x; - info.uv[1].y = b / m_atlasData->texture.resolution.y; + info.pos[1].x = bearing.x + size.x; + info.pos[1].y = size.y - bearing.y; + info.uv[1].x = r / resolution.x; + info.uv[1].y = b / resolution.y; - info.xyz[2].x = bearingX + w; - info.xyz[2].y = bearingY; //h - bearingY + h; - info.uv[2].x = r / m_atlasData->texture.resolution.x; - info.uv[2].y = t / m_atlasData->texture.resolution.y; + info.pos[2].x = bearing.x + size.x; + info.pos[2].y = bearing.y; + info.uv[2].x = r / resolution.x; + info.uv[2].y = t / resolution.y; - info.xyz[3].x = bearingX; - info.xyz[3].y = bearingY; - info.uv[3].x = l / m_atlasData->texture.resolution.x; - info.uv[3].y = t / m_atlasData->texture.resolution.y; + info.pos[3].x = bearing.x; + info.pos[3].y = bearing.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..0ec4eba --- /dev/null +++ b/openVulkanoCpp/Scene/Text/FontAtlas.cpp @@ -0,0 +1,184 @@ +/* + * 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 "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 +#include + +namespace OpenVulkano::Scene +{ + namespace + { + struct FontAtlasFileFlags + { + uint8_t packed = 1; + 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) + { + 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; }; + + std::fstream fs(path, std::ios_base::out | std::ios_base::binary); + + // Save image + { + 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); + } + + // 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() < 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 (eofHeader.flags.packed == 0) throw std::runtime_error("No longer support loading of unpacked font atlas!"); + LoadImage(data); + + 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; + } + + 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) + { + 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) + { + 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..9249eb7 --- /dev/null +++ b/openVulkanoCpp/Scene/Text/FontAtlas.hpp @@ -0,0 +1,65 @@ +/* + * 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); + + 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); } + 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/AtlasData.hpp b/openVulkanoCpp/Scene/Text/FontAtlasType.hpp similarity index 58% rename from openVulkanoCpp/Scene/AtlasData.hpp rename to openVulkanoCpp/Scene/Text/FontAtlasType.hpp index be784ec..ba6e31d 100644 --- a/openVulkanoCpp/Scene/AtlasData.hpp +++ b/openVulkanoCpp/Scene/Text/FontAtlasType.hpp @@ -6,25 +6,10 @@ #pragma once -#include "Base/Wrapper.hpp" -#include "Math/Math.hpp" -#include "Image/Image.hpp" -#include "Scene/Texture.hpp" -#include -#include #include namespace OpenVulkano::Scene { - struct GlyphInfo - { - //GlyphGeometry geometry; - //GlyphBox glyphBox; - Math::Vector3f_SIMD xyz[4] = {}; - Math::Vector2f_SIMD uv[4] = {}; - double advance = 0; - }; - class FontAtlasType { public: @@ -38,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; } @@ -49,27 +36,14 @@ 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; }; - - 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/TextDrawable.cpp b/openVulkanoCpp/Scene/TextDrawable.cpp index 88acca2..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,81 +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]; - std::memcpy(&info, atlasMetadata.Data() + read_bytes, sizeof(GlyphInfo)); - read_bytes += sizeof(GlyphInfo); - readMetadataBytes += sizeof(GlyphInfo); - } - 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) @@ -144,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;) @@ -162,20 +108,20 @@ 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++) { - 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; } @@ -193,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);