diff --git a/3rdParty/msdf/CMakeLists.txt b/3rdParty/msdf/CMakeLists.txt index 69bfce0..6de537b 100644 --- a/3rdParty/msdf/CMakeLists.txt +++ b/3rdParty/msdf/CMakeLists.txt @@ -1,96 +1,98 @@ -include(FetchContent) +if(WIN32 OR UNIX OR APPLE OR IOS) + include(FetchContent) -if(NOT DEFINED MSDFGEN_REPO) - set(MSDFGEN_REPO https://github.com/Chlumsky/msdfgen.git) -endif () + if(NOT DEFINED MSDFGEN_REPO) + set(MSDFGEN_REPO https://github.com/Chlumsky/msdfgen.git) + endif () -if(NOT DEFINED MSDFGEN_ATRLAS_REPO) - set(MSDFGEN_ATRLAS_REPO https://github.com/Chlumsky/msdf-atlas-gen.git) -endif() + if(NOT DEFINED MSDFGEN_ATRLAS_REPO) + set(MSDFGEN_ATRLAS_REPO https://github.com/Chlumsky/msdf-atlas-gen.git) + endif() -if(NOT DEFINED VCPKG_REPO) - set(VCPKG_REPO https://github.com/microsoft/vcpkg.git) -endif() + if(NOT DEFINED VCPKG_REPO) + set(VCPKG_REPO https://github.com/microsoft/vcpkg.git) + endif() + + set(VCPKG_SRC_DIR "${CMAKE_BINARY_DIR}/_deps/vcpkg-src" CACHE INTERNAL "vcpkg source dir") + if (NOT EXISTS ${VCPKG_SRC_DIR} OR VCPKG_EXECUTABLE STREQUAL "" OR NOT DEFINED VCPKG_EXECUTABLE) + message("Cloning vcpkg...") + FetchContent_Declare( + vcpkg + EXCLUDE_FROM_ALL + GIT_REPOSITORY ${VCPKG_REPO} + GIT_TAG master + GIT_SHALLOW TRUE + ) + FetchContent_MakeAvailable(vcpkg) + if (WIN32) + execute_process(COMMAND "${VCPKG_SRC_DIR}/bootstrap-vcpkg.bat") + set(VCPKG_EXECUTABLE "${VCPKG_SRC_DIR}/vcpkg.exe" CACHE INTERNAL "vcpkg executable") + else() + execute_process(COMMAND bash "${VCPKG_SRC_DIR}/bootstrap-vcpkg.sh") + set(VCPKG_EXECUTABLE "${VCPKG_SRC_DIR}/vcpkg" CACHE INTERNAL "vcpkg executable") + endif() + endif() + + if (WIN32) + set(TRIPLET x64-windows-static-md-release CACHE INTERNAL "triplet") + elseif(UNIX AND NOT APPLE) + set(TRIPLET x64-linux CACHE INTERNAL "triplet") + elseif(APPLE AND NOT IOS) + set(TRIPLET arm64-osx CACHE INTERNAL "triplet") + elseif(IOS) + set(TRIPLET arm64-ios CACHE INTERNAL "triplet") + endif() + + execute_process(COMMAND ${VCPKG_EXECUTABLE} install freetype:${TRIPLET} libpng:${TRIPLET}) + set(CMAKE_TOOLCHAIN_FILE "${CMAKE_BINARY_DIR}/_deps/vcpkg-src/scripts/buildsystems/vcpkg.cmake" CACHE STRING "cmake toolchain") + include("${VCPKG_SRC_DIR}/scripts/buildsystems/vcpkg.cmake") + list(APPEND CMAKE_PREFIX_PATH "${VCPKG_SRC_DIR}/installed/${TRIPLET}/lib") + list(APPEND CMAKE_PREFIX_PATH "${VCPKG_SRC_DIR}/installed/${TRIPLET}/include") + + set(MSDFGEN_DISABLE_SVG TRUE CACHE INTERNAL "disable msdfgen svg") + set(MSDFGEN_USE_SKIA OFF CACHE BOOL "use skia" FORCE) + set(MSDF_ATLAS_USE_SKIA OFF CACHE BOOL "use skia" FORCE) + set(MSDF_ATLAS_MSDFGEN_EXTERNAL ON CACHE BOOL "do not build msdfgen submodule" FORCE) + set(MSDFGEN_DYNAMIC_RUNTIME ON CACHE BOOL "msvc dynamic runtime" FORCE) + set(MSDF_ATLAS_DYNAMIC_RUNTIME ON CACHE BOOL "msvc dynamic runtime" FORCE) -set(VCPKG_SRC_DIR "${CMAKE_BINARY_DIR}/_deps/vcpkg-src" CACHE INTERNAL "vcpkg source dir") -if (NOT EXISTS ${VCPKG_SRC_DIR} OR VCPKG_EXECUTABLE STREQUAL "" OR NOT DEFINED VCPKG_EXECUTABLE) - message("Cloning vcpkg...") FetchContent_Declare( - vcpkg + msdfgen EXCLUDE_FROM_ALL - GIT_REPOSITORY ${VCPKG_REPO} + GIT_REPOSITORY ${MSDFGEN_REPO} + GIT_TAG v1.12 + GIT_SHALLOW TRUE + ) + FetchContent_MakeAvailable(msdfgen) + + FetchContent_Declare( + msdfgen_atlas + EXCLUDE_FROM_ALL + GIT_REPOSITORY ${MSDFGEN_ATRLAS_REPO} GIT_TAG master GIT_SHALLOW TRUE ) - FetchContent_MakeAvailable(vcpkg) - if (WIN32) - execute_process(COMMAND "${VCPKG_SRC_DIR}/bootstrap-vcpkg.bat") - set(VCPKG_EXECUTABLE "${VCPKG_SRC_DIR}/vcpkg.exe" CACHE INTERNAL "vcpkg executable") - else() - execute_process(COMMAND bash "${VCPKG_SRC_DIR}/bootstrap-vcpkg.sh") - set(VCPKG_EXECUTABLE "${VCPKG_SRC_DIR}/vcpkg" CACHE INTERNAL "vcpkg executable") - endif() + FetchContent_MakeAvailable(msdfgen_atlas) endif() -if (WIN32) - set(TRIPLET x64-windows-static-md-release CACHE INTERNAL "triplet") -elseif(UNIX AND NOT APPLE) - set(TRIPLET x64-linux CACHE INTERNAL "triplet") -elseif(APPLE AND NOT IOS) - set(TRIPLET arm64-osx CACHE INTERNAL "triplet") -elseif(IOS) - set(TRIPLET arm64-ios CACHE INTERNAL "triplet") -else() - message(FATAL_ERROR "Unknown OS, can't build msdfgen") -endif() - -execute_process(COMMAND ${VCPKG_EXECUTABLE} install freetype:${TRIPLET} libpng:${TRIPLET}) -set(CMAKE_TOOLCHAIN_FILE "${CMAKE_BINARY_DIR}/_deps/vcpkg-src/scripts/buildsystems/vcpkg.cmake" CACHE STRING "cmake toolchain") -include("${VCPKG_SRC_DIR}/scripts/buildsystems/vcpkg.cmake") -list(APPEND CMAKE_PREFIX_PATH "${VCPKG_SRC_DIR}/installed/${TRIPLET}/lib") -list(APPEND CMAKE_PREFIX_PATH "${VCPKG_SRC_DIR}/installed/${TRIPLET}/include") - -set(MSDFGEN_DISABLE_SVG TRUE CACHE INTERNAL "disable msdfgen svg") -set(MSDFGEN_USE_SKIA OFF CACHE BOOL "use skia" FORCE) -set(MSDF_ATLAS_USE_SKIA OFF CACHE BOOL "use skia" FORCE) -set(MSDF_ATLAS_MSDFGEN_EXTERNAL ON CACHE BOOL "do not build msdfgen submodule" FORCE) -set(MSDFGEN_DYNAMIC_RUNTIME ON CACHE BOOL "msvc dynamic runtime" FORCE) -set(MSDF_ATLAS_DYNAMIC_RUNTIME ON CACHE BOOL "msvc dynamic runtime" FORCE) - -FetchContent_Declare( - msdfgen - EXCLUDE_FROM_ALL - GIT_REPOSITORY ${MSDFGEN_REPO} - GIT_TAG v1.12 - GIT_SHALLOW TRUE -) -FetchContent_MakeAvailable(msdfgen) - -FetchContent_Declare( - msdfgen_atlas - EXCLUDE_FROM_ALL - GIT_REPOSITORY ${MSDFGEN_ATRLAS_REPO} - GIT_TAG master - GIT_SHALLOW TRUE -) -FetchContent_MakeAvailable(msdfgen_atlas) - function(LinkMsdf TARGET) - if(WIN32) - set(STATIC_LIB_EXT "lib") - set(freetype_lib_name "freetype") - else() - set(STATIC_LIB_EXT "a") - set(freetype_lib_name "libfreetype") - endif() - - # link freetype first to fix linkage issues on linux - target_link_libraries(${TARGET} PUBLIC "${VCPKG_SRC_DIR}/installed/${TRIPLET}/lib/${freetype_lib_name}.${STATIC_LIB_EXT}") - file(GLOB installed_libs "${VCPKG_SRC_DIR}/installed/${TRIPLET}/lib/*.${STATIC_LIB_EXT}") - foreach(lib ${installed_libs}) - get_filename_component(libname ${lib} NAME_WE) - if (NOT ${libname} STREQUAL ${freetype_lib_name}) - target_link_libraries(${TARGET} PUBLIC ${lib}) + if (WIN32 OR UNIX OR APPLE OR IOS) + target_link_libraries(${TARGET} PRIVATE msdfgen::msdfgen msdfgen::msdfgen-ext msdf-atlas-gen) + if(WIN32) + set(STATIC_LIB_EXT "lib") + set(freetype_lib_name "freetype") + else() + set(STATIC_LIB_EXT "a") + set(freetype_lib_name "libfreetype") endif() - endforeach() + # link freetype first to fix linkage issues on linux + target_link_libraries(${TARGET} PUBLIC "${VCPKG_SRC_DIR}/installed/${TRIPLET}/lib/${freetype_lib_name}.${STATIC_LIB_EXT}") + file(GLOB installed_libs "${VCPKG_SRC_DIR}/installed/${TRIPLET}/lib/*.${STATIC_LIB_EXT}") + foreach(lib ${installed_libs}) + get_filename_component(libname ${lib} NAME_WE) + if (NOT ${libname} STREQUAL ${freetype_lib_name}) + target_link_libraries(${TARGET} PUBLIC ${lib}) + endif() + endforeach() + endif() endfunction() diff --git a/CMakeLists.txt b/CMakeLists.txt index f94e957..a0ef361 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,7 @@ endif () SetupVulkan(openVulkanoCpp) CopyResourcesToExe(openVulkanoCpp "${CMAKE_CURRENT_SOURCE_DIR}/fonts" ".ttf") +CopyResourcesToExe(openVulkanoCpp "${CMAKE_CURRENT_SOURCE_DIR}/examples/ExampleSources" "*") SetShaderDependency(openVulkanoCpp ${CMAKE_CURRENT_SOURCE_DIR}/openVulkanoCpp/Shader ${SHADER_OUTPUT_DEST}) @@ -121,7 +122,7 @@ endif() list(APPEND CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR}/deps/INSTALL) target_link_libraries(openVulkanoCpp PRIVATE magic_enum yaml-cpp fmt spdlog glm pugixml stb eigen utf8cpp imgui_internal - TracyClient stud-uuid ryml unordered_dense Boost::regex units msdfgen::msdfgen msdfgen::msdfgen-ext msdf-atlas-gen) + TracyClient stud-uuid ryml unordered_dense Boost::regex units) if (NOT IOS) LinkCurl(openVulkanoCpp) endif() diff --git a/examples/ExampleApps/TextExampleApp.cpp b/examples/ExampleApps/TextExampleApp.cpp index bd64add..86a01b0 100644 --- a/examples/ExampleApps/TextExampleApp.cpp +++ b/examples/ExampleApps/TextExampleApp.cpp @@ -62,7 +62,6 @@ namespace OpenVulkano const int N = texts.size(); auto& resourceLoader = ResourceLoader::GetInstance(); const std::string fontPath = resourceLoader.GetResourcePath("Roboto-Regular.ttf"); - const std::string atlasPath = (fs::path(fontPath).parent_path() / "roboto-regular-atlas.png").string(); m_nodesPool.resize(N); m_drawablesPool.resize(N); @@ -72,11 +71,28 @@ namespace OpenVulkano // some unicode values charset.add(c); } + +#ifdef MSDFGEN_AVAILABLE m_atlasGenerator.GenerateAtlas(fontPath, charset); - +#endif + for (int i = 0; i < texts.size(); i++) { +#ifdef MSDFGEN_AVAILABLE TextDrawable* t = new TextDrawable(&m_atlasGenerator, texts[i].second); +#else + auto metadataInfoPath = resourceLoader.GetResourcePath("atlas_metadata"); + auto data = resourceLoader.GetResource("roboto-regular-atlas.png"); + auto metadataInfo = TextDrawable::ReadAtlasMetadataFile(metadataInfoPath); + Image::ImageLoaderPng loader; + static auto image = loader.loadData(reinterpret_cast(data.Data()), data.Size()); + static Texture tex; + tex.resolution = image->resolution; + tex.textureBuffer = image->data.Data(); + tex.format = image->dataFormat; + tex.size = image->data.Size(); // 1 channel + TextDrawable* t = new TextDrawable(metadataInfo, &tex, texts[i].second); +#endif // MSDFGEN_AVAILABLE t->GenerateText(texts[i].first); m_drawablesPool[i] = t; m_nodesPool[i].Init(); @@ -113,7 +129,9 @@ namespace OpenVulkano OpenVulkano::Scene::Scene m_scene; PerspectiveCamera m_cam; OpenVulkano::FreeCamCameraController m_camController; +#ifdef MSDFGEN_AVAILABLE FontAtlasGenerator m_atlasGenerator; +#endif std::vector m_drawablesPool; std::vector m_nodesPool; Vector3f_SIMD m_position = { 0, 0, -10 }; diff --git a/examples/ExampleSources/atlas_metadata b/examples/ExampleSources/atlas_metadata new file mode 100644 index 0000000..c130a42 Binary files /dev/null and b/examples/ExampleSources/atlas_metadata differ diff --git a/examples/ExampleSources/roboto-regular-atlas.png b/examples/ExampleSources/roboto-regular-atlas.png new file mode 100644 index 0000000..4faa297 Binary files /dev/null and b/examples/ExampleSources/roboto-regular-atlas.png differ diff --git a/openVulkanoCpp/Scene/AtlasMetadata.hpp b/openVulkanoCpp/Scene/AtlasMetadata.hpp new file mode 100644 index 0000000..9a94f5c --- /dev/null +++ b/openVulkanoCpp/Scene/AtlasMetadata.hpp @@ -0,0 +1,21 @@ +/* + * 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 "Math/Math.hpp" + +namespace OpenVulkano::Scene +{ + struct GlyphInfo + { + //GlyphGeometry geometry; + //GlyphBox glyphBox; + Math::Vector3f_SIMD xyz[4] = {}; + Math::Vector2f_SIMD uv[4] = {}; + double advance = 0; + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/FontAtlasGenerator.cpp b/openVulkanoCpp/Scene/FontAtlasGenerator.cpp index 7a8c4c2..b693367 100644 --- a/openVulkanoCpp/Scene/FontAtlasGenerator.cpp +++ b/openVulkanoCpp/Scene/FontAtlasGenerator.cpp @@ -6,6 +6,8 @@ #include "FontAtlasGenerator.hpp" #include "Base/Logger.hpp" +#include "Scene/AtlasMetadata.hpp" +#include namespace OpenVulkano::Scene { @@ -45,6 +47,21 @@ namespace OpenVulkano::Scene Generate(ft, font, charset, pngOutput); } + void FontAtlasGenerator::SaveAtlasMetadataInfo(const std::string& outputFile) const + { + if (m_symbols.empty()) + { + Logger::DATA->info("No glyphs loaded. Nothing to save."); + return; + } + std::fstream fs(outputFile.c_str(), std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + for (const auto& [key, val] : m_symbols) + { + fs.write(reinterpret_cast(&key), sizeof(uint32_t)); + fs.write(reinterpret_cast(&val), sizeof(GlyphInfo)); + } + } + void FontAtlasGenerator::Generate(FreetypeHandle* ft, FontHandle* font, const Charset& chset, const std::optional& pngOutput) { @@ -75,12 +92,57 @@ namespace OpenVulkano::Scene m_atlasTex.textureBuffer = (msdfgen::byte*) storage.pixels; m_atlasTex.format = OpenVulkano::DataFormat::R8_UNORM; m_atlasTex.size = storage.width * storage.height * 1; // 1 channel + + struct Bbox + { + double l = 0, r = 0, t = 0, b = 0; + }; + for (const auto& glyph: glyphsGeometry) { GlyphInfo& info = m_symbols[glyph.getCodepoint()]; - info.geometry = glyph; - info.glyphBox = m_generator.getLayout()[idx++]; + const GlyphBox& glyphBox = m_generator.getLayout()[idx++]; + + Bbox glyphBaselineBbox, glyphAtlasBbox; + glyph.getQuadPlaneBounds(glyphBaselineBbox.l, glyphBaselineBbox.b, glyphBaselineBbox.r, + glyphBaselineBbox.t); + glyph.getQuadAtlasBounds(glyphAtlasBbox.l, glyphAtlasBbox.b, glyphAtlasBbox.r, glyphAtlasBbox.t); + double bearingX = glyphBox.bounds.l; + double bearingY = glyphBox.bounds.t; + double w = glyphBaselineBbox.r - glyphBaselineBbox.l; + double h = glyphBaselineBbox.t - glyphBaselineBbox.b; + double l = glyphAtlasBbox.l; + double r = glyphAtlasBbox.r; + double t = glyphAtlasBbox.t; + double b = glyphAtlasBbox.b; + + info.xyz[0].x = bearingX; + info.xyz[0].y = h - bearingY; + info.xyz[0].z = 1; + info.uv[0].x = l / m_atlasTex.resolution.x; + info.uv[0].y = b / m_atlasTex.resolution.y; + + info.xyz[1].x = bearingX + w; + info.xyz[1].y = h - bearingY; + info.xyz[1].z = 1; + info.uv[1].x = r / m_atlasTex.resolution.x; + info.uv[1].y = b / m_atlasTex.resolution.y; + + info.xyz[2].x = bearingX + w; + info.xyz[2].y = bearingY; //h - bearingY + h; + info.xyz[2].z = 1; + info.uv[2].x = r / m_atlasTex.resolution.x; + info.uv[2].y = t / m_atlasTex.resolution.y; + + info.xyz[3].x = bearingX; + info.xyz[3].y = bearingY; + info.xyz[3].z = 1; + info.uv[3].x = l / m_atlasTex.resolution.x; + info.uv[3].y = t / m_atlasTex.resolution.y; + + info.advance = glyphBox.advance; } + if (pngOutput && !pngOutput->empty()) { savePng(m_generator.atlasStorage(), pngOutput->c_str()); diff --git a/openVulkanoCpp/Scene/FontAtlasGenerator.hpp b/openVulkanoCpp/Scene/FontAtlasGenerator.hpp index d7912e4..2cc0173 100644 --- a/openVulkanoCpp/Scene/FontAtlasGenerator.hpp +++ b/openVulkanoCpp/Scene/FontAtlasGenerator.hpp @@ -9,6 +9,7 @@ #include #include #include +#include "Scene/AtlasMetadata.hpp" #include "Scene/Texture.hpp" #include "msdfgen.h" #include "msdfgen-ext.h" @@ -20,12 +21,6 @@ namespace OpenVulkano::Scene using namespace msdf_atlas; using namespace OpenVulkano::Scene; - struct GlyphInfo - { - GlyphGeometry geometry; - GlyphBox glyphBox; - }; - class FontAtlasGenerator { public: @@ -33,14 +28,15 @@ namespace OpenVulkano::Scene const std::optional& pngOutput = std::nullopt); void GenerateAtlas(const msdfgen::byte* fontData, int length, const Charset& charset = Charset::ASCII, const std::optional& pngOutput = std::nullopt); + void SaveAtlasMetadataInfo(const std::string& outputFile) const; const Texture& GetAtlas() const { return m_atlasTex; } - std::map& GetAtlasInfo() { return m_symbols; } + std::map& GetAtlasInfo() { return m_symbols; } private: void Generate(FreetypeHandle* ft, FontHandle* font, const Charset& chset, const std::optional& pngOutput); private: ImmediateAtlasGenerator> m_generator; Texture m_atlasTex; - std::map m_symbols; + std::map m_symbols; }; -} \ No newline at end of file +} diff --git a/openVulkanoCpp/Scene/TextDrawable.cpp b/openVulkanoCpp/Scene/TextDrawable.cpp index c756485..89c653e 100644 --- a/openVulkanoCpp/Scene/TextDrawable.cpp +++ b/openVulkanoCpp/Scene/TextDrawable.cpp @@ -11,12 +11,16 @@ #include "Scene/UniformBuffer.hpp" #include "Scene/FontAtlasGenerator.hpp" #include "Base/Logger.hpp" +#include "Host/ResourceLoader.hpp" +#include #include namespace OpenVulkano::Scene { +#ifdef MSDFGEN_AVAILABLE using namespace msdfgen; using namespace msdf_atlas; +#endif Shader& TextDrawable::GetDefaultShader() { @@ -36,6 +40,42 @@ namespace OpenVulkano::Scene return textDefaultShader; } + std::map TextDrawable::ReadAtlasMetadataFile(const std::string& atlasMetadataFile) + { + OpenVulkano::Array data = OpenVulkano::Utils::ReadFile(atlasMetadataFile); + return ReadAtlasMetadata(data); + } + + std::map TextDrawable::ReadAtlasMetadata(const Array& atlasMetadata) + { + size_t len = atlasMetadata.Size(); + size_t read_bytes = 0; + uint32_t unicode = 0; + GlyphInfo info; + std::map glyphs; + while (read_bytes < len) + { + std::memcpy(&unicode, atlasMetadata.Data() + read_bytes, sizeof(uint32_t)); + read_bytes += sizeof(uint32_t); + std::memcpy(&info, atlasMetadata.Data() + read_bytes, sizeof(GlyphInfo)); + read_bytes += sizeof(GlyphInfo); + glyphs[unicode] = std::move(info); + } + return glyphs; + } + + TextDrawable::TextDrawable(const std::map& glyphData, Texture* atlasTex, + const TextConfig& config) + { + if (!atlasTex) { throw std::runtime_error("Atlas texture is nullptr"); } + if (glyphData.empty()) { throw std::runtime_error("Glyphs are not loaded"); } + m_material.texture = atlasTex; + m_glyphs = glyphData; + m_cfg = config; + m_uniBuffer.Init(sizeof(TextConfig), &m_cfg, 3); + } + +#ifdef MSDFGEN_AVAILABLE TextDrawable::TextDrawable(FontAtlasGenerator* fontAtlasGenerator, const TextConfig& config) { if (!fontAtlasGenerator) { throw std::runtime_error("FontAtlasGenerator is nullptr"); } @@ -45,80 +85,77 @@ namespace OpenVulkano::Scene m_material.texture = const_cast(&m_fontAtlasGenerator->GetAtlas()); m_uniBuffer.Init(sizeof(TextConfig), &m_cfg, 3); } +#endif - void TextDrawable::GenerateText(const std::string& text, const Math::Vector3f& pos) + void TextDrawable::GenerateText(const std::string& text, const Math::Vector3f& pos) { if (text.empty()) { return; } - // just in case if FontAtlasGenerator changed it's atlas - m_material.texture = const_cast(&m_fontAtlasGenerator->GetAtlas()); - std::map& symbols = m_fontAtlasGenerator->GetAtlasInfo(); + m_geometry.Close(); m_geometry.Init(text.size() * 4, text.size() * 6); - const Texture& atlasTex = *m_material.texture; - struct Bbox +#ifdef MSDFGEN_AVAILABLE + // TODO: better implementation to decide what to use: data from atlas generator or data read from file + // we have msdf but loaded glyphs metadata from file before + std::optional>> glyphData; + if (m_fontAtlasGenerator->GetAtlasInfo().empty() && !m_glyphs.empty()) { - double l = 0, r = 0, t = 0, b = 0; - }; - + // texture is set in ctor + glyphData = m_glyphs; + } + else + { + // just in case if FontAtlasGenerator changed it's atlas + m_material.texture = const_cast(&m_fontAtlasGenerator->GetAtlas()); + glyphData = m_fontAtlasGenerator->GetAtlasInfo(); + } + std::map& symbols = glyphData.value(); +#else + std::map& symbols = m_glyphs; +#endif + const Texture& atlasTex = *m_material.texture; double cursorX = pos.x; auto begin = text.begin(); auto end = text.end(); for (size_t i = 0; begin != end; i++) { - unicode_t c = utf8::next(begin, end); + uint32_t c = utf8::next(begin, end); if (symbols.find(c) != symbols.end()) { - Bbox glyphBaselineBbox, glyphAtlasBbox; uint32_t vIdx = i * 4; uint32_t indices[] = { 1 + vIdx, 2 + vIdx, 3 + vIdx, 1 + vIdx, 3 + vIdx, 0 + vIdx }; GlyphInfo& info = symbols.at(c); - info.geometry.getQuadPlaneBounds(glyphBaselineBbox.l, glyphBaselineBbox.b, glyphBaselineBbox.r, - glyphBaselineBbox.t); - info.geometry.getQuadAtlasBounds(glyphAtlasBbox.l, glyphAtlasBbox.b, glyphAtlasBbox.r, - glyphAtlasBbox.t); - double bearingX = info.glyphBox.bounds.l; - double bearingY = info.glyphBox.bounds.t; - double w = glyphBaselineBbox.r - glyphBaselineBbox.l; - double h = glyphBaselineBbox.t - glyphBaselineBbox.b; - double l = glyphAtlasBbox.l; - double r = glyphAtlasBbox.r; - double t = glyphAtlasBbox.t; - double b = glyphAtlasBbox.b; - double ax = cursorX + bearingX; - double ay = pos.y - (h - bearingY); - m_geometry.vertices[vIdx].position.x = ax; - m_geometry.vertices[vIdx].position.y = ay; - m_geometry.vertices[vIdx].position.z = 1; - m_geometry.vertices[vIdx].textureCoordinates.x = l / atlasTex.resolution.x; - m_geometry.vertices[vIdx].textureCoordinates.y = b / atlasTex.resolution.y; - - m_geometry.vertices[vIdx + 1].position.x = ax + w; - m_geometry.vertices[vIdx + 1].position.y = ay; - m_geometry.vertices[vIdx + 1].position.z = 1; - m_geometry.vertices[vIdx + 1].textureCoordinates.x = r / atlasTex.resolution.x; - m_geometry.vertices[vIdx + 1].textureCoordinates.y = b / atlasTex.resolution.y; - - m_geometry.vertices[vIdx + 2].position.x = ax + w; - m_geometry.vertices[vIdx + 2].position.y = ay + h; - m_geometry.vertices[vIdx + 2].position.z = 1; - m_geometry.vertices[vIdx + 2].textureCoordinates.x = r / atlasTex.resolution.x; - m_geometry.vertices[vIdx + 2].textureCoordinates.y = t / atlasTex.resolution.y; - - m_geometry.vertices[vIdx + 3].position.x = ax; - m_geometry.vertices[vIdx + 3].position.y = ay + h; - m_geometry.vertices[vIdx + 3].position.z = 1; - m_geometry.vertices[vIdx + 3].textureCoordinates.x = l / atlasTex.resolution.x; - m_geometry.vertices[vIdx + 3].textureCoordinates.y = t / atlasTex.resolution.y; - + // left bottom + m_geometry.vertices[vIdx].position.x = info.xyz[0].x + cursorX; + m_geometry.vertices[vIdx].position.y = pos.y - info.xyz[0].y; + m_geometry.vertices[vIdx].position.z = info.xyz[0].z; + m_geometry.vertices[vIdx].textureCoordinates = Math::Vector3f(info.uv[0], 0); + + // right bottom + m_geometry.vertices[vIdx + 1].position.x = info.xyz[1].x + cursorX; + m_geometry.vertices[vIdx + 1].position.y = pos.y - info.xyz[1].y; + m_geometry.vertices[vIdx + 1].position.z = info.xyz[1].z; + m_geometry.vertices[vIdx + 1].textureCoordinates = Math::Vector3f(info.uv[1], 0); + + // top right + m_geometry.vertices[vIdx + 2].position.x = info.xyz[2].x + cursorX; + m_geometry.vertices[vIdx + 2].position.y = pos.y + info.xyz[2].y; + m_geometry.vertices[vIdx + 2].position.z = info.xyz[2].z; + m_geometry.vertices[vIdx + 2].textureCoordinates = Math::Vector3f(info.uv[2], 0); + + // top left + m_geometry.vertices[vIdx + 3].position.x = info.xyz[3].x + cursorX; + m_geometry.vertices[vIdx + 3].position.y = pos.y + info.xyz[3].y; + m_geometry.vertices[vIdx + 3].position.z = info.xyz[3].z; + m_geometry.vertices[vIdx + 3].textureCoordinates = Math::Vector3f(info.uv[3], 0); m_geometry.SetIndices(indices, 6, 6 * i); // TODO: change to lower value(or ideally remove completely) to avoid overlapping and make less space between symbols // when setting for depth comparison operator will be available( <= ) - cursorX += info.glyphBox.advance + 0.08; + cursorX += info.advance + 0.08; } else { diff --git a/openVulkanoCpp/Scene/TextDrawable.hpp b/openVulkanoCpp/Scene/TextDrawable.hpp index 20453b0..18634cb 100644 --- a/openVulkanoCpp/Scene/TextDrawable.hpp +++ b/openVulkanoCpp/Scene/TextDrawable.hpp @@ -17,14 +17,20 @@ #include "Geometry.hpp" #include "UniformBuffer.hpp" #include "Base/Logger.hpp" -#include "msdfgen.h" -#include "msdfgen-ext.h" -#include "msdf-atlas-gen/msdf-atlas-gen.h" +#include "AtlasMetadata.hpp" +#if __has_include("msdfgen.h") + #include "msdfgen.h" + #include "msdfgen-ext.h" + #include "msdf-atlas-gen/msdf-atlas-gen.h" + #define MSDFGEN_AVAILABLE 1 +#endif namespace OpenVulkano::Scene { +#ifdef MSDFGEN_AVAILABLE using namespace msdfgen; using namespace msdf_atlas; +#endif // MSDFGEN_AVAILABLE struct TextConfig { @@ -42,12 +48,18 @@ namespace OpenVulkano::Scene { public: static Shader& GetDefaultShader(); + static std::map ReadAtlasMetadataFile(const std::string& atlasMetadataFile); + static std::map ReadAtlasMetadata(const Array& atlasMetadata); + TextDrawable(const std::map& glyphData, Texture* atlasTex, const TextConfig& config = TextConfig()); +#ifdef MSDFGEN_AVAILABLE TextDrawable(FontAtlasGenerator* fontAtlasGenerator, const TextConfig& config = TextConfig()); +#endif void GenerateText(const std::string& text, const Math::Vector3f& pos = Math::Vector3f(0.f)); void SetConfig(const TextConfig& cfg) { m_cfg = cfg; } void SetShader(Shader* shader) { m_shader = shader; } TextConfig& GetConfig() { return m_cfg; } Shader* GetShader() { return m_shader; } +#ifdef MSDFGEN_AVAILABLE void SetFontAtlasGenerator(FontAtlasGenerator* fontAtlasGenerator) { if (!fontAtlasGenerator || fontAtlasGenerator->GetAtlasInfo().empty()) @@ -56,14 +68,17 @@ namespace OpenVulkano::Scene return; } m_fontAtlasGenerator = fontAtlasGenerator; - m_material.texture = const_cast(&m_fontAtlasGenerator->GetAtlas()); } FontAtlasGenerator* GetFontAtlasGenerator() { return m_fontAtlasGenerator; } +#endif private: Geometry m_geometry; Material m_material; UniformBuffer m_uniBuffer; + std::map m_glyphs; +#ifdef MSDFGEN_AVAILABLE FontAtlasGenerator* m_fontAtlasGenerator = nullptr; +#endif Shader* m_shader = &GetDefaultShader(); TextConfig m_cfg; };