rework API for text rendering

This commit is contained in:
ohyzha
2024-08-04 00:10:59 +03:00
parent dcf6e72f96
commit 232ad0a938
8 changed files with 122 additions and 129 deletions

View File

@@ -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")

View File

@@ -63,23 +63,8 @@ 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++)
@@ -87,15 +72,11 @@ namespace OpenVulkano
// 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;

View File

@@ -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>

View File

@@ -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))
deinitializeFreetype(ft);
throw std::runtime_error(fmt::format("Failed to load font from file {0}", fontFile.data()));
}
Generate(ft, font, charset, pngOutput);
}
void FontAtlasGenerator::GenerateAtlas(const msdfgen::byte* fontData, int length, const Charset& charset,
const std::optional<std::string>& pngOutput)
{
absentSymbols.add(c);
}
}
if (m_loadedFont == fontFile && absentSymbols.empty())
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 };
}
}

View File

@@ -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;
};
}

View File

@@ -16,7 +16,6 @@ namespace OpenVulkano::Scene
class SimpleDrawable : public Drawable
{
protected:
Geometry* m_mesh = nullptr;
Material* m_material = nullptr;
UniformBuffer* m_uniBuffer = nullptr;

View File

@@ -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()
Shader& TextDrawable::GetDefaultShader()
{
delete m_mesh;
delete m_material;
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_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_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_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_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_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_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_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_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.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);
}
}

View File

@@ -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;
};
}