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 23, 2024
1 parent 0a8fd95 commit 8ae79e2
Show file tree
Hide file tree
Showing 27 changed files with 923 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 @@ -29,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Duplicated destructor call in AnyVector which caused double free crashes on multiple samples (**@RiscadoA**).
- Compiler Error when using -O3 flag (#1351, **@SrGesus**).
- Flipped documentation of SystemBuilder::before and SystemBuilder::after (#1371, **@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
46 changes: 46 additions & 0 deletions engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,19 @@ 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_stretch.cpp"
"src/ui/text/text.cpp"

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

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

"src/render/defaults/plugin.cpp"
"src/render/defaults/target.cpp"
"src/render/shader/plugin.cpp"
Expand Down Expand Up @@ -285,6 +294,43 @@ 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)
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
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"
}
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_element.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_element.vs.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"id": "51c11c57-c819-4a51-806c-853178ec686a"
}
38 changes: 38 additions & 0 deletions engine/include/cubos/engine/font/atlas.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/// @file
/// @brief Struct @ref cubos::engine::FontAtlas.
/// @ingroup font-plugin

#pragma once

#include <unordered_map>

#include <msdf-atlas-gen/msdf-atlas-gen.h>

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

#include <cubos/engine/api.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
struct CUBOS_ENGINE_API FontAtlas
{
CUBOS_REFLECT;

FontAtlas(msdfgen::FontHandle* font, core::gl::RenderDevice& rd);

/// @brief GPU texture containing this font atlas.
cubos::core::gl::Texture2D texture;

/// @brief Map from unicode characters to their respective glyph geometry data.
std::unordered_map<msdf_atlas::unicode_t, msdf_atlas::GlyphGeometry> glyphs;

/// @brief Information about the bitmap used to generate the texture.
msdfgen::BitmapConstRef<msdfgen::byte, 3> bitmap;
};

} // namespace cubos::engine
37 changes: 37 additions & 0 deletions engine/include/cubos/engine/font/atlas_store.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/// @file
/// @brief Resource @ref cubos::engine::FontAtlasStore.
/// @ingroup font-plugin

#pragma once

#include <memory>
#include <unordered_map>

#include <uuid.h>

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

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

namespace cubos::engine
{
struct CUBOS_ENGINE_API FontAtlasStore
{
CUBOS_REFLECT;

std::weak_ptr<FontAtlas> retrieve(const uuids::uuid& assetId);

bool contains(const uuids::uuid& assetId) const;

void store(const uuids::uuid& assetId, const std::shared_ptr<FontAtlas>& atlas);

void remove(const uuids::uuid& assetId);

const std::unordered_map<uuids::uuid, std::weak_ptr<FontAtlas>>& map() const;

private:
std::unordered_map<uuids::uuid, std::weak_ptr<FontAtlas>> mAtlas;
};

} // namespace cubos::engine
33 changes: 33 additions & 0 deletions engine/include/cubos/engine/font/bridge.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/// @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 and saves @ref Font assets.
///
/// @ingroup font-plugin
class CUBOS_ENGINE_API FontBridge : public FileBridge
{
public:
FontBridge()
: FileBridge(core::reflection::reflect<Font>())
, mFtHandle(msdfgen::initializeFreetype())

Check warning on line 20 in engine/include/cubos/engine/font/bridge.hpp

View check run for this annotation

Codecov / codecov/patch

engine/include/cubos/engine/font/bridge.hpp#L18-L20

Added lines #L18 - L20 were not covered by tests
{
}

Check warning on line 22 in engine/include/cubos/engine/font/bridge.hpp

View check run for this annotation

Codecov / codecov/patch

engine/include/cubos/engine/font/bridge.hpp#L22

Added line #L22 was not covered by tests

~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:
msdfgen::FreetypeHandle* mFtHandle;
};
} // namespace cubos::engine
30 changes: 30 additions & 0 deletions engine/include/cubos/engine/font/font.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/// @file
/// @brief Struct @ref cubos::engine::Font.
/// @ingroup font-plugin

#pragma once

#include <msdf-atlas-gen/msdf-atlas-gen.h>

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

#include <cubos/engine/api.hpp>

namespace cubos::engine
{
/// @brief Asset containing font data.
///
/// @ingroup font-plugin
struct CUBOS_ENGINE_API Font
{
CUBOS_REFLECT;

/// @brief The handle to the loaded font.
msdfgen::FontHandle* fontHandle{nullptr};

explicit Font(msdfgen::FreetypeHandle* ft, core::memory::Stream& stream);
Font(Font&& other) noexcept;
~Font();
};
} // namespace cubos::engine
28 changes: 28 additions & 0 deletions engine/include/cubos/engine/font/plugin.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/// @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.
///
/// ## Dependencies
/// - @ref assets-plugin

/// @brief Plugin entry function.
/// @param cubos @b Cubos main class.
/// @ingroup font-plugin
CUBOS_ENGINE_API void fontPlugin(Cubos& cubos);
} // namespace cubos::engine
23 changes: 23 additions & 0 deletions engine/include/cubos/engine/ui/text/plugin.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// @dir
/// @brief @ref ui-text-plugin plugin directory.

/// @file
/// @brief Plugin entry point.
/// @ingroup ui-text-plugin

#pragma once

#include <cubos/engine/prelude.hpp>

namespace cubos::engine
{
/// @defgroup ui-text-plugin
/// @ingroup ui-plugins
/// @brief Adds text element to UI.

/// @brief Plugin entry function.
/// @param cubos @b Cubos main class
/// @ingroup ui-text-plugin
CUBOS_ENGINE_API void uiTextPlugin(Cubos& cubos);

} // namespace cubos::engine
48 changes: 48 additions & 0 deletions engine/include/cubos/engine/ui/text/text.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/// @file
/// @brief Component @ref cubos::engine::UIText.
/// @ingroup ui-text-plugin

#pragma once

#include <memory>
#include <string>

#include <glm/vec4.hpp>

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

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

namespace cubos::engine
{
/// @brief Component used to draw text on the UI.
///
/// @ingroup ui-text-plugin
struct CUBOS_ENGINE_API UIText
{
CUBOS_REFLECT;

/// @brief The text of the element.
std::string text;

/// @brief The color of the text.
glm::vec4 color{1};

/// @brief The font size to draw the characters.
float fontSize{24.0F};

/// @brief The font to be used.
Asset<Font> font{AnyAsset("93cbe82e-9c9b-4c25-aa55-5105c1afd0cc")};

core::gl::VertexArray va{nullptr};

std::shared_ptr<FontAtlas> atlas{nullptr};

size_t vertexCount{0};
};
} // namespace cubos::engine
Loading

0 comments on commit 8ae79e2

Please sign in to comment.