Skip to content

Commit

Permalink
feat(ui): add UI text rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
mkuritsu committed Nov 25, 2024
1 parent c764da2 commit 49e590f
Show file tree
Hide file tree
Showing 35 changed files with 1,090 additions and 46 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Compatibility with CMake find_package (#1326, **@RiscadoA**).
- A proper Nix package which can be used to install Cubos and Tesseratos (#1327, **RiscadoA**).
- Added the option to use Shadow Normal Offset Bias algorithm (#1308, **@GalaxyCrush**)
- UI text element using MSDF for text rendering (#1300, **@mkuritsu**).

### Changed

Expand All @@ -31,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Flipped documentation of SystemBuilder::before and SystemBuilder::after (#1371, **@RiscadoA**).
- Inconsistent behavior on ECS queries on symmetric self-relations (**@RiscadoA**).
- Undefined behavior on ECS entity removal due to creating tables while iterating over tables (#1363, **@RiscadoA**).
- Made canvas draw calls sorted by layer in order to prevent undeterministic behavior when drawing elements with transparency (**@mkuritsu**).

## [v0.4.0] - 2024-10-13

Expand Down
50 changes: 50 additions & 0 deletions engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,21 @@ set(CUBOS_ENGINE_SOURCE
"src/ui/color_rect/color_rect.cpp"
"src/ui/image/plugin.cpp"
"src/ui/image/image.cpp"
"src/ui/text/plugin.cpp"
"src/ui/text/text_impl.cpp"
"src/ui/text/text_stretch.cpp"
"src/ui/text/text.cpp"

"src/fixed_step/plugin.cpp"
"src/fixed_step/fixed_delta_time.cpp"

"src/font/atlas/glyph.cpp"
"src/font/atlas/bridge.cpp"
"src/font/atlas/atlas.cpp"
"src/font/plugin.cpp"
"src/font/font.cpp"
"src/font/bridge.cpp"

"src/render/defaults/plugin.cpp"
"src/render/defaults/target.cpp"
"src/render/shader/plugin.cpp"
Expand Down Expand Up @@ -285,6 +296,44 @@ target_include_directories(imgui PUBLIC
# Finally, link the target we created for both ImGui and Implot
target_link_libraries(cubos-engine PUBLIC imgui)

# freetype (msdfgen dependency) - fails to build on windows without this
set(FT_DISABLE_ZLIB ON)
set(FT_DISABLE_BZIP2 ON)
set(FT_DISABLE_PNG ON)
set(FT_DISABLE_HARFBUZZ ON)
set(FT_DISABLE_BROTLI ON)
FetchContent_Declare(freetype
GIT_REPOSITORY "https://gitlab.freedesktop.org/freetype/freetype.git"
GIT_TAG "VER-2-13-3"
SYSTEM
FIND_PACKAGE_ARGS
)
FetchContent_MakeAvailable(freetype)
if (NOT TARGET Freetype::Freetype)
add_library(Freetype::Freetype ALIAS freetype)
endif()
set_property(TARGET freetype PROPERTY POSITION_INDEPENDENT_CODE ON)

# msdf-atlas-gen
set(MSDF_ATLAS_USE_VCPKG OFF)
set(MSDF_ATLAS_USE_SKIA OFF)
set(MSDF_ATLAS_BUILD_STANDALONE OFF)
set(MSDF_ATLAS_NO_ARTERY_FONT ON)
set(MSDFGEN_DISABLE_PNG ON)
set(MSDFGEN_BUILD_STANDALONE OFF)
set(MSDF_ATLAS_DYNAMIC_RUNTIME ON)
FetchContent_Declare(msdf-atlas-gen
GIT_REPOSITORY "https://github.com/Chlumsky/msdf-atlas-gen.git"
SYSTEM
FIND_PACKAGE_ARGS
)
FetchContent_MakeAvailable(msdf-atlas-gen)
# To prevent reallocation errors when compiling engine as a shared library
set_property(TARGET msdf-atlas-gen PROPERTY POSITION_INDEPENDENT_CODE ON)
set_property(TARGET msdfgen-core PROPERTY POSITION_INDEPENDENT_CODE ON)

target_link_libraries(cubos-engine PRIVATE msdf-atlas-gen)

# ------------------------ Configure tests and samples ------------------------

if(CUBOS_ENGINE_TESTS)
Expand Down Expand Up @@ -312,4 +361,5 @@ if(CUBOS_ENABLE_INSTALL)
install(DIRECTORY ${imgui_SOURCE_DIR}/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/imgui)
install(DIRECTORY ${implot_SOURCE_DIR}/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/implot)
install(DIRECTORY assets/ DESTINATION ${CUBOS_ENGINE_ASSETS_INSTALL_PATH})
install(DIRECTORY ${msdf-atlas-gen_SOURCE_DIR}/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/msdf-atlas-gen)
endif()
4 changes: 4 additions & 0 deletions engine/assets/font/Robot-Regular.fontatlas.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"id": "ab32bedd-8ea5-49f0-bca6-0b319feb1207",
"font": "93cbe82e-9c9b-4c25-aa55-5105c1afd0cc"
}
Binary file added engine/assets/font/Roboto-Regular.ttf
Binary file not shown.
3 changes: 3 additions & 0 deletions engine/assets/font/Roboto-Regular.ttf.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"id": "93cbe82e-9c9b-4c25-aa55-5105c1afd0cc"
}
4 changes: 4 additions & 0 deletions engine/assets/font/RussoOne-Regular.fontatlas.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"id": "bd0387d2-af3d-4c65-8561-33f5bcf6ab37",
"font": "ffc53a73-ac38-4797-93ba-8adebbca1e79"
}
Binary file added engine/assets/font/RussoOne-Regular.ttf
Binary file not shown.
3 changes: 3 additions & 0 deletions engine/assets/font/RussoOne-Regular.ttf.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"id": "ffc53a73-ac38-4797-93ba-8adebbca1e79"
}
36 changes: 36 additions & 0 deletions engine/assets/ui/text.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#version 330 core

in vec2 texCoord;
out vec4 out_color;
uniform sampler2D fontAtlas;

const float pxRange = 4.0;

layout(std140) uniform PerElement
{
vec2 xRange;
vec2 yRange;
vec4 color;
int depth;
};

float screenPxRange()
{
vec2 unitRange = vec2(pxRange) / vec2(textureSize(fontAtlas, 0));
vec2 screenTexSize = vec2(1.0) / fwidth(texCoord);
return max(0.5 * dot(unitRange, screenTexSize), 1.0);
}

float median(float r, float g, float b)
{
return max(min(r, g), min(max(r, g), b));
}

void main()
{
vec3 msd = texture(fontAtlas, texCoord).rgb;
float sd = median(msd.r, msd.g, msd.b);
float screenPxDistance = screenPxRange() * (sd - 0.5);
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
out_color = mix(vec4(0.0), color, opacity);
}
3 changes: 3 additions & 0 deletions engine/assets/ui/text.fs.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"id": "b5b43fcb-0ec3-4f3a-9e90-a7b0b9978cc5"
}
25 changes: 25 additions & 0 deletions engine/assets/ui/text.vs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#version 330 core

in vec2 in_position;
in vec2 in_texCoord;

layout(std140) uniform PerElement
{
vec2 xRange;
vec2 yRange;
vec4 color;
int depth;
};

out vec2 texCoord;

uniform MVP
{
mat4 mvp;
};

void main()
{
gl_Position = mvp * (vec4(xRange.x, yRange.x, 0, 0) + vec4(in_position, depth, 1));
texCoord = in_texCoord;
}
3 changes: 3 additions & 0 deletions engine/assets/ui/text.vs.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"id": "51c11c57-c819-4a51-806c-853178ec686a"
}
41 changes: 41 additions & 0 deletions engine/include/cubos/engine/font/atlas/atlas.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/// @file
/// @brief Struct @ref cubos::engine::FontAtlas.
/// @ingroup font-plugin

#pragma once

#include <cstdint>
#include <optional>

#include <cubos/core/gl/render_device.hpp>
#include <cubos/core/reflection/reflect.hpp>

#include <cubos/engine/api.hpp>
#include <cubos/engine/font/atlas/glyph.hpp>

namespace cubos::engine
{
/// @brief Class that holds all the necessary data about a font atlas. This font atlas represents the texure
/// created from all the different glyphs in a font, that will be then used for drawing the text.
///
/// @ingroup font-plugin
class CUBOS_ENGINE_API FontAtlas
{
public:
CUBOS_REFLECT;

FontAtlas(void* font, double minimumScale, double pixelRange, double miterLimit,
cubos::core::gl::RenderDevice& renderDevice);

std::optional<FontGlyph> requestGlyph(uint32_t unicode) const;

/// @brief Returns the GPU texture created with this font atlas.
cubos::core::gl::Texture2D texture() const;

private:
cubos::core::gl::Texture2D mTexture{nullptr};

std::unordered_map<uint32_t, FontGlyph> mGlyphs;
};

} // namespace cubos::engine
35 changes: 35 additions & 0 deletions engine/include/cubos/engine/font/atlas/bridge.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/// @file
/// @brief Class @ref cubos::engine::FontAtlasBridge.
/// @ingroup font-plugin

#pragma once

#include <cubos/core/gl/render_device.hpp>

#include <cubos/engine/assets/bridge.hpp>
#include <cubos/engine/assets/bridges/file.hpp>
#include <cubos/engine/font/atlas/atlas.hpp>
#include <cubos/engine/font/font.hpp>

namespace cubos::engine
{
/// @brief Bridge which loads @ref FontAtlas assets.
///
/// @ingroup font-plugin
class CUBOS_ENGINE_API FontAtlasBridge : public AssetBridge
{
public:
FontAtlasBridge(cubos::core::gl::RenderDevice& renderDevice)
: AssetBridge(core::reflection::reflect<FontAtlas>(), false)
, mRenderDevice(renderDevice)
{
}

protected:
bool load(Assets& assets, const AnyAsset& handle) override;
bool save(const Assets& assets, const AnyAsset& handle) override;

private:
cubos::core::gl::RenderDevice& mRenderDevice;
};
} // namespace cubos::engine
30 changes: 30 additions & 0 deletions engine/include/cubos/engine/font/atlas/glyph.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/// @file
/// @brief Struct @ref cubos::engine::Glyph.
/// @ingroup font-plugin

#pragma once

#include <glm/glm.hpp>

#include <cubos/core/reflection/reflect.hpp>

#include <cubos/engine/api.hpp>

namespace cubos::engine
{
/// @brief Struct that holds glyph data such as texcoords, position offsets and advance.
///
/// @ingroup font-plugin
struct CUBOS_ENGINE_API FontGlyph
{
CUBOS_REFLECT;

glm::vec2 texCoordsMin;
glm::vec2 texCoordsMax;

glm::vec2 positionMin;
glm::vec2 positionMax;

float advance;
};
} // namespace cubos::engine
29 changes: 29 additions & 0 deletions engine/include/cubos/engine/font/bridge.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/// @file
/// @brief Class @ref cubos::engine::FontBridge.
/// @ingroup font-plugin

#pragma once

#include <cubos/engine/assets/bridges/file.hpp>
#include <cubos/engine/font/font.hpp>

namespace cubos::engine
{
/// @brief Bridge which loads @ref Font assets.
///
/// @ingroup font-plugin
class CUBOS_ENGINE_API FontBridge : public FileBridge
{
public:
FontBridge();

~FontBridge() override;

protected:
bool loadFromFile(Assets& assets, const AnyAsset& handle, core::memory::Stream& stream) override;
bool saveToFile(const Assets& assets, const AnyAsset& handle, core::memory::Stream& stream) override;

private:
void* mFreetypeHandle;
};
} // namespace cubos::engine
42 changes: 42 additions & 0 deletions engine/include/cubos/engine/font/font.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/// @file
/// @brief Struct @ref cubos::engine::Font.
/// @ingroup font-plugin

#pragma once

#include <vector>

#include <cubos/core/memory/stream.hpp>
#include <cubos/core/reflection/reflect.hpp>

#include <cubos/engine/api.hpp>

namespace cubos::engine
{
/// @brief Asset containing raw font data containing the glyphs and their shape. This is used to create @ref
/// FontAtlas that can be used for rendering text.
///
/// @ingroup font-plugin
class CUBOS_ENGINE_API Font
{
public:
CUBOS_REFLECT;

/// @brief Creates a new Font asset from the data in the stream.
Font(void* freetypeHandle, core::memory::Stream& stream);

/// @brief Move constructor that moves the font handle to another Font asset.
Font(Font&& other) noexcept;

/// @brief Destroys the associated handle to the font.
~Font();

/// @brief Returns the handle to this loaded font.
void* handle() const;

private:
void* mHandle{nullptr};

std::vector<uint8_t> mData;
};
} // namespace cubos::engine
30 changes: 30 additions & 0 deletions engine/include/cubos/engine/font/plugin.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/// @dir
/// @brief @ref font-plugin plugin directory.

/// @file
/// @brief Plugin entry point.
/// @ingroup font-plugin

#pragma once

#include <cubos/engine/prelude.hpp>

namespace cubos::engine
{
/// @defgroup font-plugin Font
/// @ingroup engine
/// @brief Adds fonts to @b Cubos using msdfgen.
///
/// ## Bridges
/// - @ref FontBridge - loads @ref Font assets.
/// - @ref FontAtlasBridge - loads @ref FontAtlas assets.
///
/// ## Dependencies
/// - @ref assets-plugin
/// - @ref window-plugin

/// @brief Plugin entry function.
/// @param cubos @b Cubos main class.
/// @ingroup font-plugin
CUBOS_ENGINE_API void fontPlugin(Cubos& cubos);
} // namespace cubos::engine
Loading

0 comments on commit 49e590f

Please sign in to comment.