diff --git a/examples/ExampleApps/TextExampleApp.cpp b/examples/ExampleApps/TextExampleApp.cpp new file mode 100644 index 0000000..76e8ad5 --- /dev/null +++ b/examples/ExampleApps/TextExampleApp.cpp @@ -0,0 +1,172 @@ +/* + * 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 "TextExampleApp.hpp" +#include "Scene/Scene.hpp" +#include "Scene/Shader/Shader.hpp" +#include "Scene/Geometry.hpp" +#include "Scene/Text.hpp" +#include "Scene/GeometryFactory.hpp" +#include "Scene/Material.hpp" +#include "Scene/Vertex.hpp" +#include "Scene/SimpleDrawable.hpp" +#include "Scene/UI/PerformanceInfo.hpp" +#include "Scene/UniformBuffer.hpp" +#include "Input/InputManager.hpp" +#include "Host/GraphicsAppManager.hpp" +#include "Host/GLFW/WindowGLFW.hpp" +#include "Math/Math.hpp" +#include "Base/EngineConfiguration.hpp" +#include "Controller/FreeCamCameraController.hpp" +#include "Image/ImageLoaderPng.hpp" + +#ifdef _WIN32 + #undef TRANSPARENT +#endif + +namespace OpenVulkano +{ + using namespace Scene; + using namespace Input; + using namespace Math; + + class TextExampleAppImpl final : public TextExampleApp + { + public: + + void Init() override + { + auto engineConfig = OpenVulkano::EngineConfiguration::GetEngineConfiguration(); + engineConfig->SetNumThreads(4); + engineConfig->SetPreferFramebufferFormatSRGB(false); + + std::srand(1); // Fix seed for random numbers + m_scene.Init(); + m_cam.Init(70, 16, 9, 0.1f, 100); + 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; + 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"); + m_shader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); + m_shader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING); + m_shader.AddDescriptorSetLayoutBinding(UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING); + 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 }; + + Text text = Text(m_cfg); + for (int i = 0; i < N; 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); + + 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_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.SetBoostFactor(5); + + std::shared_ptr m_perfInfo = + std::make_shared(); + m_ui.AddElement(m_perfInfo); + GetGraphicsAppManager()->GetRenderer()->SetActiveUi(&m_ui); + } + + void Tick() override + { + m_camController.Tick(); + } + + void Close() override + { + } + + private: + OpenVulkano::Scene::Scene m_scene; + PerspectiveCamera m_cam; + UniformBuffer m_uniBuffer; + OpenVulkano::FreeCamCameraController m_camController; + Shader m_shader; + TextConfig m_cfg; + std::vector m_materials; + std::vector m_drawablesPool; + std::vector m_geos; + 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; + }; + + IGraphicsApp* TextExampleApp::Create() { return new TextExampleAppImpl(); } + + std::unique_ptr TextExampleApp::CreateUnique() + { + return std::make_unique(); + } + +} + +#pragma clang diagnostic pop +#pragma clang diagnostic pop \ No newline at end of file diff --git a/examples/ExampleApps/TextExampleApp.hpp b/examples/ExampleApps/TextExampleApp.hpp new file mode 100644 index 0000000..b2100f0 --- /dev/null +++ b/examples/ExampleApps/TextExampleApp.hpp @@ -0,0 +1,27 @@ +/* + * 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 "Base/IGraphicsApp.hpp" +#include + +namespace OpenVulkano +{ + class TextExampleApp : public IGraphicsApp + { + public: + static IGraphicsApp* Create(); + + static std::unique_ptr CreateUnique(); + + [[nodiscard]] std::string GetAppName() const final + { return "Text ExampleApp"; } + + [[nodiscard]] OpenVulkano::Version GetAppVersion() const final + { return {"v1.0"}; } + }; +} \ No newline at end of file diff --git a/examples/main.cpp b/examples/main.cpp index 002e431..224716a 100644 --- a/examples/main.cpp +++ b/examples/main.cpp @@ -6,6 +6,7 @@ #include "Host/GraphicsAppManager.hpp" #include "ExampleAppList.hpp" +#include "ExampleApps/TextExampleApp.hpp" #include #include diff --git a/fonts/arial.ttf b/fonts/arial.ttf new file mode 100644 index 0000000..27372d9 Binary files /dev/null and b/fonts/arial.ttf differ diff --git a/openVulkanoCpp/Scene/SimpleDrawable.hpp b/openVulkanoCpp/Scene/SimpleDrawable.hpp index 41ba61b..b541279 100644 --- a/openVulkanoCpp/Scene/SimpleDrawable.hpp +++ b/openVulkanoCpp/Scene/SimpleDrawable.hpp @@ -14,7 +14,7 @@ namespace OpenVulkano::Scene class Material; class UniformBuffer; - class SimpleDrawable final : public Drawable + class SimpleDrawable : public Drawable { Geometry* m_mesh = nullptr; Material* m_material = nullptr; diff --git a/openVulkanoCpp/Scene/Text.cpp b/openVulkanoCpp/Scene/Text.cpp new file mode 100644 index 0000000..f7c9fa3 --- /dev/null +++ b/openVulkanoCpp/Scene/Text.cpp @@ -0,0 +1,48 @@ +/* + * 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 "Text.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) + { + 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 freetype from file {0}", fontFile.data())); + } + + Shape shape; + if (loadGlyph(shape, font, symbol, FONT_SCALING_EM_NORMALIZED)) + { + 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()); + } + destroyFont(font); + deinitializeFreetype(ft); + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/Text.hpp b/openVulkanoCpp/Scene/Text.hpp new file mode 100644 index 0000000..9060787 --- /dev/null +++ b/openVulkanoCpp/Scene/Text.hpp @@ -0,0 +1,43 @@ +/* + * 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 "UpdateFrequency.hpp" +#include "Base/ICloseable.hpp" +#include "Math/Math.hpp" +#include "DataFormat.hpp" +#include "SimpleDrawable.hpp" + +namespace OpenVulkano::Scene +{ + //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; + bool applyBorder = false; + //bool sdfMultiChannel = false; + }; + + 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; } + TextConfig& GetConfig() { return m_cfg; } + private: + TextConfig m_cfg; + }; +} diff --git a/openVulkanoCpp/Shader/text.frag b/openVulkanoCpp/Shader/text.frag new file mode 100644 index 0000000..316e3bb --- /dev/null +++ b/openVulkanoCpp/Shader/text.frag @@ -0,0 +1,34 @@ +#version 450 + +layout(location = 0) in vec2 texCoord; + +layout(location = 0) out vec4 outColor; + +layout(set = 2, binding = 0) uniform sampler2D texSampler; + +layout(set = 3, binding = 0) uniform TextConfig +{ + vec3 textColor; + vec3 borderColor; + int outputSize; + float threshold; + float borderSize; + float smoothing; + bool applyBorder; +} textConfig; + +void main() +{ + float distance = texture(texSampler, texCoord).r; + float alpha = smoothstep(textConfig.threshold - textConfig.smoothing, textConfig.threshold + textConfig.smoothing, distance); + if (textConfig.applyBorder) + { + float border = smoothstep(textConfig.threshold + textConfig.borderSize - textConfig.smoothing, + textConfig.threshold + textConfig.borderSize + textConfig.smoothing, distance); + outColor = vec4(mix(textConfig.borderColor, textConfig.textColor, border), 1) * alpha; + } + else + { + outColor = vec4(textConfig.textColor, 1) * alpha; + } +} diff --git a/openVulkanoCpp/Shader/text.vert b/openVulkanoCpp/Shader/text.vert new file mode 100644 index 0000000..8f60a00 --- /dev/null +++ b/openVulkanoCpp/Shader/text.vert @@ -0,0 +1,26 @@ +#version 450 +layout(location = 0) in vec3 position; +layout(location = 1) in vec3 normal; +layout(location = 2) in vec3 tangent; +layout(location = 3) in vec3 biTangent; +layout(location = 4) in vec3 textureCoordinates; +layout(location = 5) in vec4 color; +layout(location = 0) out vec2 fragTextureCoordinates; + +layout(set = 0, binding = 0) uniform NodeData +{ + mat4 world; +} node; + +layout(set = 1, binding = 0) uniform CameraData +{ + mat4 viewProjection; + mat4 view; + mat4 projection; + vec4 camPos; +} cam; + +void main() { + gl_Position = cam.viewProjection * node.world * vec4(position, 1.0); + fragTextureCoordinates.xy = textureCoordinates.xy; +}