Skip to content

Commit

Permalink
Added: ttf font loading, and image atlas generation
Browse files Browse the repository at this point in the history
  • Loading branch information
WillisMedwell committed Feb 13, 2024
1 parent 1a303c1 commit 8f50720
Show file tree
Hide file tree
Showing 16 changed files with 363 additions and 13 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
*.out
*.app

.vs/
.vscode/
**/build/
**/build-web/
Expand Down
20 changes: 17 additions & 3 deletions code/Engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ find_package(Bullet CONFIG REQUIRED)
find_package(Bullet CONFIG REQUIRED)
find_package(lodepng CONFIG REQUIRED)
find_package(Stb REQUIRED)
find_package(Freetype REQUIRED)


include(FetchContent)

Expand All @@ -35,12 +37,12 @@ target_precompile_headers(Engine PRIVATE include/EnginePch.hpp)

if(DEFINED EMSCRIPTEN)
target_link_libraries(Engine PUBLIC
glm::glm assimp::assimp EnTT::EnTT Utily::Utily lodepng ${BULLET_LIBRARIES}
glm::glm assimp::assimp EnTT::EnTT Utily::Utily Freetype::Freetype lodepng ${BULLET_LIBRARIES}
)
target_include_directories(Engine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${Stb_INCLUDE_DIR})
target_compile_options(Engine PUBLIC
"$<$<CONFIG:Debug>:-O0;-g3;-sDEMANGLE_SUPPORT=1;-sFORCE_FILESYSTEM=1;-sASSERTIONS=1;-sSAFE_HEAP=1;-sSTACK_OVERFLOW_CHECK=2;-sNO_DISABLE_EXCEPTION_CATCHING;-Wno-unused-command-line-argument>"
"$<$<CONFIG:Release>:-Oz;-sGL_FFP_ONLY;-msimd128;-mrelaxed-simd;-msse;-msse2;-mavx;-Wno-unused-command-line-argument>"
"$<$<CONFIG:Release>:-Oz;-sGL_FFP_ONLY;-msimd128;-mrelaxed-simd;-msse;-msse2;-msse3;-msse4.1;-Wno-unused-command-line-argument>"
)
target_link_options(Engine PUBLIC -sUSE_WEBGL2=1 -sUSE_GLFW=3 -sFULL_ES3=1 -sFULL_ES2=1 -Wno-unused-command-line-argument -sALLOW_MEMORY_GROWTH)
else()
Expand All @@ -49,8 +51,20 @@ else()
find_package(GLFW3 CONFIG REQUIRED)
find_package(GLEW REQUIRED)
target_link_libraries(Engine PUBLIC
glm::glm assimp::assimp EnTT::EnTT Utily::Utily lodepng ${BULLET_LIBRARIES}
glm::glm assimp::assimp EnTT::EnTT Utily::Utily Freetype::Freetype lodepng ${BULLET_LIBRARIES}
OpenGL::GL OpenAL::OpenAL GLEW::GLEW glfw
)
if(MSVC)
target_compile_options(Engine PUBLIC
"$<$<CONFIG:Debug>:/Od;/Zi;/RTCs>"
"$<$<CONFIG:Release>:/O2;/arch:AVX>"
)
else()
target_compile_options(Engine PUBLIC
"$<$<CONFIG:Debug>:-O0;-g;-fno-omit-frame-pointer>"
"$<$<CONFIG:Release>:-O3;-march=native>"
)
endif()

target_include_directories(Engine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${OPENGL_INCLUDE_DIR} ${Stb_INCLUDE_DIR})
endif()
1 change: 1 addition & 0 deletions code/Engine/include/Components/Components.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/quaternion.hpp>

