diff --git a/examples/ExampleAppList.hpp b/examples/ExampleAppList.hpp index d49362e..bcdf0e5 100644 --- a/examples/ExampleAppList.hpp +++ b/examples/ExampleAppList.hpp @@ -11,6 +11,7 @@ #include "ExampleApps/TexturedCubeExampleApp.hpp" #include "ExampleApps/BillboardExampleApp.hpp" #include "ExampleApps/TextExampleApp.hpp" +#include "ExampleApps/LabelDrawableExampleApp.hpp" #include namespace OpenVulkano @@ -20,6 +21,7 @@ namespace OpenVulkano { "Moving Cube Example App", &MovingCubeApp::Create }, { "Textured Cube Example App", &TexturedCubeExampleApp::Create }, { "Billboard Example App", &BillboardExampleApp::Create }, - { "Text Example App", &TextExampleApp::Create } + { "Text Example App", &TextExampleApp::Create }, + { "Label Example App", &LabelDrawableExampleApp::Create } }; } diff --git a/examples/ExampleApps/BillboardExampleApp.cpp b/examples/ExampleApps/BillboardExampleApp.cpp index 3c98ba6..2b69d6a 100644 --- a/examples/ExampleApps/BillboardExampleApp.cpp +++ b/examples/ExampleApps/BillboardExampleApp.cpp @@ -14,6 +14,7 @@ #include "Scene/SimpleDrawable.hpp" #include "Scene/UI/PerformanceInfo.hpp" #include "Scene/UniformBuffer.hpp" +#include "Scene/BillboardControlBlock.hpp" #include "Input/InputManager.hpp" #include "Host/GraphicsAppManager.hpp" #include "Host/GLFW/WindowGLFW.hpp" @@ -31,12 +32,6 @@ namespace OpenVulkano { public: - struct BillboardControlBlock - { - Math::Vector2f quadSize; - bool isFixedSize; - }; - void Init() override { auto engineConfig = OpenVulkano::EngineConfiguration::GetEngineConfiguration(); @@ -55,14 +50,14 @@ namespace OpenVulkano m_quadBillboardShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/basic"); m_quadBillboardShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); m_quadBillboardShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING); - m_quadBillboardShader.AddDescriptorSetLayoutBinding(UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING); + m_quadBillboardShader.AddDescriptorSetLayoutBinding(UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING, 4); m_quadBillboardShader.topology = Topology::POINT_LIST; m_shader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/billboard"); m_shader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/basic"); m_shader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); m_shader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING); - m_shader.AddDescriptorSetLayoutBinding(UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING); + m_shader.AddDescriptorSetLayoutBinding(UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING, 4); m_shader.cullMode = CullMode::NONE; constexpr int quadsCnt = 7; @@ -72,7 +67,7 @@ namespace OpenVulkano m_bbContolBlock.quadSize = { 100.f, 100.f }; m_bbContolBlock.isFixedSize = false; m_uniBuffer.Init(sizeof(BillboardControlBlock), &m_bbContolBlock); - m_uniBuffer.setId = 3; + m_uniBuffer.setId = 4; m_drawablesPool.resize(cntDrawables); m_nodesPool.resize(cntDrawables); m_geo.resize(cntDrawables); diff --git a/examples/ExampleApps/LabelDrawableExampleApp.cpp b/examples/ExampleApps/LabelDrawableExampleApp.cpp new file mode 100644 index 0000000..1ca40b7 --- /dev/null +++ b/examples/ExampleApps/LabelDrawableExampleApp.cpp @@ -0,0 +1,136 @@ +/* + * 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 "LabelDrawableExampleApp.hpp" +#include "Scene/Scene.hpp" +#include "Scene/Shader/Shader.hpp" +#include "Scene/Geometry.hpp" +#include "Scene/TextDrawable.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 "Scene/Prefabs/LabelDrawable.hpp" +#include "Input/InputManager.hpp" +#include "Host/GraphicsAppManager.hpp" +#include "Host/GLFW/WindowGLFW.hpp" +#include "Host/ResourceLoader.hpp" +#include "Math/Math.hpp" +#include "Base/EngineConfiguration.hpp" +#include "Controller/FreeCamCameraController.hpp" +#include "Image/ImageLoaderPng.hpp" +#include "Scene/FontAtlasGenerator.hpp" +#include "Scene/IFontAtlasGenerator.hpp" +#include + +#ifdef _WIN32 + #undef TRANSPARENT +#endif + +namespace OpenVulkano +{ + using namespace Scene; + using namespace Input; + using namespace Math; + namespace fs = std::filesystem; + + class LabelDrawableExampleAppImpl final : public LabelDrawableExampleApp + { + public: + + void Init() override + { + auto engineConfig = OpenVulkano::EngineConfiguration::GetEngineConfiguration(); + engineConfig->SetNumThreads(1); + engineConfig->SetPreferFramebufferFormatSRGB(false); + + std::srand(1); // Fix seed for random numbers + m_scene.Init(); + m_cam.Init(70, 16, 9, 0.1, 100); + m_scene.SetCamera(&m_cam); + + auto& resourceLoader = ResourceLoader::GetInstance(); + auto sdfMetadataInfo = resourceLoader.GetResource("sdf_atlas_packed.png"); + + TextDrawable textDrawable(sdfMetadataInfo); + const std::vector texts = { "_!?{}.#@", "1", "XYZ", "12345" }; + const int N = texts.size(); + m_nodesPool.resize(N); + m_drawablesPool.reserve(N); + + BillboardControlBlock billboardSettings; + LabelDrawableSettings labelSettings; + + for (int i = 0; i < N; i++) + { + if (i == 3) + { + labelSettings.hasRoundedCorners = labelSettings.hasArrow = true; + } + else + { + labelSettings.hasRoundedCorners = (i % 2 == 0 ? 0 : 1); + labelSettings.hasArrow = (i % 2 == 0 ? 1 : 0); + } + bool isBillboard = (i % 2 == 0 ? 1 : 0); + LabelDrawable& label = m_drawablesPool.emplace_back(textDrawable.GetAtlasData(), labelSettings, isBillboard); + label.SetBillboardSettings(billboardSettings); + label.AddText(texts[i]); + if (i == 2) + { + for (int j = 0; j < 3; j++) + { + label.AddText("Additional text" + std::to_string(j)); + } + } + m_scene.GetRoot()->AddChild(&m_nodesPool[i]); + m_nodesPool[i].SetMatrix(Math::Utils::translate(glm::mat4x4(1.f), Vector3f(-5 + std::rand() % 5, -5 + std::rand() % 5, -std::rand() % 10))); + m_nodesPool[i].AddDrawable(&m_drawablesPool[i]); + } + + GetGraphicsAppManager()->GetRenderer()->SetScene(&m_scene); + m_camController.Init(&m_cam); + m_camController.SetDefaultKeybindings(); + m_camController.SetPosition({ 0, 0, 10 }); + m_camController.SetBoostFactor(5); + + std::shared_ptr m_perfInfo = + std::make_shared(); + m_ui.AddElement(m_perfInfo); + GetGraphicsAppManager()->GetRenderer()->SetActiveUi(&m_ui); + } + + void Close() override {} + + void Tick() override + { + m_camController.Tick(); + } + + private: + OpenVulkano::Scene::Scene m_scene; + PerspectiveCamera m_cam; + OpenVulkano::FreeCamCameraController m_camController; + std::vector m_drawablesPool; + std::vector m_nodesPool; + Vector3f_SIMD m_position = { 0, 0, -10 }; + OpenVulkano::Scene::UI::SimpleUi m_ui; + std::shared_ptr m_perfInfo; + }; + + IGraphicsApp* LabelDrawableExampleApp::Create() { return new LabelDrawableExampleAppImpl(); } + + std::unique_ptr LabelDrawableExampleApp::CreateUnique() + { + return std::make_unique(); + } + +} + +#pragma clang diagnostic pop +#pragma clang diagnostic pop \ No newline at end of file diff --git a/examples/ExampleApps/LabelDrawableExampleApp.hpp b/examples/ExampleApps/LabelDrawableExampleApp.hpp new file mode 100644 index 0000000..12198b1 --- /dev/null +++ b/examples/ExampleApps/LabelDrawableExampleApp.hpp @@ -0,0 +1,25 @@ +/* + * 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 LabelDrawableExampleApp : public IGraphicsApp + { + public: + static IGraphicsApp* Create(); + + static std::unique_ptr CreateUnique(); + + [[nodiscard]] std::string GetAppName() const final { return "Label drawable ExampleApp"; } + + [[nodiscard]] OpenVulkano::Version GetAppVersion() const final { return { "v1.0" }; } + }; +} \ No newline at end of file diff --git a/examples/ExampleApps/TextExampleApp.cpp b/examples/ExampleApps/TextExampleApp.cpp index 4a06706..4a97377 100644 --- a/examples/ExampleApps/TextExampleApp.cpp +++ b/examples/ExampleApps/TextExampleApp.cpp @@ -38,6 +38,8 @@ namespace OpenVulkano using namespace Math; namespace fs = std::filesystem; + //#define CREATE_NEW_ATLAS 1 + class TextExampleAppImpl final : public TextExampleApp { public: @@ -68,12 +70,14 @@ namespace OpenVulkano m_nodesPool.resize(N * 2); m_drawablesPool.resize(N * 2); -#ifdef MSDFGEN_AVAILABLE +#if defined(MSDFGEN_AVAILABLE) && defined(CREATE_NEW_ATLAS) msdf_atlas::Charset charset = SdfFontAtlasGenerator::LoadAllGlyphs(fontPath); m_atlasGenerator.GenerateAtlas(fontPath, charset); m_msdfAtlasGenerator.GenerateAtlas(fontPath, charset); + m_atlasGenerator.SaveAtlasMetadataInfo("sdf_atlas.png"); + m_msdfAtlasGenerator.SaveAtlasMetadataInfo("msdf_atlas"); #else - auto sdfMetadataInfo = resourceLoader.GetResource("sdf_atlas_packed"); + auto sdfMetadataInfo = resourceLoader.GetResource("sdf_atlas_packed.png"); auto msdfMetadataInfo = resourceLoader.GetResource("msdf_atlas_packed.png"); #endif @@ -81,15 +85,15 @@ namespace OpenVulkano { int textIdx = i % texts.size(); TextDrawable* t = nullptr; -#ifdef MSDFGEN_AVAILABLE +#if defined(MSDFGEN_AVAILABLE) && defined(CREATE_NEW_ATLAS) if (i < texts.size()) { - t = new TextDrawable(&m_atlasGenerator, texts[textIdx].second); + t = new TextDrawable(m_atlasGenerator.GetAtlasData(), texts[textIdx].second); t->SetShader(&TextDrawable::GetSdfDefaultShader()); } else { - t = new TextDrawable(&m_msdfAtlasGenerator, texts[textIdx].second); + t = new TextDrawable(m_msdfAtlasGenerator.GetAtlasData(), texts[textIdx].second); t->SetShader(&TextDrawable::GetMsdfDefaultShader()); } #else diff --git a/examples/ExampleSources/msdf_atlas_packed.png b/examples/ExampleSources/msdf_atlas_packed.png index abfafdf..c9baf4d 100644 Binary files a/examples/ExampleSources/msdf_atlas_packed.png and b/examples/ExampleSources/msdf_atlas_packed.png differ diff --git a/examples/ExampleSources/sdf_atlas_packed b/examples/ExampleSources/sdf_atlas_packed deleted file mode 100644 index a1d4b1d..0000000 Binary files a/examples/ExampleSources/sdf_atlas_packed and /dev/null differ diff --git a/examples/ExampleSources/sdf_atlas_packed.png b/examples/ExampleSources/sdf_atlas_packed.png new file mode 100644 index 0000000..9fa36e7 Binary files /dev/null and b/examples/ExampleSources/sdf_atlas_packed.png differ diff --git a/openVulkanoCpp/Math/AABB.hpp b/openVulkanoCpp/Math/AABB.hpp index 4f9b944..88469df 100644 --- a/openVulkanoCpp/Math/AABB.hpp +++ b/openVulkanoCpp/Math/AABB.hpp @@ -106,6 +106,11 @@ namespace OpenVulkano::Math return Math::Utils::all(Math::Utils::greaterThan(min, position)) && Math::Utils::all(Math::Utils::lessThan(position, max)); } + [[nodiscard]] bool IsEmpty() const + { + return min == Math::Vector3f(INFINITY) && max == Math::Vector3f(-INFINITY); + } + /** * \brief Resets the AABB to min=Inf, max=-Inf, same as Init() */ diff --git a/openVulkanoCpp/Scene/AtlasData.hpp b/openVulkanoCpp/Scene/AtlasData.hpp new file mode 100644 index 0000000..7bfd8f7 --- /dev/null +++ b/openVulkanoCpp/Scene/AtlasData.hpp @@ -0,0 +1,63 @@ +/* + * 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 "Math/Math.hpp" +#include "Image/Image.hpp" +#include "Scene/Texture.hpp" +#include +#include + +namespace OpenVulkano::Scene +{ + struct GlyphInfo + { + //GlyphGeometry geometry; + //GlyphBox glyphBox; + Math::Vector3f_SIMD xyz[4] = {}; + Math::Vector2f_SIMD uv[4] = {}; + double advance = 0; + }; + + class FontAtlasType + { + public: + enum Type : int16_t + { + SDF = 0, + MSDF, + UNKNOWN + }; + static constexpr std::string_view DEFAULT_FG_SHADERS[] = { "Shader/text", "Shader/msdfText" }; + + public: + FontAtlasType(Type type) : m_type(type) {} + Type GetType() const { return m_type; } + const std::string_view& GetDefaultFragmentShader() const + { + return DEFAULT_FG_SHADERS[static_cast(m_type)]; + } + private: + Type m_type; + }; + + struct AtlasMetadata + { + // vertical difference between baselines + double lineHeight = 0; + int16_t atlasType = FontAtlasType::UNKNOWN; + }; + + struct AtlasData + { + std::map glyphs; + AtlasMetadata meta; + std::unique_ptr img; + Texture texture; + }; + +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/AtlasMetadata.hpp b/openVulkanoCpp/Scene/BillboardControlBlock.hpp similarity index 52% rename from openVulkanoCpp/Scene/AtlasMetadata.hpp rename to openVulkanoCpp/Scene/BillboardControlBlock.hpp index 99f6e7f..7e37e72 100644 --- a/openVulkanoCpp/Scene/AtlasMetadata.hpp +++ b/openVulkanoCpp/Scene/BillboardControlBlock.hpp @@ -10,19 +10,9 @@ namespace OpenVulkano::Scene { - struct GlyphInfo + struct BillboardControlBlock { - //GlyphGeometry geometry; - //GlyphBox glyphBox; - Math::Vector3f_SIMD xyz[4] = {}; - Math::Vector2f_SIMD uv[4] = {}; - double advance = 0; + Math::Vector2f quadSize = { 100.f, 100.f }; + int32_t isFixedSize = false; }; - - struct AtlasMetadata - { - // vertical difference between baselines - double lineHeight = 0; - }; - -} \ No newline at end of file +} diff --git a/openVulkanoCpp/Scene/FontAtlasGenerator.cpp b/openVulkanoCpp/Scene/FontAtlasGenerator.cpp index 1d0cf5d..dd0bdb9 100644 --- a/openVulkanoCpp/Scene/FontAtlasGenerator.cpp +++ b/openVulkanoCpp/Scene/FontAtlasGenerator.cpp @@ -8,7 +8,7 @@ #include "FontAtlasGenerator.hpp" #include "Base/Logger.hpp" -#include "Scene/AtlasMetadata.hpp" +#include "Scene/AtlasData.hpp" #include #include #include @@ -120,7 +120,7 @@ namespace OpenVulkano::Scene void FontAtlasGenerator::SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile) const { - if (m_symbols.empty()) + if (m_atlasData->glyphs.empty()) { Logger::DATA->info("No glyphs loaded. Nothing to save."); return; @@ -134,9 +134,9 @@ namespace OpenVulkano::Scene SavePng(fileName); } std::fstream fs(fileName.c_str(), std::ios_base::out | std::ios_base::binary | (packedFlag ? std::ios_base::app : std::ios_base::trunc)); - fs.write(reinterpret_cast(&m_meta), sizeof(AtlasMetadata)); + fs.write(reinterpret_cast(&m_atlasData->meta), sizeof(AtlasMetadata)); uint64_t metadataBytes = sizeof(AtlasMetadata); - for (const auto& [key, val] : m_symbols) + for (const auto& [key, val]: m_atlasData->glyphs) { fs.write(reinterpret_cast(&key), sizeof(uint32_t)); fs.write(reinterpret_cast(&val), sizeof(GlyphInfo)); @@ -181,7 +181,8 @@ namespace OpenVulkano::Scene void FontAtlasGenerator::Generate(FreetypeHandle* ft, FontHandle* font, const Charset& chset, const std::optional& pngOutput) { - m_symbols.clear(); + m_atlasData.reset(new AtlasData); + std::vector glyphsGeometry; // FontGeometry is a helper class that loads a set of glyphs from a single font. FontGeometry fontGeometry(&glyphsGeometry); @@ -219,9 +220,12 @@ namespace OpenVulkano::Scene if constexpr (Channels == 3) { // store RGB as RGBA + m_atlasData->img = std::make_unique(); + m_atlasData->img->data = Array(width * height * 4); + m_atlasData->img->resolution = Math::Vector3ui(width, height, 1); + m_atlasData->img->dataFormat = OpenVulkano::DataFormat::R8G8B8A8_UNORM; const BitmapConstRef storage = generator.atlasStorage(); - msdfgen::Bitmap bitmap(width, height); - msdfgen::byte* data = static_cast(bitmap); + msdfgen::byte* data = static_cast(m_atlasData->img->data.Data()); for (size_t srcPos = 0, dstPos = 0; srcPos < width * height * 3; srcPos += 3, dstPos += 4) { data[dstPos] = storage.pixels[srcPos]; @@ -229,19 +233,23 @@ namespace OpenVulkano::Scene data[dstPos + 2] = storage.pixels[srcPos + 2]; data[dstPos + 3] = 255; } - m_atlasStorage = std::move(bitmap); } - else + else { - m_atlasStorage = generator.atlasStorage(); + m_atlasData->img = std::make_unique(); + m_atlasData->img->data = Array(width * height); + m_atlasData->img->resolution = Math::Vector3ui(width, height, 1); + m_atlasData->img->dataFormat = OpenVulkano::DataFormat::R8_UNORM; + const msdfgen::BitmapConstRef& storage = generator.atlasStorage(); + memcpy(m_atlasData->img->data.Data(), storage.pixels, width * height); } - m_atlasTex.resolution = Math::Vector3ui(m_atlasStorage.width(), m_atlasStorage.height(), 1); - m_atlasTex.textureBuffer = (msdfgen::byte*) m_atlasStorage; - m_atlasTex.format = (channelsCount == 1 ? OpenVulkano::DataFormat::R8_UNORM : OpenVulkano::DataFormat::R8G8B8A8_UNORM); - m_atlasTex.size = m_atlasStorage.width() * m_atlasStorage.height() * channelsCount; - - m_meta.lineHeight = fontGeometry.getMetrics().lineHeight; + m_atlasData->texture.resolution = m_atlasData->img->resolution; + m_atlasData->texture.textureBuffer = m_atlasData->img->data.Data(); + m_atlasData->texture.format = m_atlasData->img->dataFormat; + m_atlasData->texture.size = width * height * channelsCount; + m_atlasData->meta.lineHeight = fontGeometry.getMetrics().lineHeight; + m_atlasData->meta.atlasType = channelsCount == 1 ? FontAtlasType::SDF : FontAtlasType::MSDF; struct Bbox { @@ -250,7 +258,7 @@ namespace OpenVulkano::Scene for (const auto& glyph: glyphsGeometry) { - GlyphInfo& info = m_symbols[glyph.getCodepoint()]; + GlyphInfo& info = m_atlasData->glyphs[glyph.getCodepoint()]; const GlyphBox& glyphBox = generator.getLayout()[idx++]; Bbox glyphBaselineBbox, glyphAtlasBbox; @@ -268,27 +276,27 @@ namespace OpenVulkano::Scene info.xyz[0].x = bearingX; info.xyz[0].y = h - bearingY; - info.xyz[0].z = 1; - info.uv[0].x = l / m_atlasTex.resolution.x; - info.uv[0].y = b / m_atlasTex.resolution.y; + info.xyz[0].z = 0; + info.uv[0].x = l / m_atlasData->texture.resolution.x; + info.uv[0].y = b / m_atlasData->texture.resolution.y; info.xyz[1].x = bearingX + w; info.xyz[1].y = h - bearingY; - info.xyz[1].z = 1; - info.uv[1].x = r / m_atlasTex.resolution.x; - info.uv[1].y = b / m_atlasTex.resolution.y; + info.xyz[1].z = 0; + info.uv[1].x = r / m_atlasData->texture.resolution.x; + info.uv[1].y = b / m_atlasData->texture.resolution.y; info.xyz[2].x = bearingX + w; info.xyz[2].y = bearingY; //h - bearingY + h; - info.xyz[2].z = 1; - info.uv[2].x = r / m_atlasTex.resolution.x; - info.uv[2].y = t / m_atlasTex.resolution.y; + info.xyz[2].z = 0; + info.uv[2].x = r / m_atlasData->texture.resolution.x; + info.uv[2].y = t / m_atlasData->texture.resolution.y; info.xyz[3].x = bearingX; info.xyz[3].y = bearingY; - info.xyz[3].z = 1; - info.uv[3].x = l / m_atlasTex.resolution.x; - info.uv[3].y = t / m_atlasTex.resolution.y; + info.xyz[3].z = 0; + info.uv[3].x = l / m_atlasData->texture.resolution.x; + info.uv[3].y = t / m_atlasData->texture.resolution.y; info.advance = glyphBox.advance; } @@ -304,13 +312,13 @@ namespace OpenVulkano::Scene stbi_flip_vertically_on_write(1); if (std::filesystem::path(output).extension() == ".png") { - stbi_write_png(output.c_str(), m_atlasStorage.width(), m_atlasStorage.height(), channelsCount, - m_atlasStorage.operator const msdfgen::byte*(), channelsCount * m_atlasStorage.width()); + stbi_write_png(output.c_str(), m_atlasData->img->resolution.x, m_atlasData->img->resolution.y, + channelsCount, m_atlasData->img->data.Data(), channelsCount * m_atlasData->img->resolution.x); } else { - stbi_write_png((output + ".png").c_str(), m_atlasStorage.width(), m_atlasStorage.height(), channelsCount, - m_atlasStorage.operator const msdfgen::byte*(), channelsCount * m_atlasStorage.width()); + stbi_write_png((output + ".png").c_str(), m_atlasData->img->resolution.x, m_atlasData->img->resolution.y, + channelsCount, m_atlasData->img->data.Data(), channelsCount * m_atlasData->img->resolution.x); } } template class FontAtlasGenerator<1>; diff --git a/openVulkanoCpp/Scene/FontAtlasGenerator.hpp b/openVulkanoCpp/Scene/FontAtlasGenerator.hpp index c86b6ae..bac326c 100644 --- a/openVulkanoCpp/Scene/FontAtlasGenerator.hpp +++ b/openVulkanoCpp/Scene/FontAtlasGenerator.hpp @@ -8,7 +8,7 @@ #if __has_include("msdfgen.h") -#include "Scene/AtlasMetadata.hpp" +#include "Scene/AtlasData.hpp" #include "IFontAtlasGenerator.hpp" #include "Scene/Texture.hpp" #include @@ -41,7 +41,6 @@ namespace OpenVulkano::Scene msdf_atlas::BitmapAtlasStorage>; public: using Generator = std::conditional::type; - using AtlasData = std::conditional, msdfgen::Bitmap>::type; using Config = FontAtlasGeneratorConfig; static constexpr int channelsCount = (Channels == 1 ? 1 : 4); static msdf_atlas::Charset LoadAllGlyphs(const std::variant>& data); @@ -57,9 +56,7 @@ namespace OpenVulkano::Scene const std::optional& pngOutput = std::nullopt); void SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile = true) const override; void SetGeneratorConfig(const Config& config) { m_config = config; } - const Texture& GetAtlas() const override { return m_atlasTex; } - std::map& GetGlyphsInfo() override { return m_symbols; } - AtlasMetadata& GetAtlasMetadata() override { return m_meta; } + std::shared_ptr GetAtlasData() const { return m_atlasData; } Config& GetGeneratorConfig() { return m_config; } private: void InitFreetypeFromFile(msdfgen::FreetypeHandle*& ft, msdfgen::FontHandle*& font, const std::string& file); @@ -70,11 +67,8 @@ namespace OpenVulkano::Scene void SavePng(const std::string& output) const; private: - Texture m_atlasTex; - AtlasMetadata m_meta; Config m_config; - std::map m_symbols; - AtlasData m_atlasStorage; + std::shared_ptr m_atlasData; }; using SdfFontAtlasGenerator = FontAtlasGenerator<1>; using MsdfFontAtlasGenerator = FontAtlasGenerator<3>; diff --git a/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp b/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp index aeae92d..bd641c1 100644 --- a/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp +++ b/openVulkanoCpp/Scene/IFontAtlasGenerator.hpp @@ -6,8 +6,7 @@ #pragma once -#include "Scene/AtlasMetadata.hpp" -#include "Scene/Texture.hpp" +#include "Scene/AtlasData.hpp" #include #include #include @@ -25,8 +24,6 @@ namespace OpenVulkano::Scene virtual void GenerateAtlas(const Array& fontData, int length, const std::set& charset, const std::optional& pngOutput = std::nullopt) = 0; virtual void SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile = true) const = 0; - virtual const Texture& GetAtlas() const = 0; - virtual std::map& GetGlyphsInfo() = 0; - virtual AtlasMetadata& GetAtlasMetadata() = 0; + virtual std::shared_ptr GetAtlasData() const = 0; }; } diff --git a/openVulkanoCpp/Scene/Prefabs/LabelDrawable.cpp b/openVulkanoCpp/Scene/Prefabs/LabelDrawable.cpp new file mode 100644 index 0000000..4ca1aff --- /dev/null +++ b/openVulkanoCpp/Scene/Prefabs/LabelDrawable.cpp @@ -0,0 +1,161 @@ +/* + * 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 "LabelDrawable.hpp" +#include "Scene/TextDrawable.hpp" +#include "Scene/DrawEncoder.hpp" +#include "Scene/IFontAtlasGenerator.hpp" +#include "Base/Logger.hpp" +#include + +namespace OpenVulkano::Scene +{ + using namespace Math; + + LabelDrawable::LabelDrawable(const std::shared_ptr& atlasData, const LabelDrawableSettings& settings, + bool isBillboard) + : Drawable(DrawEncoder::GetDrawEncoder(), DrawPhase::MAIN) + { + if (atlasData->glyphs.empty() || !atlasData->texture.size) + { + throw std::runtime_error("Can't create label drawable. Either glyphs or texture is empty"); + } + m_atlasData = atlasData; + m_isBillboard = isBillboard; + SetLabelSettings(settings); + SetupShaders(); + SetupBuffers(); + } + + void LabelDrawable::SetLabelSettings(const LabelDrawableSettings& settings) + { + m_settings = settings; + m_labelData.color = settings.backgroundColor; + m_labelData.hasRoundedCorners = settings.hasRoundedCorners; + m_labelData.hasArrow = settings.hasArrow; + m_labelData.cornerRadius = settings.cornerRadius * settings.cornerRadius; + m_labelData.arrowLength = settings.arrowLength; + m_labelData.arrowWidth = settings.arrowWidth; + } + + void LabelDrawable::AddText(const std::string& text, const TextConfig& config) + { + if (text.empty()) + { + return; + } + + TextDrawable& textDrawable = m_texts.emplace_back(m_atlasData, config); + // do not render glyph's background + textDrawable.GetConfig().backgroundColor.a = 0; + textDrawable.SetShader(&m_textShader); + double lineHeight = m_atlasData->meta.lineHeight; + + textDrawable.GenerateText(text, m_position); + RecalculateBbox(textDrawable.GetBoundingBox()); + // update position for next text entry + m_position.y = m_bbox.GetMin().y - lineHeight; + + const auto& min = m_bbox.GetMin(); + const auto& max = m_bbox.GetMax(); + Vertex v, v2, v3, v4; + const float offset = 0.001; + const float yOffset = m_settings.hasArrow ? m_settings.arrowLength : 0; + v.position = Vector3f(min.x - m_settings.horizontalOffset, min.y - m_settings.verticalOffset - yOffset, min.z - offset); + v2.position = Vector3f(max.x + m_settings.horizontalOffset, min.y - m_settings.verticalOffset - yOffset, min.z - offset); + v3.position = Vector3f(max.x + m_settings.horizontalOffset, max.y + m_settings.verticalOffset, min.z - offset); + v4.position = Vector3f(min.x - m_settings.horizontalOffset, max.y + m_settings.verticalOffset, min.z - offset); + + m_labelData.textSize.x = v2.position.x - v.position.x; + m_labelData.textSize.y = v3.position.y - v.position.y; + m_labelData.bboxCenter.x = (v2.position.x + v.position.x) / 2; + m_labelData.bboxCenter.y = (v3.position.y + v.position.y) / 2; + } + + void LabelDrawable::SetBillboardSettings(const BillboardControlBlock& settings) + { + m_billboardSettings = settings; + } + + void LabelDrawable::RecalculateBbox(const Math::AABB& other) + { + if (m_bbox.IsEmpty()) + { + m_bbox = other; + } + else + { + auto& currentMin = m_bbox.GetMin(); + auto& currentMax = m_bbox.GetMax(); + currentMin.x = std::min(currentMin.x, other.min.x); + currentMin.y = std::min(currentMin.y, other.min.y); + currentMax.x = std::max(currentMax.x, other.max.x); + currentMax.y = std::max(currentMax.y, other.max.y); + } + } + + void LabelDrawable::SetupShaders() + { + if (!m_isBillboard) + { + m_backgroundShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/label"); + } + else + { + m_backgroundShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/labelBillboard"); + // binding for billboard's buffer + DescriptorSetLayoutBinding binding = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING; + binding.stageFlags = ShaderProgramType::Type::VERTEX; + m_backgroundShader.AddDescriptorSetLayoutBinding(binding, 4); + } + m_backgroundShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/label"); + m_backgroundShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING, 2); + m_backgroundShader.AddDescriptorSetLayoutBinding(UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING, 5); + m_backgroundShader.topology = Topology::TRIANGLE_STRIP; + m_backgroundShader.cullMode = CullMode::NONE; + SetShader(&m_backgroundShader); + + FontAtlasType fontAtlasType(static_cast(m_atlasData->meta.atlasType)); + if (!m_isBillboard) + { + m_textShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/text"); + } + else + { + m_textShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/billboard"); + DescriptorSetLayoutBinding billboardUniformBinding = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING; + billboardUniformBinding.stageFlags = ShaderProgramType::Type::VERTEX; + m_textShader.AddDescriptorSetLayoutBinding(billboardUniformBinding, 4); + } + + DescriptorSetLayoutBinding textUniformBinding = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING; + textUniformBinding.stageFlags = ShaderProgramType::FRAGMENT; + m_textShader.AddDescriptorSetLayoutBinding(textUniformBinding, 3); + m_textShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, + std::string(fontAtlasType.GetDefaultFragmentShader())); + m_textShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); + m_textShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING, 2); + m_textShader.alphaBlend = true; + m_textShader.cullMode = CullMode::NONE; + m_textShader.depthWrite = false; + m_textShader.depthCompareOp = CompareOp::LESS_OR_EQUAL; + } + + void LabelDrawable::SetupBuffers() + { + m_billboardBuffer.size = sizeof(BillboardControlBlock); + m_billboardBuffer.data = &m_billboardSettings; + m_billboardBuffer.setId = 4; + DescriptorSetLayoutBinding binding = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING; + binding.stageFlags = ShaderProgramType::Type::VERTEX; + m_billboardBuffer.binding = binding; + + m_labelBuffer.size = sizeof(LabelUniformData); + m_labelBuffer.data = &m_labelData; + m_labelBuffer.setId = 5; + m_labelBuffer.binding = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING; + } +} diff --git a/openVulkanoCpp/Scene/Prefabs/LabelDrawable.hpp b/openVulkanoCpp/Scene/Prefabs/LabelDrawable.hpp new file mode 100644 index 0000000..c8269fb --- /dev/null +++ b/openVulkanoCpp/Scene/Prefabs/LabelDrawable.hpp @@ -0,0 +1,83 @@ +/* + * 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/Wrapper.hpp" +#include "Scene/Drawable.hpp" +#include "Scene/Shader/Shader.hpp" +#include "Scene/Texture.hpp" +#include "Scene/UniformBuffer.hpp" +#include "Scene/Vertex.hpp" +#include "Scene/BillboardControlBlock.hpp" +#include "Math/AABB.hpp" +#include "Scene/TextDrawable.hpp" +#include + +namespace OpenVulkano::Scene +{ + + struct LabelDrawableSettings + { + Math::Vector4f backgroundColor = { 1, 0, 0, 1 }; + float horizontalOffset = 0.05f; + float verticalOffset = 0.05f; + float cornerRadius = 0.05f; + float arrowLength = 0.5f; + float arrowWidth = 0.2f; + int32_t hasRoundedCorners = false; + int32_t hasArrow = false; + }; + + struct LabelUniformData + { + Math::Vector4f textSize = { 0, 0, 0, 0 }; + Math::Vector4f color = { 0, 0, 0, 0 }; + Math::Vector4f bboxCenter = { 0, 0, 0, 0 }; + float cornerRadius = 0.f; + float arrowLength = 0.f; + float arrowWidth = 0.f; + int32_t hasRoundedCorners = false; + int32_t hasArrow = false; + }; + + class LabelDrawable final : public Drawable + { + public: + LabelDrawable(const std::shared_ptr& atlasData, + const LabelDrawableSettings& settings = LabelDrawableSettings(), bool isBillboard = false); + void AddText(const std::string& text, const TextConfig& config = TextConfig()); + void SetLabelSettings(const LabelDrawableSettings& settings); + void SetBillboardSettings(const BillboardControlBlock& settings); + void SetPosition(const Math::Vector3f& pos) { m_position = pos; } + std::list& GetTexts() { return m_texts; } + LabelDrawableSettings& GetSettings() { return m_settings; } + UniformBuffer* GetBillboardBuffer() { return &m_billboardBuffer; } + UniformBuffer* GetLabelBuffer() { return &m_labelBuffer; } + BillboardControlBlock& GetBillboardSettings() { return m_billboardSettings; } + Math::Vector3f& GetPosition() { return m_position; } + bool IsBillboard() const { return m_isBillboard; } + const Math::AABB& GetBoundingBox() const { return m_bbox; } + private: + void RecalculateBbox(const Math::AABB& other); + void SetupShaders(); + void SetupBuffers(); + private: + Shader m_backgroundShader; + UniformBuffer m_billboardBuffer; + UniformBuffer m_labelBuffer; + // list over vector to prevent memory reallocation and crash + std::list m_texts; + Shader m_textShader; + LabelDrawableSettings m_settings; + LabelUniformData m_labelData; + std::shared_ptr m_atlasData; + BillboardControlBlock m_billboardSettings; + Math::Vector3f m_position = { 0, 0, 0 }; + Math::AABB m_bbox; + bool m_isBillboard; + }; +} diff --git a/openVulkanoCpp/Scene/Shader/Shader.hpp b/openVulkanoCpp/Scene/Shader/Shader.hpp index ed1da7d..efdfaa5 100644 --- a/openVulkanoCpp/Scene/Shader/Shader.hpp +++ b/openVulkanoCpp/Scene/Shader/Shader.hpp @@ -60,8 +60,10 @@ namespace OpenVulkano::Scene ShaderProgram(ShaderProgramType type, const std::string& name) : type(type), name(name) {} ShaderProgram(const ShaderProgram& program) = default; + ShaderProgram& operator=(const ShaderProgram& program) = default; - ShaderProgram(ShaderProgram&& program) noexcept : type(program.type), name(std::move(program.name)) {} + ShaderProgram(ShaderProgram&& program) noexcept = default; + ShaderProgram& operator=(ShaderProgram&& program) noexcept = default; [[nodiscard]] std::string GetShaderNameOpenGL() const { diff --git a/openVulkanoCpp/Scene/TextDrawable.cpp b/openVulkanoCpp/Scene/TextDrawable.cpp index 39f2a71..d9fd170 100644 --- a/openVulkanoCpp/Scene/TextDrawable.cpp +++ b/openVulkanoCpp/Scene/TextDrawable.cpp @@ -30,7 +30,9 @@ namespace OpenVulkano::Scene sdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/text"); sdfDefaultShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); sdfDefaultShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING); - sdfDefaultShader.AddDescriptorSetLayoutBinding(UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING); + DescriptorSetLayoutBinding desc = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING; + desc.stageFlags = ShaderProgramType::FRAGMENT; + sdfDefaultShader.AddDescriptorSetLayoutBinding(desc); sdfDefaultShader.alphaBlend = true; sdfDefaultShader.cullMode = CullMode::NONE; once = false; @@ -48,7 +50,9 @@ namespace OpenVulkano::Scene msdfDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/msdfText"); msdfDefaultShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); msdfDefaultShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING); - msdfDefaultShader.AddDescriptorSetLayoutBinding(UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING); + DescriptorSetLayoutBinding desc = UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING; + desc.stageFlags = ShaderProgramType::FRAGMENT; + msdfDefaultShader.AddDescriptorSetLayoutBinding(desc); msdfDefaultShader.alphaBlend = true; msdfDefaultShader.cullMode = CullMode::NONE; once = false; @@ -56,6 +60,13 @@ namespace OpenVulkano::Scene return msdfDefaultShader; } + TextDrawable::TextDrawable(const TextConfig& config) + { + m_cfg = config; + m_uniBuffer.Init(sizeof(TextConfig), &m_cfg, 3); + m_uniBuffer.binding.stageFlags = ShaderProgramType::FRAGMENT; + } + TextDrawable::TextDrawable(const Array& atlasMetadata, const TextConfig& config) : TextDrawable(atlasMetadata, nullptr, config) { @@ -79,20 +90,22 @@ namespace OpenVulkano::Scene std::memcpy(&metadataBytes, atlasMetadata.Data() + (atlasMetadata.Size() - sizeof(uint32_t) - sizeof(uint64_t)), sizeof(uint64_t)); uint64_t offsetToMetadata = atlasMetadata.Size() - metadataBytes - sizeof(uint32_t) - sizeof(uint64_t); + m_atlasData = std::make_shared(); if (isPacked) { - m_texture = Texture(); - m_material.texture = &m_texture.value(); - m_img = Image::IImageLoader::loadData((const uint8_t*) atlasMetadata.Data(), + m_atlasData->texture = Texture(); + m_material.texture = &m_atlasData->texture; + m_atlasData->img = Image::IImageLoader::loadData((const uint8_t*) atlasMetadata.Data(), offsetToMetadata); - m_material.texture->format = m_img->dataFormat; - m_material.texture->resolution = m_img->resolution; - m_material.texture->size = m_img->data.Size(); - m_material.texture->textureBuffer = m_img->data.Data(); + m_material.texture->format = m_atlasData->img->dataFormat; + m_material.texture->resolution = m_atlasData->img->resolution; + m_material.texture->size = m_atlasData->img->data.Size(); + m_material.texture->textureBuffer = m_atlasData->img->data.Data(); } else { if (atlasTex == nullptr) { throw std::runtime_error("Atlas texture cannot be null with non-packed atlas metadata"); } + m_atlasData->texture = *atlasTex; m_material.texture = atlasTex; } @@ -101,7 +114,7 @@ namespace OpenVulkano::Scene size_t readMetadataBytes = 0; uint32_t unicode = 0; - std::memcpy(&m_meta, atlasMetadata.Data() + read_bytes, sizeof(AtlasMetadata)); + std::memcpy(&m_atlasData->meta, atlasMetadata.Data() + read_bytes, sizeof(AtlasMetadata)); read_bytes += sizeof(AtlasMetadata); readMetadataBytes += sizeof(AtlasMetadata); while (readMetadataBytes < metadataBytes) @@ -109,34 +122,26 @@ namespace OpenVulkano::Scene std::memcpy(&unicode, atlasMetadata.Data() + read_bytes, sizeof(uint32_t)); read_bytes += sizeof(uint32_t); readMetadataBytes += sizeof(uint32_t); - GlyphInfo& info = m_glyphs[unicode]; + GlyphInfo& info = m_atlasData->glyphs[unicode]; std::memcpy(&info, atlasMetadata.Data() + read_bytes, sizeof(GlyphInfo)); read_bytes += sizeof(GlyphInfo); readMetadataBytes += sizeof(GlyphInfo); } m_cfg = config; m_uniBuffer.Init(sizeof(TextConfig), &m_cfg, 3); + m_uniBuffer.binding.stageFlags = ShaderProgramType::FRAGMENT; } - TextDrawable::TextDrawable(const std::map& glyphData, Texture* atlasTex, - const TextConfig& config) + TextDrawable::TextDrawable(const std::shared_ptr& atlasData, const TextConfig& config) { - if (!atlasTex) { throw std::runtime_error("Atlas texture is nullptr"); } - if (glyphData.empty()) { throw std::runtime_error("Glyphs are not loaded"); } - m_material.texture = atlasTex; - m_glyphs = glyphData; + if (!atlasData || atlasData->glyphs.empty() || !atlasData->texture.textureBuffer) + { + throw std::runtime_error("Cannot initialize text drawable with empty atlas data"); + } + m_atlasData = atlasData; m_cfg = config; m_uniBuffer.Init(sizeof(TextConfig), &m_cfg, 3); - } - - TextDrawable::TextDrawable(IFontAtlasGenerator* fontAtlasGenerator, const TextConfig& config) - { - if (!fontAtlasGenerator) { throw std::runtime_error("FontAtlasGenerator is nullptr"); } - if (fontAtlasGenerator->GetGlyphsInfo().empty()) { throw std::runtime_error("Glyphs are not loaded"); } - m_fontAtlasGenerator = fontAtlasGenerator; - m_cfg = config; - m_material.texture = const_cast(&m_fontAtlasGenerator->GetAtlas()); - m_uniBuffer.Init(sizeof(TextConfig), &m_cfg, 3); + m_uniBuffer.binding.stageFlags = ShaderProgramType::FRAGMENT; } void TextDrawable::GenerateText(const std::string& text, const Math::Vector3f& pos) @@ -163,39 +168,20 @@ namespace OpenVulkano::Scene size_t len = GetActualLength(); m_geometry.Close(); m_geometry.Init(len * 4, len * 6); - - // TODO: better implementation to decide what to use: data from atlas generator or data read from file - // we have msdf but loaded glyphs metadata from file before AtlasMetadata* meta; std::map* symbols; - if (m_fontAtlasGenerator) - { - if (m_fontAtlasGenerator->GetGlyphsInfo().empty() && !m_glyphs.empty()) - { - // texture is set in ctor - symbols = &m_glyphs; - } - else - { - // just in case if FontAtlasGenerator changed it's atlas - m_material.texture = const_cast(&m_fontAtlasGenerator->GetAtlas()); - symbols = &m_fontAtlasGenerator->GetGlyphsInfo(); - } - meta = &m_fontAtlasGenerator->GetAtlasMetadata(); - } - else - { - symbols = &m_glyphs; - meta = &m_meta; - } + m_material.texture = &m_atlasData->texture; + symbols = &m_atlasData->glyphs; + meta = &m_atlasData->meta; - const Texture& atlasTex = *m_material.texture; double cursorX = pos.x; auto begin = text.begin(); auto end = text.end(); const double lineHeight = meta->lineHeight; double posY = pos.y; int i = 0; + Math::Vector3f bmin(pos), bmax(pos); + bool firstGlyph = true; while (begin != end) { uint32_t c = utf8::next(begin, end); @@ -251,18 +237,26 @@ namespace OpenVulkano::Scene // 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.advance + 0.08; + if (firstGlyph) + { + bmin.x = m_geometry.vertices[vIdx].position.x; + firstGlyph = false; + } + bmax.x = std::max(bmax.x, m_geometry.vertices[vIdx + 1].position.x); + bmax.y = std::max(bmax.y, m_geometry.vertices[vIdx + 2].position.y); + bmin.y = std::min(bmin.y, m_geometry.vertices[vIdx + 1].position.y); ++i; } + m_bbox.Init(bmin, bmax); SimpleDrawable::Init(m_shader, &m_geometry, &m_material, &m_uniBuffer); } - void TextDrawable::SetFontAtlasGenerator(IFontAtlasGenerator* fontAtlasGenerator) + void TextDrawable::SetAtlasData(const std::shared_ptr& atlasData) { - if (!fontAtlasGenerator || fontAtlasGenerator->GetGlyphsInfo().empty()) + if (!atlasData || atlasData->glyphs.empty() || !atlasData->texture.textureBuffer) { - Logger::RENDER->error("FontAtlasGenerator is either nullptr or doesn't contain glyphs info"); - return; + throw std::runtime_error("Cannot initialize text drawable with empty atlas data"); } - m_fontAtlasGenerator = fontAtlasGenerator; + m_atlasData = atlasData; } } \ No newline at end of file diff --git a/openVulkanoCpp/Scene/TextDrawable.hpp b/openVulkanoCpp/Scene/TextDrawable.hpp index 1cc99b4..2d50129 100644 --- a/openVulkanoCpp/Scene/TextDrawable.hpp +++ b/openVulkanoCpp/Scene/TextDrawable.hpp @@ -11,7 +11,7 @@ #include "Material.hpp" #include "Geometry.hpp" #include "UniformBuffer.hpp" -#include "AtlasMetadata.hpp" +#include "AtlasData.hpp" #include "Image/Image.hpp" #include #include @@ -38,28 +38,26 @@ namespace OpenVulkano::Scene public: static Shader& GetSdfDefaultShader(); static Shader& GetMsdfDefaultShader(); + TextDrawable(const TextConfig& config = TextConfig()); TextDrawable(const Array& atlasMetadata, const TextConfig& config = TextConfig()); TextDrawable(const std::string& atlasMetadataFile, const TextConfig& config = TextConfig()); TextDrawable(const std::string& atlasMetadataFile, Texture* atlasTex, const TextConfig& config = TextConfig()); TextDrawable(const Array& atlasMetadata, Texture* atlasTex, const TextConfig& config = TextConfig()); - TextDrawable(const std::map& glyphData, Texture* atlasTex, const TextConfig& config = TextConfig()); - TextDrawable(IFontAtlasGenerator* fontAtlasGenerator, const TextConfig& config = TextConfig()); + TextDrawable(const std::shared_ptr& atlasData, const TextConfig& config = TextConfig()); void GenerateText(const std::string& text, const Math::Vector3f& pos = Math::Vector3f(0.f)); void SetConfig(const TextConfig& cfg) { m_cfg = cfg; } void SetShader(Shader* shader) { m_shader = shader; } + void SetAtlasData(const std::shared_ptr& atlasData); + Math::AABB& GetBoundingBox() { return m_bbox; } TextConfig& GetConfig() { return m_cfg; } Shader* GetShader() { return m_shader; } - void SetFontAtlasGenerator(IFontAtlasGenerator* fontAtlasGenerator); - IFontAtlasGenerator* GetFontAtlasGenerator() { return m_fontAtlasGenerator; } + std::shared_ptr GetAtlasData() { return m_atlasData; } private: Geometry m_geometry; Material m_material; UniformBuffer m_uniBuffer; - std::map m_glyphs; - AtlasMetadata m_meta; - std::unique_ptr m_img; - std::optional m_texture; - IFontAtlasGenerator* m_fontAtlasGenerator = nullptr; + std::shared_ptr m_atlasData; + Math::AABB m_bbox; Shader* m_shader = nullptr; TextConfig m_cfg; }; diff --git a/openVulkanoCpp/Shader/billboard.vert b/openVulkanoCpp/Shader/billboard.vert index 261d543..1442ae1 100644 --- a/openVulkanoCpp/Shader/billboard.vert +++ b/openVulkanoCpp/Shader/billboard.vert @@ -21,7 +21,7 @@ layout(set = 1, binding = 0) uniform CameraData vec4 camPos; } cam; -layout(set = 3, binding = 0) uniform BillboardData +layout(set = 4, binding = 0) uniform BillboardData { vec2 size; bool isFixedSize; diff --git a/openVulkanoCpp/Shader/billboardFromSinglePoint.geom b/openVulkanoCpp/Shader/billboardFromSinglePoint.geom index 13e60f0..c41d35e 100644 --- a/openVulkanoCpp/Shader/billboardFromSinglePoint.geom +++ b/openVulkanoCpp/Shader/billboardFromSinglePoint.geom @@ -24,7 +24,7 @@ layout(set = 1, binding = 0) uniform CameraData float pixelScaleFactor; } cam; -layout(set = 3, binding = 0) uniform BillboardData +layout(set = 4, binding = 0) uniform BillboardData { vec2 size; bool isFixedSize; diff --git a/openVulkanoCpp/Shader/label.frag b/openVulkanoCpp/Shader/label.frag new file mode 100644 index 0000000..33c8fc5 --- /dev/null +++ b/openVulkanoCpp/Shader/label.frag @@ -0,0 +1,73 @@ +#version 450 + +layout(location = 1) in vec2 texCoord; +layout(location = 0) out vec4 outColor; + +layout(set = 5, binding = 0) uniform LabelData +{ + vec4 textSize; + vec4 color; + vec4 bboxCenter; + float radius; + float arrowLength; + float arrowWidth; + bool hasRoundedCorners; + bool hasArrow; +} labelInfo; + +void main() +{ + if (labelInfo.hasRoundedCorners || labelInfo.hasArrow) + { + vec2 bbox = vec2(labelInfo.textSize); + const float arrowLength = int(labelInfo.hasArrow) * labelInfo.arrowLength; + float arrowWidth = labelInfo.arrowWidth; + vec2 uvScaled = texCoord; + + float distX = min(uvScaled.x, bbox.x - uvScaled.x); + float distY = min(uvScaled.y - arrowLength, bbox.y - uvScaled.y); + float distanceFromCorner = distX * distY; + + if (distanceFromCorner < labelInfo.radius) + { + // plain arrow + if (labelInfo.hasArrow && !labelInfo.hasRoundedCorners) + { + // Some bias to prevent line between arrow and label + + // check that we are dealing with lower parts of the label where arrow should be drawn, + // to prevent discarding fragments from upper corners + if (distY <= 0.01 && uvScaled.y < (bbox.y - arrowWidth) && arrowLength != 0.0f) + { + arrowWidth = arrowWidth * ((arrowLength + distY) / arrowLength); + if (distX < (bbox.x - arrowWidth) * 0.5) + { + discard; + } + } + } + // TODO: rounded corners + rounded arrow. + // now renders rounded corners and sharp arrow + else if (labelInfo.hasArrow && labelInfo.hasRoundedCorners) + { + if (distY <= 0.05 && uvScaled.y < (bbox.y - arrowWidth) && arrowLength != 0.0f) + { + arrowWidth = arrowWidth * ((arrowLength + distY) / arrowLength); + if (distX < (bbox.x - arrowWidth) * 0.5) + { + discard; + } + } + else + { + discard; + } + } + // no arrow, rounded corners + else + { + discard; + } + } + } + outColor = labelInfo.color; +} \ No newline at end of file diff --git a/openVulkanoCpp/Shader/label.vert b/openVulkanoCpp/Shader/label.vert new file mode 100644 index 0000000..ca0380d --- /dev/null +++ b/openVulkanoCpp/Shader/label.vert @@ -0,0 +1,45 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(set = 0, binding = 0) uniform NodeData +{ + mat4 world; +} node; + +layout(set = 1, binding = 0) uniform CameraData +{ + mat4 viewProjection; +} cam; + +layout(set = 5, binding = 0) uniform LabelData +{ + vec4 textSize; + vec4 color; + vec4 bboxCenter; + float radius; + float arrowLength; + bool hasRoundedCorners; + bool hasArrow; +} labelInfo; + +layout(location = 0) out vec4 color; +layout(location = 1) out vec2 textureCoordinates; + +// Background plane positions are in clipped space +const vec4 PLANE[4] = vec4[]( + vec4(-0.5, -0.5, 0, 1), vec4(0.5, -0.5, 0, 1), vec4(-0.5, 0.5, 0, 1), vec4(0.5, 0.5, 0, 1) + + ); +const vec2 TEX_COORDS[4] = vec2[]( + vec2(0, 0), vec2(1, 0), vec2(0, 1), vec2(1, 1) + ); + +void main() { + vec4 position = PLANE[gl_VertexIndex]; + vec2 bbox = labelInfo.textSize.xy; + position.xy *= bbox; + position.xy += vec2(labelInfo.bboxCenter); + position.z = -0.001; + gl_Position = cam.viewProjection * node.world * position; + textureCoordinates = TEX_COORDS[gl_VertexIndex] * bbox; +} diff --git a/openVulkanoCpp/Shader/labelBillboard.vert b/openVulkanoCpp/Shader/labelBillboard.vert new file mode 100644 index 0000000..00e1d02 --- /dev/null +++ b/openVulkanoCpp/Shader/labelBillboard.vert @@ -0,0 +1,87 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +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; + float nearPlane; + float farPlane; + float width; + float height; + float fov; + float aspect; + float scaleFactor; + float pixelScaleFactor; +} cam; + +layout(set = 4, binding = 0) uniform BillboardData +{ + vec2 size; + bool isFixedSize; +} billboardInfo; + +layout(set = 5, binding = 0) uniform LabelData +{ + vec4 textSize; + vec4 color; + vec4 bboxCenter; + float radius; + float arrowLength; + bool hasRoundedCorners; + bool hasArrow; +} labelInfo; + +layout(location = 0) out vec4 color; +layout(location = 1) out vec2 textureCoordinates; + +// Background plane positions are in clipped space +const vec4 PLANE[4] = vec4[]( + vec4(-0.5, -0.5, 0, 1), vec4(0.5, -0.5, 0, 1), vec4(-0.5, 0.5, 0, 1), vec4(0.5, 0.5, 0, 1) + + ); +const vec2 TEX_COORDS[4] = vec2[]( + vec2(0, 0), vec2(1, 0), vec2(0, 1), vec2(1, 1) + ); + +void main() { + vec4 position = PLANE[gl_VertexIndex]; + vec2 bbox = labelInfo.textSize.xy; + position.xy *= bbox; + position.xy += vec2(labelInfo.bboxCenter); + position.z = -0.001; + textureCoordinates = TEX_COORDS[gl_VertexIndex] * bbox; + + if (!billboardInfo.isFixedSize) + { + mat4 mv = cam.view * node.world; + + mv[0][0] = 1; + mv[0][1] = 0; + mv[0][2] = 0; + + mv[1][0] = 0; + mv[1][1] = 1; + mv[1][2] = 0; + + mv[2][0] = 0; + mv[2][1] = 0; + mv[2][2] = 1; + + gl_Position = cam.projection * mv * vec4(position.xyz, 1); + } + else + { + vec4 billboardPos = vec4(0.5, 0.5, 0.5, 1); + vec4 viewPos = cam.view * node.world * billboardPos; + float dist = -viewPos.z; + gl_Position = cam.projection * (viewPos + vec4(position.xy*dist*0.2,0,0)); + } +} diff --git a/openVulkanoCpp/Shader/msdfText.frag b/openVulkanoCpp/Shader/msdfText.frag index a6c3c78..2878e2e 100644 --- a/openVulkanoCpp/Shader/msdfText.frag +++ b/openVulkanoCpp/Shader/msdfText.frag @@ -1,6 +1,6 @@ #version 450 -layout(location = 0) in vec2 texCoord; +layout(location = 1) in vec2 texCoord; layout(location = 0) out vec4 outColor; diff --git a/openVulkanoCpp/Shader/text.frag b/openVulkanoCpp/Shader/text.frag index b874c24..5333c6f 100644 --- a/openVulkanoCpp/Shader/text.frag +++ b/openVulkanoCpp/Shader/text.frag @@ -1,6 +1,6 @@ #version 450 -layout(location = 0) in vec2 texCoord; +layout(location = 1) in vec2 texCoord; layout(location = 0) out vec4 outColor; diff --git a/openVulkanoCpp/Shader/text.vert b/openVulkanoCpp/Shader/text.vert index 8f60a00..1e9ef24 100644 --- a/openVulkanoCpp/Shader/text.vert +++ b/openVulkanoCpp/Shader/text.vert @@ -5,7 +5,7 @@ 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(location = 1) out vec2 fragTextureCoordinates; layout(set = 0, binding = 0) uniform NodeData { diff --git a/openVulkanoCpp/Vulkan/Scene/LabelDrawableVulkanEncoder.cpp b/openVulkanoCpp/Vulkan/Scene/LabelDrawableVulkanEncoder.cpp new file mode 100644 index 0000000..e7ff24b --- /dev/null +++ b/openVulkanoCpp/Vulkan/Scene/LabelDrawableVulkanEncoder.cpp @@ -0,0 +1,126 @@ +/* + * 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 "Scene/Prefabs/LabelDrawable.hpp" +#include "Scene/SimpleDrawable.hpp" +#include "Scene/TextDrawable.hpp" +#include "Scene/DrawEncoder.hpp" +#include "VulkanGeometry.hpp" +#include "Vulkan/VulkanDrawContext.hpp" +#include "Vulkan/Scene/VulkanTexture.hpp" +#include "Vulkan/Scene/VulkanUniformBuffer.hpp" + +using namespace OpenVulkano::Scene; + +namespace OpenVulkano::Vulkan +{ + + void EncodeBackground(LabelDrawable* labelDrawable, Vulkan::VulkanDrawContext* drawContext) + { + if (labelDrawable->IsBillboard()) + { + OpenVulkano::Scene::UniformBuffer* buffer = labelDrawable->GetBillboardBuffer(); + VulkanUniformBuffer* vkBuffer = static_cast(buffer->renderBuffer); + if (!vkBuffer) + { + vkBuffer = drawContext->renderer->GetResourceManager().PrepareUniformBuffer(buffer); + } + vkBuffer->Record(drawContext); + } + + OpenVulkano::Scene::UniformBuffer* labelBuffer = labelDrawable->GetLabelBuffer(); + VulkanUniformBuffer* vkBuffer = static_cast(labelBuffer->renderBuffer); + if (!vkBuffer) + { + vkBuffer = drawContext->renderer->GetResourceManager().PrepareUniformBuffer(labelBuffer); + } + vkBuffer->Record(drawContext); + + for (Node* node: labelDrawable->GetNodes()) + { + if (!node->IsEnabled()) [[unlikely]] + { + continue; + } + drawContext->EncodeNode(node); + } + drawContext->commandBuffer.draw(4, 1, 0, 0); + } + + void EncodeTextDrawable(LabelDrawable* labelDrawable, Vulkan::VulkanDrawContext* drawContext) + { + for (TextDrawable& entry : labelDrawable->GetTexts()) + { + OpenVulkano::Scene::Shader* shader = entry.GetShader(); + drawContext->EncodeShader(shader); + Geometry* mesh = entry.GetMesh(); + VulkanGeometry* renderGeo = static_cast(mesh->renderGeo); + if (!renderGeo) + { + renderGeo = drawContext->renderer->GetResourceManager().PrepareGeometry(mesh); + } + renderGeo->RecordBind(drawContext->commandBuffer); + + std::array uniforms = { nullptr, nullptr }; + // fragment shader buffer + uniforms[0] = entry.GetBuffer(); + if (labelDrawable->IsBillboard()) + { + // vertex shader buffer + uniforms[1] = labelDrawable->GetBillboardBuffer(); + } + + for (OpenVulkano::Scene::UniformBuffer* buffer : uniforms) + { + if (buffer) + { + VulkanUniformBuffer* vkBuffer = static_cast(buffer->renderBuffer); + if (!vkBuffer) + { + vkBuffer = drawContext->renderer->GetResourceManager().PrepareUniformBuffer(buffer); + } + vkBuffer->Record(drawContext); + } + } + + if (Material* material = entry.GetMaterial()) + { + if (Texture* texture = material->texture) + { + VulkanTexture* renderTexture = static_cast(texture->renderTexture); + if (!renderTexture) + { + drawContext->renderer->GetResourceManager().PrepareMaterial(entry.GetMaterial()); + renderTexture = static_cast(texture->renderTexture); + } + renderTexture->Record(drawContext); + } + } + + for (Node* node: labelDrawable->GetNodes()) + { + if (!node->IsEnabled()) [[unlikely]] + { + continue; + } + drawContext->EncodeNode(node); + renderGeo->RecordDraw(drawContext->commandBuffer); + } + } + } + + void EncodeLabelDrawable(Drawable* instance, Vulkan::VulkanDrawContext* drawContext) + { + LabelDrawable* labelDrawable = static_cast(instance); + EncodeBackground(labelDrawable, drawContext); + EncodeTextDrawable(labelDrawable, drawContext); + } +} + +namespace +{ + void* labelDrawableVulkanEncoderReg = DrawEncoder::RegisterVulkanEncodeFunction(&OpenVulkano::Vulkan::EncodeLabelDrawable); +} \ No newline at end of file