diff --git a/3rdParty/CMakeLists.txt b/3rdParty/CMakeLists.txt index 59f0d8e..1a8e201 100644 --- a/3rdParty/CMakeLists.txt +++ b/3rdParty/CMakeLists.txt @@ -34,14 +34,17 @@ add_subdirectory(libjpeg-turbo) add_subdirectory(msdf) add_subdirectory(eastl) add_subdirectory(moodycamel_concurrentqueue) +add_subdirectory(tinyusdz) +add_subdirectory(dds_image) + if (NOT IOS AND ENABLE_CURL) add_subdirectory(curl) endif() -if(ENABLE_TEST) +if (ENABLE_TEST) add_subdirectory(catch2) -endif() +endif () -add_subdirectory(tinyusdz) -add_subdirectory(ktx-software) -add_subdirectory(dds_image) \ No newline at end of file +if (ENABLE_KTX) + add_subdirectory(ktx-software) +endif () \ No newline at end of file diff --git a/3rdParty/ftxui/CMakeLists.txt b/3rdParty/ftxui/CMakeLists.txt index d485171..11e8372 100644 --- a/3rdParty/ftxui/CMakeLists.txt +++ b/3rdParty/ftxui/CMakeLists.txt @@ -9,6 +9,6 @@ FetchContent_Declare( EXCLUDE_FROM_ALL GIT_REPOSITORY ${FTXUI_REPO} GIT_TAG daa421fa6ad97c8a6c9bd43f77c81862bfa52c77 - GIT_SHALLOW TRUE + GIT_SHALLOW FALSE ) FetchContent_MakeAvailable(ftxui) \ No newline at end of file diff --git a/3rdParty/pugixml/CMakeLists.txt b/3rdParty/pugixml/CMakeLists.txt index c9e670c..c3a1b60 100644 --- a/3rdParty/pugixml/CMakeLists.txt +++ b/3rdParty/pugixml/CMakeLists.txt @@ -9,7 +9,7 @@ FetchContent_Declare( EXCLUDE_FROM_ALL GIT_REPOSITORY ${PUGIXML_REPO} GIT_TAG 4bc14418d12d289dd9978fdce9490a45deeb653e - GIT_SHALLOW TRUE + GIT_SHALLOW FALSE ) set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) set(PUGIXML_BUILD_TESTS OFF CACHE BOOL "" FORCE) diff --git a/CMakeLists.txt b/CMakeLists.txt index ef70745..9e2299a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,7 @@ SetOptimisationSettings() option(TRACY_ENABLE "Enable Tracy Profiler" OFF) option(ENABLE_ASSIMP "If assimp should be used" ON) option(ENABLE_CURL "If curl should be used" ON) +option(ENABLE_KTX "If ktx images should be supported" ON) option(ENABLE_TEST "Enable testing" ON) option(ENABLE_EXAMPLE "Enable examples" ON) option(ENABLE_MSDF "Enable msdf library" ON) diff --git a/cmake/functions/GenerateTriplet.cmake b/cmake/functions/GenerateTriplet.cmake new file mode 100644 index 0000000..973fb46 --- /dev/null +++ b/cmake/functions/GenerateTriplet.cmake @@ -0,0 +1,43 @@ +function(GenerateTriplet OUTPUT_VARIABLE) + # Determine system architecture + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm|aarch)64$") + set(ARCH "arm64") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm|aarch).*") + set(ARCH "arm") + elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(ARCH "x64") + elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(ARCH "x86") + else() + message(FATAL_ERROR "Unsupported architecture") + return() + endif() + + # Determine OS and create vcpkg-compatible triplet + if(WIN32) + # Windows triplets: x64-windows, x64-windows-static, x64-windows-dynamic + if(BUILD_SHARED_LIBS) + set(TRIPLET "${ARCH}-windows-dynamic") + else() + set(TRIPLET "${ARCH}-windows-static") + endif() + elseif(APPLE) + if (IOS) + set(TRIPLET "${ARCH}-ios") + else () + # macOS triplets: x64-osx, arm64-osx + set(TRIPLET "${ARCH}-osx") + endif () + elseif(UNIX) + # Linux triplets: x64-linux, arm64-linux + set(TRIPLET "${ARCH}-linux") + else() + message(FATAL_ERROR "Unsupported operating system") + return() + endif() + + message(STATUS "Generated vcpkg-compatible triplet: ${TRIPLET}") + + # Set the output variable in the parent scope + set(${OUTPUT_VARIABLE} ${TRIPLET} PARENT_SCOPE) +endfunction() \ No newline at end of file diff --git a/examples/ExampleApps/LabelDrawableExampleApp.cpp b/examples/ExampleApps/LabelDrawableExampleApp.cpp index 72271d8..a076eaf 100644 --- a/examples/ExampleApps/LabelDrawableExampleApp.cpp +++ b/examples/ExampleApps/LabelDrawableExampleApp.cpp @@ -24,7 +24,7 @@ #include "Base/EngineConfiguration.hpp" #include "Controller/FreeCamCameraController.hpp" #include "Image/ImageLoaderPng.hpp" -#include "Scene/FontAtlasGenerator.hpp" +#include "Scene/SdfFontAtlasGenerator.hpp" #include "Scene/IFontAtlasGenerator.hpp" #include diff --git a/examples/ExampleApps/TextExampleApp.cpp b/examples/ExampleApps/TextExampleApp.cpp index 3436a83..9ed0549 100644 --- a/examples/ExampleApps/TextExampleApp.cpp +++ b/examples/ExampleApps/TextExampleApp.cpp @@ -23,8 +23,9 @@ #include "Base/EngineConfiguration.hpp" #include "Controller/FreeCamCameraController.hpp" #include "Image/ImageLoaderPng.hpp" -#include "Scene/FontAtlasGenerator.hpp" +#include "Scene/SdfFontAtlasGenerator.hpp" #include "Scene/IFontAtlasGenerator.hpp" +#include "Scene/BitmapFontAtlasGenerator.hpp" #include #ifdef _WIN32 @@ -38,7 +39,8 @@ namespace OpenVulkano using namespace Math; namespace fs = std::filesystem; - //#define CREATE_NEW_ATLAS 1 + constexpr int CREATE_BITMAP_ATLAS = 0; + constexpr int CREATE_NEW_ATLAS = 0; class TextExampleAppImpl final : public TextExampleApp { @@ -66,25 +68,34 @@ namespace OpenVulkano const int N = texts.size(); auto& resourceLoader = ResourceLoader::GetInstance(); const std::string fontPath = resourceLoader.GetResourcePath("Roboto-Regular.ttf"); - m_nodesPool.resize(N * 2); - m_drawablesPool.resize(N * 2); - -#if defined(MSDFGEN_AVAILABLE) && defined(CREATE_NEW_ATLAS) - msdf_atlas::Charset charset = SdfFontAtlasGenerator::LoadAllGlyphs(fontPath); - m_atlasGenerator.GenerateAtlas(fontPath, charset); - m_msdfAtlasGenerator.GenerateAtlas(fontPath, charset); - m_atlasGenerator.SaveAtlasMetadataInfo("sdf_atlas.png"); - m_msdfAtlasGenerator.SaveAtlasMetadataInfo("msdf_atlas"); + m_nodesPool.resize(N * 3); + m_drawablesPool.resize(N * 3); + + if constexpr (CREATE_BITMAP_ATLAS) + { + std::set s = BitmapFontAtlasGenerator::LoadAllGlyphs(fontPath); + BitmapFontAtlasGenerator generator; + generator.GenerateAtlas(fontPath, s); + generator.SaveAtlasMetadataInfo("bitmap_atlas"); + } + +#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"); #else auto sdfMetadataInfo = resourceLoader.GetResource("sdf_atlas_packed.png"); auto msdfMetadataInfo = resourceLoader.GetResource("msdf_atlas_packed.png"); + auto bitmapMetadataInfo = resourceLoader.GetResource("bitmap_atlas_packed.png"); #endif - for (int i = 0; i < texts.size() * 2; i++) + for (int i = 0; i < texts.size() * 3; i++) { int textIdx = i % texts.size(); TextDrawable* t = nullptr; -#if defined(MSDFGEN_AVAILABLE) && defined(CREATE_NEW_ATLAS) +#if defined(MSDFGEN_AVAILABLE) && CREATE_NEW_ATLAS if (i < texts.size()) { t = new TextDrawable(m_atlasGenerator.GetAtlasData(), texts[textIdx].second); @@ -96,15 +107,24 @@ namespace OpenVulkano t->SetShader(&TextDrawable::GetMsdfDefaultShader()); } #else - if (i < texts.size()) + int xOffset = 0; + if (i < N) { t = new TextDrawable(sdfMetadataInfo, texts[textIdx].second); t->SetShader(&TextDrawable::GetSdfDefaultShader()); + xOffset = -5; } - else + else if (i >= N && i < N * 2) { t = new TextDrawable(msdfMetadataInfo, texts[textIdx].second); t->SetShader(&TextDrawable::GetMsdfDefaultShader()); + xOffset = 15; + } + else + { + t = new TextDrawable(bitmapMetadataInfo, texts[textIdx].second); + t->SetShader(&TextDrawable::GetBitmapDefaultShader()); + xOffset = 35; } // OR use separate texture + metadata file //auto metadataInfo = resourceLoader.GetResource("atlas_metadata"); @@ -121,7 +141,7 @@ namespace OpenVulkano t->GenerateText(texts[textIdx].first); m_drawablesPool[i] = t; m_nodesPool[i].Init(); - m_nodesPool[i].SetMatrix(Math::Utils::translate(glm::mat4x4(1.f), Vector3f((i < texts.size() ? -5 : 15), 2 - textIdx * 2, 0))); + m_nodesPool[i].SetMatrix(Math::Utils::translate(glm::mat4x4(1.f), Vector3f(xOffset, 2 - textIdx * 2, 0))); m_nodesPool[i].AddDrawable(m_drawablesPool[i]); m_scene.GetRoot()->AddChild(&m_nodesPool[i]); } diff --git a/examples/ExampleSources/bitmap_atlas_packed.png b/examples/ExampleSources/bitmap_atlas_packed.png new file mode 100644 index 0000000..d8071d6 Binary files /dev/null and b/examples/ExampleSources/bitmap_atlas_packed.png differ diff --git a/openVulkanoCpp/Base/Utils.cpp b/openVulkanoCpp/Base/Utils.cpp index 98c80c7..1e2f28f 100644 --- a/openVulkanoCpp/Base/Utils.cpp +++ b/openVulkanoCpp/Base/Utils.cpp @@ -70,7 +70,7 @@ namespace OpenVulkano if (!file.is_open()) { if (emptyOnMissing) return {}; - if constexpr (std::is_same_v>, std::filesystem::path>) + if constexpr (std::is_same_v, std::filesystem::path>) { throw std::runtime_error("Failed to open file '" + filePath.string() + "'!"); } diff --git a/openVulkanoCpp/Base/Utils.hpp b/openVulkanoCpp/Base/Utils.hpp index 8782429..7654bc9 100644 --- a/openVulkanoCpp/Base/Utils.hpp +++ b/openVulkanoCpp/Base/Utils.hpp @@ -190,13 +190,20 @@ namespace OpenVulkano static Array ReadFile(const T& filePath, bool emptyOnMissing = false, bool nullTerminateString = false); + template + static Array ReadFile(const char (&filePath)[N], bool emptyOnMissing = false, + bool nullTerminateString = false) + { + return ReadFile(std::string(filePath), emptyOnMissing, nullTerminateString); + } + template static int GetUniqueTypeId() { static const int id = uniqueTypeID++; return id; } - + static std::string DemangleTypeName(const char* name); }; } diff --git a/openVulkanoCpp/CMakeLists.txt b/openVulkanoCpp/CMakeLists.txt index 97462fa..87fd36b 100644 --- a/openVulkanoCpp/CMakeLists.txt +++ b/openVulkanoCpp/CMakeLists.txt @@ -35,15 +35,15 @@ endif () file(GLOB SHADER_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Shader/*) list(FILTER SHADER_SRC_FILES EXCLUDE REGEX ".*\\.(hpp|cpp)$") -add_library(openVulkanoCpp ${MAIN_FILE} ${SHADER_SRC_FILES} ${GENERATED_SHADER_SOURCES}) +FilterPlatformPaths(sources) +add_library(openVulkanoCpp ${MAIN_FILE} ${SHADER_SRC_FILES} ${GENERATED_SHADER_SOURCES} ${sources}) set_property(TARGET openVulkanoCpp PROPERTY CXX_STANDARD 20) source_group("Shaders" FILES ${SHADER_SRC_FILES}) +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${sources}) -FilterPlatformPaths(sources) SetWarningSettings(openVulkanoCpp) SetGlmDefines(openVulkanoCpp) -target_sources(openVulkanoCpp PRIVATE ${sources}) target_include_directories(openVulkanoCpp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${SHADER_OUTPUT_DEST}) if(APPLE) @@ -67,7 +67,10 @@ if (NOT ANDROID AND NOT IOS) endif() endif() -target_link_libraries(openVulkanoCpp PUBLIC magic_enum yaml-cpp fmt spdlog glm pugixml stb eigen utf8cpp imgui_internal TracyClient stud-uuid ryml unordered_dense concurrentqueue units ktx dds_image) +target_link_libraries(openVulkanoCpp PUBLIC magic_enum yaml-cpp fmt spdlog glm pugixml stb eigen utf8cpp imgui_internal TracyClient stud-uuid ryml unordered_dense concurrentqueue units dds_image) +if (ENABLE_KTX) + target_link_libraries(openVulkanoCpp PUBLIC ktx) +endif () LinkAssimp(openVulkanoCpp) LinkLibArchive(openVulkanoCpp) LinkLibJpegTurbo(openVulkanoCpp) diff --git a/openVulkanoCpp/Extensions/STBZlibCompressor.hpp b/openVulkanoCpp/Extensions/STBZlibCompressor.hpp new file mode 100644 index 0000000..f21274b --- /dev/null +++ b/openVulkanoCpp/Extensions/STBZlibCompressor.hpp @@ -0,0 +1,33 @@ +/* + * 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 +#include + +namespace Extensions +{ + static unsigned char* STBZlibCompressor(unsigned char* data, int data_len, int* out_len, int quality) + { + uLong maxCompressedSize = compressBound(data_len); + void* outData = malloc(maxCompressedSize); + if (!outData) + { + *out_len = 0; + return nullptr; + } + int result = compress2(static_cast(outData), &maxCompressedSize, data, data_len, Z_BEST_COMPRESSION); + if (result != Z_OK) + { + free(outData); + *out_len = 0; + return nullptr; + } + *out_len = static_cast(maxCompressedSize); + return static_cast(outData); + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Host/iOS/OpenVulkanoView.mm b/openVulkanoCpp/Host/iOS/OpenVulkanoView.mm index 76f1dec..d8ad2f2 100644 --- a/openVulkanoCpp/Host/iOS/OpenVulkanoView.mm +++ b/openVulkanoCpp/Host/iOS/OpenVulkanoView.mm @@ -30,6 +30,7 @@ using namespace OpenVulkano; - (id)initWithWindow:(MetalViewWindow*)win { window = win; + window->SetContentScale(UIScreen.mainScreen.nativeScale); return self; } diff --git a/openVulkanoCpp/Image/ImageLoaderKtx.cpp b/openVulkanoCpp/Image/ImageLoaderKtx.cpp index afef434..5caa0f4 100644 --- a/openVulkanoCpp/Image/ImageLoaderKtx.cpp +++ b/openVulkanoCpp/Image/ImageLoaderKtx.cpp @@ -5,7 +5,9 @@ */ #include "ImageLoaderKtx.hpp" +#include "Base/Logger.hpp" +#if __has_include("ktx.h") #include #include @@ -13,22 +15,72 @@ #include #include -namespace -{ - void KtxDestroy(ktxTexture* tex) - { - ktxTexture_Destroy(tex); - } -} - namespace OpenVulkano::Image { + namespace + { + struct KTXTextureDeleter { + void operator()(ktxTexture* texture) const { + if (texture) { + ktxTexture_Destroy(texture); + } + } + }; + + std::unique_ptr ExtractImage(ktxTexture* texture) + { + if (!texture->pData) + { + throw std::runtime_error("No image data found in KTX texture."); + } + + auto width = static_cast(texture->baseWidth); + auto height = static_cast(texture->baseHeight); + + auto image = std::make_unique(); + image->resolution.x = width; + image->resolution.y = height; + image->resolution.z = 1; + + if (texture->classId == ktxTexture1_c) + { + ktxTexture1* tx = reinterpret_cast(texture); + DataFormat format = DataFormat::Format::UNDEFINED; + switch (tx->glInternalformat) + { + case 0x8051: /* GL_RGB8_EXT */ format = DataFormat::Format::R8G8B8_UNORM; break; + case 0x8054: /* GL_RGB16_EXT */ format = DataFormat::Format::R16G16B16_UNORM; break; + case 0x8057: /* GL_RGB5_A1_EXT */ format = DataFormat::Format::R5G5B5A1_UNORM_PACK16; break; + case 0x8058: /* GL_RGBA8_EXT */ format = DataFormat::Format::R8G8B8A8_UNORM; break; + case 0x8059: /* GL_RGB10_A2_EXT */ format = DataFormat::Format::A2R10G10B10_UNORM_PACK32; break; + case 0x805B: /* GL_RGBA16_EXT */ format = DataFormat::Format::R16G16B16A16_UNORM; break; + default: throw std::runtime_error("Unhandled texture1 format: " + tx->glInternalformat); + } + image->dataFormat = format; + } + else if (texture->classId == ktxTexture2_c) + { + ktxTexture2* tx = reinterpret_cast(texture); + image->dataFormat = reinterpret_cast(tx->vkFormat); + } + else [[unlikely]] + { + throw std::runtime_error("ktxTexture has unhandled classId!"); + } + + image->data = Array(texture->dataSize); + memcpy(image->data.Data(), texture->pData, image->data.Size()); + + return image; + } + } + std::unique_ptr ImageLoaderKtx::loadFromFile(const std::string& filePath) { ktxTexture* tmp = nullptr; KTX_error_code error_code = ktxTexture_CreateFromNamedFile(filePath.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &tmp); - std::unique_ptr texture(tmp, &KtxDestroy); + std::unique_ptr texture(tmp); if (error_code != KTX_SUCCESS) { @@ -44,7 +96,7 @@ namespace OpenVulkano::Image ktxTexture* tmp = nullptr; KTX_error_code error_code = ktxTexture_CreateFromMemory(buffer.data(), buffer.size(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &tmp); - std::unique_ptr texture(tmp, &KtxDestroy); + std::unique_ptr texture(tmp); if (error_code != KTX_SUCCESS) { @@ -60,7 +112,7 @@ namespace OpenVulkano::Image ktxTexture* tmp = nullptr; KTX_error_code result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &tmp); - std::unique_ptr texture(tmp, &KtxDestroy); + std::unique_ptr texture(tmp); if (result != KTX_SUCCESS) { @@ -71,51 +123,26 @@ namespace OpenVulkano::Image Math::Vector2i dimensions(texture->baseWidth, texture->baseHeight); return dimensions; } - - std::unique_ptr ImageLoaderKtx::ExtractImage(ktxTexture* texture) +} +#else +namespace OpenVulkano::Image +{ + std::unique_ptr ImageLoaderKtx::loadFromFile(const std::string& filePath) { - if (!texture->pData) - { - throw std::runtime_error("No image data found in KTX texture."); - } - - auto width = static_cast(texture->baseWidth); - auto height = static_cast(texture->baseHeight); - - auto image = std::make_unique(); - image->resolution.x = width; - image->resolution.y = height; - image->resolution.z = 1; - - if (texture->classId == ktxTexture1_c) - { - ktxTexture1* tx = reinterpret_cast(texture); - DataFormat format = DataFormat::Format::UNDEFINED; - switch (tx->glInternalformat) - { - case 0x8051: /* GL_RGB8_EXT */ format = DataFormat::Format::R8G8B8_UNORM; break; - case 0x8054: /* GL_RGB16_EXT */ format = DataFormat::Format::R16G16B16_UNORM; break; - case 0x8057: /* GL_RGB5_A1_EXT */ format = DataFormat::Format::R5G5B5A1_UNORM_PACK16; break; - case 0x8058: /* GL_RGBA8_EXT */ format = DataFormat::Format::R8G8B8A8_UNORM; break; - case 0x8059: /* GL_RGB10_A2_EXT */ format = DataFormat::Format::A2R10G10B10_UNORM_PACK32; break; - case 0x805B: /* GL_RGBA16_EXT */ format = DataFormat::Format::R16G16B16A16_UNORM; break; - default: throw std::runtime_error("Unhandled texture1 format: " + tx->glInternalformat); - } - image->dataFormat = format; - } - else if (texture->classId == ktxTexture2_c) - { - ktxTexture2* tx = reinterpret_cast(texture); - image->dataFormat = reinterpret_cast(tx->vkFormat); - } - else [[unlikely]] - { - throw std::runtime_error("ktxTexture has unhandled classId!"); - } - - image->data = Array(texture->dataSize); - memcpy(image->data.Data(), texture->pData, image->data.Size()); - - return image; + Logger::DATA->error("KTX loading not available."); + return nullptr; } -} \ No newline at end of file + + std::unique_ptr ImageLoaderKtx::loadFromMemory(const std::vector& buffer) + { + Logger::DATA->error("KTX loading not available."); + return nullptr; + } + + Math::Vector2i ImageLoaderKtx::GetImageDimensions(const std::string& filename) + { + Logger::DATA->error("KTX loading not available."); + return {0,0}; + } +} +#endif \ No newline at end of file diff --git a/openVulkanoCpp/Image/ImageLoaderKtx.hpp b/openVulkanoCpp/Image/ImageLoaderKtx.hpp index c9f9db9..dd86078 100644 --- a/openVulkanoCpp/Image/ImageLoaderKtx.hpp +++ b/openVulkanoCpp/Image/ImageLoaderKtx.hpp @@ -8,8 +8,6 @@ #include "ImageLoader.hpp" -struct ktxTexture; - namespace OpenVulkano::Image { class ImageLoaderKtx : public IImageLoader @@ -18,8 +16,5 @@ namespace OpenVulkano::Image std::unique_ptr loadFromFile(const std::string& filePath) override; std::unique_ptr loadFromMemory(const std::vector& buffer) override; Math::Vector2i GetImageDimensions(const std::string& filename) override; - - protected: - std::unique_ptr ExtractImage(ktxTexture* texture); }; } diff --git a/openVulkanoCpp/Input/InputKey.hpp b/openVulkanoCpp/Input/InputKey.hpp index 8e37b7b..a528fb6 100644 --- a/openVulkanoCpp/Input/InputKey.hpp +++ b/openVulkanoCpp/Input/InputKey.hpp @@ -250,16 +250,21 @@ namespace OpenVulkano::Input enum Button: int16_t { BUTTON_TAP = 0, - BUTTON_TWO_FINGER_TAP + BUTTON_TWO_FINGER_TAP, + BUTTON_LAST = BUTTON_TWO_FINGER_TAP }; enum Axis : int16_t { AXIS_TAP_X = 0, AXIS_TAP_Y, + AXIS_TAP_X_ABS, + AXIS_TAP_Y_ABS, AXIS_TAP_DURATION, AXIS_TAP_TWO_FINGERS_X, AXIS_TAP_TWO_FINGERS_Y, + AXIS_TAP_TWO_FINGERS_X_ABS, + AXIS_TAP_TWO_FINGERS_Y_ABS, AXIS_TAP_TWO_FINGER_DURATION, AXIS_PAN_X, AXIS_PAN_Y, diff --git a/openVulkanoCpp/Input/Touch/InputDeviceTouch.cpp b/openVulkanoCpp/Input/Touch/InputDeviceTouch.cpp index 8c31340..e3a2663 100644 --- a/openVulkanoCpp/Input/Touch/InputDeviceTouch.cpp +++ b/openVulkanoCpp/Input/Touch/InputDeviceTouch.cpp @@ -194,6 +194,8 @@ namespace OpenVulkano::Input auto diff = m_nextTap - m_lastTap; m_axes[InputKey::Touch::Axis::AXIS_TAP_X] = diff.x; m_axes[InputKey::Touch::Axis::AXIS_TAP_Y] = diff.y; + m_axes[InputKey::Touch::Axis::AXIS_TAP_X_ABS] = m_nextTap.x; + m_axes[InputKey::Touch::Axis::AXIS_TAP_Y_ABS] = m_nextTap.y; m_lastTap = m_nextTap; // Pan diff --git a/openVulkanoCpp/Scene/AtlasData.hpp b/openVulkanoCpp/Scene/AtlasData.hpp index 08dc6a6..ed75309 100644 --- a/openVulkanoCpp/Scene/AtlasData.hpp +++ b/openVulkanoCpp/Scene/AtlasData.hpp @@ -31,6 +31,7 @@ namespace OpenVulkano::Scene { SDF = 0, MSDF, + BITMAP, UNKNOWN }; static constexpr std::string_view DEFAULT_FG_SHADERS[] = { "Shader/text", "Shader/msdfText" }; diff --git a/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.cpp b/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.cpp new file mode 100644 index 0000000..a0ff547 --- /dev/null +++ b/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.cpp @@ -0,0 +1,130 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include "BitmapFontAtlasGenerator.hpp" +#include "Base/Logger.hpp" + +namespace OpenVulkano::Scene +{ + void BitmapFontAtlasGenerator::GenerateAtlas(const std::string& fontFile, const std::set& charset, + const std::optional& pngOutput) + { + Generate(fontFile, charset, pngOutput); + } + + void BitmapFontAtlasGenerator::GenerateAtlas(const Array& fontData, const std::set& charset, + const std::optional& pngOutput) + { + Generate(fontData, charset, pngOutput); + } + + void BitmapFontAtlasGenerator::Generate(const std::variant>& source, + const std::set& chset, + const std::optional& pngOutput) + { + if (chset.empty()) + { + Logger::APP->info("Charset is empty. Nothing to generate."); + return; + } + + m_atlasData = std::make_shared(); + const auto& [lib, face] = FontAtlasGeneratorBase::InitFreetype(source); + + Math::Vector2ui cellSize; + if (m_pixelSizeConfig.isPixelSize) + { + cellSize = { m_pixelSizeConfig.size, m_pixelSizeConfig.size }; + // set pixel width/height lower than glyph size above, otherwise some glyphs will be cropped or some overlapping will be present + FT_Set_Pixel_Sizes(face.get(), 0, cellSize.y - cellSize.y / 3); + } + else + { + const float pixelSize = (m_pixelSizeConfig.size * m_pixelSizeConfig.dpi) / 72.0f; + //int fontHeight = round((face->bbox.yMax - face->bbox.yMin) * pixelSize / face->units_per_EM); + //int fontWidth = round((face->bbox.xMax - face->bbox.xMin) * pixelSize / face->units_per_EM); + cellSize = { pixelSize, pixelSize }; + FT_Set_Char_Size(face.get(), 0, static_cast(m_pixelSizeConfig.size) * 64, + static_cast(m_pixelSizeConfig.dpi), static_cast(m_pixelSizeConfig.dpi)); + } + + const double sq = std::sqrt(chset.size()); + const size_t glyphsPerRow = (static_cast(sq)) + (sq - static_cast(sq) != 0); + const size_t rows = (chset.size() / glyphsPerRow) + (chset.size() % glyphsPerRow != 0); + const Math::Vector2ui atlasResolution = { glyphsPerRow * cellSize.x, rows * cellSize.y }; + + // same as in msdfgen lib by default. see import-font.h for reference + // TODO: probably also support keeping coordinates as the integer values native to the font file + // but since some algorithms have already been implemented for EM_NORMALIZED mode, currently there is no support for default font metrics (ints) + // The coordinates will be normalized to the em size, i.e. 1 = 1 em + const double scaleFactor = (1. / face->units_per_EM); + SetupAtlasData(atlasResolution, face->height * scaleFactor, FontAtlasType::BITMAP); + + size_t loadedGlyphs = 0; + FT_Error error = 0; + int currentPosX = 0; + int currentPosY = 0; + Math::Vector2ui gridPos = { 0, 0 }; + for (uint32_t codepoint : chset) + { + error = FT_Load_Char(face.get(), codepoint, FT_LOAD_RENDER); + if (error) + { + Logger::APP->error("FT_Load_Char for codepoint {} has failed. {}", codepoint, GetFreetypeErrorDescription(error)); + continue; + } + + FT_GlyphSlot slot = face->glyph; + if (slot->bitmap.width > cellSize.x || slot->bitmap.rows > cellSize.y) + { + Logger::APP->warn("Glyph size exceeds grid cell size: {}x{} exceeds {}x{}", slot->bitmap.width, slot->bitmap.rows, cellSize.x, cellSize.y); + // skip such glyph for now to avoid crash + continue; + } + + const size_t firstGlyphByte = (gridPos.y * cellSize.x + gridPos.x * atlasResolution.x * cellSize.y); + for (int row = 0; row < slot->bitmap.rows; row++) + { + for (int col = 0; col < slot->bitmap.width; col++) + { + m_atlasData->img->data[firstGlyphByte + row * atlasResolution.x + col] = slot->bitmap.buffer[(slot->bitmap.rows - 1 - row) * slot->bitmap.pitch + col]; + } + } + + GlyphInfo& glyphInfo = m_atlasData->glyphs[codepoint]; + const Math::Vector2d glyphMetrics = { slot->metrics.width * scaleFactor, slot->metrics.height * scaleFactor }; + const Math::Vector2d glyphBearing = { slot->metrics.horiBearingX * scaleFactor, slot->metrics.horiBearingY * scaleFactor }; + // metrics are 1/64 of a pixel + constexpr double toPixelScaler = 1. / 64; + const Math::Vector2d whPixel = { static_cast(slot->metrics.width * toPixelScaler), + static_cast(slot->metrics.height * toPixelScaler) }; + Math::AABB glyphAtlasAABB(Math::Vector3f(currentPosX, currentPosY, 0), Math::Vector3f(currentPosX + whPixel.x, currentPosY + whPixel.y, 0)); + SetGlyphData(glyphInfo, glyphBearing, glyphMetrics, glyphAtlasAABB, slot->advance.x * scaleFactor); + + currentPosX += cellSize.x; + loadedGlyphs++; + + if (currentPosX + cellSize.x > atlasResolution.x) + { + currentPosX = 0; + currentPosY += cellSize.y; + gridPos.y = 0; + gridPos.x++; + } + else + { + gridPos.y++; + } + } + + if (pngOutput) + { + SavePng(*pngOutput); + } + Logger::APP->debug("Created atlas with {} glyphs, {} glyphs could not be loaded", loadedGlyphs, chset.size() - loadedGlyphs); + + } +} diff --git a/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.hpp b/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.hpp new file mode 100644 index 0000000..7ec1fad --- /dev/null +++ b/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.hpp @@ -0,0 +1,33 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include "FontAtlasGeneratorBase.hpp" + +namespace OpenVulkano::Scene +{ + struct FontPixelSizeConfig + { + float size = 16.f; + float dpi = 72.f; + bool isPixelSize = true; + }; + + class BitmapFontAtlasGenerator : public FontAtlasGeneratorBase + { + public: + BitmapFontAtlasGenerator(FontPixelSizeConfig config = FontPixelSizeConfig()) : FontAtlasGeneratorBase(1), m_pixelSizeConfig(config) {} + void GenerateAtlas(const std::string& fontFile, const std::set& charset, + const std::optional& pngOutput = std::nullopt) override; + void GenerateAtlas(const Array& fontData, const std::set& charset, + const std::optional& pngOutput = std::nullopt) override; + private: + void Generate(const std::variant>& source, const std::set& chset, const std::optional& pngOutput); + private: + FontPixelSizeConfig m_pixelSizeConfig; + }; +} diff --git a/openVulkanoCpp/Scene/FontAtlasGenerator.cpp b/openVulkanoCpp/Scene/FontAtlasGenerator.cpp deleted file mode 100644 index dd0bdb9..0000000 --- a/openVulkanoCpp/Scene/FontAtlasGenerator.cpp +++ /dev/null @@ -1,327 +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/. - */ - -#if __has_include("msdfgen.h") - -#include "FontAtlasGenerator.hpp" -#include "Base/Logger.hpp" -#include "Scene/AtlasData.hpp" -#include -#include -#include -#define STBI_MSC_SECURE_CRT -#define STB_IMAGE_WRITE_IMPLEMENTATION -#include -#include -#include FT_FREETYPE_H -#include -#include - -namespace OpenVulkano::Scene -{ - using namespace msdfgen; - using namespace msdf_atlas; - - FontAtlasGeneratorConfig FontAtlasGeneratorConfig::sdfDefaultConfig = { 42, 1.0, 5 }; - FontAtlasGeneratorConfig FontAtlasGeneratorConfig::msdfDefaultConfig = { 32, 1.0, 3 }; - - template - Charset FontAtlasGenerator::LoadAllGlyphs(const std::variant>& data) - { - FT_Library library; - auto error = FT_Init_FreeType(&library); - if (error) { throw std::runtime_error("Could not initalize freetype library\n"); } - FT_Face face; - if (std::holds_alternative(data)) - { - error = FT_New_Face(library, std::get<0>(data).c_str(), 0, &face); - } - else - { - auto& arr = std::get<1>(data); - error = FT_New_Memory_Face(library, (const FT_Byte*)(arr.Data()), arr.Size(), 0, &face); - } - if (error == FT_Err_Unknown_File_Format) { throw std::runtime_error("Unknown font file format\n"); } - else if (error) { throw std::runtime_error("Font file could not be opened or read or it's corrupted\n"); } - - // some fancy font without unicode charmap - if (face->charmap == nullptr) { throw std::runtime_error("Selected font doesn't contain unicode charmap"); } - Charset s; - FT_UInt glyphIndex; - FT_ULong unicode = FT_Get_First_Char(face, &glyphIndex); - while (glyphIndex != 0) - { - s.add(unicode); - unicode = FT_Get_Next_Char(face, unicode, &glyphIndex); - } - FT_Done_Face(face); - FT_Done_FreeType(library); - return s; - } - - template - void FontAtlasGenerator::GenerateAtlas(const std::string& fontFile, const std::set& charset, - const std::optional& pngOutput) - { - FreetypeHandle* ft; - FontHandle* font; - InitFreetypeFromFile(ft, font, fontFile); - Charset s; - std::for_each(s.begin(), s.end(), [&](uint32_t unicode) { s.add(unicode); }); - Generate(ft, font, s, pngOutput); - } - - template - FontAtlasGenerator::FontAtlasGenerator() - { - if constexpr (Channels == 1) m_config = FontAtlasGeneratorConfig::sdfDefaultConfig; - else m_config = FontAtlasGeneratorConfig::msdfDefaultConfig; - } - - template - void FontAtlasGenerator::GenerateAtlas(const Array& fontData, int length, - const std::set& charset, - const std::optional& pngOutput) - { - FreetypeHandle* ft; - FontHandle* font; - InitFreetypeFromBuffer(ft, font, (const msdfgen::byte*)(fontData.Data()), length); - Charset s; - std::for_each(s.begin(), s.end(), [&](uint32_t unicode) { s.add(unicode); }); - Generate(ft, font, s, pngOutput); - } - - template - void FontAtlasGenerator::GenerateAtlas(const std::string& fontFile, const Charset& charset, - const std::optional& pngOutput) - { - // TODO: dynamic atlas and add only those symbols which are not present yet in current atlas - FreetypeHandle* ft; - FontHandle* font; - InitFreetypeFromFile(ft, font, fontFile); - Generate(ft, font, charset, pngOutput); - } - - template - void FontAtlasGenerator::GenerateAtlas(const msdfgen::byte* fontData, int length, - const Charset& charset, - const std::optional& pngOutput) - { - FreetypeHandle* ft; - FontHandle* font; - InitFreetypeFromBuffer(ft, font, fontData, length); - Generate(ft, font, charset, pngOutput); - } - - template - void FontAtlasGenerator::SaveAtlasMetadataInfo(const std::string& outputFile, - bool packIntoSingleFile) const - { - if (m_atlasData->glyphs.empty()) - { - Logger::DATA->info("No glyphs loaded. Nothing to save."); - return; - } - std::string fileName = outputFile; - uint32_t packedFlag = packIntoSingleFile; - if (packIntoSingleFile) - { - std::filesystem::path fPath(fileName); - fileName = (fPath.parent_path() / fPath.stem()).string() + "_packed.png"; - SavePng(fileName); - } - std::fstream fs(fileName.c_str(), std::ios_base::out | std::ios_base::binary | (packedFlag ? std::ios_base::app : std::ios_base::trunc)); - fs.write(reinterpret_cast(&m_atlasData->meta), sizeof(AtlasMetadata)); - uint64_t metadataBytes = sizeof(AtlasMetadata); - for (const auto& [key, val]: m_atlasData->glyphs) - { - fs.write(reinterpret_cast(&key), sizeof(uint32_t)); - fs.write(reinterpret_cast(&val), sizeof(GlyphInfo)); - metadataBytes += sizeof(uint32_t); - metadataBytes += sizeof(GlyphInfo); - } - fs.write(reinterpret_cast(&metadataBytes), sizeof(uint64_t)); - fs.write(reinterpret_cast(&packedFlag), sizeof(uint32_t)); - } - - template - void FontAtlasGenerator::InitFreetypeFromFile(FreetypeHandle*& ft, FontHandle*& font, - const std::string& fontFile) - { - ft = initializeFreetype(); - if (!ft) { throw std::runtime_error("Failed to initialize freetype"); } - font = loadFont(ft, fontFile.data()); - if (!font) - { - deinitializeFreetype(ft); - ft = nullptr; - throw std::runtime_error(fmt::format("Failed to load font from file {0}", fontFile.data())); - } - } - - template - void FontAtlasGenerator::InitFreetypeFromBuffer(FreetypeHandle*& ft, FontHandle*& font, - const msdfgen::byte* fontData, int length) - { - ft = initializeFreetype(); - if (!ft) { throw std::runtime_error("Failed to initialize freetype"); } - font = loadFontData(ft, fontData, length); - if (!font) - { - deinitializeFreetype(ft); - ft = nullptr; - throw std::runtime_error("Failed to load font data from given buffer"); - } - } - - template - void FontAtlasGenerator::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. - FontGeometry fontGeometry(&glyphsGeometry); - fontGeometry.loadCharset(font, 1, chset); - - if constexpr (Channels == 3) - { - const double maxCornerAngle = 3.0; - for (GlyphGeometry& glyph: glyphsGeometry) - glyph.edgeColoring(&msdfgen::edgeColoringByDistance, maxCornerAngle, 0); - } - - TightAtlasPacker packer; - packer.setDimensionsConstraint(DimensionsConstraint::SQUARE); - int width, height; - const int glyphsPerRow = std::sqrt(glyphsGeometry.size()); - const int glyphSize = m_config.glyphSize; - const int rowWidth = glyphSize * glyphsPerRow; - packer.setDimensions(rowWidth, rowWidth); - // something to play with. should not be too high. - // more value - more sdf impact - packer.setPixelRange(m_config.pixelRange); - packer.setMiterLimit(m_config.miterLimit); - packer.pack(glyphsGeometry.data(), glyphsGeometry.size()); - packer.getDimensions(width, height); - - Generator generator; - generator.resize(width, height); - GeneratorAttributes attributes; - generator.setAttributes(attributes); - generator.setThreadCount(4); - generator.generate(glyphsGeometry.data(), glyphsGeometry.size()); - - int idx = 0; - if constexpr (Channels == 3) - { - // store RGB as RGBA - m_atlasData->img = std::make_unique(); - m_atlasData->img->data = Array(width * height * 4); - m_atlasData->img->resolution = Math::Vector3ui(width, height, 1); - m_atlasData->img->dataFormat = OpenVulkano::DataFormat::R8G8B8A8_UNORM; - const BitmapConstRef storage = generator.atlasStorage(); - msdfgen::byte* data = static_cast(m_atlasData->img->data.Data()); - for (size_t srcPos = 0, dstPos = 0; srcPos < width * height * 3; srcPos += 3, dstPos += 4) - { - data[dstPos] = storage.pixels[srcPos]; - data[dstPos + 1] = storage.pixels[srcPos + 1]; - data[dstPos + 2] = storage.pixels[srcPos + 2]; - data[dstPos + 3] = 255; - } - } - else - { - m_atlasData->img = std::make_unique(); - m_atlasData->img->data = Array(width * height); - m_atlasData->img->resolution = Math::Vector3ui(width, height, 1); - m_atlasData->img->dataFormat = OpenVulkano::DataFormat::R8_UNORM; - const msdfgen::BitmapConstRef& storage = generator.atlasStorage(); - memcpy(m_atlasData->img->data.Data(), storage.pixels, width * height); - } - - m_atlasData->texture.resolution = m_atlasData->img->resolution; - m_atlasData->texture.textureBuffer = m_atlasData->img->data.Data(); - m_atlasData->texture.format = m_atlasData->img->dataFormat; - m_atlasData->texture.size = width * height * channelsCount; - m_atlasData->meta.lineHeight = fontGeometry.getMetrics().lineHeight; - m_atlasData->meta.atlasType = channelsCount == 1 ? FontAtlasType::SDF : FontAtlasType::MSDF; - - struct Bbox - { - double l = 0, r = 0, t = 0, b = 0; - }; - - for (const auto& glyph: glyphsGeometry) - { - GlyphInfo& info = m_atlasData->glyphs[glyph.getCodepoint()]; - const GlyphBox& glyphBox = 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 = 0; - info.uv[0].x = l / m_atlasData->texture.resolution.x; - info.uv[0].y = b / m_atlasData->texture.resolution.y; - - info.xyz[1].x = bearingX + w; - info.xyz[1].y = h - bearingY; - info.xyz[1].z = 0; - info.uv[1].x = r / m_atlasData->texture.resolution.x; - info.uv[1].y = b / m_atlasData->texture.resolution.y; - - info.xyz[2].x = bearingX + w; - info.xyz[2].y = bearingY; //h - bearingY + h; - info.xyz[2].z = 0; - info.uv[2].x = r / m_atlasData->texture.resolution.x; - info.uv[2].y = t / m_atlasData->texture.resolution.y; - - info.xyz[3].x = bearingX; - info.xyz[3].y = bearingY; - info.xyz[3].z = 0; - info.uv[3].x = l / m_atlasData->texture.resolution.x; - info.uv[3].y = t / m_atlasData->texture.resolution.y; - - info.advance = glyphBox.advance; - } - - if (pngOutput && !pngOutput->empty()) { SavePng(pngOutput.value()); } - destroyFont(font); - deinitializeFreetype(ft); - } - - template - void FontAtlasGenerator::SavePng(const std::string& output) const - { - stbi_flip_vertically_on_write(1); - if (std::filesystem::path(output).extension() == ".png") - { - stbi_write_png(output.c_str(), m_atlasData->img->resolution.x, m_atlasData->img->resolution.y, - channelsCount, m_atlasData->img->data.Data(), channelsCount * m_atlasData->img->resolution.x); - } - else - { - stbi_write_png((output + ".png").c_str(), m_atlasData->img->resolution.x, m_atlasData->img->resolution.y, - channelsCount, m_atlasData->img->data.Data(), channelsCount * m_atlasData->img->resolution.x); - } - } - template class FontAtlasGenerator<1>; - template class FontAtlasGenerator<3>; -} -#endif \ No newline at end of file diff --git a/openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp b/openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp new file mode 100644 index 0000000..b1218af --- /dev/null +++ b/openVulkanoCpp/Scene/FontAtlasGeneratorBase.cpp @@ -0,0 +1,174 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include "FontAtlasGeneratorBase.hpp" +#include "Base/Logger.hpp" +#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 +{ + std::pair + FontAtlasGeneratorBase::InitFreetype(const std::variant>& source) + { + FT_Library library; + auto error = FT_Init_FreeType(&library); + if (error) + { + throw std::runtime_error(fmt::format("Could not initalize freetype library. {}", GetFreetypeErrorDescription(error))); + } + FT_Face face; + if (std::holds_alternative(source)) + { + error = FT_New_Face(library, std::get<0>(source).c_str(), 0, &face); + } + else + { + auto& arr = std::get<1>(source); + error = FT_New_Memory_Face(library, (const FT_Byte*) (arr.Data()), arr.Size(), 0, &face); + } + if (error == FT_Err_Unknown_File_Format) + { + throw std::runtime_error("Unknown font file format\n"); + } + else if (error) + { + throw std::runtime_error(fmt::format("Font file could not be opened or read or it's corrupted. {}", GetFreetypeErrorDescription(error))); + } + + // some fancy font without unicode charmap + if (face->charmap == nullptr) + { + throw std::runtime_error("Selected font doesn't contain unicode charmap"); + } + return { FtLibraryRecPtr(library), FtFaceRecPtr(face) }; + } + + std::string FontAtlasGeneratorBase::GetFreetypeErrorDescription(FT_Error error) + { + if (const char* s = FT_Error_String(error)) + { + return s; + } + 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 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.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.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.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.advance = advance; + } + + std::set FontAtlasGeneratorBase::LoadAllGlyphs(const std::variant>& data) + { + const auto& [lib, face] = InitFreetype(data); + std::set s; + FT_UInt glyphIndex; + FT_ULong unicode = FT_Get_First_Char(face.get(), &glyphIndex); + while (glyphIndex != 0) + { + s.insert(unicode); + unicode = FT_Get_Next_Char(face.get(), unicode, &glyphIndex); + } + return s; + } + +} diff --git a/openVulkanoCpp/Scene/FontAtlasGeneratorBase.hpp b/openVulkanoCpp/Scene/FontAtlasGeneratorBase.hpp new file mode 100644 index 0000000..c4eae22 --- /dev/null +++ b/openVulkanoCpp/Scene/FontAtlasGeneratorBase.hpp @@ -0,0 +1,36 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include "IFontAtlasGenerator.hpp" +#include "Math/AABB.hpp" +#include "FreetypeHelper.hpp" +#include +#include + +namespace OpenVulkano::Scene +{ + class FontAtlasGeneratorBase : public IFontAtlasGenerator + { + public: + FontAtlasGeneratorBase(int channelsCount) : m_channelsCount(channelsCount) {} + void SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile = true) const override; + std::shared_ptr GetAtlasData() const { return m_atlasData; } + int GetAtlasChannelsCount() const { return m_channelsCount; } + static std::set LoadAllGlyphs(const std::variant>& data); + + protected: + void SavePng(std::string output) const; + void SetupAtlasData(Math::Vector2ui textureResolution, double lineHeight, FontAtlasType::Type atlasType); + void SetGlyphData(GlyphInfo& info, Math::Vector2d bearing, Math::Vector2d size, const Math::AABB& aabb, double advance); + static std::string GetFreetypeErrorDescription(FT_Error error); + static std::pair InitFreetype(const std::variant>& source); + protected: + int m_channelsCount; + std::shared_ptr m_atlasData; + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/FreetypeHelper.hpp b/openVulkanoCpp/Scene/FreetypeHelper.hpp new file mode 100644 index 0000000..7860f50 --- /dev/null +++ b/openVulkanoCpp/Scene/FreetypeHelper.hpp @@ -0,0 +1,28 @@ +/* + * 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 +#include FT_FREETYPE_H +#include + +namespace OpenVulkano::Scene +{ + struct LibDeleter + { + void operator()(FT_Library lib) { FT_Done_FreeType(lib); } + }; + + struct FaceDeleter + { + void operator()(FT_Face face) { FT_Done_Face(face); } + }; + + using FtLibraryRecPtr = std::unique_ptr; + using FtFaceRecPtr = std::unique_ptr; + +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp b/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp index bd641c1..cdb4094 100644 --- a/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp +++ b/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp @@ -21,7 +21,7 @@ namespace OpenVulkano::Scene public: virtual void GenerateAtlas(const std::string& fontFile, const std::set& charset, const std::optional& pngOutput = std::nullopt) = 0; - virtual void GenerateAtlas(const Array& fontData, int length, const std::set& charset, + virtual void GenerateAtlas(const Array& fontData, const std::set& charset, const std::optional& pngOutput = std::nullopt) = 0; virtual void SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile = true) const = 0; virtual std::shared_ptr GetAtlasData() const = 0; diff --git a/openVulkanoCpp/Scene/SceneIntersectionTestController.cpp b/openVulkanoCpp/Scene/SceneIntersectionTestController.cpp index bb18d68..aa2a3bb 100644 --- a/openVulkanoCpp/Scene/SceneIntersectionTestController.cpp +++ b/openVulkanoCpp/Scene/SceneIntersectionTestController.cpp @@ -59,10 +59,10 @@ namespace OpenVulkano::Scene m_actionClick->BindKey(Input::InputKey::Touch::BUTTON_TAP); m_actionClickX = input->GetAction("ClickIntersectionPosX"); - m_actionClickX->BindKey(Input::InputKey::Touch::Axis::AXIS_TAP_X); + m_actionClickX->BindKey(Input::InputKey::Touch::Axis::AXIS_TAP_X_ABS); m_actionClickX->BindKey(Input::InputKey::Mouse::Axis::AXIS_X_ABS); m_actionClickY = input->GetAction("ClickIntersectionPosY"); - m_actionClickY->BindKey(Input::InputKey::Touch::Axis::AXIS_TAP_Y); + m_actionClickY->BindKey(Input::InputKey::Touch::Axis::AXIS_TAP_Y_ABS); m_actionClickY->BindKey(Input::InputKey::Mouse::Axis::AXIS_Y_ABS); } -} \ No newline at end of file +} diff --git a/openVulkanoCpp/Scene/SdfFontAtlasGenerator.cpp b/openVulkanoCpp/Scene/SdfFontAtlasGenerator.cpp new file mode 100644 index 0000000..4ca0e97 --- /dev/null +++ b/openVulkanoCpp/Scene/SdfFontAtlasGenerator.cpp @@ -0,0 +1,206 @@ +/* + * 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/. + */ + +#if __has_include("msdfgen.h") + +#include "SdfFontAtlasGenerator.hpp" +#include "Base/Logger.hpp" +#include +#include +#include + +namespace OpenVulkano::Scene +{ + using namespace msdfgen; + using namespace msdf_atlas; + + SdfFontAtlasGeneratorConfig SdfFontAtlasGeneratorConfig::sdfDefaultConfig = { 42, 1.0, 5 }; + SdfFontAtlasGeneratorConfig SdfFontAtlasGeneratorConfig::msdfDefaultConfig = { 32, 1.0, 3 }; + + template + void SdfFontAtlasGeneratorGeneric::GenerateAtlas(const std::string& fontFile, const std::set& charset, + const std::optional& pngOutput) + { + FreetypeHandle* ft; + FontHandle* font; + InitFreetypeFromFile(ft, font, fontFile); + Charset s; + std::for_each(s.begin(), s.end(), [&](uint32_t unicode) { s.add(unicode); }); + Generate(ft, font, s, pngOutput); + } + + template SdfFontAtlasGeneratorGeneric::SdfFontAtlasGeneratorGeneric() : FontAtlasGeneratorBase(Channels) + { + if constexpr (Channels == 1) m_config = SdfFontAtlasGeneratorConfig::sdfDefaultConfig; + else m_config = SdfFontAtlasGeneratorConfig::msdfDefaultConfig; + } + + template + void SdfFontAtlasGeneratorGeneric::GenerateAtlas(const Array& fontData, const std::set& charset, + const std::optional& pngOutput) + { + FreetypeHandle* ft; + FontHandle* font; + InitFreetypeFromBuffer(ft, font, (const msdfgen::byte*)(fontData.Data()), fontData.Size()); + Charset s; + std::for_each(s.begin(), s.end(), [&](uint32_t unicode) { s.add(unicode); }); + Generate(ft, font, s, pngOutput); + } + + template + void SdfFontAtlasGeneratorGeneric::GenerateAtlas(const std::string& fontFile, const Charset& charset, + const std::optional& pngOutput) + { + // TODO: dynamic atlas and add only those symbols which are not present yet in current atlas + FreetypeHandle* ft; + FontHandle* font; + InitFreetypeFromFile(ft, font, fontFile); + Generate(ft, font, charset, pngOutput); + } + + template + void SdfFontAtlasGeneratorGeneric::GenerateAtlas(const msdfgen::byte* fontData, int length, + const Charset& charset, + const std::optional& pngOutput) + { + FreetypeHandle* ft; + FontHandle* font; + InitFreetypeFromBuffer(ft, font, fontData, length); + Generate(ft, font, charset, pngOutput); + } + + template + void SdfFontAtlasGeneratorGeneric::InitFreetypeFromFile(FreetypeHandle*& ft, FontHandle*& font, + const std::string& fontFile) + { + ft = initializeFreetype(); + if (!ft) { throw std::runtime_error("Failed to initialize freetype"); } + font = loadFont(ft, fontFile.data()); + if (!font) + { + deinitializeFreetype(ft); + ft = nullptr; + throw std::runtime_error(fmt::format("Failed to load font from file {0}", fontFile.data())); + } + } + + template + void SdfFontAtlasGeneratorGeneric::InitFreetypeFromBuffer(FreetypeHandle*& ft, FontHandle*& font, + const msdfgen::byte* fontData, int length) + { + ft = initializeFreetype(); + if (!ft) { throw std::runtime_error("Failed to initialize freetype"); } + font = loadFontData(ft, fontData, length); + if (!font) + { + deinitializeFreetype(ft); + ft = nullptr; + throw std::runtime_error("Failed to load font data from given buffer"); + } + } + + template + 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. + FontGeometry fontGeometry(&glyphsGeometry); + fontGeometry.loadCharset(font, 1, chset); + + if constexpr (Channels == 3) + { + const double maxCornerAngle = 3.0; + for (GlyphGeometry& glyph: glyphsGeometry) + glyph.edgeColoring(&msdfgen::edgeColoringByDistance, maxCornerAngle, 0); + } + + TightAtlasPacker packer; + packer.setDimensionsConstraint(DimensionsConstraint::SQUARE); + int width, height; + const int glyphsPerRow = std::sqrt(glyphsGeometry.size()); + const int glyphSize = m_config.glyphSize; + const int rowWidth = glyphSize * glyphsPerRow; + packer.setDimensions(rowWidth, rowWidth); + // something to play with. should not be too high. + // more value - more sdf impact + packer.setPixelRange(m_config.pixelRange); + packer.setMiterLimit(m_config.miterLimit); + packer.pack(glyphsGeometry.data(), glyphsGeometry.size()); + packer.getDimensions(width, height); + + Generator generator; + generator.resize(width, height); + GeneratorAttributes attributes; + generator.setAttributes(attributes); + generator.setThreadCount(4); + generator.generate(glyphsGeometry.data(), glyphsGeometry.size()); + + int idx = 0; + SetupAtlasData(Math::Vector3ui(width, height, 1), fontGeometry.getMetrics().lineHeight, + channelsCount == 1 ? FontAtlasType::SDF : FontAtlasType::MSDF); + + if constexpr (Channels == 3) + { + // store RGB as RGBA + const BitmapConstRef storage = generator.atlasStorage(); + msdfgen::byte* data = static_cast(m_atlasData->img->data.Data()); + for (size_t srcPos = 0, dstPos = 0; srcPos < width * height * 3; srcPos += 3, dstPos += 4) + { + data[dstPos] = storage.pixels[srcPos]; + data[dstPos + 1] = storage.pixels[srcPos + 1]; + data[dstPos + 2] = storage.pixels[srcPos + 2]; + data[dstPos + 3] = 255; + } + } + else + { + const msdfgen::BitmapConstRef& storage = generator.atlasStorage(); + memcpy(m_atlasData->img->data.Data(), storage.pixels, width * height); + } + + struct Bbox + { + double l = 0, r = 0, t = 0, b = 0; + }; + + for (const auto& glyph: glyphsGeometry) + { + GlyphInfo& info = m_atlasData->glyphs[glyph.getCodepoint()]; + const GlyphBox& glyphBox = 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; + + Math::AABB glyphAtlasAABB; + glyphAtlasAABB.min.x = l; + glyphAtlasAABB.min.y = b; + glyphAtlasAABB.max.x = r; + glyphAtlasAABB.max.y = t; + SetGlyphData(info, { bearingX, bearingY }, { w, h }, glyphAtlasAABB, glyphBox.advance); + } + + if (pngOutput && !pngOutput->empty()) { SavePng(pngOutput.value()); } + destroyFont(font); + deinitializeFreetype(ft); + } + + template class SdfFontAtlasGeneratorGeneric<1>; + template class SdfFontAtlasGeneratorGeneric<3>; +} +#endif \ No newline at end of file diff --git a/openVulkanoCpp/Scene/FontAtlasGenerator.hpp b/openVulkanoCpp/Scene/SdfFontAtlasGenerator.hpp similarity index 72% rename from openVulkanoCpp/Scene/FontAtlasGenerator.hpp rename to openVulkanoCpp/Scene/SdfFontAtlasGenerator.hpp index bac326c..fdaabb3 100644 --- a/openVulkanoCpp/Scene/FontAtlasGenerator.hpp +++ b/openVulkanoCpp/Scene/SdfFontAtlasGenerator.hpp @@ -8,9 +8,7 @@ #if __has_include("msdfgen.h") -#include "Scene/AtlasData.hpp" -#include "IFontAtlasGenerator.hpp" -#include "Scene/Texture.hpp" +#include "FontAtlasGeneratorBase.hpp" #include #include #include @@ -22,17 +20,17 @@ namespace OpenVulkano::Scene { - struct FontAtlasGeneratorConfig + struct SdfFontAtlasGeneratorConfig { - static FontAtlasGeneratorConfig sdfDefaultConfig; - static FontAtlasGeneratorConfig msdfDefaultConfig; + static SdfFontAtlasGeneratorConfig sdfDefaultConfig; + static SdfFontAtlasGeneratorConfig msdfDefaultConfig; int glyphSize; double miterLimit; msdfgen::Range pixelRange; }; template - class FontAtlasGenerator : public IFontAtlasGenerator + class SdfFontAtlasGeneratorGeneric final : public FontAtlasGeneratorBase { private: using SdfGenerator = msdf_atlas::ImmediateAtlasGenerator>; public: using Generator = std::conditional::type; - using Config = FontAtlasGeneratorConfig; + using Config = SdfFontAtlasGeneratorConfig; static constexpr int channelsCount = (Channels == 1 ? 1 : 4); - static msdf_atlas::Charset LoadAllGlyphs(const std::variant>& data); - FontAtlasGenerator(); + SdfFontAtlasGeneratorGeneric(); void GenerateAtlas(const std::string& fontFile, const std::set& charset, const std::optional& pngOutput = std::nullopt) override; - void GenerateAtlas(const Array& fontData, int length, const std::set& charset, + void GenerateAtlas(const Array& fontData, const std::set& charset, const std::optional& pngOutput = std::nullopt) override; void GenerateAtlas(const std::string& fontFile, const msdf_atlas::Charset& charset = msdf_atlas::Charset::ASCII, const std::optional& pngOutput = std::nullopt); void GenerateAtlas(const msdfgen::byte* fontData, int length, const msdf_atlas::Charset& charset = msdf_atlas::Charset::ASCII, const std::optional& pngOutput = std::nullopt); - void SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile = true) const override; void SetGeneratorConfig(const Config& config) { m_config = config; } - std::shared_ptr GetAtlasData() const { return m_atlasData; } Config& GetGeneratorConfig() { return m_config; } private: void InitFreetypeFromFile(msdfgen::FreetypeHandle*& ft, msdfgen::FontHandle*& font, const std::string& file); @@ -64,13 +59,10 @@ namespace OpenVulkano::Scene const msdfgen::byte* fontData, int length); void Generate(msdfgen::FreetypeHandle* ft, msdfgen::FontHandle* font, const msdf_atlas::Charset& chset, const std::optional& pngOutput); - void SavePng(const std::string& output) const; - private: Config m_config; - std::shared_ptr m_atlasData; }; - using SdfFontAtlasGenerator = FontAtlasGenerator<1>; - using MsdfFontAtlasGenerator = FontAtlasGenerator<3>; + using SdfFontAtlasGenerator = SdfFontAtlasGeneratorGeneric<1>; + using MsdfFontAtlasGenerator = SdfFontAtlasGeneratorGeneric<3>; } #endif diff --git a/openVulkanoCpp/Scene/TextDrawable.cpp b/openVulkanoCpp/Scene/TextDrawable.cpp index be09db9..adffe2b 100644 --- a/openVulkanoCpp/Scene/TextDrawable.cpp +++ b/openVulkanoCpp/Scene/TextDrawable.cpp @@ -26,8 +26,8 @@ namespace OpenVulkano::Scene static Shader sdfDefaultShader; if (once) { - sdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/text"); - sdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/text"); + sdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/sdfText"); + sdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/sdfText"); sdfDefaultShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); sdfDefaultShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING); DescriptorSetLayoutBinding desc = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING; @@ -46,7 +46,7 @@ namespace OpenVulkano::Scene static Shader msdfDefaultShader; if (once) { - msdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/text"); + msdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/sdfText"); msdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/msdfText"); msdfDefaultShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); msdfDefaultShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING); @@ -60,6 +60,26 @@ namespace OpenVulkano::Scene return msdfDefaultShader; } + Shader& TextDrawable::GetBitmapDefaultShader() + { + static bool once = true; + static Shader bitmapDefaultShader; + if (once) + { + bitmapDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/text"); + bitmapDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/text"); + bitmapDefaultShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); + bitmapDefaultShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING); + DescriptorSetLayoutBinding desc = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING; + desc.stageFlags = ShaderProgramType::FRAGMENT; + bitmapDefaultShader.AddDescriptorSetLayoutBinding(desc); + bitmapDefaultShader.alphaBlend = true; + bitmapDefaultShader.cullMode = CullMode::NONE; + once = false; + } + return bitmapDefaultShader; + } + TextDrawable::TextDrawable(const TextConfig& config) { m_cfg = config; @@ -129,6 +149,10 @@ namespace OpenVulkano::Scene m_cfg = config; m_uniBuffer.Init(sizeof(TextConfig), &m_cfg, 3); m_uniBuffer.binding.stageFlags = ShaderProgramType::FRAGMENT; + if (m_atlasData->meta.atlasType == FontAtlasType::BITMAP) + { + m_material.texture->m_samplerConfig = &SamplerConfig::NEAREST; + } } TextDrawable::TextDrawable(const std::shared_ptr& atlasData, const TextConfig& config) diff --git a/openVulkanoCpp/Scene/TextDrawable.hpp b/openVulkanoCpp/Scene/TextDrawable.hpp index 3896651..f8d6e3f 100644 --- a/openVulkanoCpp/Scene/TextDrawable.hpp +++ b/openVulkanoCpp/Scene/TextDrawable.hpp @@ -38,6 +38,7 @@ namespace OpenVulkano::Scene public: static Shader& GetSdfDefaultShader(); static Shader& GetMsdfDefaultShader(); + static Shader& GetBitmapDefaultShader(); TextDrawable(const TextConfig& config = TextConfig()); TextDrawable(const Array& atlasMetadata, const TextConfig& config = TextConfig()); TextDrawable(const std::string& atlasMetadataFile, const TextConfig& config = TextConfig()); diff --git a/openVulkanoCpp/Shader/sdfText.frag b/openVulkanoCpp/Shader/sdfText.frag new file mode 100644 index 0000000..579d9c7 --- /dev/null +++ b/openVulkanoCpp/Shader/sdfText.frag @@ -0,0 +1,39 @@ +#version 450 + +layout(location = 1) in vec2 texCoord; + +layout(location = 0) out vec4 outColor; + +layout(set = 2, binding = 0) uniform sampler2D texSampler; + +layout(set = 3, binding = 0) uniform TextConfig +{ + vec4 textColor; + vec4 borderColor; + vec4 backgroundColor; + float threshold; + float borderSize; + float smoothing; + bool applyBorder; +} textConfig; + +void main() +{ + float distance = texture(texSampler, texCoord).r; + float alpha = smoothstep(textConfig.threshold - textConfig.smoothing, textConfig.threshold + textConfig.smoothing, distance); + if (textConfig.applyBorder) + { + float border = smoothstep(textConfig.threshold + textConfig.borderSize - textConfig.smoothing, + textConfig.threshold + textConfig.borderSize + textConfig.smoothing, distance); + outColor = mix(textConfig.borderColor, textConfig.textColor, border) * alpha; + } + else + { + outColor = vec4(textConfig.textColor) * alpha; + } + + if (textConfig.backgroundColor.a != 0) + { + outColor = mix(textConfig.backgroundColor, outColor, alpha); + } +} diff --git a/openVulkanoCpp/Shader/sdfText.vert b/openVulkanoCpp/Shader/sdfText.vert new file mode 100644 index 0000000..c259c65 --- /dev/null +++ b/openVulkanoCpp/Shader/sdfText.vert @@ -0,0 +1,26 @@ +#version 450 +layout(location = 0) in vec3 position; +layout(location = 1) in vec3 normal; +layout(location = 2) in vec3 tangent; +layout(location = 3) in vec3 biTangent; +layout(location = 4) in vec3 textureCoordinates; +layout(location = 5) in vec4 color; +layout(location = 1) out vec2 fragTextureCoordinates; + +layout(set = 0, binding = 0) uniform NodeData +{ + mat4 world; +} node; + +layout(set = 1, binding = 0) uniform CameraData +{ + mat4 viewProjection; + mat4 view; + mat4 projection; + vec4 camPos; +} cam; + +void main() { + gl_Position = cam.viewProjection * node.world * vec4(position, 1.0); + fragTextureCoordinates.xy = textureCoordinates.xy; +} diff --git a/openVulkanoCpp/Shader/text.frag b/openVulkanoCpp/Shader/text.frag index 5333c6f..b906e40 100644 --- a/openVulkanoCpp/Shader/text.frag +++ b/openVulkanoCpp/Shader/text.frag @@ -1,5 +1,6 @@ #version 450 +layout(location = 0) in vec4 color; layout(location = 1) in vec2 texCoord; layout(location = 0) out vec4 outColor; @@ -12,28 +13,13 @@ layout(set = 3, binding = 0) uniform TextConfig vec4 borderColor; vec4 backgroundColor; float threshold; - float borderSize; - float smoothing; - bool applyBorder; + float borderSize; + float smoothing; + bool applyBorder; } textConfig; void main() { - float distance = texture(texSampler, texCoord).r; - float alpha = smoothstep(textConfig.threshold - textConfig.smoothing, textConfig.threshold + textConfig.smoothing, distance); - if (textConfig.applyBorder) - { - float border = smoothstep(textConfig.threshold + textConfig.borderSize - textConfig.smoothing, - textConfig.threshold + textConfig.borderSize + textConfig.smoothing, distance); - outColor = mix(textConfig.borderColor, textConfig.textColor, border) * alpha; - } - else - { - outColor = vec4(textConfig.textColor) * alpha; - } - - if (textConfig.backgroundColor.a != 0) - { - outColor = mix(textConfig.backgroundColor, outColor, alpha); - } + vec4 sampled = vec4(1.0, 1.0, 1.0, texture(texSampler, texCoord).r); + outColor = vec4(textConfig.textColor) * sampled; } diff --git a/openVulkanoCpp/Shader/text.vert b/openVulkanoCpp/Shader/text.vert index 1e9ef24..47f4b61 100644 --- a/openVulkanoCpp/Shader/text.vert +++ b/openVulkanoCpp/Shader/text.vert @@ -5,6 +5,8 @@ layout(location = 2) in vec3 tangent; layout(location = 3) in vec3 biTangent; layout(location = 4) in vec3 textureCoordinates; layout(location = 5) in vec4 color; + +layout(location = 0) out vec4 outColor; layout(location = 1) out vec2 fragTextureCoordinates; layout(set = 0, binding = 0) uniform NodeData @@ -23,4 +25,5 @@ layout(set = 1, binding = 0) uniform CameraData void main() { gl_Position = cam.viewProjection * node.world * vec4(position, 1.0); fragTextureCoordinates.xy = textureCoordinates.xy; + outColor = color; } diff --git a/openVulkanoCpp/Vulkan/DepthBufferQuery.cpp b/openVulkanoCpp/Vulkan/DepthBufferQuery.cpp index 98e200e..fd1a48f 100644 --- a/openVulkanoCpp/Vulkan/DepthBufferQuery.cpp +++ b/openVulkanoCpp/Vulkan/DepthBufferQuery.cpp @@ -12,11 +12,19 @@ namespace OpenVulkano::Vulkan { + namespace + { + constexpr uint32_t SAMPLE_SIZE_WIDTH = 5; + constexpr uint32_t SAMPLE_SIZE_HEIGHT = 5; + constexpr uint32_t SAMPLE_SIZE = SAMPLE_SIZE_WIDTH * SAMPLE_SIZE_HEIGHT; + constexpr uint32_t SAMPLE_CENTER = SAMPLE_SIZE / 2; + } + void DepthBufferQuery::Init() { auto device = renderer.GetContext().device->device; - vk::BufferCreateInfo bufferInfo = { {}, 25 * sizeof(float), vk::BufferUsageFlagBits::eTransferDst }; + vk::BufferCreateInfo bufferInfo = { {}, SAMPLE_SIZE * sizeof(float), vk::BufferUsageFlagBits::eTransferDst }; bufferDepth = device.createBuffer(bufferInfo); const vk::MemoryRequirements memRequirements = device.getBufferMemoryRequirements(bufferDepth); size_t size = memRequirements.size; @@ -45,32 +53,27 @@ namespace OpenVulkano::Vulkan float DepthBufferQuery::GetQueriedValue() const { if (cpuDepthBuffer[0] == -2) return -2; - if (cpuDepthBuffer[12] > 0 && cpuDepthBuffer[12] < 1) return cpuDepthBuffer[12]; - double val = 0; - int validCount = 0; - for (int i = 0; i < 25; i++) + if (cpuDepthBuffer[SAMPLE_CENTER] > 0 && cpuDepthBuffer[SAMPLE_CENTER] < 1) return cpuDepthBuffer[SAMPLE_CENTER]; + std::sort(cpuDepthBuffer, cpuDepthBuffer + SAMPLE_SIZE); + uint32_t start = UINT32_MAX, end = UINT32_MAX; + for (uint32_t i = 0; i < SAMPLE_SIZE; i++) { float f = cpuDepthBuffer[i]; - if (f > 0 && f < 1) - { - val += f; - validCount++; - } + if (f > 0 && start == UINT32_MAX) start = i; + if (f < 1 && end == UINT32_MAX) end = i; + else if (f >= 1) break; } - if (validCount == 0) return 1; // Prevent divide by 0 - return val / validCount; + if (start == UINT32_MAX || end == UINT32_MAX) return cpuDepthBuffer[SAMPLE_CENTER]; + return cpuDepthBuffer[(start + end) / 2]; } vk::Offset3D DepthBufferQuery::GetCopyOffset() const { vk::Extent3D depthExtent = renderer.GetContext().swapChain.GetCurrentDepthBuffer().extent; - int32_t x = static_cast(depthQueryCoordinates.x * depthExtent.width); - x = std::min(depthExtent.width - 5, std::max(0, x)); - + x = std::min(depthExtent.width - SAMPLE_SIZE_WIDTH, std::max(0, x)); int32_t y = static_cast(depthQueryCoordinates.y * depthExtent.height); - y = std::min(depthExtent.height - 5, std::max(0, y)); - + y = std::min(depthExtent.height - SAMPLE_SIZE_HEIGHT, std::max(0, y)); return { x, y, 0 }; } @@ -78,13 +81,13 @@ namespace OpenVulkano::Vulkan { if (!copyDepthBuffer) return; copyDepthBuffer = false; - std::fill(cpuDepthBuffer, cpuDepthBuffer + 25, -2.0f); // Invalidate data in buffer to allow detecting if copy is done + std::fill(cpuDepthBuffer, cpuDepthBuffer + SAMPLE_SIZE, -2.0f); // Invalidate data in buffer to allow detecting if copy is done const vk::ImageAspectFlags aspectMask = vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil; const Image& depthBufferImage = renderer.GetContext().swapChain.GetCurrentDepthBuffer(); - constexpr vk::Extent3D copySize = { 5, 5, 1 }; + constexpr vk::Extent3D copySize = { SAMPLE_SIZE_WIDTH, SAMPLE_SIZE_HEIGHT, 1 }; const vk::ImageSubresourceLayers layout = { vk::ImageAspectFlagBits::eDepth, 0, 0, 1 }; - vk::BufferImageCopy imgCopy = { 0, 5, 5, layout, GetCopyOffset(), copySize }; + vk::BufferImageCopy imgCopy = { 0, 0, 0, layout, GetCopyOffset(), copySize }; const vk::ImageMemoryBarrier imgMemBarrier({}, vk::AccessFlagBits::eTransferRead, vk::ImageLayout::eDepthStencilAttachmentOptimal, vk::ImageLayout::eTransferSrcOptimal, 0, 0, depthBufferImage.image, vk::ImageSubresourceRange(aspectMask, 0, 1, 0, 1)); commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTransfer, {}, nullptr, nullptr, imgMemBarrier); diff --git a/tests/Base/UtilsTest.cpp b/tests/Base/UtilsTest.cpp index 46078e4..69d93a1 100644 --- a/tests/Base/UtilsTest.cpp +++ b/tests/Base/UtilsTest.cpp @@ -259,4 +259,10 @@ TEST_CASE("Split with non-null-terminated strings", "[Utils]") input = { ",,,|", 3 }; result = Utils::Split(input, ','); REQUIRE(result.empty()); +} + +TEST_CASE("Read file from string literal", "[Utils]") +{ + // all we need is to check if it compiles + REQUIRE(Utils::ReadFile("", true).Empty()); } \ No newline at end of file diff --git a/tests/Host/WebResourceLoaderTest.cpp b/tests/Host/WebResourceLoaderTest.cpp index 04c4a87..caac1aa 100644 --- a/tests/Host/WebResourceLoaderTest.cpp +++ b/tests/Host/WebResourceLoaderTest.cpp @@ -71,16 +71,16 @@ TEST_CASE("GetCacheFilePath", "[WebResourceLoader]") REQUIRE(cachePath.parent_path().filename().string() == "resources"); } -TEST_CASE("DownloadResource from non-ssl uri", "[WebResourceLoader]") -{ - TestWebResourceLoader loader; - std::string url = "http://neverssl.com"; - Array resourceData = loader.DownloadResource(url); - REQUIRE(!resourceData.Empty()); - std::filesystem::path cachePath = loader.GetCacheFilePath(url); - REQUIRE(std::filesystem::exists(cachePath)); - std::filesystem::remove(cachePath); -} +//TEST_CASE("DownloadResource from non-ssl uri", "[WebResourceLoader]") +//{ +// TestWebResourceLoader loader; +// std::string url = "http://neverssl.com"; +// Array resourceData = loader.DownloadResource(url); +// REQUIRE(!resourceData.Empty()); +// std::filesystem::path cachePath = loader.GetCacheFilePath(url); +// REQUIRE(std::filesystem::exists(cachePath)); +// std::filesystem::remove(cachePath); +//} TEST_CASE("DownloadResource with curl", "[WebResourceLoader]") {