namespace Components {
Expand Down
58 changes: 58 additions & 0 deletions code/Engine/include/Media/Font.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#pragma once

#include <Utily/Utily.hpp>

#include "Media/Image.hpp"
#include <array>
#include <limits>
#include <ranges>

namespace Media {

namespace FontAtlasConstants {
consteval static auto gen_drawable_chars() {
constexpr char first_printable = char(32);
constexpr char last_printable = char(127);
constexpr size_t n = last_printable - first_printable;
std::array<char, n> chars {};
std::ranges::copy(std::views::iota(first_printable, last_printable), chars.begin());
return chars;
}
constexpr static auto DRAWABLE_CHARS = gen_drawable_chars();

consteval static auto gen_is_char_drawable_table() -> std::array<bool, 256> {
auto table = std::array<bool, 256> { false };
#if 0
for (auto [i, e] : table | std::views::enumerate) {
e = std::ranges::contains(DRAWABLE_CHARS, static_cast<char>(i));
}
#else
std::ptrdiff_t i = 0;
for (auto iter = table.begin(); iter != table.end(); ++iter, ++i) {
*iter = std::ranges::find(DRAWABLE_CHARS, static_cast<char>(i)) != DRAWABLE_CHARS.end();
}
#endif
return table;
}
constexpr static auto IS_CHAR_DRAWABLE = gen_is_char_drawable_table();
}

class Font
{
public:
Font() = default;
Font(const Font&) = delete;
Font(Font&& other);

[[nodiscard]] auto init(std::vector<uint8_t>& encoded_ttf) noexcept -> Utily::Result<void, Utily::Error>;
[[nodiscard]] auto gen_image_atlas(uint32_t char_height_px) -> Utily::Result<Media::Image, Utily::Error>;

void stop() noexcept;

~Font();

private:
void* _font_face = nullptr;
};

}
16 changes: 13 additions & 3 deletions code/Engine/include/Media/Image.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
namespace Media {

enum class ColourFormat : uint32_t {
greyscale = GL_R8,
rgb = GL_RGB8,
rgba = GL_RGBA8,
s_rgb = GL_SRGB8,
s_rgba = GL_SRGB8_ALPHA8
s_rgba = GL_SRGB8_ALPHA8,
};

class Image
Expand All @@ -30,12 +31,21 @@ namespace Media {
bool is_gamma_corrected) noexcept
-> Utily::Result<void, Utily::Error>;

[[nodiscard]] auto init_raw(
std::vector<uint8_t>&& raw_data,
uint32_t width,
uint32_t height,
ColourFormat format) noexcept
-> Utily::Result<void, Utily::Error>;

[[nodiscard]] auto save_to_disk(std::filesystem::path path) noexcept
-> Utily::Result<void, Utily::Error>;

void stop() noexcept;

void resize(float scale) noexcept;
void resize(uint32_t width, uint32_t height) noexcept;


[[nodiscard]] auto data() noexcept -> std::optional<std::tuple<std::span<const uint8_t>, uint32_t, uint32_t, ColourFormat>>;

void add_fence(Renderer::Fence&& fence) noexcept;
Expand Down
1 change: 1 addition & 0 deletions code/Engine/include/Media/Media.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#pragma once

#include "Media/Image.hpp"
#include "Media/Font.hpp"
160 changes: 160 additions & 0 deletions code/Engine/src/Media/Font.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#include "Media/Font.hpp"

#include <ft2build.h>
#include <iostream>

#include FT_FREETYPE_H

struct FreeType {
FT_Library library = nullptr;

FreeType() {
if (auto error = FT_Init_FreeType(&library); error) {
const auto ft_err_msg = std::string_view { FT_Error_String(error) };
std::cerr
<< "Failed to initialise FreeType library: \n"
<< ft_err_msg
<< std::endl;
}
}
~FreeType() {
if (library != nullptr) {
FT_Done_FreeType(library);
}
library = nullptr;
}
};

static FreeType free_type {};

namespace Media {
auto Font::init(std::vector<uint8_t>& encoded_ttf) noexcept -> Utily::Result<void, Utily::Error> {
FT_Face ff = nullptr;
if (auto error = FT_New_Memory_Face(free_type.library, encoded_ttf.data(), encoded_ttf.size(), 0, &ff); error) {
return Utily::Error { FT_Error_String(error) };
}
_font_face = ff;
return {};
}

auto Font::gen_image_atlas(uint32_t char_height_px) -> Utily::Result<Media::Image, Utily::Error> {
if (_font_face == nullptr) {
return Utily::Error { "Font not initialised." };
}
FT_Face ff = reinterpret_cast<FT_Face>(_font_face);

if (auto error = FT_Set_Pixel_Sizes(ff, char_height_px, char_height_px); error) {
return Utily::Error { FT_Error_String(error) };
}

int ff_bm_max_top = 0;
int ff_bm_max_spanline_dist = 0;
int ff_bm_max_width = 0;

struct FreeTypeGlyph {
std::vector<uint8_t> buffer;
int width, height;
int left, top;
};

constexpr size_t N = FontAtlasConstants::DRAWABLE_CHARS.size();
Utily::StaticVector<FreeTypeGlyph, N> ff_glyphs;

for (const char& c : FontAtlasConstants::DRAWABLE_CHARS) {
auto glyph_index = FT_Get_Char_Index(ff, static_cast<std::uint32_t>(c));

if (auto error = FT_Load_Glyph(ff, glyph_index, FT_LOAD_DEFAULT); error) [[unlikely]] {
return Utily::Error { FT_Error_String(error) };
}
if (auto error = FT_Render_Glyph(ff->glyph, FT_Render_Mode::FT_RENDER_MODE_NORMAL); error) [[unlikely]] {
return Utily::Error { FT_Error_String(error) };
}

const int curr_spanline_dist = ff->glyph->bitmap.rows - ff->glyph->bitmap_top;
const int curr_width = ff->glyph->bitmap.width + ff->glyph->bitmap_left;
const int curr_top = ff->glyph->bitmap_top;

ff_bm_max_width = (curr_width > ff_bm_max_width) ? curr_width : ff_bm_max_width;
ff_bm_max_top = (curr_top > ff_bm_max_top) ? curr_top : ff_bm_max_top;
ff_bm_max_spanline_dist = (curr_spanline_dist > ff_bm_max_spanline_dist) ? curr_spanline_dist : ff_bm_max_spanline_dist;

auto glyph_data = std::span<uint8_t> { ff->glyph->bitmap.buffer, ff->glyph->bitmap.width * ff->glyph->bitmap.rows };

ff_glyphs.emplace_back(
FreeTypeGlyph {
.buffer = { glyph_data.begin(), glyph_data.end() },
.width = static_cast<int>(ff->glyph->bitmap.width),
.height = static_cast<int>(ff->glyph->bitmap.rows),
.left = ff->glyph->bitmap_left,
.top = ff->glyph->bitmap_top });
}

const size_t internal_bm_w = ff_bm_max_width;
const size_t internal_bm_h = ff_bm_max_spanline_dist + ff_bm_max_top;
const size_t internal_spanline = ff_bm_max_spanline_dist;

std::vector<uint8_t> img;
img.resize(internal_bm_w * internal_bm_h * FontAtlasConstants::DRAWABLE_CHARS.size());
std::ranges::fill(img, (uint8_t)0);
#if 1
for (const auto [i, c] : FontAtlasConstants::DRAWABLE_CHARS | std::views::enumerate) {
#else
auto& dc = FontAtlasConstants::DRAWABLE_CHARS;
std::ptrdiff_t i = 0;
for (auto iter = dc.begin(); iter != dc.end(); ++iter, ++i) {
#endif
const auto& ff_glyph = ff_glyphs[i];

std::ptrdiff_t bitmap_offset = (internal_bm_w * internal_bm_h * i);
uint8_t* bitmap = img.data() + bitmap_offset;
assert(bitmap_offset < img.size());

for (int y = 0; y < ff_glyph.height; ++y) {
for (int x = 0; x < ff_glyph.width; ++x) {
const uint8_t& val = *(ff_glyph.buffer.data() + (y * ff_glyph.width + x));
uint8_t& dest = *(bitmap + (internal_bm_w * (internal_spanline - ff_glyph.top + y)) + x + ff_glyph.left);
dest = val;
}
}
}

#if 0
for (size_t y = 0; y < internal_bm_h * FontAtlasConstants::DRAWABLE_CHARS.size(); ++y) {
for (size_t x = 0; x < internal_bm_w; ++x) {
if (y == internal_spanline) {
std::cout << '-';
} else {
auto res = static_cast<int>(std::clamp(*(img.data() + (y * internal_bm_w + x)), (uint8_t)0, (uint8_t)9));
if (res == 0) {
std::cout << ' ';
} else {
std::cout << res;
}
}
}
std::cout << "|\n|";
}
#endif
Media::Image image;
auto result = image.init_raw(
std::move(img),
internal_bm_w,
internal_bm_h * FontAtlasConstants::DRAWABLE_CHARS.size(),
Media::ColourFormat::greyscale);
if (result.has_error()) {
return result.error();
}
return std::move(image);
}

void Font::stop() noexcept {
if (_font_face != nullptr) {
FT_Done_Face(reinterpret_cast<FT_Face>(_font_face));
}
_font_face = nullptr;
}

Font::~Font() {
stop();
}
}
42 changes: 42 additions & 0 deletions code/Engine/src/Media/Image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ namespace Media {
bool is_gamma_corrected) noexcept
-> Utily::Result<void, Utily::Error> {

if (_fence) {
_fence->wait_for_sync();
_fence = std::nullopt;
}

if (_data.size()) {
_data.clear();
}
Expand All @@ -47,6 +52,8 @@ namespace Media {
case ColourFormat::rgba:
case ColourFormat::s_rgba:
return LodePNGColorType::LCT_RGBA;
case ColourFormat::greyscale:
return LodePNGColorType::LCT_GREY;
}
};

Expand All @@ -67,6 +74,41 @@ namespace Media {
return {};
}

auto Image::init_raw(
std::vector<uint8_t>&& raw_data,
uint32_t width,
uint32_t height,
ColourFormat format) noexcept
-> Utily::Result<void, Utily::Error> {
if (_fence) {
_fence->wait_for_sync();
_fence = std::nullopt;
}
_data = std::move(raw_data);
_format = format;
_width = width;
_height = height;

if (format == ColourFormat::greyscale && _data.size() != _width * _height) {
return Utily::Error("Unexpected image size");
}
return {};
}

auto Image::save_to_disk(std::filesystem::path path) noexcept
-> Utily::Result<void, Utily::Error> {
std::vector<uint8_t> encoded;
lodepng::State state;
state.info_raw.colortype = LodePNGColorType::LCT_GREY;
if (auto error = lodepng::encode(encoded, _data, _width, _height, state); error) {
return Utily::Error { lodepng_error_text(error) };
}
if (auto error = lodepng::save_file(encoded, path.string()); error) {
return Utily::Error { lodepng_error_text(error) };
}
return {};
}

void Image::stop() noexcept {
_data.reserve(0);
_width = 0;
Expand Down
1 change: 1 addition & 0 deletions code/Engine/src/Renderer/OpenglContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ namespace Renderer {
#if defined(CONFIG_TARGET_NATIVE)
if (_window) {
glfwDestroyWindow(_window.value());
_window = std::nullopt;
}
glfwTerminate();
#endif
Expand Down
Loading

0 comments on commit 8f50720

Please sign in to comment.