Merge pull request 'Atlas factory' (#189) from atlas_factory into master

Reviewed-on: https://git.madvoxel.net/OpenVulkano/OpenVulkano/pulls/189
Reviewed-by: Georg Hagen <georg.hagen@madvoxel.com>
This commit is contained in:
Oleksii_Hyzha
2025-01-21 14:31:10 +01:00
17 changed files with 613 additions and 7 deletions

View File

@@ -32,8 +32,8 @@ jobs:
- name: Install Dev Packages
if: matrix.os == 'ubuntu-latest'
run: >
sudo apt update && sudo apt install -y extra-cmake-modules libwayland-dev libxkbcommon-dev xorg-dev libarchive-dev libassimp-dev ninja-build glslang-tools glslang-dev unzip zip libcurl4-openssl-dev libfreetype-dev libjpeg-turbo8-dev nasm
&& sudo wget https://sourceforge.net/projects/bin2c/files/1.1/bin2c-1.1.zip && sudo unzip bin2c-1.1.zip && cd bin2c && sudo gcc -o bin2c bin2c.c && sudo mv bin2c /usr/bin
sudo apt update && sudo apt install -y extra-cmake-modules libwayland-dev fonts-ubuntu libxkbcommon-dev xorg-dev libarchive-dev libassimp-dev ninja-build glslang-tools glslang-dev unzip zip libcurl4-openssl-dev libfreetype-dev libjpeg-turbo8-dev nasm
&& sudo wget https://sourceforge.net/projects/bin2c/files/1.1/bin2c-1.1.zip && sudo unzip bin2c-1.1.zip && cd bin2c && sudo gcc -o bin2c bin2c.c && sudo mv bin2c /usr/bin && fc-cache -f -v
- name: Get cmake checks from cache
uses: actions/cache@v4

96
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,96 @@
{
"files.associations": {
"cctype": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"csetjmp": "cpp",
"csignal": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"*.ipp": "cpp",
"condition_variable": "cpp",
"thread": "cpp",
"*.inc": "cpp",
"any": "cpp",
"array": "cpp",
"atomic": "cpp",
"hash_map": "cpp",
"hash_set": "cpp",
"strstream": "cpp",
"barrier": "cpp",
"bit": "cpp",
"*.tcc": "cpp",
"bitset": "cpp",
"cfenv": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"cinttypes": "cpp",
"codecvt": "cpp",
"compare": "cpp",
"complex": "cpp",
"concepts": "cpp",
"coroutine": "cpp",
"cstdint": "cpp",
"deque": "cpp",
"forward_list": "cpp",
"list": "cpp",
"map": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"regex": "cpp",
"source_location": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"rope": "cpp",
"slist": "cpp",
"fstream": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"latch": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"ranges": "cpp",
"scoped_allocator": "cpp",
"semaphore": "cpp",
"shared_mutex": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"syncstream": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp",
"valarray": "cpp",
"variant": "cpp"
}
}

View File

@@ -157,6 +157,24 @@ namespace OpenVulkano
return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix);
}
static void ToLower(std::string& str)
{
std::transform(str.begin(), str.end(), str.begin(), [](char c) { return std::tolower(c); });
}
static void ToLower(const std::string& str, char* dest)
{
std::transform(str.begin(), str.end(), dest, [](char c) { return std::tolower(c); });
}
static std::string ToLower(const std::string& str)
{
std::string dest;
dest.resize(str.size());
std::transform(str.begin(), str.end(), dest.begin(), [](char c) { return std::tolower(c); });
return dest;
}
static std::pair<std::string, std::string> SplitAtLastOccurrence(const std::string& str, char splitAt)
{
size_t pos = str.rfind(splitAt);

View File

@@ -0,0 +1,56 @@
/*
* 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 "Host/SystemFontResolver.hpp"
#include "Base/Logger.hpp"
#include "Base/Utils.hpp"
#include <fontconfig/fontconfig.h>
#include <memory>
namespace OpenVulkano
{
const std::string& SystemFontResolver::GetSystemFontPath(const std::string& fontName)
{
// fontName -> fontPath
static std::map<std::string, std::string> fontFilesMapping = ReadSystemFonts();
static std::string fallbackString;
auto it = fontFilesMapping.find(Utils::ToLower(fontName));
return it == fontFilesMapping.end() ? fallbackString : it->second;
}
std::map<std::string, std::string> SystemFontResolver::ReadSystemFonts()
{
std::unique_ptr<FcConfig, decltype(&FcConfigDestroy)> config(FcInitLoadConfigAndFonts(), &FcConfigDestroy);
std::unique_ptr<FcPattern, decltype(&FcPatternDestroy)> pat(FcPatternCreate(), &FcPatternDestroy);
std::unique_ptr<FcObjectSet, decltype(&FcObjectSetDestroy)> os(FcObjectSetBuild(FC_FAMILY, FC_STYLE, FC_LANG, FC_FILE, NULL), &FcObjectSetDestroy);
std::unique_ptr<FcFontSet, decltype(&FcFontSetDestroy)> fs(FcFontList(config.get(), pat.get(), os.get()), &FcFontSetDestroy);
if (!fs)
{
Logger::DATA->warn("Could not get system fonts");
return {};
}
std::map<std::string, std::string> fontFilesMapping;
for (int i = 0; i < fs->nfont; ++i)
{
FcPattern* font = fs->fonts[i];
FcChar8* file;
FcChar8* style;
FcChar8* family;
if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch &&
FcPatternGetString(font, FC_FAMILY, 0, &family) == FcResultMatch &&
FcPatternGetString(font, FC_STYLE, 0, &style) == FcResultMatch)
{
std::string fontFull = std::string(reinterpret_cast<char*>(family)) + " " +
std::string(reinterpret_cast<char*>(style));
Utils::ToLower(fontFull);
fontFilesMapping[std::move(fontFull)] = std::string(reinterpret_cast<char*>(file));
}
}
return fontFilesMapping;
}
}

View File

@@ -0,0 +1,22 @@
/*
* 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 "Host/SystemFontResolver.hpp"
#include <filesystem>
namespace OpenVulkano
{
const std::string& SystemFontResolver::GetSystemFontPath(const std::string& fontName)
{
static std::string fallbackString;
return fallbackString;
}
std::map<std::string, std::string> SystemFontResolver::ReadSystemFonts()
{
return {};
}
}

View File

@@ -0,0 +1,22 @@
/*
* 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 <string>
#include <map>
namespace OpenVulkano
{
class SystemFontResolver
{
public:
static const std::string& GetSystemFontPath(const std::string& fontName);
private:
static std::map<std::string, std::string> ReadSystemFonts();
};
}

View File

@@ -0,0 +1,142 @@
/*
* 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 "Host/SystemFontResolver.hpp"
#include "Base/Utils.hpp"
#include "Base/Logger.hpp"
#include <Windows.h>
#include <dwrite_3.h>
#include <filesystem>
#define QFR_DESCRIPTION 1
#pragma comment(lib, "Dwrite.lib")
namespace
{
template<typename T>
struct DirectWriteAutoReleasable final
{
DirectWriteAutoReleasable() : ptr(nullptr) {}
~DirectWriteAutoReleasable()
{
if (ptr)
{
ptr->Release();
}
}
T* operator->() { return ptr; }
T* ptr;
};
}
namespace OpenVulkano
{
const std::string& SystemFontResolver::GetSystemFontPath(const std::string& fontName)
{
// font name -> filename
static std::map<std::string, std::string> fontFileMapping = ReadSystemFonts();
static std::string fallbackString;
auto it = fontFileMapping.find(Utils::ToLower(fontName));
return it == fontFileMapping.end() ? fallbackString : it->second;
}
std::map<std::string, std::string> SystemFontResolver::ReadSystemFonts()
{
DirectWriteAutoReleasable<IDWriteFactory7> dwrite;
HRESULT hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory7), (IUnknown**) &dwrite);
if (!SUCCEEDED(hr))
{
Logger::DATA->error("Could not read system fonts. DWriteCreateFactory has failed. Error code {}", hr);
return {};
}
DirectWriteAutoReleasable<IDWriteFontSet> matchingFonts;
hr = dwrite->GetSystemFontSet(&matchingFonts.ptr);
if (!SUCCEEDED(hr))
{
Logger::DATA->error("Could not read system fonts. GetSystemFontSet has failed. Error code {}", hr);
return {};
}
std::map<std::string, std::string> fontFileMapping;
const UINT32 familyCount = matchingFonts->GetFontCount();
// thank you Microsoft for function that is not even documented, but exists and it's the only function that
// can return real font name from font filename (e.g. font filename is times.ttf that corresponds to Times New Roman)
int(WINAPI* GetFontResourceInfoW)(wchar_t*, unsigned long*, void*, unsigned long);
*(FARPROC*) &GetFontResourceInfoW = GetProcAddress(GetModuleHandleA("gdi32"), "GetFontResourceInfoW");
for (UINT32 i = 0; i < familyCount; ++i)
{
DirectWriteAutoReleasable<IDWriteFontFaceReference> faceRef;
hr = matchingFonts->GetFontFaceReference(i, &faceRef.ptr);
if (!SUCCEEDED(hr))
{
continue;
}
DirectWriteAutoReleasable<IDWriteFontFile> file;
hr = faceRef->GetFontFile(&file.ptr);
if (!SUCCEEDED(hr))
{
continue;
}
DirectWriteAutoReleasable<IDWriteFontFileLoader> loader;
hr = file->GetLoader(&loader.ptr);
if (!SUCCEEDED(hr))
{
continue;
}
DirectWriteAutoReleasable<IDWriteLocalFontFileLoader> localLoader;
hr = loader->QueryInterface(&localLoader.ptr);
if (!SUCCEEDED(hr))
{
continue;
}
const void* fileKey;
UINT32 fileKeySize;
hr = file->GetReferenceKey(&fileKey, &fileKeySize);
if (!SUCCEEDED(hr))
{
continue;
}
// Get font path
WCHAR filePath[MAX_PATH];
hr = localLoader->GetFilePathFromKey(fileKey, fileKeySize, filePath, MAX_PATH);
if (!SUCCEEDED(hr))
{
continue;
}
// Get font name
unsigned long size = 0;
if (!GetFontResourceInfoW(filePath, &size, NULL, QFR_DESCRIPTION))
{
continue;
}
std::wstring fontName;
fontName.resize(size);
if (GetFontResourceInfoW(filePath, &size, fontName.data(), QFR_DESCRIPTION))
{
// remove null-terminated characters since size is always bigger than needed
std::string fontNameCropped(fontName.begin(), fontName.end());
const size_t realSize = strlen(fontNameCropped.data());
fontNameCropped.resize(realSize);
Utils::ToLower(fontNameCropped);
fontFileMapping[std::move(fontNameCropped)] = std::string(filePath, filePath + wcslen(filePath));
}
}
return fontFileMapping;
}
}

View File

@@ -0,0 +1,22 @@
/*
* 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 "Host/SystemFontResolver.hpp"
#include <filesystem>
namespace OpenVulkano
{
const std::string& SystemFontResolver::GetSystemFontPath(const std::string& fontName)
{
static std::string fallbackString;
return fallbackString;
}
std::map<std::string, std::string> SystemFontResolver::ReadSystemFonts()
{
return {};
}
}

View File

@@ -157,9 +157,8 @@ namespace OpenVulkano::Scene
+ glyph.firstGlyphByteInAtlas;
for (int row = 0; row < slot->bitmap.rows; row++)
{
std::memcpy(baseAddress + row * m_atlasData->GetTexture()->resolution.x,
&slot->bitmap.buffer[(slot->bitmap.rows - 1 - row) * slot->bitmap.pitch],
slot->bitmap.width);
std::memcpy(baseAddress - row * m_atlasData->GetTexture()->resolution.x,
&slot->bitmap.buffer[row * slot->bitmap.pitch], slot->bitmap.width);
}
}
else

View File

@@ -45,6 +45,8 @@ namespace OpenVulkano::Scene
void DeserializeMetadata(const std::span<char>& data);
public:
using Ptr = std::shared_ptr<FontAtlas>;
FontAtlas() = default;
FontAtlas(const Math::Vector2ui textureResolution, const double lineHeight, const FontAtlasType atlasType,
DataFormat dataFormat)

View File

@@ -0,0 +1,106 @@
/*
* 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 "FontAtlasFactory.hpp"
#include "Scene/SdfFontAtlasGenerator.hpp"
#include "Scene/BitmapFontAtlasGenerator.hpp"
#include "Host/SystemFontResolver.hpp"
#include "Base/Logger.hpp"
#include "Host/ResourceLoader.hpp"
#include <utility>
namespace OpenVulkano::Scene
{
FontAtlasFactory::FontIdentifier::FontIdentifier(const std::string& font_, const std::set<uint32_t>& charset,
SubpixelLayout subpixelLayout_, float ptSize_,
FontAtlasType atlasType_)
: font(font_), subpixelLayout(subpixelLayout_), ptSize(ptSize_), atlasType(atlasType_)
{
std::for_each(charset.begin(), charset.end(), [&](uint32_t c) { charsetHash ^= c; });
}
bool FontAtlasFactory::FontIdentifier::FontIdentifier::operator<(const FontIdentifier& other) const
{
return std::tie(atlasType, charsetHash, ptSize, subpixelLayout, font)
< std::tie(other.atlasType, other.charsetHash, other.ptSize, other.subpixelLayout, other.font);
}
FontAtlas::Ptr FontAtlasFactory::GetFontAtlasScalable(const std::string& fontIdentifier, bool msdf,
const std::set<uint32_t>& charset) const
{
const auto& fontData = FindFont(fontIdentifier);
if (fontData.Empty())
{
Logger::DATA->warn("Could not find font {}", fontIdentifier);
return nullptr;
}
const std::set<uint32_t>& setRef = (charset.empty() ? FontAtlasGeneratorBase::LoadAllGlyphs(fontData) : charset);
FontIdentifier id(fontIdentifier, setRef, SubpixelLayout::UNKNOWN, 0,
msdf ? FontAtlasType::MSDF : FontAtlasType::SDF);
auto it = m_atlasesCache.find(id);
if (it != m_atlasesCache.end())
{
return it->second;
}
if (msdf)
{
MsdfFontAtlasGenerator msdfGen;
msdfGen.GenerateAtlas(fontData, setRef);
return m_atlasesCache.insert({ id, msdfGen.GetAtlas() }).first->second;
}
SdfFontAtlasGenerator sdfGen;
sdfGen.GenerateAtlas(fontData, setRef);
return m_atlasesCache.insert({ id, sdfGen.GetAtlas() }).first->second;
}
FontAtlas::Ptr FontAtlasFactory::GetFontAtlas(const std::string& fontIdentifier, float ptSize,
SubpixelLayout subpixelLayout,
const std::set<uint32_t>& charset) const
{
const auto& fontData = FindFont(fontIdentifier);
if (fontData.Empty())
{
Logger::DATA->warn("Could not find font {}", fontIdentifier);
return nullptr;
}
const std::set<uint32_t>& setRef = (charset.empty() ? FontAtlasGeneratorBase::LoadAllGlyphs(fontData) : charset);
FontIdentifier id(fontIdentifier, setRef, subpixelLayout, ptSize,
subpixelLayout ? FontAtlasType::BITMAP_SUBPIXEL : FontAtlasType::BITMAP);
auto it = m_atlasesCache.find(id);
if (it != m_atlasesCache.end())
{
return it->second;
}
FontPixelSizeConfig cfg(ptSize);
BitmapFontAtlasGenerator bitmapGen(cfg, subpixelLayout);
bitmapGen.GenerateAtlas(fontData, setRef);
return m_atlasesCache.insert({ id, bitmapGen.GetAtlas() }).first->second;
}
Array<char> FontAtlasFactory::FindFont(const std::string& fontIdentifier) const
{
Array<char> resource = ResourceLoader::GetInstance().GetResource(fontIdentifier);
if (resource.Empty())
{
if (!std::filesystem::exists(fontIdentifier))
{
if (!m_allowSystemFonts)
{
return {};
}
return Utils::ReadFile(SystemFontResolver::GetSystemFontPath(fontIdentifier), true);
}
return Utils::ReadFile(fontIdentifier);
}
return resource;
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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 "FontAtlas.hpp"
#include "Scene/SubpixelLayout.hpp"
#include "Data/Containers/Array.hpp"
namespace OpenVulkano::Scene
{
class FontAtlasFactory final
{
struct FontIdentifier
{
FontIdentifier(const std::string& font_, const std::set<uint32_t>& charset, SubpixelLayout subpixelLayout_,
float ptSize_, FontAtlasType atlasType_);
bool operator<(const FontIdentifier& other) const;
std::string font;
uint32_t charsetHash = 0;
SubpixelLayout subpixelLayout = SubpixelLayout::UNKNOWN;
float ptSize = 0;
FontAtlasType atlasType;
};
public:
FontAtlasFactory(bool allowSystemFonts = true) : m_allowSystemFonts(allowSystemFonts) {}
[[nodiscard]] FontAtlas::Ptr GetFontAtlasScalable(const std::string& fontIdentifier, bool msdf = true,
const std::set<uint32_t>& charset = {}) const;
[[nodiscard]] FontAtlas::Ptr GetFontAtlas(const std::string& fontIdentifier, float ptSize,
SubpixelLayout subpixelLayout = SubpixelLayout::UNKNOWN,
const std::set<uint32_t>& charset = {}) const;
private:
Array<char> FindFont(const std::string& fontFile) const;
private:
const bool m_allowSystemFonts;
mutable std::map<FontIdentifier, FontAtlas::Ptr> m_atlasesCache;
};
}

View File

@@ -15,6 +15,17 @@ if (APPLE)
list(FILTER SOURCES EXCLUDE REGEX "ExeAppendedZipResourceLoader")
endif()
if (LINUX)
find_program(LSB_RELEASE_EXEC lsb_release)
execute_process(COMMAND ${LSB_RELEASE_EXEC} -is
OUTPUT_VARIABLE LSB_RELEASE_ID_SHORT
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if (NOT ${LSB_RELEASE_ID_SHORT} STREQUAL "Ubuntu")
list(FILTER SOURCES EXCLUDE REGEX "*Ubuntu*")
endif()
endif()
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${SOURCES})
file(GLOB_RECURSE RESOURCES "${ROOT_FOLDER}/resources/*.rc" "${ROOT_FOLDER}/resources/*.h")
list(APPEND SOURCES ${RESOURCES})

View File

@@ -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/.
*/
#include <catch2/catch_all.hpp>
#include "Host/SystemFontResolver.hpp"
#include "Base/Logger.hpp"
#include <filesystem>
using namespace OpenVulkano;
TEST_CASE("Search system fonts")
{
Logger::SetupLogger("", "tests.log");
// assume these fonts are present since they are default
std::string path = SystemFontResolver::GetSystemFontPath("Ubuntu Regular");
REQUIRE(path == "/usr/share/fonts/truetype/ubuntu/Ubuntu-R.ttf");
path = SystemFontResolver::GetSystemFontPath("ubuntu regular");
REQUIRE(path == "/usr/share/fonts/truetype/ubuntu/Ubuntu-R.ttf");
path = SystemFontResolver::GetSystemFontPath("Ubuntu Mono Bold Italic");
REQUIRE(path == "/usr/share/fonts/truetype/ubuntu/UbuntuMono-BI.ttf");
path = SystemFontResolver::GetSystemFontPath("ubuntu mono bold italic");
REQUIRE(path == "/usr/share/fonts/truetype/ubuntu/UbuntuMono-BI.ttf");
path = SystemFontResolver::GetSystemFontPath("NON-EXISTING Font");
REQUIRE(path.empty());
}

View File

@@ -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/.
*/
#include <catch2/catch_all.hpp>
#include "Base/Logger.hpp"
#include "Host/SystemFontResolver.hpp"
#include <filesystem>
#include "Scene/Text/FontAtlasFactory.hpp"
using namespace OpenVulkano;
TEST_CASE("Search system fonts")
{
Logger::SetupLogger("", "tests.log");
// assume these fonts are present since they are default
std::filesystem::path path = SystemFontResolver::GetSystemFontPath("Arial");
REQUIRE(path.filename() == "ARIAL.TTF");
// assume these fonts are present since they are default
path = SystemFontResolver::GetSystemFontPath("Times New Roman");
REQUIRE(path.filename() == "TIMES.TTF");
path = SystemFontResolver::GetSystemFontPath("Arial Bold Italic");
REQUIRE(path.filename() == "ARIALBI.TTF");
path = SystemFontResolver::GetSystemFontPath("NON-EXISTING Font");
REQUIRE(path.empty());
}