rework API for text rendering
This commit is contained in:
2
3rdParty/msdf/CMakeLists.txt
vendored
2
3rdParty/msdf/CMakeLists.txt
vendored
@@ -36,7 +36,7 @@ if (WIN32)
|
||||
set(TRIPLET x64-windows-static-md-release CACHE INTERNAL "triplet")
|
||||
elseif(UNIX AND NOT APPLE)
|
||||
set(TRIPLET x64-linux CACHE INTERNAL "triplet")
|
||||
elseif(APPLE)
|
||||
elseif(APPLE AND NOT IOS)
|
||||
set(TRIPLET arm64-osx CACHE INTERNAL "triplet")
|
||||
elseif(IOS)
|
||||
set(TRIPLET arm64-ios CACHE INTERNAL "triplet")
|
||||
|
||||
@@ -63,39 +63,20 @@ namespace OpenVulkano
|
||||
auto& resourceLoader = ResourceLoader::GetInstance();
|
||||
const std::string fontPath = resourceLoader.GetResourcePath("Roboto-Regular.ttf");
|
||||
const std::string atlasPath = (fs::path(fontPath).parent_path() / "roboto-regular-atlas.png").string();
|
||||
|
||||
m_nodesPool.resize(N);
|
||||
m_drawablesPool.resize(N);
|
||||
m_uniBuffers.resize(N);
|
||||
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
m_uniBuffers[i].Init(sizeof(TextConfig), &texts[i].second, 3);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
||||
Charset charset = Charset::ASCII;
|
||||
for (unicode_t c = 0x0410; c <= 0x041F; c++)
|
||||
{
|
||||
// some unicode values
|
||||
charset.add(c);
|
||||
}
|
||||
m_atlasGenerator.GenerateAtlas(fontPath, atlasPath, charset);
|
||||
m_atlasGenerator.GenerateAtlas(fontPath, charset);
|
||||
|
||||
for (int i = 0; i < texts.size(); i++)
|
||||
{
|
||||
TextDrawable* t = new TextDrawable();
|
||||
t->SetFontAtlasGenerator(&m_atlasGenerator);
|
||||
t->SetConfig(texts[i].second);
|
||||
t->SetUniformBuffer(&m_uniBuffers[i]);
|
||||
t->SetShader(&m_shader);
|
||||
TextDrawable* t = new TextDrawable(&m_atlasGenerator, texts[i].second);
|
||||
t->GenerateText(texts[i].first);
|
||||
m_drawablesPool[i] = t;
|
||||
m_nodesPool[i].Init();
|
||||
@@ -131,9 +112,7 @@ namespace OpenVulkano
|
||||
private:
|
||||
OpenVulkano::Scene::Scene m_scene;
|
||||
PerspectiveCamera m_cam;
|
||||
std::vector<UniformBuffer> m_uniBuffers;
|
||||
OpenVulkano::FreeCamCameraController m_camController;
|
||||
Shader m_shader;
|
||||
FontAtlasGenerator m_atlasGenerator;
|
||||
std::vector<SimpleDrawable*> m_drawablesPool;
|
||||
std::vector<Node> m_nodesPool;
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ImageLoader.hpp"
|
||||
#include "Base/Logger.hpp"
|
||||
#include <memory>
|
||||
|
||||
@@ -12,36 +12,47 @@ namespace OpenVulkano::Scene
|
||||
using namespace msdfgen;
|
||||
using namespace msdf_atlas;
|
||||
|
||||
void FontAtlasGenerator::GenerateAtlas(const std::string& fontFile, const std::string& outputFile, const Charset& chset)
|
||||
void FontAtlasGenerator::GenerateAtlas(const std::string& fontFile, const Charset& charset, const std::optional<std::string>& pngOutput)
|
||||
{
|
||||
if (chset.empty())
|
||||
if (charset.empty())
|
||||
{
|
||||
Logger::RENDER->info("Provided charset is empty. Atlas will not be generated");
|
||||
return;
|
||||
}
|
||||
// TODO: dynamic atlas and add only those symbols which are not present yet in current atlas
|
||||
Charset absentSymbols;
|
||||
for (auto c : chset)
|
||||
FreetypeHandle* ft = initializeFreetype();
|
||||
if (!ft) { throw std::runtime_error("Failed to initialize freetype"); }
|
||||
FontHandle* font = loadFont(ft, fontFile.data());
|
||||
if (!font)
|
||||
{
|
||||
if (!m_symbols.contains(c))
|
||||
{
|
||||
absentSymbols.add(c);
|
||||
}
|
||||
deinitializeFreetype(ft);
|
||||
throw std::runtime_error(fmt::format("Failed to load font from file {0}", fontFile.data()));
|
||||
}
|
||||
if (m_loadedFont == fontFile && absentSymbols.empty())
|
||||
Generate(ft, font, charset, pngOutput);
|
||||
}
|
||||
|
||||
void FontAtlasGenerator::GenerateAtlas(const msdfgen::byte* fontData, int length, const Charset& charset,
|
||||
const std::optional<std::string>& pngOutput)
|
||||
{
|
||||
FreetypeHandle* ft = initializeFreetype();
|
||||
if (!ft) { throw std::runtime_error("Failed to initialize freetype"); }
|
||||
FontHandle* font = loadFontData(ft, fontData, length);
|
||||
if (!font)
|
||||
{
|
||||
return;
|
||||
deinitializeFreetype(ft);
|
||||
throw std::runtime_error("Failed to load font data from given buffer");
|
||||
}
|
||||
Generate(ft, font, charset, pngOutput);
|
||||
}
|
||||
|
||||
void FontAtlasGenerator::Generate(FreetypeHandle* ft, FontHandle* font, const Charset& chset,
|
||||
const std::optional<std::string>& pngOutput)
|
||||
{
|
||||
m_symbols.clear();
|
||||
m_loadedFont = fontFile;
|
||||
|
||||
std::vector<GlyphGeometry> glyphsGeometry;
|
||||
std::pair<FreetypeHandle*, FontHandle*> 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);
|
||||
fontGeometry.loadCharset(font, 1, chset);
|
||||
|
||||
TightAtlasPacker packer;
|
||||
packer.setDimensionsConstraint(DimensionsConstraint::SQUARE);
|
||||
@@ -66,29 +77,15 @@ namespace OpenVulkano::Scene
|
||||
m_atlasTex.size = storage.width * storage.height * 1; // 1 channel
|
||||
for (const auto& glyph: glyphsGeometry)
|
||||
{
|
||||
GlyphInfo info;
|
||||
GlyphInfo& info = m_symbols[glyph.getCodepoint()];
|
||||
info.geometry = glyph;
|
||||
info.glyphBox = m_generator.getLayout()[idx++];
|
||||
m_symbols[glyph.getCodepoint()] = std::move(info);
|
||||
}
|
||||
savePng(m_generator.atlasStorage(), outputFile.c_str());
|
||||
if (pngOutput && !pngOutput->empty())
|
||||
{
|
||||
savePng(m_generator.atlasStorage(), pngOutput->c_str());
|
||||
}
|
||||
destroyFont(font);
|
||||
deinitializeFreetype(ft);
|
||||
}
|
||||
|
||||
std::pair<FreetypeHandle*, FontHandle*> 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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <map>
|
||||
#include "Scene/Texture.hpp"
|
||||
#include "msdfgen.h"
|
||||
@@ -28,15 +29,18 @@ namespace OpenVulkano::Scene
|
||||
class FontAtlasGenerator
|
||||
{
|
||||
public:
|
||||
void GenerateAtlas(const std::string& fontFile, const std::string& outputFile, const Charset& = Charset::ASCII);
|
||||
void GenerateAtlas(const std::string& fontFile, const Charset& charset = Charset::ASCII,
|
||||
const std::optional<std::string>& pngOutput = std::nullopt);
|
||||
void GenerateAtlas(const msdfgen::byte* fontData, int length, const Charset& charset = Charset::ASCII,
|
||||
const std::optional<std::string>& pngOutput = std::nullopt);
|
||||
const Texture& GetAtlas() const { return m_atlasTex; }
|
||||
std::map<unicode_t, GlyphInfo>& GetAtlasInfo() { return m_symbols; }
|
||||
private:
|
||||
std::pair<FreetypeHandle*, FontHandle*> GetHandlers(const std::string& fontFile);
|
||||
void Generate(FreetypeHandle* ft, FontHandle* font, const Charset& chset,
|
||||
const std::optional<std::string>& pngOutput);
|
||||
private:
|
||||
ImmediateAtlasGenerator<float, 1, sdfGenerator, BitmapAtlasStorage<msdfgen::byte, 1>> m_generator;
|
||||
Texture m_atlasTex;
|
||||
std::map<unicode_t, GlyphInfo> m_symbols;
|
||||
std::string m_loadedFont;
|
||||
};
|
||||
}
|
||||
@@ -16,7 +16,6 @@ namespace OpenVulkano::Scene
|
||||
|
||||
class SimpleDrawable : public Drawable
|
||||
{
|
||||
protected:
|
||||
Geometry* m_mesh = nullptr;
|
||||
Material* m_material = nullptr;
|
||||
UniformBuffer* m_uniBuffer = nullptr;
|
||||
|
||||
@@ -11,51 +11,51 @@
|
||||
#include "Scene/UniformBuffer.hpp"
|
||||
#include "Scene/FontAtlasGenerator.hpp"
|
||||
#include "Base/Logger.hpp"
|
||||
#include "utf8.h"
|
||||
#include "fmt/core.h"
|
||||
#include <utf8.h>
|
||||
|
||||
namespace OpenVulkano::Scene
|
||||
{
|
||||
using namespace msdfgen;
|
||||
using namespace msdf_atlas;
|
||||
|
||||
TextDrawable::~TextDrawable()
|
||||
{
|
||||
delete m_mesh;
|
||||
delete m_material;
|
||||
Shader& TextDrawable::GetDefaultShader()
|
||||
{
|
||||
static bool once = true;
|
||||
static Shader textDefaultShader;
|
||||
if (once)
|
||||
{
|
||||
textDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/text");
|
||||
textDefaultShader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/text");
|
||||
textDefaultShader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription());
|
||||
textDefaultShader.AddDescriptorSetLayoutBinding(Texture::DESCRIPTOR_SET_LAYOUT_BINDING);
|
||||
textDefaultShader.AddDescriptorSetLayoutBinding(UniformBuffer::DESCRIPTOR_SET_LAYOUT_BINDING);
|
||||
textDefaultShader.alphaBlend = true;
|
||||
textDefaultShader.cullMode = CullMode::NONE;
|
||||
once = false;
|
||||
}
|
||||
return textDefaultShader;
|
||||
}
|
||||
|
||||
TextDrawable::TextDrawable(FontAtlasGenerator* fontAtlasGenerator, const TextConfig& config)
|
||||
{
|
||||
if (!fontAtlasGenerator) { throw std::runtime_error("FontAtlasGenerator is nullptr"); }
|
||||
if (fontAtlasGenerator->GetAtlasInfo().empty()) { throw std::runtime_error("Glyphs are not loaded"); }
|
||||
m_fontAtlasGenerator = fontAtlasGenerator;
|
||||
m_cfg = config;
|
||||
m_material.texture = const_cast<Texture*>(&m_fontAtlasGenerator->GetAtlas());
|
||||
m_uniBuffer.Init(sizeof(TextConfig), &m_cfg, 3);
|
||||
}
|
||||
|
||||
void TextDrawable::GenerateText(const std::string& text, const Math::Vector3f& pos)
|
||||
{
|
||||
if (!m_fontAtlasGenerator)
|
||||
{
|
||||
Logger::RENDER->error("Can't draw text. FontAtlasGenerator is nullptr");
|
||||
return;
|
||||
}
|
||||
if (m_mesh)
|
||||
{
|
||||
delete m_mesh;
|
||||
m_mesh = nullptr;
|
||||
}
|
||||
if (m_material)
|
||||
{
|
||||
delete m_material;
|
||||
m_material = nullptr;
|
||||
}
|
||||
if (text.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::map<unicode_t, GlyphInfo>& symbols = m_fontAtlasGenerator->GetAtlasInfo();
|
||||
if (symbols.empty())
|
||||
{
|
||||
throw std::runtime_error("Glyphs are not loaded");
|
||||
}
|
||||
|
||||
m_mesh = new Geometry();
|
||||
m_material = new Material();
|
||||
m_mesh->freeAfterUpload = false;
|
||||
m_mesh->Init(text.size() * 4, text.size() * 6);
|
||||
m_geometry.Close();
|
||||
m_geometry.Init(text.size() * 4, text.size() * 6);
|
||||
const Texture& atlasTex = *m_material.texture;
|
||||
|
||||
struct Bbox
|
||||
{
|
||||
@@ -89,34 +89,31 @@ namespace OpenVulkano::Scene
|
||||
double ax = cursorX + bearingX;
|
||||
double ay = pos.y - (h - bearingY);
|
||||
|
||||
const Texture& atlasTex = m_fontAtlasGenerator->GetAtlas();
|
||||
m_material->texture = const_cast<Texture*>(&atlasTex);
|
||||
|
||||
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 / atlasTex.resolution.x;
|
||||
m_mesh->vertices[vIdx].textureCoordinates.y = b / atlasTex.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 / atlasTex.resolution.x;
|
||||
m_mesh->vertices[vIdx + 1].textureCoordinates.y = b / atlasTex.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 / atlasTex.resolution.x;
|
||||
m_mesh->vertices[vIdx + 2].textureCoordinates.y = t / atlasTex.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 / atlasTex.resolution.x;
|
||||
m_mesh->vertices[vIdx + 3].textureCoordinates.y = t / atlasTex.resolution.y;
|
||||
|
||||
m_mesh->SetIndices(indices, 6, 6 * i);
|
||||
m_geometry.vertices[vIdx].position.x = ax;
|
||||
m_geometry.vertices[vIdx].position.y = ay;
|
||||
m_geometry.vertices[vIdx].position.z = 1;
|
||||
m_geometry.vertices[vIdx].textureCoordinates.x = l / atlasTex.resolution.x;
|
||||
m_geometry.vertices[vIdx].textureCoordinates.y = b / atlasTex.resolution.y;
|
||||
|
||||
m_geometry.vertices[vIdx + 1].position.x = ax + w;
|
||||
m_geometry.vertices[vIdx + 1].position.y = ay;
|
||||
m_geometry.vertices[vIdx + 1].position.z = 1;
|
||||
m_geometry.vertices[vIdx + 1].textureCoordinates.x = r / atlasTex.resolution.x;
|
||||
m_geometry.vertices[vIdx + 1].textureCoordinates.y = b / atlasTex.resolution.y;
|
||||
|
||||
m_geometry.vertices[vIdx + 2].position.x = ax + w;
|
||||
m_geometry.vertices[vIdx + 2].position.y = ay + h;
|
||||
m_geometry.vertices[vIdx + 2].position.z = 1;
|
||||
m_geometry.vertices[vIdx + 2].textureCoordinates.x = r / atlasTex.resolution.x;
|
||||
m_geometry.vertices[vIdx + 2].textureCoordinates.y = t / atlasTex.resolution.y;
|
||||
|
||||
m_geometry.vertices[vIdx + 3].position.x = ax;
|
||||
m_geometry.vertices[vIdx + 3].position.y = ay + h;
|
||||
m_geometry.vertices[vIdx + 3].position.z = 1;
|
||||
m_geometry.vertices[vIdx + 3].textureCoordinates.x = l / atlasTex.resolution.x;
|
||||
m_geometry.vertices[vIdx + 3].textureCoordinates.y = t / atlasTex.resolution.y;
|
||||
|
||||
m_geometry.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;
|
||||
@@ -127,5 +124,6 @@ namespace OpenVulkano::Scene
|
||||
Logger::RENDER->error("Could not find glyph for character {}", c);
|
||||
}
|
||||
}
|
||||
SimpleDrawable::Init(m_shader, &m_geometry, &m_material, &m_uniBuffer);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,10 @@
|
||||
#include "SimpleDrawable.hpp"
|
||||
#include "FontAtlasGenerator.hpp"
|
||||
#include "Texture.hpp"
|
||||
#include "Material.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "UniformBuffer.hpp"
|
||||
#include "Base/Logger.hpp"
|
||||
#include "msdfgen.h"
|
||||
#include "msdfgen-ext.h"
|
||||
#include "msdf-atlas-gen/msdf-atlas-gen.h"
|
||||
@@ -37,16 +41,30 @@ namespace OpenVulkano::Scene
|
||||
class TextDrawable : public SimpleDrawable
|
||||
{
|
||||
public:
|
||||
TextDrawable() = default;
|
||||
~TextDrawable();
|
||||
void SetUniformBuffer(UniformBuffer* buffer) { m_uniBuffer = buffer; }
|
||||
static Shader& GetDefaultShader();
|
||||
TextDrawable(FontAtlasGenerator* fontAtlasGenerator, 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; }
|
||||
TextConfig& GetConfig() { return m_cfg; }
|
||||
void SetFontAtlasGenerator(FontAtlasGenerator* fontAtlasGenerator) { m_fontAtlasGenerator = fontAtlasGenerator; }
|
||||
Shader* GetShader() { return m_shader; }
|
||||
void SetFontAtlasGenerator(FontAtlasGenerator* fontAtlasGenerator)
|
||||
{
|
||||
if (!fontAtlasGenerator || fontAtlasGenerator->GetAtlasInfo().empty())
|
||||
{
|
||||
Logger::RENDER->error("FontAtlasGenerator is either nullptr or doesn't contain glyphs info");
|
||||
return;
|
||||
}
|
||||
m_fontAtlasGenerator = fontAtlasGenerator;
|
||||
m_material.texture = const_cast<Texture*>(&m_fontAtlasGenerator->GetAtlas());
|
||||
}
|
||||
FontAtlasGenerator* GetFontAtlasGenerator() { return m_fontAtlasGenerator; }
|
||||
private:
|
||||
Geometry m_geometry;
|
||||
Material m_material;
|
||||
UniformBuffer m_uniBuffer;
|
||||
FontAtlasGenerator* m_fontAtlasGenerator = nullptr;
|
||||
Shader* m_shader = &GetDefaultShader();
|
||||
TextConfig m_cfg;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user