diff --git a/.gitmodules b/.gitmodules index a5ab2acf..a487eaeb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -44,6 +44,9 @@ path = libs/sol2 url = https://github.com/ThePhD/sol2.git branch = main +[submodule "libs/msdf_atlas_gen"] + path = libs/msdf_atlas_gen + url = https://github.com/gustavo-tomas/msdf-atlas-gen.git [submodule "libs/implot"] path = libs/implot url = https://github.com/epezent/implot.git diff --git a/libs/msdf_atlas_gen b/libs/msdf_atlas_gen new file mode 160000 index 00000000..ae0fdda6 --- /dev/null +++ b/libs/msdf_atlas_gen @@ -0,0 +1 @@ +Subproject commit ae0fdda621783cdef46edbaa26bae28b4b8d3cfa diff --git a/magnolia/assets/fonts/OFL.txt b/magnolia/assets/fonts/OFL.txt new file mode 100644 index 00000000..4fc61702 --- /dev/null +++ b/magnolia/assets/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/magnolia/assets/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf b/magnolia/assets/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf new file mode 100644 index 00000000..8312b2ce Binary files /dev/null and b/magnolia/assets/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf differ diff --git a/magnolia/assets/fonts/OpenSans-VariableFont_wdth,wght.ttf b/magnolia/assets/fonts/OpenSans-VariableFont_wdth,wght.ttf new file mode 100644 index 00000000..ac587b48 Binary files /dev/null and b/magnolia/assets/fonts/OpenSans-VariableFont_wdth,wght.ttf differ diff --git a/magnolia/assets/fonts/README.txt b/magnolia/assets/fonts/README.txt new file mode 100644 index 00000000..2548322c --- /dev/null +++ b/magnolia/assets/fonts/README.txt @@ -0,0 +1,100 @@ +Open Sans Variable Font +======================= + +This download contains Open Sans as both variable fonts and static fonts. + +Open Sans is a variable font with these axes: + wdth + wght + +This means all the styles are contained in these files: + OpenSans-VariableFont_wdth,wght.ttf + OpenSans-Italic-VariableFont_wdth,wght.ttf + +If your app fully supports variable fonts, you can now pick intermediate styles +that aren’t available as static fonts. Not all apps support variable fonts, and +in those cases you can use the static font files for Open Sans: + static/OpenSans_Condensed-Light.ttf + static/OpenSans_Condensed-Regular.ttf + static/OpenSans_Condensed-Medium.ttf + static/OpenSans_Condensed-SemiBold.ttf + static/OpenSans_Condensed-Bold.ttf + static/OpenSans_Condensed-ExtraBold.ttf + static/OpenSans_SemiCondensed-Light.ttf + static/OpenSans_SemiCondensed-Regular.ttf + static/OpenSans_SemiCondensed-Medium.ttf + static/OpenSans_SemiCondensed-SemiBold.ttf + static/OpenSans_SemiCondensed-Bold.ttf + static/OpenSans_SemiCondensed-ExtraBold.ttf + static/OpenSans-Light.ttf + static/OpenSans-Regular.ttf + static/OpenSans-Medium.ttf + static/OpenSans-SemiBold.ttf + static/OpenSans-Bold.ttf + static/OpenSans-ExtraBold.ttf + static/OpenSans_Condensed-LightItalic.ttf + static/OpenSans_Condensed-Italic.ttf + static/OpenSans_Condensed-MediumItalic.ttf + static/OpenSans_Condensed-SemiBoldItalic.ttf + static/OpenSans_Condensed-BoldItalic.ttf + static/OpenSans_Condensed-ExtraBoldItalic.ttf + static/OpenSans_SemiCondensed-LightItalic.ttf + static/OpenSans_SemiCondensed-Italic.ttf + static/OpenSans_SemiCondensed-MediumItalic.ttf + static/OpenSans_SemiCondensed-SemiBoldItalic.ttf + static/OpenSans_SemiCondensed-BoldItalic.ttf + static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf + static/OpenSans-LightItalic.ttf + static/OpenSans-Italic.ttf + static/OpenSans-MediumItalic.ttf + static/OpenSans-SemiBoldItalic.ttf + static/OpenSans-BoldItalic.ttf + static/OpenSans-ExtraBoldItalic.ttf + +Get started +----------- + +1. Install the font files you want to use + +2. Use your app's font picker to view the font family and all the +available styles + +Learn more about variable fonts +------------------------------- + + https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts + https://variablefonts.typenetwork.com + https://medium.com/variable-fonts + +In desktop apps + + https://theblog.adobe.com/can-variable-fonts-illustrator-cc + https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts + +Online + + https://developers.google.com/fonts/docs/getting_started + https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide + https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts + +Installing fonts + + MacOS: https://support.apple.com/en-us/HT201749 + Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux + Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows + +Android Apps + + https://developers.google.com/fonts/docs/android + https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts + +License +------- +Please read the full license text (OFL.txt) to understand the permissions, +restrictions and requirements for usage, redistribution, and modification. + +You can use them in your products & projects – print or digital, +commercial or otherwise. + +This isn't legal advice, please consider consulting a lawyer and see the full +license for all details. diff --git a/magnolia/assets/fonts/static/OpenSans-Bold.ttf b/magnolia/assets/fonts/static/OpenSans-Bold.ttf new file mode 100644 index 00000000..98c74e0a Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans-Bold.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans-BoldItalic.ttf b/magnolia/assets/fonts/static/OpenSans-BoldItalic.ttf new file mode 100644 index 00000000..85589283 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans-BoldItalic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans-ExtraBold.ttf b/magnolia/assets/fonts/static/OpenSans-ExtraBold.ttf new file mode 100644 index 00000000..4eb33935 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans-ExtraBold.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans-ExtraBoldItalic.ttf b/magnolia/assets/fonts/static/OpenSans-ExtraBoldItalic.ttf new file mode 100644 index 00000000..75789b42 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans-ExtraBoldItalic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans-Italic.ttf b/magnolia/assets/fonts/static/OpenSans-Italic.ttf new file mode 100644 index 00000000..29ff6938 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans-Italic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans-Light.ttf b/magnolia/assets/fonts/static/OpenSans-Light.ttf new file mode 100644 index 00000000..ea175cc3 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans-Light.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans-LightItalic.ttf b/magnolia/assets/fonts/static/OpenSans-LightItalic.ttf new file mode 100644 index 00000000..edbfe0b7 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans-LightItalic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans-Medium.ttf b/magnolia/assets/fonts/static/OpenSans-Medium.ttf new file mode 100644 index 00000000..ae716936 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans-Medium.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans-MediumItalic.ttf b/magnolia/assets/fonts/static/OpenSans-MediumItalic.ttf new file mode 100644 index 00000000..6d1e09b2 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans-MediumItalic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans-Regular.ttf b/magnolia/assets/fonts/static/OpenSans-Regular.ttf new file mode 100644 index 00000000..67803bb6 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans-Regular.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans-SemiBold.ttf b/magnolia/assets/fonts/static/OpenSans-SemiBold.ttf new file mode 100644 index 00000000..e5ab4644 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans-SemiBold.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans-SemiBoldItalic.ttf b/magnolia/assets/fonts/static/OpenSans-SemiBoldItalic.ttf new file mode 100644 index 00000000..cd23e154 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans-SemiBoldItalic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_Condensed-Bold.ttf b/magnolia/assets/fonts/static/OpenSans_Condensed-Bold.ttf new file mode 100644 index 00000000..525397da Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_Condensed-Bold.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_Condensed-BoldItalic.ttf b/magnolia/assets/fonts/static/OpenSans_Condensed-BoldItalic.ttf new file mode 100644 index 00000000..d6c9bc0a Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_Condensed-BoldItalic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_Condensed-ExtraBold.ttf b/magnolia/assets/fonts/static/OpenSans_Condensed-ExtraBold.ttf new file mode 100644 index 00000000..3e600b9a Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_Condensed-ExtraBold.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_Condensed-ExtraBoldItalic.ttf b/magnolia/assets/fonts/static/OpenSans_Condensed-ExtraBoldItalic.ttf new file mode 100644 index 00000000..03936508 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_Condensed-ExtraBoldItalic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_Condensed-Italic.ttf b/magnolia/assets/fonts/static/OpenSans_Condensed-Italic.ttf new file mode 100644 index 00000000..fdf0a52e Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_Condensed-Italic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_Condensed-Light.ttf b/magnolia/assets/fonts/static/OpenSans_Condensed-Light.ttf new file mode 100644 index 00000000..459be7b4 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_Condensed-Light.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_Condensed-LightItalic.ttf b/magnolia/assets/fonts/static/OpenSans_Condensed-LightItalic.ttf new file mode 100644 index 00000000..5f05d08e Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_Condensed-LightItalic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_Condensed-Medium.ttf b/magnolia/assets/fonts/static/OpenSans_Condensed-Medium.ttf new file mode 100644 index 00000000..802200d2 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_Condensed-Medium.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_Condensed-MediumItalic.ttf b/magnolia/assets/fonts/static/OpenSans_Condensed-MediumItalic.ttf new file mode 100644 index 00000000..b43786bb Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_Condensed-MediumItalic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_Condensed-Regular.ttf b/magnolia/assets/fonts/static/OpenSans_Condensed-Regular.ttf new file mode 100644 index 00000000..a2a83ac6 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_Condensed-Regular.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_Condensed-SemiBold.ttf b/magnolia/assets/fonts/static/OpenSans_Condensed-SemiBold.ttf new file mode 100644 index 00000000..75bcd43c Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_Condensed-SemiBold.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_Condensed-SemiBoldItalic.ttf b/magnolia/assets/fonts/static/OpenSans_Condensed-SemiBoldItalic.ttf new file mode 100644 index 00000000..9fcaa52e Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_Condensed-SemiBoldItalic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_SemiCondensed-Bold.ttf b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-Bold.ttf new file mode 100644 index 00000000..dc927fc9 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-Bold.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_SemiCondensed-BoldItalic.ttf b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-BoldItalic.ttf new file mode 100644 index 00000000..7601048e Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-BoldItalic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_SemiCondensed-ExtraBold.ttf b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-ExtraBold.ttf new file mode 100644 index 00000000..d6864b1d Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-ExtraBold.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf new file mode 100644 index 00000000..ec7ade58 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_SemiCondensed-Italic.ttf b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-Italic.ttf new file mode 100644 index 00000000..7fc00c82 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-Italic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_SemiCondensed-Light.ttf b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-Light.ttf new file mode 100644 index 00000000..5936496a Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-Light.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_SemiCondensed-LightItalic.ttf b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-LightItalic.ttf new file mode 100644 index 00000000..7ced21a7 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-LightItalic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_SemiCondensed-Medium.ttf b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-Medium.ttf new file mode 100644 index 00000000..25b1aadf Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-Medium.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_SemiCondensed-MediumItalic.ttf b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-MediumItalic.ttf new file mode 100644 index 00000000..fd87f785 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-MediumItalic.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_SemiCondensed-Regular.ttf b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-Regular.ttf new file mode 100644 index 00000000..5b09b35b Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-Regular.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_SemiCondensed-SemiBold.ttf b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-SemiBold.ttf new file mode 100644 index 00000000..fff3a372 Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-SemiBold.ttf differ diff --git a/magnolia/assets/fonts/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf new file mode 100644 index 00000000..3874205d Binary files /dev/null and b/magnolia/assets/fonts/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf differ diff --git a/magnolia/src/core/application.cpp b/magnolia/src/core/application.cpp index 6fde346b..664b13fa 100644 --- a/magnolia/src/core/application.cpp +++ b/magnolia/src/core/application.cpp @@ -55,8 +55,12 @@ namespace mag shader_loader = create_unique(); LOG_SUCCESS("ShaderLoader initialized"); + // Create the font loader + font_loader = create_unique(); + LOG_SUCCESS("FontLoader initialized"); + // Create the texture manager - texture_loader = create_unique(); + texture_manager = create_unique(); LOG_SUCCESS("TextureManager initialized"); // Create the material manager @@ -71,6 +75,10 @@ namespace mag shader_manager = create_unique(); LOG_SUCCESS("ShaderManager initialized"); + // Create the font manager + font_manager = create_unique(); + LOG_SUCCESS("FontManager initialized"); + // Create the physics engine physics_engine = create_unique(); LOG_SUCCESS("Physics initialized"); diff --git a/magnolia/src/core/application.hpp b/magnolia/src/core/application.hpp index 6c7edde7..97e9a617 100644 --- a/magnolia/src/core/application.hpp +++ b/magnolia/src/core/application.hpp @@ -10,6 +10,8 @@ #include "platform/file_dialog.hpp" #include "renderer/renderer.hpp" #include "renderer/shader.hpp" +#include "resources/font.hpp" +#include "resources/font_loader.hpp" #include "resources/image_loader.hpp" #include "resources/material.hpp" #include "resources/material_loader.hpp" @@ -46,11 +48,13 @@ namespace mag ImageLoader& get_image_loader() { return *image_loader; }; MaterialLoader& get_material_loader() { return *material_loader; }; ModelLoader& get_model_loader() { return *model_loader; }; - TextureManager& get_texture_manager() { return *texture_loader; }; + ShaderLoader& get_shader_loader() { return *shader_loader; }; + FontLoader& get_font_loader() { return *font_loader; }; + TextureManager& get_texture_manager() { return *texture_manager; }; MaterialManager& get_material_manager() { return *material_manager; }; ModelManager& get_model_manager() { return *model_manager; }; - ShaderLoader& get_shader_loader() { return *shader_loader; }; ShaderManager& get_shader_manager() { return *shader_manager; }; + FontManager& get_font_manager() { return *font_manager; }; PhysicsEngine& get_physics_engine() { return *physics_engine; }; FileDialog& get_file_dialog() { return *file_dialog; }; @@ -66,10 +70,12 @@ namespace mag unique material_loader; unique model_loader; unique shader_loader; - unique texture_loader; + unique font_loader; + unique texture_manager; unique material_manager; unique model_manager; unique shader_manager; + unique font_manager; unique physics_engine; unique file_dialog; diff --git a/magnolia/src/ecs/components.hpp b/magnolia/src/ecs/components.hpp index 35139543..0857e305 100644 --- a/magnolia/src/ecs/components.hpp +++ b/magnolia/src/ecs/components.hpp @@ -124,6 +124,24 @@ namespace mag Camera camera; }; + class Font; + struct TextComponent : public Component + { + TextComponent(const str& text, const ref& font, const vec4& color = vec4(1), const f32 kerning = 0, + const f32 line_spacing = 0) + : text(text), color(color), kerning(kerning), line_spacing(line_spacing), font(font) + { + } + + CLONE(TextComponent); + + str text; + vec4 color; + f32 kerning; + f32 line_spacing; + ref font; + }; + class Script; struct ScriptComponent : public Component { diff --git a/magnolia/src/renderer/renderer.cpp b/magnolia/src/renderer/renderer.cpp index 29dbe906..e85fe34d 100644 --- a/magnolia/src/renderer/renderer.cpp +++ b/magnolia/src/renderer/renderer.cpp @@ -2,7 +2,9 @@ #include "core/assert.hpp" #include "core/logger.hpp" +#include "ecs/components.hpp" #include "renderer/buffers.hpp" +#include "renderer/test_model.hpp" #include "resources/model.hpp" namespace mag @@ -65,6 +67,24 @@ namespace mag command_buffer.bind_index_buffer(ibo_it->second->get_buffer()); } + void Renderer::bind_buffers(TextComponent* text_c) + { + auto vbo_it = vertex_buffers.find(text_c); + auto ibo_it = index_buffers.find(text_c); + + if (vbo_it == vertex_buffers.end() || ibo_it == index_buffers.end()) + { + LOG_ERROR("Text '{0}' was not uploaded to the GPU", static_cast(text_c)); + return; + } + + auto& command_buffer = context->get_curr_frame().command_buffer; + + // Bind buffers + command_buffer.bind_vertex_buffer(vbo_it->second->get_buffer()); + command_buffer.bind_index_buffer(ibo_it->second->get_buffer()); + } + void Renderer::update_model(Model* model) { auto vbo_it = vertex_buffers.find(model); @@ -149,7 +169,22 @@ namespace mag const vk::Extent3D extent(image->width, image->height, 1); // @TODO: check for supported formats - const vk::Format format = vk::Format::eR8G8B8A8Srgb; + vk::Format format = vk::Format::eR8G8B8A8Srgb; + + if (image->format == ImageFormat::Srgb) + { + format = vk::Format::eR8G8B8A8Srgb; + } + + else if (image->format == ImageFormat::Unorm) + { + format = vk::Format::eR8G8B8A8Unorm; + } + + else + { + LOG_ERROR("Invalid image format: '{0}'", static_cast(image->format)); + } images[image] = create_ref(extent, image->pixels, format, @@ -173,6 +208,38 @@ namespace mag images.erase(it); } + void Renderer::upload_text(TextComponent* text_c, const std::vector& vertices, + const std::vector& indices) + { + auto vbo_it = vertex_buffers.find(text_c); + auto ibo_it = index_buffers.find(text_c); + + if (vbo_it != vertex_buffers.end() || ibo_it != index_buffers.end()) + { + vertex_buffers[text_c]->resize(vertices.data(), VEC_SIZE_BYTES(vertices)); + index_buffers[text_c]->resize(indices.data(), VEC_SIZE_BYTES(indices)); + return; + } + + vertex_buffers[text_c] = create_ref(vertices.data(), VEC_SIZE_BYTES(vertices)); + index_buffers[text_c] = create_ref(indices.data(), VEC_SIZE_BYTES(indices)); + } + + void Renderer::remove_text(TextComponent* text_c) + { + auto vbo_it = vertex_buffers.find(text_c); + auto ibo_it = index_buffers.find(text_c); + + if (vbo_it == vertex_buffers.end() || ibo_it == index_buffers.end()) + { + LOG_ERROR("Tried to remove invalid text '{0}'", static_cast(text_c)); + return; + } + + vertex_buffers.erase(vbo_it); + index_buffers.erase(ibo_it); + } + void Renderer::on_event(Event& e) { EventDispatcher dispatcher(e); diff --git a/magnolia/src/renderer/renderer.hpp b/magnolia/src/renderer/renderer.hpp index 8aeee62f..1c127ba6 100644 --- a/magnolia/src/renderer/renderer.hpp +++ b/magnolia/src/renderer/renderer.hpp @@ -11,7 +11,8 @@ namespace mag { struct Model; - struct Material; + struct TextComponent; + struct QuadVertex; class Renderer { @@ -29,6 +30,7 @@ namespace mag // @TODO: temp? void bind_buffers(Model* model); + void bind_buffers(TextComponent* text_c); ref get_renderer_image(Image* image); // @TODO: temp? @@ -40,15 +42,20 @@ namespace mag void remove_image(Image* image); void update_image(Image* image); + void upload_text(TextComponent* text_c, const std::vector& vertices, + const std::vector& indices); + + void remove_text(TextComponent* text_c); + private: void on_resize(WindowResizeEvent& e); Window& window; unique context; - // Model data - std::map> vertex_buffers; - std::map> index_buffers; + // Buffer data + std::map> vertex_buffers; + std::map> index_buffers; // Image data std::map> images; diff --git a/magnolia/src/resources/font.cpp b/magnolia/src/resources/font.cpp new file mode 100644 index 00000000..179850dc --- /dev/null +++ b/magnolia/src/resources/font.cpp @@ -0,0 +1,69 @@ +#include "resources/font.hpp" + +#include "core/application.hpp" + +namespace mag +{ +#define DEFAULT_FONT_NAME "magnolia/assets/fonts/static/OpenSans-Regular.ttf" + + // @TODO: figure out a way to make this a task. + + ref FontManager::get(const str& name) + { + // Font found + auto it = fonts.find(name); + if (it != fonts.end()) + { + return it->second; + } + + auto& app = get_application(); + // auto& job_system = app.get_job_system(); + auto& renderer = app.get_renderer(); + auto& font_loader = app.get_font_loader(); + + // Create a new font + Font* font = new Font(); + + fonts[name] = ref(font); + + if (font_loader.load(name, font)) + { + renderer.upload_image(&font->atlas_image); + } + + // Send font data to the GPU + // renderer.upload_image(font); + + // Temporary font to load data into + // Font* transfer_font = new Font(*font); + + // Load in another thread + // auto execute = [&font_loader, name, transfer_font] + // { + // // If the load fails we still have valid data + // return font_loader.load(name, transfer_font); + // }; + + // // Callback when finished loading + // auto load_finished_callback = [font, transfer_font, &renderer](const b8 result) + // { + // // Update the font and the renderer font data + // if (result == true) + // { + // *font = *transfer_font; + // renderer.update_image(font); + // } + + // // We can dispose of the temporary font now + // delete transfer_font; + // }; + + // Job load_job = Job(execute, load_finished_callback); + // job_system.add_job(load_job); + + return fonts[name]; + } + + ref FontManager::get_default() { return get(DEFAULT_FONT_NAME); } +} // namespace mag diff --git a/magnolia/src/resources/font.hpp b/magnolia/src/resources/font.hpp new file mode 100644 index 00000000..c2248b8f --- /dev/null +++ b/magnolia/src/resources/font.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "core/types.hpp" +#include "resources/image.hpp" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#include "msdf_atlas_gen/msdf-atlas-gen/msdf-atlas-gen.h" +#pragma GCC diagnostic pop + +// This implementation was based on the cherno's implementation: https://www.youtube.com/watch?v=iMuiim9loOg +// Also see: https://github.com/TheCherno/Hazel/blob/master/Hazel/src/Hazel/Renderer/Font.h + +namespace mag +{ +#define PIXEL_RANGE 2.0 // @TODO: hardcoded? + + struct Font + { + str file_path = ""; + Image atlas_image; + + msdf_atlas::FontGeometry font_geometry; + std::vector glyphs; + }; + + class FontManager + { + public: + ref get(const str& name); + ref get_default(); + + private: + std::map> fonts; + }; +} // namespace mag diff --git a/magnolia/src/resources/font_loader.cpp b/magnolia/src/resources/font_loader.cpp new file mode 100644 index 00000000..cbb9a376 --- /dev/null +++ b/magnolia/src/resources/font_loader.cpp @@ -0,0 +1,156 @@ +#include "resources/font_loader.hpp" + +#include + +#include "core/application.hpp" +#include "core/logger.hpp" +#include "resources/font.hpp" + +// @TODO: move this to file system +#define STB_IMAGE_WRITE_IMPLEMENTATION +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#include "stb/stb_image_write.h" +#pragma GCC diagnostic pop + +namespace mag +{ + // Edge coloring macros +#define DEFAULT_ANGLE_THRESHOLD 3.0 +#define LCG_MULTIPLIER 6364136223846793005ull +#define LCG_INCREMENT 1442695040888963407ull + + template GenFunc> + static void CreateAndCacheAtlas(const str& fontName, f32 fontSize, + const std::vector& glyphs, + const msdf_atlas::FontGeometry& fontGeometry, const u32 width, const u32 height, + Image& image) + { + (void)fontName; + (void)fontSize; + (void)fontGeometry; + + msdf_atlas::GeneratorAttributes attributes; + attributes.config.overlapSupport = true; + attributes.scanlinePass = true; + + msdf_atlas::ImmediateAtlasGenerator> generator(width, + height); + generator.setAttributes(attributes); + generator.setThreadCount(8); // @TODO: hardcoded + generator.generate(glyphs.data(), static_cast(glyphs.size())); + + msdfgen::BitmapConstRef bitmap = generator.atlasStorage(); + + const u64 image_size = bitmap.width * bitmap.height * N; + + image.width = bitmap.width; + image.height = bitmap.height; + image.channels = N; + image.mip_levels = 1; + image.format = ImageFormat::Unorm; + image.pixels = std::vector(reinterpret_cast(bitmap.pixels), + reinterpret_cast(bitmap.pixels) + image_size); + + // @TODO: move this to file system + stbi_flip_vertically_on_write(true); + if (!stbi_write_png((fontName + "_native.png").c_str(), image.width, image.height, image.channels, + image.pixels.data(), image.width * image.channels)) + { + ASSERT(false, "@TODO: OOUUUFF"); + } + } + + FontLoader::FontLoader() : freetype(msdfgen::initializeFreetype()) {} + + FontLoader::~FontLoader() { msdfgen::deinitializeFreetype(static_cast(freetype)); } + + b8 FontLoader::load(const str& file_path, Font* font) + { + if (!font) + { + LOG_ERROR("Invalid font ptr"); + return false; + } + + font->file_path = file_path; + + auto& app = get_application(); + auto& file_system = app.get_file_system(); + + auto ft = static_cast(freetype); + if (!ft) + { + LOG_ERROR("Failed to initialize freetype"); + return false; + } + + Buffer buffer; + file_system.read_binary_data(file_path, buffer); + + msdfgen::FontHandle* font_handle = msdfgen::loadFontData(ft, buffer.data.data(), buffer.get_size()); + if (!font_handle) + { + LOG_ERROR("Failed to load font file: {0}", file_path); + return false; + } + + struct CharsetRange + { + u32 Begin, End; + }; + + // From imgui_draw.cpp + static const CharsetRange charsetRanges[] = {{0x0020, 0x00FF}}; + + msdf_atlas::Charset charset; + for (CharsetRange range : charsetRanges) + { + for (u32 c = range.Begin; c <= range.End; c++) charset.add(c); + } + + const f64 fontScale = 1.0; + font->font_geometry = msdf_atlas::FontGeometry(&font->glyphs); + i32 glyphsLoaded = font->font_geometry.loadCharset(font_handle, fontScale, charset); + LOG_INFO("Loaded {0} glyphs from font (out of {1})", glyphsLoaded, charset.size()); + + f64 emSize = 64.0; + + msdf_atlas::TightAtlasPacker atlasPacker; + atlasPacker.setPixelRange(PIXEL_RANGE); + atlasPacker.setMiterLimit(1.0); + atlasPacker.setScale(emSize); + + const i32 remaining = atlasPacker.pack(font->glyphs.data(), static_cast(font->glyphs.size())); + if (remaining != 0) + { + LOG_ERROR("Failed to pack font atlas from font: {0}", file_path); + msdfgen::destroyFont(font_handle); + + return false; + } + + i32 width, height; + atlasPacker.getDimensions(width, height); + emSize = atlasPacker.getScale(); + + // Edge coloring + u64 coloringSeed = 0; + u64 glyphSeed = coloringSeed; + + for (msdf_atlas::GlyphGeometry& glyph : font->glyphs) + { + glyphSeed *= LCG_MULTIPLIER; + glyph.edgeColoring(msdfgen::edgeColoringInkTrap, DEFAULT_ANGLE_THRESHOLD, glyphSeed); + } + + CreateAndCacheAtlas( + file_path, static_cast(emSize), font->glyphs, font->font_geometry, width, height, font->atlas_image); + + msdfgen::destroyFont(font_handle); + + return true; + } + + b8 FontLoader::is_extension_supported(const str& extension_with_dot) { return extension_with_dot == ".ttf"; } +}; // namespace mag diff --git a/magnolia/src/resources/font_loader.hpp b/magnolia/src/resources/font_loader.hpp new file mode 100644 index 00000000..595dc1dd --- /dev/null +++ b/magnolia/src/resources/font_loader.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "core/types.hpp" + +namespace mag +{ + struct Font; + + class FontLoader + { + public: + FontLoader(); + ~FontLoader(); + + b8 load(const str& file_path, Font* font); + b8 is_extension_supported(const str& extension_with_dot); + + private: + void* freetype = nullptr; + }; +}; // namespace mag diff --git a/magnolia/src/resources/image.hpp b/magnolia/src/resources/image.hpp index bf62af10..0aff4b7e 100644 --- a/magnolia/src/resources/image.hpp +++ b/magnolia/src/resources/image.hpp @@ -1,19 +1,25 @@ #pragma once #include -#include #include #include "core/types.hpp" namespace mag { + enum class ImageFormat + { + Srgb, + Unorm + }; + struct Image { u8 channels = 4; u32 width = 64; u32 height = 64; u32 mip_levels = 1; + ImageFormat format = ImageFormat::Srgb; std::vector pixels = std::vector(64 * 64 * 4, 153); }; diff --git a/magnolia/src/scene/scene.cpp b/magnolia/src/scene/scene.cpp index deefce20..3f02af10 100644 --- a/magnolia/src/scene/scene.cpp +++ b/magnolia/src/scene/scene.cpp @@ -204,6 +204,17 @@ namespace mag // // // build_render_graph(draw_size); // } + void Scene::add_text(const str& path) + { + auto& app = get_application(); + auto& font_manager = app.get_font_manager(); + + const str text = "Lorem Ipsum"; + const auto entity = ecs->create_entity(); + ecs->add_component(entity, new TransformComponent()); + ecs->add_component(entity, new TextComponent(text, font_manager.get(path))); + } + void Scene::add_model(const str& path) { auto& app = get_application(); diff --git a/magnolia/src/scene/scene.hpp b/magnolia/src/scene/scene.hpp index 1b32689a..c7157294 100644 --- a/magnolia/src/scene/scene.hpp +++ b/magnolia/src/scene/scene.hpp @@ -29,6 +29,7 @@ namespace mag void add_model(const str& path); void add_sprite(const str& path); + void add_text(const str& path); void remove_entity(const u32 id); diff --git a/magnolia/src/scene/scene_serializer.cpp b/magnolia/src/scene/scene_serializer.cpp index f0881bb4..1666e52d 100644 --- a/magnolia/src/scene/scene_serializer.cpp +++ b/magnolia/src/scene/scene_serializer.cpp @@ -7,18 +7,21 @@ namespace mag { - json& operator<<(json& out, const vec2& v) + template + json& insert_value(json& out, const T& v) { - for (i32 i = 0; i < v.length(); i++) out.push_back(v[i]); - return out; - } + if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) + { + for (i32 i = 0; i < v.length(); i++) out.push_back(v[i]); + } - json& operator<<(json& out, const vec3& v) - { - for (i32 i = 0; i < v.length(); i++) out.push_back(v[i]); return out; } + json& operator<<(json& out, const vec2& v) { return insert_value(out, v); } + json& operator<<(json& out, const vec3& v) { return insert_value(out, v); } + json& operator<<(json& out, const vec4& v) { return insert_value(out, v); } + SceneSerializer::SceneSerializer(Scene& scene) : scene(scene) {} void SceneSerializer::serialize(const str& file_path) @@ -106,6 +109,15 @@ namespace mag entity["ScriptComponent"]["FilePath"] = component->file_path; } + if (auto component = ecs.get_component(entity_id)) + { + entity["TextComponent"]["Font"] = component->font->file_path; + entity["TextComponent"]["Color"] << component->color; + entity["TextComponent"]["Kerning"] = component->kerning; + entity["TextComponent"]["LineSpacing"] = component->line_spacing; + entity["TextComponent"]["Text"] = component->text; + } + data["Entities"].push_back(entity); } @@ -170,9 +182,9 @@ namespace mag if (entity.contains("ModelComponent")) { const auto& component = entity["ModelComponent"]; - const str file_path = component["FilePath"]; + const str model_file_path = component["FilePath"]; - const auto& model = app.get_model_manager().get(file_path); + const auto& model = app.get_model_manager().get(model_file_path); ecs.add_component(entity_id, new ModelComponent(model)); } @@ -241,9 +253,25 @@ namespace mag { const auto& component = entity["ScriptComponent"]; - const str file_path = component["FilePath"]; + const str script_file_path = component["FilePath"]; + + ecs.add_component(entity_id, new ScriptComponent(script_file_path)); + } + + if (entity.contains("TextComponent")) + { + const auto& component = entity["TextComponent"]; + + const str font = component["Font"]; + const str text = component["Text"]; + const f32 kerning = component["Kerning"]; + const f32 line_spacing = component["LineSpacing"]; + + vec4 color; + for (i32 i = 0; i < color.length(); i++) color[i] = component["Color"][i].get(); - ecs.add_component(entity_id, new ScriptComponent(file_path)); + ecs.add_component(entity_id, new TextComponent(text, app.get_font_manager().get(font), color, + kerning, line_spacing)); } } } diff --git a/premake5.lua b/premake5.lua index 7b22b9d4..187764cc 100644 --- a/premake5.lua +++ b/premake5.lua @@ -19,9 +19,11 @@ workspace "magnolia" libdir = "build/windows/lib" end + -- @TODO: package this usr_includes = { - "/usr/include/lua5.4" + "/usr/include/lua5.4", + "/usr/include/freetype2" } lib_includes = @@ -43,6 +45,10 @@ workspace "magnolia" "libs/bullet/src", "libs/sol2/include", + "libs/msdf_atlas_gen", + "libs/msdf_atlas_gen/msdfgen", + "libs/msdf_atlas_gen/msdf-atlas-gen", + -- @TODO: this is necessary because 'config.h' is generated by the cmake "build/linux/assimp/include" } @@ -54,7 +60,11 @@ workspace "magnolia" "Bullet3Common", "Bullet3Dynamics", "Bullet3Collision", "Bullet3Geometry", "BulletLinearMath", - "lua5.4" + "png", -- @TODO: package this + "freetype", -- @TODO: package this + "msdfatlasgen", -- @TODO: package this + "msdfgen", -- @TODO: package this + "lua5.4" -- @TODO: package this } -- @TODO: consistent build folders/output @@ -493,3 +503,79 @@ project "implot" pic "on" systemversion "latest" staticruntime "on" + +-- msdfgen ------------------------------------------------------------------------------------------------------------- +project "msdfgen" + kind "staticlib" + language "c++" + cppdialect "c++20" + + targetdir (libdir) + objdir ("build/%{cfg.system}/%{prj.name}/%{cfg.buildcfg}") + + files + { + "libs/msdf_atlas_gen/msdfgen/**.h", + + "libs/msdf_atlas_gen/msdfgen/core/**.h", + "libs/msdf_atlas_gen/msdfgen/core/**.hpp", + "libs/msdf_atlas_gen/msdfgen/core/**.cpp", + + "libs/msdf_atlas_gen/msdfgen/ext/**.h", + "libs/msdf_atlas_gen/msdfgen/ext/**.hpp", + "libs/msdf_atlas_gen/msdfgen/ext/**.cpp" + } + + includedirs + { + "libs/msdf_atlas_gen", + "libs/msdf_atlas_gen/msdfgen", + "libs/msdf_atlas_gen/msdfgen/core", + "libs/msdf_atlas_gen/msdfgen/ext", + "/usr/include/freetype2" -- @TODO: freetype as dependency + } + + defines + { + "MSDFGEN_USE_LIBPNG", + "MSDFGEN_USE_CPP11" + } + + filter "system:linux" + pic "on" + systemversion "latest" + staticruntime "on" + +-- msdf_atlas_gen ------------------------------------------------------------------------------------------------------ +project "msdfatlasgen" + kind "staticlib" + language "c++" + cppdialect "c++20" + + targetdir (libdir) + objdir ("build/%{cfg.system}/%{prj.name}/%{cfg.buildcfg}") + + files + { + "libs/msdf_atlas_gen/msdf-atlas-gen/**.h", + "libs/msdf_atlas_gen/msdf-atlas-gen/**.hpp", + "libs/msdf_atlas_gen/msdf-atlas-gen/**.cpp" + } + + includedirs + { + "libs/msdf_atlas_gen", + "libs/msdf_atlas_gen/msdfgen", + "libs/msdf_atlas_gen/msdf-atlas-gen", + "libs/msdf_atlas_gen/artery-font-format" + } + + links + { + "msdfgen" + } + + filter "system:linux" + pic "on" + systemversion "latest" + staticruntime "on" \ No newline at end of file diff --git a/sprout_editor/assets/scenes/Test.mag.json b/sprout_editor/assets/scenes/Test.mag.json index 6337c604..5c80f876 100644 --- a/sprout_editor/assets/scenes/Test.mag.json +++ b/sprout_editor/assets/scenes/Test.mag.json @@ -94,6 +94,40 @@ ], "Intensity": 100.0 } + }, + { + "NameComponent": { + "Name": "Text" + }, + "TransformComponent": { + "Translation": [ + -50.0, + 50.0, + 0.0 + ], + "Rotation": [ + 0.0, + 0.0, + 0.0 + ], + "Scale": [ + 10.0, + 10.0, + 10.0 + ] + }, + "TextComponent": { + "Font": "sprout_editor/assets/fonts/Roboto/Roboto-Regular.ttf", + "Color": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "Kerning": 0.0, + "LineSpacing": 0.0, + "Text": "Lorem Ipsum" + } } ] } \ No newline at end of file diff --git a/sprout_editor/assets/shaders/text.frag b/sprout_editor/assets/shaders/text.frag new file mode 100644 index 00000000..7424be65 --- /dev/null +++ b/sprout_editor/assets/shaders/text.frag @@ -0,0 +1,40 @@ +#version 460 + +#include "text.include.glsl" + +layout (location = 0) in vec2 in_tex_coords; + +layout (location = 0) out vec4 out_frag_color; + +// @TODO: move this to utils or smth +float median(float r, float g, float b) +{ + return max(min(r, g), min(max(r, g), b)); +} + +float screen_pixel_range(float pixel_range, sampler2D tex, vec2 tex_coord) +{ + vec2 unit_range = vec2(pixel_range) / vec2(textureSize(tex, 0)); + vec2 screen_tex_size = vec2(1.0) / fwidth(tex_coord); + return max(0.5 * dot(unit_range, screen_tex_size), 1.0); +} + +void main() +{ + vec4 msd = texture(u_atlas_texture, in_tex_coords).rgba; + float sd = median(msd.r, msd.g, msd.b); + + float screen_pixel_distance = + screen_pixel_range(u_text_info.pixel_range, u_atlas_texture, in_tex_coords) * (sd - 0.5); + + float opacity = clamp(screen_pixel_distance + 0.5, 0.0, 1.0); + + // @TODO: the 'perfect' blending needs to mix between the background and text color. To achieve that, we can sample + // the color from a render attachment and use it as background color. However, to do that, we would need to handle + // descriptors and im not going to do that rn. + // out_frag_color = mix(u_text_info.color, background_color, opacity); + + if (opacity < 0.5) discard; // Discard transparent fragments + + out_frag_color = vec4(u_text_info.color.rgb, opacity); +} diff --git a/sprout_editor/assets/shaders/text.include.glsl b/sprout_editor/assets/shaders/text.include.glsl new file mode 100644 index 00000000..73651dc0 --- /dev/null +++ b/sprout_editor/assets/shaders/text.include.glsl @@ -0,0 +1,17 @@ +#include "include/types.glsl" + +// Global buffer +layout (set = 0, binding = 0) uniform GlobalBuffer +{ + // Camera + mat4 view; + mat4 projection; +} u_global; + +// Texture +layout (set = 1, binding = 0) uniform sampler2D u_atlas_texture; +layout (set = 2, binding = 0) uniform TextInfoBuffer +{ + vec4 color; + float pixel_range; +} u_text_info; diff --git a/sprout_editor/assets/shaders/text.vert b/sprout_editor/assets/shaders/text.vert new file mode 100644 index 00000000..70d93351 --- /dev/null +++ b/sprout_editor/assets/shaders/text.vert @@ -0,0 +1,14 @@ +#version 460 + +#include "text.include.glsl" + +layout (location = 0) in vec3 in_position; +layout (location = 1) in vec2 in_tex_coords; + +layout (location = 0) out vec2 out_tex_coords; + +void main() +{ + gl_Position = PROJ_MATRIX * VIEW_MATRIX * vec4(in_position, 1.0); + out_tex_coords = in_tex_coords; +} diff --git a/sprout_editor/assets/shaders/text_shader.mag.json b/sprout_editor/assets/shaders/text_shader.mag.json new file mode 100644 index 00000000..a8f8f21f --- /dev/null +++ b/sprout_editor/assets/shaders/text_shader.mag.json @@ -0,0 +1,19 @@ +{ + "Shader": "Text", + "Files": [ + "text.vert.spv", + "text.frag.spv" + ], + "Pipeline": { + "InputAssembly": { + "Topology": "Triangle" + }, + "Rasterization": { + "PolygonMode": "Fill", + "CullMode": "Back" + }, + "ColorBlend": { + "Enabled": false + } + } +} \ No newline at end of file diff --git a/sprout_editor/src/panels/properties_panel.cpp b/sprout_editor/src/panels/properties_panel.cpp index 057eb4bd..cd98e85a 100644 --- a/sprout_editor/src/panels/properties_panel.cpp +++ b/sprout_editor/src/panels/properties_panel.cpp @@ -1,6 +1,8 @@ #include "panels/properties_panel.hpp" #include "icon_font_cpp/IconsFontAwesome6.h" +#include "imgui/imgui_stdlib.h" +#include "resources/font.hpp" #include "resources/model.hpp" namespace sprout @@ -8,6 +10,63 @@ namespace sprout #define MIN_VALUE -1'000'000'000 #define MAX_VALUE +1'000'000'000 + template + void editable_field(const str &field_name, T &value, const T &reset_value, const T &min_value, const T &max_value) + { + const char *format = "%.3f"; + const f32 left_offset = 100.0f; + const ImGuiInputTextFlags input_flags = ImGuiInputTextFlags_EnterReturnsTrue; + + T editable_value = value; + + ImGui::Text("%s", field_name.c_str()); + ImGui::SameLine(left_offset); + + const str label = str("##") + field_name.c_str(); + + // Select input according to the T type + + b8 input = false; + + if constexpr (std::is_same_v) + { + input = ImGui::InputFloat(label.c_str(), &editable_value, 0.0f, 0.0f, format, input_flags); + } + + else if constexpr (std::is_same_v) + { + input = ImGui::InputDouble(label.c_str(), &editable_value, 0.0f, 0.0f, format, input_flags); + } + + else if constexpr (std::is_same_v) + { + input = ImGui::InputFloat2(label.c_str(), value_ptr(editable_value), format, input_flags); + } + + else if constexpr (std::is_same_v) + { + input = ImGui::InputFloat3(label.c_str(), value_ptr(editable_value), format, input_flags); + } + + else if constexpr (std::is_same_v) + { + input = ImGui::InputFloat4(label.c_str(), value_ptr(editable_value), format, input_flags); + } + + if (input) + { + value = clamp(editable_value, min_value, max_value); + } + + // Reset + const str reset_label = str(ICON_FA_CIRCLE) + label; + ImGui::SameLine(); + if (ImGui::Button(reset_label.c_str())) + { + value = reset_value; + } + } + void PropertiesPanel::render(const ImGuiWindowFlags window_flags, ECS &ecs, const u32 selected_entity_id) { ImGui::Begin(ICON_FA_LIST " Properties", NULL, window_flags); @@ -20,9 +79,9 @@ namespace sprout { if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen)) { - editable_field("Translation", transform->translation, vec3(0), vec3(MIN_VALUE), vec3(MAX_VALUE)); - editable_field("Rotation", transform->rotation, vec3(0), vec3(-180), vec3(180)); - editable_field("Scale", transform->scale, vec3(1), vec3(0.0001), vec3(MAX_VALUE)); + editable_field("Translation", transform->translation, vec3(0), vec3(MIN_VALUE), vec3(MAX_VALUE)); + editable_field("Rotation", transform->rotation, vec3(0), vec3(-180), vec3(180)); + editable_field("Scale", transform->scale, vec3(1), vec3(0.0001), vec3(MAX_VALUE)); } } @@ -68,7 +127,7 @@ namespace sprout ImGui::SameLine(left_offset); ImGui::ColorEdit4("##Color", value_ptr(light->color), flags); - editable_field("Intensity", light->intensity, 1.0f, 0.0f, MAX_VALUE); + editable_field("Intensity", light->intensity, 1.0f, 0.0f, MAX_VALUE); } } @@ -77,18 +136,20 @@ namespace sprout { if (ImGui::CollapsingHeader("BoxCollider", ImGuiTreeNodeFlags_DefaultOpen)) { - editable_field("Dimensions", component->dimensions, vec3(1), vec3(0.001), vec3(MAX_VALUE)); + editable_field("Dimensions", component->dimensions, vec3(1), vec3(0.001), vec3(MAX_VALUE)); } } + // Rigidbody if (auto component = ecs.get_component(selected_entity_id)) { if (ImGui::CollapsingHeader("Rigidbody", ImGuiTreeNodeFlags_DefaultOpen)) { - editable_field("Mass", component->mass, 1.0f, 0.0f, MAX_VALUE); + editable_field("Mass", component->mass, 1.0f, 0.0f, MAX_VALUE); } } + // Camera if (auto component = ecs.get_component(selected_entity_id)) { if (ImGui::CollapsingHeader("Camera", ImGuiTreeNodeFlags_DefaultOpen)) @@ -97,78 +158,57 @@ namespace sprout f32 far = component->camera.get_far(); f32 fov = component->camera.get_fov(); - editable_field("Near", near, 1.0f, 0.1f, MAX_VALUE); - editable_field("Far", far, 1.0f, 0.1f, MAX_VALUE); - editable_field("Fov", fov, 60.0f, 30.0f, 120.0f); + editable_field("Near", near, 1.0f, 0.1f, MAX_VALUE); + editable_field("Far", far, 1.0f, 0.1f, MAX_VALUE); + editable_field("Fov", fov, 60.0f, 30.0f, 120.0f); component->camera.set_near_far({near, far}); component->camera.set_fov(fov); } } - if (auto component = ecs.get_component(selected_entity_id)) + // Text + if (auto component = ecs.get_component(selected_entity_id)) { - if (ImGui::CollapsingHeader("Script", ImGuiTreeNodeFlags_DefaultOpen)) + if (ImGui::CollapsingHeader("Text", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::TextWrapped("File Path: %s", component->file_path.c_str()); - } - } + const f32 left_offset = 100.0f; - end: - ImGui::End(); - } + ImGui::TextWrapped("Font"); + ImGui::SameLine(left_offset); + ImGui::Text("%s", component->font->file_path.c_str()); - void PropertiesPanel::editable_field(const str &field_name, vec3 &value, const vec3 &reset_value, - const vec3 &min_value, const vec3 &max_value) - { - const char *format = "%.2f"; - const f32 left_offset = 100.0f; - const ImGuiInputTextFlags input_flags = ImGuiInputTextFlags_EnterReturnsTrue; + editable_field("Kerning", component->kerning, 0.0f, 0.0f, 10.0f); + editable_field("Line Spacing", component->line_spacing, 0.0f, 0.0f, 10.0f); - vec3 editable_value = value; + const ImGuiColorEditFlags flags = ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_NoAlpha; - ImGui::Text("%s", field_name.c_str()); - ImGui::SameLine(left_offset); + ImGui::TextWrapped("Color"); + ImGui::SameLine(left_offset); + ImGui::ColorEdit4("##TextColor", value_ptr(component->color), flags); - const str label = str("##") + field_name.c_str(); - if (ImGui::InputFloat3(label.c_str(), value_ptr(editable_value), format, input_flags)) - { - value = clamp(editable_value, min_value, max_value); - } + str input_str = component->text; + input_str.resize(1000); - // Reset - const str reset_label = str(ICON_FA_CIRCLE) + label; - ImGui::SameLine(); - if (ImGui::Button(reset_label.c_str())) - { - value = reset_value; + ImGui::TextWrapped("Text"); + ImGui::SameLine(left_offset); + if (ImGui::InputTextMultiline("##InputText", &input_str, {0, 0})) + { + component->text = input_str; + } + } } - } - void PropertiesPanel::editable_field(const str &field_name, f32 &value, const f32 reset_value, const f32 min_value, - const f32 max_value) - { - const char *format = "%.2f"; - const f32 left_offset = 100.0f; - const ImGuiInputTextFlags input_flags = ImGuiInputTextFlags_EnterReturnsTrue; - - f32 editable_value = value; - - ImGui::Text("%s", field_name.c_str()); - ImGui::SameLine(left_offset); - - const str label = str("##") + field_name.c_str(); - if (ImGui::InputFloat(label.c_str(), &editable_value, 0.0f, 0.0f, format, input_flags)) + // Script + if (auto component = ecs.get_component(selected_entity_id)) { - value = clamp(editable_value, min_value, max_value); + if (ImGui::CollapsingHeader("Script", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::TextWrapped("File Path: %s", component->file_path.c_str()); + } } - // Reset - const str reset_label = str(ICON_FA_CIRCLE) + label; - ImGui::SameLine(); - if (ImGui::Button(reset_label.c_str())) - { - value = reset_value; - } + end: + ImGui::End(); } }; // namespace sprout diff --git a/sprout_editor/src/panels/properties_panel.hpp b/sprout_editor/src/panels/properties_panel.hpp index 552775a6..d1915c28 100644 --- a/sprout_editor/src/panels/properties_panel.hpp +++ b/sprout_editor/src/panels/properties_panel.hpp @@ -11,12 +11,5 @@ namespace sprout { public: void render(const ImGuiWindowFlags window_flags, ECS &ecs, const u32 selected_entity_id); - - private: - void editable_field(const str &field_name, vec3 &value, const vec3 &reset_value, const vec3 &min_value, - const vec3 &max_value); - - void editable_field(const str &field_name, f32 &value, const f32 reset_value, const f32 min_value, - const f32 max_value); }; }; // namespace sprout diff --git a/sprout_editor/src/panels/viewport_panel.cpp b/sprout_editor/src/panels/viewport_panel.cpp index 39c810b5..98bdddd0 100644 --- a/sprout_editor/src/panels/viewport_panel.cpp +++ b/sprout_editor/src/panels/viewport_panel.cpp @@ -17,6 +17,7 @@ namespace sprout auto &app = get_application(); auto &editor = get_editor(); auto &image_loader = app.get_image_loader(); + auto &font_loader = app.get_font_loader(); auto &scene = editor.get_active_scene(); auto &open_scenes = editor.get_open_scenes(); @@ -171,6 +172,12 @@ namespace sprout scene.add_sprite(path); } + // Check if asset is a font + else if (font_loader.is_extension_supported(extension)) + { + scene.add_text(path); + } + else { LOG_ERROR("Extension not supported: {0}", extension); diff --git a/sprout_editor/src/passes/scene_pass.cpp b/sprout_editor/src/passes/scene_pass.cpp index 1c29e82f..6e9eb28a 100644 --- a/sprout_editor/src/passes/scene_pass.cpp +++ b/sprout_editor/src/passes/scene_pass.cpp @@ -3,7 +3,9 @@ #include "core/application.hpp" #include "editor.hpp" #include "renderer/render_graph.hpp" +#include "renderer/test_model.hpp" #include "renderer/type_conversions.hpp" +#include "resources/font.hpp" #include "resources/material.hpp" namespace sprout @@ -31,6 +33,7 @@ namespace sprout // Shaders mesh_shader = shader_manager.get("sprout_editor/assets/shaders/mesh_shader.mag.json"); sprite_shader = shader_manager.get("sprout_editor/assets/shaders/sprite_shader.mag.json"); + text_shader = shader_manager.get("sprout_editor/assets/shaders/text_shader.mag.json"); add_output_attachment("OutputColor", AttachmentType::Color, size); add_output_attachment("OutputDepth", AttachmentType::Depth, size); @@ -57,6 +60,7 @@ namespace sprout auto model_entities = ecs.get_all_components_of_types(); auto light_entities = ecs.get_all_components_of_types(); auto sprite_entities = ecs.get_all_components_of_types(); + auto text_entities = ecs.get_all_components_of_types(); // Render models @@ -162,5 +166,160 @@ namespace sprout performance_results.rendered_triangles += 2; performance_results.draw_calls++; } + + // Render text + + for (const auto& [transform, text_c] : text_entities) + { + draw_text(text_c, transform->get_transformation_matrix()); + } + } + + void ScenePass::draw_text(TextComponent* text_c, const mat4& transform) + { + auto& app = get_application(); + auto& renderer = app.get_renderer(); + + std::vector vertices; + std::vector indices; + + const auto& font_geometry = text_c->font->font_geometry; + const auto& metrics = font_geometry.getMetrics(); + const auto& font_atlas = renderer.get_renderer_image(&text_c->font->atlas_image); + + f64 x = 0.0; + f64 fs_scale = 1.0 / (metrics.ascenderY - metrics.descenderY); + f64 y = 0.0; + + const f64 space_glyph_advance = font_geometry.getGlyph(' ')->getAdvance(); + + const f64 pixel_range = PIXEL_RANGE; + + for (u32 i = 0; i < text_c->text.size(); i++) + { + char character = text_c->text[i]; + if (character == '\r') continue; + + if (character == '\n') + { + x = 0; + y -= fs_scale * metrics.lineHeight + text_c->line_spacing; + continue; + } + + if (character == ' ') + { + f64 advance = space_glyph_advance; + if (i < text_c->text.size() - 1) + { + char next_character = text_c->text[i + 1]; + f64 d_advance; + font_geometry.getAdvance(d_advance, character, next_character); + advance = static_cast(d_advance); + } + + x += fs_scale * advance + text_c->kerning; + continue; + } + + if (character == '\t') + { + // @TODO: is this right? + x += 4.0f * (fs_scale * space_glyph_advance + text_c->kerning); + continue; + } + + auto glyph = font_geometry.getGlyph(character); + if (!glyph) glyph = font_geometry.getGlyph('?'); + if (!glyph) return; + + f64 al, ab, ar, at; + glyph->getQuadAtlasBounds(al, ab, ar, at); + vec2 tex_coord_min((f64)al, (f64)ab); + vec2 tex_coord_max((f64)ar, (f64)at); + + f64 pl, pb, pr, pt; + glyph->getQuadPlaneBounds(pl, pb, pr, pt); + vec2 quad_min((f64)pl, (f64)pb); + vec2 quad_max((f64)pr, (f64)pt); + + quad_min *= fs_scale, quad_max *= fs_scale; + quad_min += vec2(x, y); + quad_max += vec2(x, y); + + const f64 texel_width = 1.0f / font_atlas->get_extent().width; + const f64 texel_height = 1.0f / font_atlas->get_extent().height; + + tex_coord_min *= vec2(texel_width, texel_height); + tex_coord_max *= vec2(texel_width, texel_height); + + QuadVertex v0, v1, v2, v3; + + // Bottom left + v0.position = transform * vec4(quad_min, 0.0f, 1.0f); + v0.tex_coords = tex_coord_min; + + // Top left + v1.position = transform * vec4(quad_min.x, quad_max.y, 0.0f, 1.0f); + v1.tex_coords = {tex_coord_min.x, tex_coord_max.y}; + + // Top right + v2.position = transform * vec4(quad_max, 0.0f, 1.0f); + v2.tex_coords = tex_coord_max; + + // Bottom right + v3.position = transform * vec4(quad_max.x, quad_min.y, 0.0f, 1.0f); + v3.tex_coords = {tex_coord_max.x, tex_coord_min.y}; + + const u32 offset = vertices.size(); + + vertices.push_back(v3); + vertices.push_back(v2); + vertices.push_back(v1); + vertices.push_back(v0); + + indices.push_back(0 + offset); + indices.push_back(1 + offset); + indices.push_back(2 + offset); + indices.push_back(2 + offset); + indices.push_back(3 + offset); + indices.push_back(0 + offset); + + if (i < text_c->text.size() - 1) + { + f64 advance = glyph->getAdvance(); + char next_character = text_c->text[i + 1]; + font_geometry.getAdvance(advance, character, next_character); + + x += fs_scale * advance + text_c->kerning; + } + } + + // @TODO: one of the ugliest hacks known to man + + renderer.upload_text(text_c, vertices, indices); + + // render + { + auto& app = get_application(); + auto& renderer = app.get_renderer(); + auto& editor = get_editor(); + auto& scene = editor.get_active_scene(); + const auto& camera = scene.get_camera(); + + text_shader->bind(); + + text_shader->set_uniform("u_global", "view", value_ptr(camera.get_view())); + text_shader->set_uniform("u_global", "projection", value_ptr(camera.get_projection())); + + text_shader->set_uniform("u_text_info", "color", value_ptr(text_c->color)); + text_shader->set_uniform("u_text_info", "pixel_range", &pixel_range); + + text_shader->set_texture("u_atlas_texture", &text_c->font->atlas_image); + + renderer.bind_buffers(text_c); + + renderer.draw_indexed(indices.size()); + } } }; // namespace sprout diff --git a/sprout_editor/src/passes/scene_pass.hpp b/sprout_editor/src/passes/scene_pass.hpp index 122e5537..a730d41d 100644 --- a/sprout_editor/src/passes/scene_pass.hpp +++ b/sprout_editor/src/passes/scene_pass.hpp @@ -1,5 +1,6 @@ #pragma once +#include "ecs/components.hpp" #include "renderer/render_graph.hpp" #include "renderer/shader.hpp" @@ -15,7 +16,10 @@ namespace sprout virtual void on_render(RenderGraph& render_graph) override; private: + void draw_text(TextComponent* text_c, const mat4& transform); + ref mesh_shader; ref sprite_shader; + ref text_shader; }; }; // namespace sprout