diff --git a/examples/ExampleApps/TextExampleApp.cpp b/examples/ExampleApps/TextExampleApp.cpp index 76e8ad5..68f0058 100644 --- a/examples/ExampleApps/TextExampleApp.cpp +++ b/examples/ExampleApps/TextExampleApp.cpp @@ -22,6 +22,7 @@ #include "Base/EngineConfiguration.hpp" #include "Controller/FreeCamCameraController.hpp" #include "Image/ImageLoaderPng.hpp" +#include "Scene/FontAtlasGenerator.hpp" #ifdef _WIN32 #undef TRANSPARENT @@ -49,19 +50,13 @@ namespace OpenVulkano m_scene.SetCamera(&m_cam); m_cfg.applyBorder = true; - //m_cfg.smoothing = 1 / 16.f; - const std::string symbols = "Ak?"; - const int N = symbols.size(); - const std::string font = (OpenVulkano::Utils::GetFontsDirectory() / "arial.ttf").string(); - Image::ImageLoaderPng pngLoader; + const std::array texts = { "ABab?.^{}_cdFGETG123)(", "Hello, World!" }; + const int N = texts.size(); + const std::string font = (OpenVulkano::Utils::GetFontsDirectory() / "Roboto-Regular.ttf").string(); m_uniBuffer.Init(sizeof(TextConfig), &m_cfg, 3); - m_materials.resize(N); - m_geos.resize(N); m_nodesPool.resize(N); m_drawablesPool.resize(N); - m_textures.resize(N); - m_sdfs.resize(N); m_shader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/text"); m_shader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/text"); @@ -71,59 +66,27 @@ namespace OpenVulkano m_shader.alphaBlend = true; m_shader.cullMode = CullMode::NONE; - static float vertices[] = { - // positions // texture coords - -0.5f, 0.5f, 0.0f, 1.0f, - -0.5f, -0.5f, 0.0f, 0.0f, - 0.5f, -0.5f, 1.0f, 0.0f, - 0.5f, 0.5f, 1.0f, 1.0f - }; - uint32_t indices[] = { 1, 2, 3, 1, 3, 0 }; + m_atlasGenerator.GenerateAtlas(font, (OpenVulkano::Utils::GetBuildDirectory() / "atlas.png").string()); - Text text = Text(m_cfg); - for (int i = 0; i < N; i++) + for (int i = 0; i < texts.size(); i++) { - m_drawablesPool[i].SetDrawPhase(OpenVulkano::Scene::DrawPhase::BACKGROUND); - const std::string fileName = std::string("output") + std::to_string(i + 1) + ".png"; - const std::string pngOutput = (OpenVulkano::Utils::GetBuildDirectory() / fileName).string(); - - auto& drawable = m_drawablesPool[i]; - auto& mat = m_materials[i]; - auto& tex = m_textures[i]; - auto& geo = m_geos[i]; - drawable.Init(&m_shader, &geo, &mat, &m_uniBuffer); - - text.Init(font, symbols[i], pngOutput); - m_sdfs[i] = pngLoader.loadFromFile(pngOutput); - auto& sdf = m_sdfs[i]; - tex.resolution = sdf->resolution; - tex.textureBuffer = sdf->data.Data(); - tex.format = sdf->dataFormat; - tex.size = sdf->data.Size(); - mat.texture = &tex; - - geo.Init(4, 6); - for (int j = 0; j < geo.vertexCount; j++) - { - geo.vertices[j].position.x = vertices[j * 4]; - geo.vertices[j].position.y = vertices[j * 4 + 1]; - geo.vertices[j].position.z = 0.f; - geo.vertices[j].textureCoordinates.x = vertices[j * 4 + 2]; - geo.vertices[j].textureCoordinates.y = vertices[j * 4 + 3]; - } - geo.SetIndices(indices, 6); - + Text* t = new Text(); + t->SetFontAtlasGenerator(&m_atlasGenerator); + t->SetConfig(m_cfg); + t->SetUniformBuffer(&m_uniBuffer); + t->SetShader(&m_shader); + t->GenerateText(texts[i]); + m_drawablesPool[i] = t; m_nodesPool[i].Init(); - m_nodesPool[i].SetMatrix(Math::Utils::translate( - glm::mat4x4(1.f), Vector3f(-3 + std::rand() % 3, -2 + std::rand() % 2, 2))); - m_nodesPool[i].AddDrawable(&drawable); + m_nodesPool[i].SetMatrix( + Math::Utils::translate(glm::mat4x4(1.f), Vector3f(-3 + std::rand() % 3, -2 + std::rand() % 4, 2))); + m_nodesPool[i].AddDrawable(m_drawablesPool[i]); m_scene.GetRoot()->AddChild(&m_nodesPool[i]); } - GetGraphicsAppManager()->GetRenderer()->SetScene(&m_scene); m_camController.Init(&m_cam); m_camController.SetDefaultKeybindings(); - m_camController.SetPosition({ -2, -1, 5 }); + m_camController.SetPosition({ 0, 0, 5 }); m_camController.SetBoostFactor(5); std::shared_ptr m_perfInfo = @@ -139,6 +102,10 @@ namespace OpenVulkano void Close() override { + for (SimpleDrawable* d: m_drawablesPool) + { + delete d; + } } private: @@ -148,12 +115,9 @@ namespace OpenVulkano OpenVulkano::FreeCamCameraController m_camController; Shader m_shader; TextConfig m_cfg; - std::vector m_materials; - std::vector m_drawablesPool; - std::vector m_geos; + FontAtlasGenerator m_atlasGenerator; + std::vector m_drawablesPool; std::vector m_nodesPool; - std::vector m_textures; - std::vector> m_sdfs; Vector3f_SIMD m_position = { 0, 0, -10 }; OpenVulkano::Scene::UI::SimpleUi m_ui; std::shared_ptr m_perfInfo; diff --git a/fonts/LICENSE.txt b/fonts/LICENSE.txt new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/fonts/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/fonts/Roboto-Regular.ttf b/fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/fonts/Roboto-Regular.ttf differ diff --git a/fonts/arial.ttf b/fonts/arial.ttf deleted file mode 100644 index 27372d9..0000000 Binary files a/fonts/arial.ttf and /dev/null differ diff --git a/openVulkanoCpp/Scene/FontAtlasGenerator.cpp b/openVulkanoCpp/Scene/FontAtlasGenerator.cpp new file mode 100644 index 0000000..89e565a --- /dev/null +++ b/openVulkanoCpp/Scene/FontAtlasGenerator.cpp @@ -0,0 +1,95 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include "FontAtlasGenerator.hpp" +#include "Base/Logger.hpp" + +namespace OpenVulkano::Scene +{ + using namespace msdfgen; + using namespace msdf_atlas; + + void FontAtlasGenerator::GenerateAtlas(const std::string& fontFile, const std::string& outputFile, const Charset& chset) + { + if (chset.empty()) + { + return; + } + // TODO: dynamic atlas and add only those symbols which are not present yet in current atlas + Charset absentSymbols; + for (auto c : chset) + { + if (!m_symbols.contains(c)) + { + absentSymbols.add(c); + } + } + if (m_loadedFont == fontFile && absentSymbols.empty()) + { + return; + } + m_symbols.clear(); + m_loadedFont = fontFile; + + std::vector glyphsGeometry; + std::pair handlers = GetHandlers(fontFile); + FreetypeHandle* ft = handlers.first; + FontHandle* font = handlers.second; + + // FontGeometry is a helper class that loads a set of glyphs from a single font. + FontGeometry fontGeometry(&glyphsGeometry); + fontGeometry.loadCharset(font, 1, absentSymbols); + + TightAtlasPacker packer; + packer.setDimensionsConstraint(DimensionsConstraint::SQUARE); + int width = 1024, height = 1024; + packer.setDimensions(width, height); + // more value - more sdf impact + packer.setPixelRange(26.0); + packer.setMiterLimit(1.0); + packer.pack(glyphsGeometry.data(), glyphsGeometry.size()); + + m_generator.resize(width, height); + GeneratorAttributes attributes; + m_generator.setAttributes(attributes); + m_generator.setThreadCount(4); + m_generator.generate(glyphsGeometry.data(), glyphsGeometry.size()); + + int idx = 0; + BitmapConstRef storage = m_generator.atlasStorage(); + for (const auto& glyph: glyphsGeometry) + { + unicode_t c = static_cast(glyph.getCodepoint()); + GlyphInfo info; + info.texture.resolution = Math::Vector3ui(storage.width, storage.height, 1); + info.texture.textureBuffer = (msdfgen::byte*)storage.pixels; + info.texture.format = OpenVulkano::DataFormat::R8_UNORM; + info.texture.size = storage.width * storage.height * 1; // 1 channel + info.geometry = glyph; + info.glyphBox = m_generator.getLayout()[idx++]; + m_symbols[c] = std::move(info); + } + savePng(m_generator.atlasStorage(), outputFile.c_str()); + destroyFont(font); + deinitializeFreetype(ft); + } + + std::pair FontAtlasGenerator::GetHandlers(const std::string& fontFile) + { + FreetypeHandle* ft = initializeFreetype(); + if (!ft) + { + throw std::runtime_error("Failed to initialize freetype"); + } + FontHandle* font = loadFont(ft, fontFile.data()); + if (!font) + { + deinitializeFreetype(ft); + throw std::runtime_error(fmt::format("Failed to load font from file {0}", fontFile.data())); + } + return { ft, font }; + } +} diff --git a/openVulkanoCpp/Scene/FontAtlasGenerator.hpp b/openVulkanoCpp/Scene/FontAtlasGenerator.hpp new file mode 100644 index 0000000..29969f6 --- /dev/null +++ b/openVulkanoCpp/Scene/FontAtlasGenerator.hpp @@ -0,0 +1,41 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include +#include +#include "Scene/Texture.hpp" +#include "msdfgen.h" +#include "msdfgen-ext.h" +#include "msdf-atlas-gen/msdf-atlas-gen.h" + +namespace OpenVulkano::Scene +{ + using namespace msdfgen; + using namespace msdf_atlas; + using namespace OpenVulkano::Scene; + + struct GlyphInfo + { + GlyphGeometry geometry; + GlyphBox glyphBox; + Texture texture; + }; + + class FontAtlasGenerator + { + public: + void GenerateAtlas(const std::string& fontFile, const std::string& outputFile, const Charset& = Charset::ASCII); + std::map& GetAtlasInfo() { return m_symbols; } + private: + std::pair GetHandlers(const std::string& fontFile); + private: + ImmediateAtlasGenerator> m_generator; + std::map m_symbols; + std::string m_loadedFont; + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/Geometry.cpp b/openVulkanoCpp/Scene/Geometry.cpp index 0c88cee..5dff1db 100644 --- a/openVulkanoCpp/Scene/Geometry.cpp +++ b/openVulkanoCpp/Scene/Geometry.cpp @@ -192,18 +192,17 @@ namespace OpenVulkano::Scene #endif } - void Geometry::SetIndices(const uint32_t* data, uint32_t size, uint32_t offset) const + void Geometry::SetIndices(const uint32_t* data, uint32_t size, uint32_t indicesOffset) const { - size += offset; - for(; offset < size; offset++) + for(uint32_t i = 0; i < size; i++) { if (indexType == VertexIndexType::UINT16) { - static_cast(indices)[offset] = static_cast(data[offset]); + static_cast(indices)[i + indicesOffset] = static_cast(data[i]); } else { - static_cast(indices)[offset] = data[offset]; + static_cast(indices)[i + indicesOffset] = data[i]; } } } diff --git a/openVulkanoCpp/Scene/Geometry.hpp b/openVulkanoCpp/Scene/Geometry.hpp index c38b9cb..769bf52 100644 --- a/openVulkanoCpp/Scene/Geometry.hpp +++ b/openVulkanoCpp/Scene/Geometry.hpp @@ -60,7 +60,7 @@ namespace OpenVulkano void Init(aiMesh* mesh); - void SetIndices(const uint32_t* data, uint32_t size, uint32_t offset = 0) const; + void SetIndices(const uint32_t* data, uint32_t size, uint32_t indicesOffset = 0) const; void Close() override; diff --git a/openVulkanoCpp/Scene/SimpleDrawable.hpp b/openVulkanoCpp/Scene/SimpleDrawable.hpp index b541279..fe14d00 100644 --- a/openVulkanoCpp/Scene/SimpleDrawable.hpp +++ b/openVulkanoCpp/Scene/SimpleDrawable.hpp @@ -16,14 +16,16 @@ namespace OpenVulkano::Scene class SimpleDrawable : public Drawable { + protected: Geometry* m_mesh = nullptr; Material* m_material = nullptr; UniformBuffer* m_uniBuffer = nullptr; public: SimpleDrawable(const DrawPhase phase = DrawPhase::MAIN) - : Drawable(DrawEncoder::GetDrawEncoder(), phase) - {} + : Drawable(DrawEncoder::GetDrawEncoder(), phase) + { + } explicit SimpleDrawable(const SimpleDrawable* toCopy) : Drawable(DrawEncoder::GetDrawEncoder(), toCopy->GetDrawPhase()) diff --git a/openVulkanoCpp/Scene/Text.cpp b/openVulkanoCpp/Scene/Text.cpp index f7c9fa3..b996ffd 100644 --- a/openVulkanoCpp/Scene/Text.cpp +++ b/openVulkanoCpp/Scene/Text.cpp @@ -5,44 +5,123 @@ */ #include "Text.hpp" +#include "Scene/Geometry.hpp" +#include "Scene/Material.hpp" +#include "Scene/Vertex.hpp" +#include "Scene/UniformBuffer.hpp" +#include "Scene/FontAtlasGenerator.hpp" +#include "Base/Logger.hpp" #include "fmt/core.h" -#include "msdfgen.h" -#include "msdfgen-ext.h" -#include "msdf-atlas-gen/msdf-atlas-gen.h" namespace OpenVulkano::Scene { using namespace msdfgen; using namespace msdf_atlas; - void Text::Init(const std::string_view fontFile, char8_t symbol, const std::string_view outputFile) + Text::~Text() + { + delete m_mesh; + delete m_material; + } + + void Text::GenerateText(const std::string& text, const Math::Vector3f& pos) { - FreetypeHandle *ft = initializeFreetype(); - if (!ft) + if (!m_fontAtlasGenerator) { - throw std::runtime_error("Failed to initialize freetype"); + Logger::RENDER->error("Can't draw text. FontAtlasGenerator is nullptr"); + return; } - FontHandle *font = loadFont(ft, fontFile.data()); - if (!font) + if (m_mesh) { - deinitializeFreetype(ft); - throw std::runtime_error(fmt::format("Failed to load font freetype from file {0}", fontFile.data())); + delete m_mesh; + m_mesh = nullptr; + } + if (m_material) + { + delete m_material; + m_material = nullptr; + } + if (text.empty()) + { + return; + } + std::map& symbols = m_fontAtlasGenerator->GetAtlasInfo(); + if (symbols.empty()) + { + throw std::runtime_error("Glyphs are not loaded"); } - Shape shape; - if (loadGlyph(shape, font, symbol, FONT_SCALING_EM_NORMALIZED)) + m_mesh = new Geometry(); + m_material = new Material(); + m_mesh->freeAfterUpload = false; + m_mesh->Init(text.size() * 4, text.size() * 6); + + struct Bbox { - shape.normalize(); - Bitmap sdf(m_cfg.outputSize, m_cfg.outputSize); - // scale, translation (in em's) - Projection proj(m_cfg.outputSize, Vector2(0.125, 0.125)); - // distance mapping - Range rng(0.075); - SDFTransformation t(proj, rng); - generateSDF(sdf, shape, t); - savePng(sdf, outputFile.data()); + double l = 0, r = 0, t = 0, b = 0; + }; + + double cursorX = pos.x; + for (size_t i = 0; i < text.size(); i++) + { + unicode_t c = text[i]; + if (symbols.find(c) != symbols.end()) + { + Bbox glyphBaselineBbox, glyphAtlasBbox; + int vIdx = i * 4; + uint32_t indices[] = { 1 + vIdx, 2 + vIdx, 3 + vIdx, 1 + vIdx, 3 + vIdx, 0 + vIdx }; + GlyphInfo& info = symbols.at(c); + info.geometry.getQuadPlaneBounds(glyphBaselineBbox.l, glyphBaselineBbox.b, glyphBaselineBbox.r, + glyphBaselineBbox.t); + info.geometry.getQuadAtlasBounds(glyphAtlasBbox.l, glyphAtlasBbox.b, glyphAtlasBbox.r, + glyphAtlasBbox.t); + double bearingX = info.glyphBox.bounds.l; + double bearingY = info.glyphBox.bounds.t; + double w = glyphBaselineBbox.r - glyphBaselineBbox.l; + double h = glyphBaselineBbox.t - glyphBaselineBbox.b; + double l = glyphAtlasBbox.l; + double r = glyphAtlasBbox.r; + double t = glyphAtlasBbox.t; + double b = glyphAtlasBbox.b; + double ax = cursorX + bearingX; + double ay = pos.y - (h - bearingY); + + m_material->texture = &info.texture; + + m_mesh->vertices[vIdx].position.x = ax; + m_mesh->vertices[vIdx].position.y = ay; + m_mesh->vertices[vIdx].position.z = 1; + m_mesh->vertices[vIdx].textureCoordinates.x = l / info.texture.resolution.x; + m_mesh->vertices[vIdx].textureCoordinates.y = b / info.texture.resolution.y; + + m_mesh->vertices[vIdx + 1].position.x = ax + w; + m_mesh->vertices[vIdx + 1].position.y = ay; + m_mesh->vertices[vIdx + 1].position.z = 1; + m_mesh->vertices[vIdx + 1].textureCoordinates.x = r / info.texture.resolution.x; + m_mesh->vertices[vIdx + 1].textureCoordinates.y = b / info.texture.resolution.y; + + m_mesh->vertices[vIdx + 2].position.x = ax + w; + m_mesh->vertices[vIdx + 2].position.y = ay + h; + m_mesh->vertices[vIdx + 2].position.z = 1; + m_mesh->vertices[vIdx + 2].textureCoordinates.x = r / info.texture.resolution.x; + m_mesh->vertices[vIdx + 2].textureCoordinates.y = t / info.texture.resolution.y; + + m_mesh->vertices[vIdx + 3].position.x = ax; + m_mesh->vertices[vIdx + 3].position.y = ay + h; + m_mesh->vertices[vIdx + 3].position.z = 1; + m_mesh->vertices[vIdx + 3].textureCoordinates.x = l / info.texture.resolution.x; + m_mesh->vertices[vIdx + 3].textureCoordinates.y = t / info.texture.resolution.y; + + m_mesh->SetIndices(indices, 6, 6 * i); + // TODO: change to lower value(or ideally remove completely) to avoid overlapping and make less space between symbols + // when setting for depth comparison operator will be available( <= ) + cursorX += info.glyphBox.advance +0.08; + } + else + { + // throw ? replace with ? character (if available) ? + Logger::RENDER->error(fmt::format("Could not find glyph for character", c)); + } } - destroyFont(font); - deinitializeFreetype(ft); } } \ No newline at end of file diff --git a/openVulkanoCpp/Scene/Text.hpp b/openVulkanoCpp/Scene/Text.hpp index 9060787..00a9076 100644 --- a/openVulkanoCpp/Scene/Text.hpp +++ b/openVulkanoCpp/Scene/Text.hpp @@ -11,20 +11,24 @@ #include "Math/Math.hpp" #include "DataFormat.hpp" #include "SimpleDrawable.hpp" +#include "FontAtlasGenerator.hpp" +#include "Texture.hpp" +#include "msdfgen.h" +#include "msdfgen-ext.h" +#include "msdf-atlas-gen/msdf-atlas-gen.h" namespace OpenVulkano::Scene { - //using namespace msdfgen; - //using namespace msdf_atlas; + using namespace msdfgen; + using namespace msdf_atlas; struct TextConfig { Math::Vector4f textColor = { 1, 1, 1, 0 }; // vec4 to match paddding (multiple of 16) Math::Vector3f borderColor = { 1, 0, 0 }; - int outputSize = 256; - float threshold = 0.5f; - float borderSize = 0.2f; - float smoothing = 1.f/16.f; + float threshold = 0.4f; + float borderSize = 0.05f; + float smoothing = 1.f/32.f; bool applyBorder = false; //bool sdfMultiChannel = false; }; @@ -32,12 +36,16 @@ namespace OpenVulkano::Scene class Text : public SimpleDrawable { public: - Text(const TextConfig& cfg) : m_cfg(cfg) {} - void Init(const std::string_view fontFile, char8_t symbol, const std::string_view outputFile); - void Init(const std::string_view fontFile, std::vector symbols, const std::string_view outputFile); - void setConfig(const TextConfig& cfg) { m_cfg = cfg; } + Text() = default; + ~Text(); + void SetUniformBuffer(UniformBuffer* buffer) { m_uniBuffer = buffer; } + void GenerateText(const std::string& text, const Math::Vector3f& pos = Math::Vector3f(0.f)); + void SetConfig(const TextConfig& cfg) { m_cfg = cfg; } TextConfig& GetConfig() { return m_cfg; } + void SetFontAtlasGenerator(FontAtlasGenerator* fontAtlasGenerator) { m_fontAtlasGenerator = fontAtlasGenerator; } + FontAtlasGenerator* GetFontAtlasGenerator() { return m_fontAtlasGenerator; } private: + FontAtlasGenerator* m_fontAtlasGenerator = nullptr; TextConfig m_cfg; }; } diff --git a/openVulkanoCpp/Shader/text.frag b/openVulkanoCpp/Shader/text.frag index 316e3bb..a8d852b 100644 --- a/openVulkanoCpp/Shader/text.frag +++ b/openVulkanoCpp/Shader/text.frag @@ -10,7 +10,6 @@ layout(set = 3, binding = 0) uniform TextConfig { vec3 textColor; vec3 borderColor; - int outputSize; float threshold; float borderSize; float smoothing; diff --git a/openVulkanoCpp/Vulkan/Scene/VulkanTexture.hpp b/openVulkanoCpp/Vulkan/Scene/VulkanTexture.hpp index 5a079d4..9fd6da4 100644 --- a/openVulkanoCpp/Vulkan/Scene/VulkanTexture.hpp +++ b/openVulkanoCpp/Vulkan/Scene/VulkanTexture.hpp @@ -19,6 +19,7 @@ namespace OpenVulkano::Vulkan class VulkanTexture : public Scene::RenderTexture, public IRecordable, public Image { public: + static inline vk::SamplerCreateInfo DEFAULT_SAMPLER_CONFIG { {}, vk::Filter::eLinear, vk::Filter::eLinear }; vk::Sampler m_sampler; vk::DescriptorSet m_descriptorSet;