From 595eef336458c1d2b2940cfa0e9a84b396af54e5 Mon Sep 17 00:00:00 2001 From: mkuritsu Date: Wed, 13 Nov 2024 03:12:59 +0000 Subject: [PATCH] feat(ui): add UI text rendering --- CHANGELOG.md | 2 + core/include/cubos/core/gl/render_device.hpp | 1 + core/src/gl/ogl_render_device.cpp | 5 + engine/CMakeLists.txt | 46 ++++ engine/assets/font/Roboto-Regular.ttf | Bin 0 -> 168260 bytes engine/assets/font/Roboto-Regular.ttf.meta | 3 + engine/assets/ui/text.fs | 36 ++++ engine/assets/ui/text.fs.meta | 3 + engine/assets/ui/text_element.vs | 25 +++ engine/assets/ui/text_element.vs.meta | 3 + engine/include/cubos/engine/font/atlas.hpp | 38 ++++ .../include/cubos/engine/font/atlas_store.hpp | 37 ++++ engine/include/cubos/engine/font/bridge.hpp | 33 +++ engine/include/cubos/engine/font/font.hpp | 30 +++ engine/include/cubos/engine/font/plugin.hpp | 28 +++ .../include/cubos/engine/ui/text/plugin.hpp | 23 ++ engine/include/cubos/engine/ui/text/text.hpp | 48 +++++ .../cubos/engine/ui/text/text_stretch.hpp | 19 ++ engine/samples/ui/main.cpp | 42 +++- engine/src/defaults/plugin.cpp | 4 + engine/src/font/atlas.cpp | 58 +++++ engine/src/font/atlas_store.cpp | 43 ++++ engine/src/font/bridge.cpp | 34 +++ engine/src/font/font.cpp | 54 +++++ engine/src/font/plugin.cpp | 32 +++ engine/src/ui/canvas/plugin.cpp | 89 +++++--- engine/src/ui/text/plugin.cpp | 202 ++++++++++++++++++ engine/src/ui/text/text.cpp | 19 ++ engine/src/ui/text/text_stretch.cpp | 8 + 29 files changed, 919 insertions(+), 46 deletions(-) create mode 100644 engine/assets/font/Roboto-Regular.ttf create mode 100644 engine/assets/font/Roboto-Regular.ttf.meta create mode 100644 engine/assets/ui/text.fs create mode 100644 engine/assets/ui/text.fs.meta create mode 100644 engine/assets/ui/text_element.vs create mode 100644 engine/assets/ui/text_element.vs.meta create mode 100644 engine/include/cubos/engine/font/atlas.hpp create mode 100644 engine/include/cubos/engine/font/atlas_store.hpp create mode 100644 engine/include/cubos/engine/font/bridge.hpp create mode 100644 engine/include/cubos/engine/font/font.hpp create mode 100644 engine/include/cubos/engine/font/plugin.hpp create mode 100644 engine/include/cubos/engine/ui/text/plugin.hpp create mode 100644 engine/include/cubos/engine/ui/text/text.hpp create mode 100644 engine/include/cubos/engine/ui/text/text_stretch.hpp create mode 100644 engine/src/font/atlas.cpp create mode 100644 engine/src/font/atlas_store.cpp create mode 100644 engine/src/font/bridge.cpp create mode 100644 engine/src/font/font.cpp create mode 100644 engine/src/font/plugin.cpp create mode 100644 engine/src/ui/text/plugin.cpp create mode 100644 engine/src/ui/text/text.cpp create mode 100644 engine/src/ui/text/text_stretch.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index ce9fcede96..00e797109d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Allow identifying assets in code from their path (#1177. **@GalaxyCrush**). - Added an Audio asset (#230, **@Dageus**, **@diogomsmiranda**). - Hold function for debug_camera (#1030, **@jdbaracho**). +- UI text element using MSDF for text rendering (#1300, **@mkuritsu**). ### Changed @@ -24,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Crash when opening the Play Pause menu (**SrGesus**). - Duplicated destructor call in AnyVector which caused double free crashes on multiple samples (**@RiscadoA**). - Compiler Error when using -O3 flag (#1351, **@SrGesus**). +- 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 diff --git a/core/include/cubos/core/gl/render_device.hpp b/core/include/cubos/core/gl/render_device.hpp index 99c8e456f4..87abfc2b73 100644 --- a/core/include/cubos/core/gl/render_device.hpp +++ b/core/include/cubos/core/gl/render_device.hpp @@ -238,6 +238,7 @@ namespace cubos::core::gl /// @ingroup core-gl enum class TextureFormat { + RGB8UInt, R8SNorm, ///< 1 channel 8 bits normalized signed integer. R16SNorm, ///< 1 channel 16 bits normalized signed integer. RG8SNorm, ///< 2 channel 8 bits normalized signed integer. diff --git a/core/src/gl/ogl_render_device.cpp b/core/src/gl/ogl_render_device.cpp index b41b8277db..34fe34bd01 100644 --- a/core/src/gl/ogl_render_device.cpp +++ b/core/src/gl/ogl_render_device.cpp @@ -41,6 +41,11 @@ static bool textureFormatToGL(TextureFormat texFormat, GLenum& internalFormat, G { switch (texFormat) { + case TextureFormat::RGB8UInt: + internalFormat = GL_RGB; + format = GL_RGB; + type = GL_UNSIGNED_BYTE; + break; case TextureFormat::R8UNorm: internalFormat = GL_R8; format = GL_RED; diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 6e102bcfdd..db0d72f5d3 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -1,6 +1,8 @@ # engine/CMakeLists.txt # Cubos engine build configuration +include(FetchContent) + option(BUILD_ENGINE_SAMPLES "Build cubos engine samples" OFF) option(BUILD_ENGINE_TESTS "Build cubos engine tests?" OFF) option(BUILD_ENGINE_SHARED "Build cubos engine as shared library?" ON) @@ -113,10 +115,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" @@ -237,6 +248,41 @@ add_library(stb_image INTERFACE) target_include_directories(stb_image SYSTEM INTERFACE "lib/stb_image") target_link_libraries(cubos-engine PUBLIC stb_image) +# 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) +add_library(Freetype::Freetype ALIAS freetype) +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 PUBLIC msdf-atlas-gen) + # Add engine tests if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_ENGINE_TESTS) add_subdirectory(tests) diff --git a/engine/assets/font/Roboto-Regular.ttf b/engine/assets/font/Roboto-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2d116d920580367caf6a179155f344ede856bf31 GIT binary patch literal 168260 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∾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 TwL*XSfJ*Ay literal 0 HcmV?d00001 diff --git a/engine/assets/font/Roboto-Regular.ttf.meta b/engine/assets/font/Roboto-Regular.ttf.meta new file mode 100644 index 0000000000..fa8f7495da --- /dev/null +++ b/engine/assets/font/Roboto-Regular.ttf.meta @@ -0,0 +1,3 @@ +{ + "id": "93cbe82e-9c9b-4c25-aa55-5105c1afd0cc" +} \ No newline at end of file diff --git a/engine/assets/ui/text.fs b/engine/assets/ui/text.fs new file mode 100644 index 0000000000..a71d96d3d3 --- /dev/null +++ b/engine/assets/ui/text.fs @@ -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); +} diff --git a/engine/assets/ui/text.fs.meta b/engine/assets/ui/text.fs.meta new file mode 100644 index 0000000000..6ff14a6b2b --- /dev/null +++ b/engine/assets/ui/text.fs.meta @@ -0,0 +1,3 @@ +{ + "id": "b5b43fcb-0ec3-4f3a-9e90-a7b0b9978cc5" +} diff --git a/engine/assets/ui/text_element.vs b/engine/assets/ui/text_element.vs new file mode 100644 index 0000000000..1c1f657ff5 --- /dev/null +++ b/engine/assets/ui/text_element.vs @@ -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; +} diff --git a/engine/assets/ui/text_element.vs.meta b/engine/assets/ui/text_element.vs.meta new file mode 100644 index 0000000000..f037a34244 --- /dev/null +++ b/engine/assets/ui/text_element.vs.meta @@ -0,0 +1,3 @@ +{ + "id": "51c11c57-c819-4a51-806c-853178ec686a" +} diff --git a/engine/include/cubos/engine/font/atlas.hpp b/engine/include/cubos/engine/font/atlas.hpp new file mode 100644 index 0000000000..744e7299c1 --- /dev/null +++ b/engine/include/cubos/engine/font/atlas.hpp @@ -0,0 +1,38 @@ +/// @file +/// @brief Struct @ref cubos::engine::FontAtlas. +/// @ingroup font-plugin + +#pragma once + +#include + +#include + +#include +#include + +#include + +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 glyphs; + + /// @brief Information about the bitmap used to generate the texture. + msdfgen::BitmapConstRef bitmap; + }; + +} // namespace cubos::engine diff --git a/engine/include/cubos/engine/font/atlas_store.hpp b/engine/include/cubos/engine/font/atlas_store.hpp new file mode 100644 index 0000000000..47bc840254 --- /dev/null +++ b/engine/include/cubos/engine/font/atlas_store.hpp @@ -0,0 +1,37 @@ +/// @file +/// @brief Resource @ref cubos::engine::FontAtlasStore. +/// @ingroup font-plugin + +#pragma once + +#include +#include + +#include + +#include + +#include +#include + +namespace cubos::engine +{ + struct CUBOS_ENGINE_API FontAtlasStore + { + CUBOS_REFLECT; + + std::weak_ptr retrieve(const uuids::uuid& assetId); + + bool contains(const uuids::uuid& assetId) const; + + void store(const uuids::uuid& assetId, const std::shared_ptr& atlas); + + void remove(const uuids::uuid& assetId); + + const std::unordered_map>& map() const; + + private: + std::unordered_map> mAtlas; + }; + +} // namespace cubos::engine diff --git a/engine/include/cubos/engine/font/bridge.hpp b/engine/include/cubos/engine/font/bridge.hpp new file mode 100644 index 0000000000..0695541c43 --- /dev/null +++ b/engine/include/cubos/engine/font/bridge.hpp @@ -0,0 +1,33 @@ +/// @file +/// @brief Class @ref cubos::engine::FontBridge. +/// @ingroup font-plugin + +#pragma once + +#include +#include + +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()) + , mFtHandle(msdfgen::initializeFreetype()) + { + } + + ~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 diff --git a/engine/include/cubos/engine/font/font.hpp b/engine/include/cubos/engine/font/font.hpp new file mode 100644 index 0000000000..2e8ad3439f --- /dev/null +++ b/engine/include/cubos/engine/font/font.hpp @@ -0,0 +1,30 @@ +/// @file +/// @brief Struct @ref cubos::engine::Font. +/// @ingroup font-plugin + +#pragma once + +#include + +#include +#include + +#include + +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 diff --git a/engine/include/cubos/engine/font/plugin.hpp b/engine/include/cubos/engine/font/plugin.hpp new file mode 100644 index 0000000000..7b83c3ccd0 --- /dev/null +++ b/engine/include/cubos/engine/font/plugin.hpp @@ -0,0 +1,28 @@ +/// @dir +/// @brief @ref font-plugin plugin directory. + +/// @file +/// @brief Plugin entry point. +/// @ingroup font-plugin + +#pragma once + +#include + +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 diff --git a/engine/include/cubos/engine/ui/text/plugin.hpp b/engine/include/cubos/engine/ui/text/plugin.hpp new file mode 100644 index 0000000000..2ea208e18e --- /dev/null +++ b/engine/include/cubos/engine/ui/text/plugin.hpp @@ -0,0 +1,23 @@ +/// @dir +/// @brief @ref ui-text-plugin plugin directory. + +/// @file +/// @brief Plugin entry point. +/// @ingroup ui-text-plugin + +#pragma once + +#include + +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 diff --git a/engine/include/cubos/engine/ui/text/text.hpp b/engine/include/cubos/engine/ui/text/text.hpp new file mode 100644 index 0000000000..31964ae1cc --- /dev/null +++ b/engine/include/cubos/engine/ui/text/text.hpp @@ -0,0 +1,48 @@ +/// @file +/// @brief Component @ref cubos::engine::UIText. +/// @ingroup ui-text-plugin + +#pragma once + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +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{AnyAsset("93cbe82e-9c9b-4c25-aa55-5105c1afd0cc")}; + + core::gl::VertexArray va{nullptr}; + + std::shared_ptr atlas{nullptr}; + + size_t vertexCount{0}; + }; +} // namespace cubos::engine diff --git a/engine/include/cubos/engine/ui/text/text_stretch.hpp b/engine/include/cubos/engine/ui/text/text_stretch.hpp new file mode 100644 index 0000000000..937840cb2c --- /dev/null +++ b/engine/include/cubos/engine/ui/text/text_stretch.hpp @@ -0,0 +1,19 @@ +/// @file +/// @brief Component @ref cubos::engine::UITextStretch. +/// @ingroup ui-text-plugin + +#pragma once + +#include + +#include + +namespace cubos::engine +{ + /// @brief Component which makes a UI element fit the text it has. + /// @ingroup ui-text-plugin + struct CUBOS_ENGINE_API UITextStretch + { + CUBOS_REFLECT; + }; +} // namespace cubos::engine diff --git a/engine/samples/ui/main.cpp b/engine/samples/ui/main.cpp index 0bb195c3d9..671eb697e0 100644 --- a/engine/samples/ui/main.cpp +++ b/engine/samples/ui/main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -22,6 +23,9 @@ #include #include #include +#include +#include +#include #include using namespace cubos::engine; @@ -43,6 +47,8 @@ int main(int argc, char** argv) cubos.plugin(imagePlugin); cubos.plugin(uiImagePlugin); cubos.plugin(colorRectPlugin); + cubos.plugin(fontPlugin); + cubos.plugin(uiTextPlugin); cubos.startupSystem("load and set the Input Bindings") .tagged(assetsTag) @@ -61,7 +67,7 @@ int main(int argc, char** argv) /// [Set up Canvas] /// [Set up Background] auto elementBg = commands.create() - .add(UIElement{}) + .add(UIElement{.layer = 0}) .add(UIHorizontalStretch{20, 20}) .add(UIVerticalStretch{20, 20}) .add(UIColorRect{{1, 1, 1, 1}}) @@ -72,28 +78,42 @@ int main(int argc, char** argv) /// [Set up Panel] auto elementPanel = commands.create() - .add(UIElement{.offset = {-50, 0}, .size = {200, 600}, .pivot = {1, 0.5F}, .anchor = {1, 0.5F}}) + .add(UIElement{ + .offset = {-50, 0}, .size = {200, 600}, .pivot = {1, 0.5F}, .anchor = {1, 0.5F}, .layer = 1}) .add(UIColorRect{{1, 0, 0, 1}}) .entity(); commands.relate(elementPanel, elementBg, ChildOf{}); /// [Set up Panel] /// [Set up Logo] - auto logo = commands.create() - .add(UIElement{.offset = {50, -50}, .size = {200, 200}, .pivot = {0, 1}, .anchor = {0, 1}}) - .add(UIImage{AnyAsset("50423317-a543-4614-9f4e-c2df975f5c0d")}) - .entity(); + auto logo = + commands.create() + .add(UIElement{.offset = {50, -50}, .size = {200, 200}, .pivot = {0, 1}, .anchor = {0, 1}, .layer = 1}) + .add(UIImage{AnyAsset("50423317-a543-4614-9f4e-c2df975f5c0d")}) + .entity(); commands.relate(logo, elementBg, ChildOf{}); /// [Set up Logo] /// [Set up Long Logo] - auto longLogo = commands.create() - .add(UIElement{.offset = {50, 50}, .size = {400, 400}, .pivot = {0, 0}, .anchor = {0, 0}}) - .add(UIImage{AnyAsset("6c24c031-2eac-47c9-be30-485238c3e355")}) - .add(UINativeAspectRatio{}) - .entity(); + auto longLogo = + commands.create() + .add(UIElement{.offset = {50, 50}, .size = {400, 400}, .pivot = {0, 0}, .anchor = {0, 0}, .layer = 1}) + .add(UIImage{AnyAsset("6c24c031-2eac-47c9-be30-485238c3e355")}) + .add(UINativeAspectRatio{}) + .entity(); commands.relate(longLogo, elementBg, ChildOf{}); /// [Set up Long Logo] + + /// [Setup title] + auto title = + commands.create() + .add(UIElement{ + .offset = {0, -20}, .size = {100, 100}, .pivot = {0.5F, 0.5F}, .anchor = {0.5F, 1.0F}, .layer = 2}) + .add(UIText{.text = "cubosengine.org", .color = {0.13, 0.14, 0.15, 1}, .fontSize = 48.0F}) + .add(UITextStretch{}) + .entity(); + commands.relate(title, elementBg, ChildOf{}); + /// [Setup title] }); cubos.system("change scale mode").call([](Commands cmds, const Input& input, Query query) { diff --git a/engine/src/defaults/plugin.cpp b/engine/src/defaults/plugin.cpp index d052f9eb3e..212710cbcd 100644 --- a/engine/src/defaults/plugin.cpp +++ b/engine/src/defaults/plugin.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -14,6 +15,7 @@ #include #include #include +#include #include #include @@ -32,6 +34,7 @@ void cubos::engine::defaultsPlugin(Cubos& cubos) cubos.plugin(assetsPlugin); cubos.plugin(collisionsPlugin); cubos.plugin(windowPlugin); + cubos.plugin(fontPlugin); cubos.plugin(scenePlugin); cubos.plugin(voxelsPlugin); @@ -43,6 +46,7 @@ void cubos::engine::defaultsPlugin(Cubos& cubos) cubos.plugin(uiCanvasPlugin); cubos.plugin(colorRectPlugin); + cubos.plugin(uiTextPlugin); cubos.plugin(gizmosPlugin); cubos.plugin(imguiPlugin); diff --git a/engine/src/font/atlas.cpp b/engine/src/font/atlas.cpp new file mode 100644 index 0000000000..ade1a2790b --- /dev/null +++ b/engine/src/font/atlas.cpp @@ -0,0 +1,58 @@ +#include +#include + +#include + +#include +#include +#include +#include + +#include + +CUBOS_REFLECT_IMPL(cubos::engine::FontAtlas) +{ + return cubos::core::reflection::Type::create("cubos::engine::FontAtlas"); +} + +cubos::engine::FontAtlas::FontAtlas(msdfgen::FontHandle* font, core::gl::RenderDevice& rd) +{ + std::vector glyphs; + msdf_atlas::FontGeometry fontGeometry(&glyphs); + fontGeometry.loadCharset(font, 1.0, msdf_atlas::Charset::ASCII); + + const double maxCornerAngle = 3.0; + for (msdf_atlas::GlyphGeometry& glyph : glyphs) + { + glyph.edgeColoring(&msdfgen::edgeColoringInkTrap, maxCornerAngle, 0); + } + msdf_atlas::TightAtlasPacker packer; + packer.setDimensionsConstraint(msdf_atlas::DimensionsConstraint::SQUARE); + packer.setMinimumScale(24.0); + packer.setPixelRange(4.0); + packer.setMiterLimit(1.0); + packer.pack(glyphs.data(), static_cast(glyphs.size())); + int width = 0; + int height = 0; + packer.getDimensions(width, height); + + msdf_atlas::ImmediateAtlasGenerator> + generator(width, height); + msdf_atlas::GeneratorAttributes attributes; + generator.setAttributes(attributes); + generator.setThreadCount(4); + generator.generate(glyphs.data(), static_cast(glyphs.size())); + + this->bitmap = generator.atlasStorage(); + for (const msdf_atlas::GlyphGeometry& glyph : glyphs) + { + this->glyphs[glyph.getCodepoint()] = glyph; + } + core::gl::Texture2DDesc desc{.data = {bitmap.pixels}, + .width = static_cast(bitmap.width), + .height = static_cast(bitmap.height), + .usage = core::gl::Usage::Default, + .format = core::gl::TextureFormat::RGB8UInt}; + this->texture = rd.createTexture2D(desc); +} diff --git a/engine/src/font/atlas_store.cpp b/engine/src/font/atlas_store.cpp new file mode 100644 index 0000000000..1ddc81568d --- /dev/null +++ b/engine/src/font/atlas_store.cpp @@ -0,0 +1,43 @@ +#include + +#include + +#include +#include +#include +#include + +#include +#include + +using namespace cubos::engine; + +CUBOS_REFLECT_IMPL(FontAtlasStore) +{ + return cubos::core::ecs::TypeBuilder("cubos::engine::FontAtlasStore").build(); +} + +std::weak_ptr FontAtlasStore::retrieve(const uuids::uuid& assetId) +{ + return mAtlas[assetId]; +} + +bool FontAtlasStore::contains(const uuids::uuid& assetId) const +{ + return mAtlas.contains(assetId); +} + +void FontAtlasStore::store(const uuids::uuid& assetId, const std::shared_ptr& atlas) +{ + mAtlas[assetId] = atlas; +} + +void FontAtlasStore::remove(const uuids::uuid& assetId) +{ + mAtlas.erase(mAtlas.find(assetId)); +} + +const std::unordered_map>& FontAtlasStore::map() const +{ + return mAtlas; +} diff --git a/engine/src/font/bridge.cpp b/engine/src/font/bridge.cpp new file mode 100644 index 0000000000..558ec99454 --- /dev/null +++ b/engine/src/font/bridge.cpp @@ -0,0 +1,34 @@ +#include + +#include + +#include +#include +#include + +using namespace cubos::engine; + +FontBridge::~FontBridge() +{ + msdfgen::deinitializeFreetype(mFtHandle); +} + +bool FontBridge::loadFromFile(Assets& assets, const AnyAsset& handle, core::memory::Stream& stream) +{ + Font font{mFtHandle, stream}; + if (font.fontHandle == nullptr) + { + return false; + } + assets.store(handle, std::move(font)); + return true; +} + +bool FontBridge::saveToFile(const Assets& assets, const AnyAsset& handle, core::memory::Stream& stream) +{ + (void)assets; + (void)handle; + (void)stream; + CUBOS_ERROR("Saving fonts is currently unsupported!"); + return false; +} diff --git a/engine/src/font/font.cpp b/engine/src/font/font.cpp new file mode 100644 index 0000000000..f14162f305 --- /dev/null +++ b/engine/src/font/font.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include + +#include + +CUBOS_REFLECT_IMPL(cubos::engine::Font) +{ + return cubos::core::reflection::Type::create("cubos::engine::Font"); +} + +namespace cubos::engine +{ + Font::Font(msdfgen::FreetypeHandle* ft, core::memory::Stream& stream) + { + if (ft == nullptr) + { + CUBOS_ERROR("Couldn't load font: Invalid Freetype handle"); + return; + } + stream.seek(0, cubos::core::memory::SeekOrigin::End); + size_t streamSize = stream.tell(); + std::vector contents{}; + contents.reserve(streamSize); + stream.seek(0, cubos::core::memory::SeekOrigin::Begin); + if (stream.read(contents.data(), streamSize) < streamSize) + { + CUBOS_ERROR("Couldn't load font: Read less than stream length"); + fontHandle = nullptr; + return; + } + fontHandle = msdfgen::loadFontData(ft, contents.data(), static_cast(streamSize)); + if (fontHandle == nullptr) + { + CUBOS_ERROR("Couldn't load font: Failed to load data"); + } + } + + Font::Font(Font&& other) noexcept + : fontHandle(other.fontHandle) + { + other.fontHandle = nullptr; + } + + Font::~Font() + { + if (fontHandle != nullptr) + { + msdfgen::destroyFont(fontHandle); + } + } +} // namespace cubos::engine diff --git a/engine/src/font/plugin.cpp b/engine/src/font/plugin.cpp new file mode 100644 index 0000000000..c22d40757f --- /dev/null +++ b/engine/src/font/plugin.cpp @@ -0,0 +1,32 @@ +#include + +#include + +#include +#include +#include +#include +#include + +void cubos::engine::fontPlugin(Cubos& cubos) +{ + cubos.depends(assetsPlugin); + + cubos.resource(); + + cubos.startupSystem("setup Font assets bridge").tagged(assetsBridgeTag).call([](Assets& assets) { + auto bridge = std::make_shared(); + assets.registerBridge(".ttf", bridge); + assets.registerBridge(".otf", bridge); + }); + + cubos.system("cleanup atlas store").call([](FontAtlasStore& store) { + for (const auto& [assetId, atlas] : store.map()) + { + if (atlas.expired()) + { + store.remove(assetId); + } + } + }); +} diff --git a/engine/src/ui/canvas/plugin.cpp b/engine/src/ui/canvas/plugin.cpp index 2455487a94..c5b080f105 100644 --- a/engine/src/ui/canvas/plugin.cpp +++ b/engine/src/ui/canvas/plugin.cpp @@ -3,6 +3,7 @@ #include +#include #include #include @@ -38,21 +39,25 @@ namespace cubos::core::gl::DepthStencilState dss; cubos::core::gl::ConstantBuffer mvpBuffer; - State(cubos::core::gl::DepthStencilState depthStencilState, cubos::core::gl::ConstantBuffer mvpConstantBuffer) + cubos::core::gl::BlendState bs; + State(cubos::core::gl::DepthStencilState depthStencilState, cubos::core::gl::ConstantBuffer mvpConstantBuffer, + cubos::core::gl ::BlendState blendState) : dss(std::move(depthStencilState)) , mvpBuffer(std::move(mvpConstantBuffer)) + , bs(std::move(blendState)) { } }; } // namespace static void copyCommands(Query query, UIElement& element, Entity entity, - std::map& commands) + std::map>& commands) { for (size_t i = 0; i < element.drawList.size(); i++) { + int depth = element.layer * 100 + element.hierarchyDepth; auto entry = element.drawList.entry(i); - commands[&entry.type].push(entry.type, entry.command, element.drawList.data(i)); + commands[depth][&entry.type].push(entry.type, entry.command, element.drawList.data(i)); } element.drawList.clear(); for (auto [child, childElement, _] : query.pin(1, entity)) @@ -91,14 +96,26 @@ void cubos::engine::uiCanvasPlugin(Cubos& cubos) cubos.startupSystem("setup canvas state") .after(windowInitTag) .call([](Commands cmds, const core::io::Window& window) { - cubos::core::gl::DepthStencilStateDesc dssd; - dssd.depth.enabled = true; - dssd.depth.writeEnabled = true; - dssd.depth.near = -1.0F; - dssd.depth.compare = Compare::LEqual; + cubos::core::gl::DepthStencilStateDesc dssd = {.depth = {.enabled = false}, .stencil = {.enabled = false}}; + cubos::core::gl::BlendStateDesc bsd = { + .blendEnabled = true, + .color = + { + .src = BlendFactor::SrcAlpha, + .dst = BlendFactor::InvSrcAlpha, + .op = BlendOp::Add, + }, + .alpha = + { + .src = BlendFactor::One, + .dst = BlendFactor::Zero, + .op = BlendOp::Add, + }, + }; cmds.emplaceResource(window->renderDevice().createDepthStencilState(dssd), window->renderDevice().createConstantBuffer(sizeof(glm::mat4), nullptr, - cubos::core::gl::Usage::Dynamic)); + cubos::core::gl::Usage::Dynamic), + window->renderDevice().createBlendState(bsd)); }); cubos.system("scale canvas") @@ -139,7 +156,7 @@ void cubos::engine::uiCanvasPlugin(Cubos& cubos) canvas.virtualSize = canvas.referenceSize; } - canvas.mat = glm::ortho(0, canvas.virtualSize.x, 0, canvas.virtualSize.y); + canvas.mat = glm::ortho(0, canvas.virtualSize.x, 0, canvas.virtualSize.y, -1000.0F, 1000.0F); } }); @@ -256,7 +273,6 @@ void cubos::engine::uiCanvasPlugin(Cubos& cubos) window->renderDevice().clearColor(0.0F, 0.0F, 0.0F, 0.0F); target.cleared = true; } - window->renderDevice().clearDepth(1); } }); @@ -275,6 +291,7 @@ void cubos::engine::uiCanvasPlugin(Cubos& cubos) const State& state) { // For each render target with a UI canvas. window->renderDevice().setDepthStencilState(state.dss); + window->renderDevice().setBlendState(state.bs); for (auto [entity, canvas, renderTarget] : canvasQuery) { @@ -284,42 +301,44 @@ void cubos::engine::uiCanvasPlugin(Cubos& cubos) state.mvpBuffer->fill(&canvas.mat, sizeof(glm::mat4)); // Extract draw commands from all children UI elements, recursively. - std::map commands; + std::map> layerCommands; for (auto [child, childElement, _] : drawsToQuery.pin(1, entity)) { - copyCommands(childrenQuery, childElement, child, commands); + copyCommands(childrenQuery, childElement, child, layerCommands); } - // For each draw command type. - for (const auto& [type, list] : commands) + for (const auto& [layer, commands] : layerCommands) { - - // Prepare state common to all instances of this draw command type. - window->renderDevice().setShaderPipeline(type->pipeline); - type->pipeline->getBindingPoint("MVP")->bind(state.mvpBuffer); - type->constantBufferBindingPoint->bind(type->constantBuffer); - - // For all instances of this draw command type. - for (size_t i = 0; i < list.size(); i++) + // For each draw command type. + for (const auto& [type, list] : commands) { - // Prepare command-specific state and issue draw call. - auto entry = list.entry(i); - window->renderDevice().setVertexArray(entry.command.vertexArray); + // Prepare state common to all instances of this draw command type. + window->renderDevice().setShaderPipeline(type->pipeline); + type->pipeline->getBindingPoint("MVP")->bind(state.mvpBuffer); + type->constantBufferBindingPoint->bind(type->constantBuffer); - type->constantBuffer->fill(list.data(i), type->perElementSize); - - for (size_t j = 0; j < UIDrawList::Type::MaxTextures; j++) + // For all instances of this draw command type. + for (size_t i = 0; i < list.size(); i++) { - if (type->texBindingPoint[j] == nullptr) + // Prepare command-specific state and issue draw call. + auto entry = list.entry(i); + window->renderDevice().setVertexArray(entry.command.vertexArray); + + type->constantBuffer->fill(list.data(i), type->perElementSize); + + for (size_t j = 0; j < UIDrawList::Type::MaxTextures; j++) { - continue; + if (type->texBindingPoint[j] == nullptr) + { + continue; + } + type->texBindingPoint[j]->bind(entry.command.textures[j]); + type->texBindingPoint[j]->bind(entry.command.samplers[j]); } - type->texBindingPoint[j]->bind(entry.command.textures[j]); - type->texBindingPoint[j]->bind(entry.command.samplers[j]); + window->renderDevice().drawTriangles(entry.command.vertexOffset, entry.command.vertexCount); } - window->renderDevice().drawTriangles(entry.command.vertexOffset, entry.command.vertexCount); } } } }); -} \ No newline at end of file +} diff --git a/engine/src/ui/text/plugin.cpp b/engine/src/ui/text/plugin.cpp new file mode 100644 index 0000000000..a4e76972c9 --- /dev/null +++ b/engine/src/ui/text/plugin.cpp @@ -0,0 +1,202 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using cubos::core::io::Window; + +using namespace cubos::engine; + +namespace +{ + struct PerElement + { + glm::vec2 xRange; + glm::vec2 yRange; + glm::vec4 color; + int depth; + }; + + struct State + { + CUBOS_ANONYMOUS_REFLECT(State); + + UIDrawList::Type drawType; + Sampler sampler; + + State(RenderDevice& renderDevice, const ShaderPipeline& pipeline) + { + SamplerDesc sd = {.minFilter = TextureFilter::Linear, .magFilter = TextureFilter::Linear}; + sampler = renderDevice.createSampler(sd); + + drawType.constantBuffer = renderDevice.createConstantBuffer(sizeof(PerElement), nullptr, Usage::Dynamic); + drawType.texBindingPoint[0] = pipeline->getBindingPoint("fontAtlas"); + drawType.constantBufferBindingPoint = pipeline->getBindingPoint("PerElement"); + drawType.perElementSize = sizeof(PerElement); + drawType.pipeline = pipeline; + } + }; +} // namespace + +void cubos::engine::uiTextPlugin(Cubos& cubos) +{ + static const Asset VertexShader = AnyAsset("51c11c57-c819-4a51-806c-853178ec686a"); + static const Asset PixelShader = AnyAsset("b5b43fcb-0ec3-4f3a-9e90-a7b0b9978cc5"); + + cubos.component(); + cubos.component(); + + cubos.depends(uiCanvasPlugin); + cubos.depends(windowPlugin); + cubos.depends(shaderPlugin); + cubos.depends(assetsPlugin); + cubos.depends(fontPlugin); + + cubos.uninitResource(); + + cubos.startupSystem("setup UI text") + .tagged(assetsTag) + .after(windowInitTag) + .call([](Commands cmds, const Window& window, const Assets& assets) { + auto& rd = window->renderDevice(); + auto vs = assets.read(VertexShader)->shaderStage(); + auto ps = assets.read(PixelShader)->shaderStage(); + cmds.emplaceResource(rd, rd.createShaderPipeline(vs, ps)); + }); + + cubos.system("load UI text atlas") + .call([](const Window& window, const Assets& assets, FontAtlasStore& atlasStore, Query query) { + for (auto [uiText] : query) + { + if (uiText.atlas == nullptr) + { + auto fontAsset = assets.load(uiText.font); + uuids::uuid assetId = fontAsset.getId().value(); + if (atlasStore.contains(assetId)) + { + uiText.atlas = std::shared_ptr{atlasStore.retrieve(assetId)}; + } + else + { + auto font = assets.read(uiText.font); + uiText.atlas = std::make_shared(font->fontHandle, window->renderDevice()); + atlasStore.store(assetId, uiText.atlas); + } + } + } + }); + + cubos.system("load UI text vertex array") + .call([](const Window& window, const State& state, + Query> query) { + for (auto [element, uiText, optStretch] : query) + { + if (uiText.va == nullptr && uiText.atlas != nullptr) + { + std::size_t vertexCount = uiText.text.size() * 6; + uiText.vertexCount = vertexCount; + float xPos = 0; + float yPos = 0; + + VertexArrayDesc vad; + vad.elementCount = 2; + vad.elements[0].name = "in_position"; + vad.elements[0].type = cubos::core::gl::Type::Float; + vad.elements[0].size = 2; + vad.elements[0].buffer.index = 0; + vad.elements[0].buffer.offset = 0; + vad.elements[0].buffer.stride = 4 * sizeof(float); + vad.elements[1].name = "in_texCoord"; + vad.elements[1].type = cubos::core::gl::Type::Float; + vad.elements[1].size = 2; + vad.elements[1].buffer.index = 0; + vad.elements[1].buffer.offset = 2 * sizeof(float); + vad.elements[1].buffer.stride = 4 * sizeof(float); + vad.shaderPipeline = state.drawType.pipeline; + + std::vector verts; + verts.reserve(4 * vertexCount); + /// @todo make add support for Unicode + /// @note This repeats a lot of vertices because currently the UI system does not support drawing + /// Indexed, something that needs to be worked on + for (char c : uiText.text) + { + auto uChar = static_cast(c); + const msdf_atlas::GlyphGeometry& glyph = uiText.atlas->glyphs[uChar]; + auto advance = static_cast(glyph.getAdvance() * uiText.fontSize); + if (c == ' ') + { + xPos += advance; + continue; + } + double left; + double bottom; + double right; + double top; + glyph.getQuadAtlasBounds(left, bottom, right, top); + auto texCoordLeft = static_cast(left / uiText.atlas->bitmap.width); + auto texCoordBottom = static_cast(bottom / uiText.atlas->bitmap.height); + auto texCoordRight = static_cast(right / uiText.atlas->bitmap.width); + auto texCoordTop = static_cast(top / uiText.atlas->bitmap.height); + + glyph.getQuadPlaneBounds(left, bottom, right, top); + auto posLeft = static_cast(left * uiText.fontSize + xPos); + auto posBottom = static_cast(bottom * uiText.fontSize + yPos); + auto posRight = static_cast(right * uiText.fontSize + xPos); + auto posTop = static_cast(top * uiText.fontSize + yPos); + + std::array charVerts{ + posLeft, posBottom, texCoordLeft, texCoordBottom, posRight, posBottom, + texCoordRight, texCoordBottom, posLeft, posTop, texCoordLeft, texCoordTop, + posLeft, posTop, texCoordLeft, texCoordTop, posRight, posBottom, + texCoordRight, texCoordBottom, posRight, posTop, texCoordRight, texCoordTop}; + /// @todo maybe possible to optimize with a memcpy, instead of iterating/pushing_back? + for (float f : charVerts) + { + verts.push_back(f); + } + xPos += advance; + } + vad.buffers[0] = window->renderDevice().createVertexBuffer(verts.size() * sizeof(float), + verts.data(), Usage::Default); + uiText.va = window->renderDevice().createVertexArray(vad); + + if (optStretch.contains()) + { + element.size = {xPos, uiText.fontSize}; + } + } + } + }); + + cubos.system("draw UI text").tagged(uiDrawTag).call([](const State& state, Query query) { + for (auto [element, uiText] : query) + { + if (uiText.va != nullptr) + { + glm::vec2 min = element.position - element.size * element.pivot; + glm::vec2 max = element.position + element.size * (glm::vec2(1.0F, 1.0F) - element.pivot); + element + .draw( + state.drawType, uiText.va, 0, uiText.vertexCount, + PerElement{ + {min.x, max.x}, {min.y, max.y}, uiText.color, element.layer * 100 + element.hierarchyDepth}) + .withTexture(0, uiText.atlas->texture, state.sampler); + } + } + }); +} diff --git a/engine/src/ui/text/text.cpp b/engine/src/ui/text/text.cpp new file mode 100644 index 0000000000..8701a40721 --- /dev/null +++ b/engine/src/ui/text/text.cpp @@ -0,0 +1,19 @@ +#include +#include +#include +#include +#include + +#include + +using namespace cubos::engine; + +CUBOS_REFLECT_IMPL(UIText) +{ + return core::ecs::TypeBuilder("cubos::engine::UIText") + .withField("text", &UIText::text) + .withField("color", &UIText::color) + .withField("fontSize", &UIText::fontSize) + .withField("font", &UIText::font) + .build(); +} diff --git a/engine/src/ui/text/text_stretch.cpp b/engine/src/ui/text/text_stretch.cpp new file mode 100644 index 0000000000..b639a5ecf2 --- /dev/null +++ b/engine/src/ui/text/text_stretch.cpp @@ -0,0 +1,8 @@ +#include + +#include + +CUBOS_REFLECT_IMPL(cubos::engine::UITextStretch) +{ + return cubos::core::ecs::TypeBuilder("cubos::engine::UITextStretch").build(); +}