464 lines
15 KiB
C++
464 lines
15 KiB
C++
#include "pch.h"
|
|
#include "log.h"
|
|
#include "font.h"
|
|
#include "shader.h"
|
|
#include "asset.h"
|
|
#include "util.h"
|
|
#include "app.h"
|
|
#include "renderer_gl/opengl_capabilities.h"
|
|
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
|
|
namespace {
|
|
|
|
[[nodiscard]] GLint font_atlas_internal_format() noexcept
|
|
{
|
|
return static_cast<GLint>(pp::renderer::gl::texture_format_for_channel_count(1U).internal_format);
|
|
}
|
|
|
|
[[nodiscard]] GLint font_atlas_pixel_format() noexcept
|
|
{
|
|
return static_cast<GLint>(pp::renderer::gl::texture_format_for_channel_count(1U).pixel_format);
|
|
}
|
|
|
|
void gen_buffers_adapter(std::uint32_t count, std::uint32_t* ids) noexcept
|
|
{
|
|
glGenBuffers(static_cast<GLsizei>(count), ids);
|
|
}
|
|
|
|
void bind_buffer_adapter(std::uint32_t target, std::uint32_t buffer) noexcept
|
|
{
|
|
glBindBuffer(static_cast<GLenum>(target), static_cast<GLuint>(buffer));
|
|
}
|
|
|
|
void buffer_data_adapter(
|
|
std::uint32_t target,
|
|
std::intptr_t byte_count,
|
|
const void* data,
|
|
std::uint32_t usage) noexcept
|
|
{
|
|
glBufferData(static_cast<GLenum>(target), static_cast<GLsizeiptr>(byte_count), data, static_cast<GLenum>(usage));
|
|
}
|
|
|
|
void gen_vertex_arrays_adapter(std::uint32_t count, std::uint32_t* ids) noexcept
|
|
{
|
|
glGenVertexArrays(static_cast<GLsizei>(count), ids);
|
|
}
|
|
|
|
void bind_vertex_array_adapter(std::uint32_t vertex_array) noexcept
|
|
{
|
|
glBindVertexArray(static_cast<GLuint>(vertex_array));
|
|
}
|
|
|
|
void enable_vertex_attrib_array_adapter(std::uint32_t index) noexcept
|
|
{
|
|
glEnableVertexAttribArray(static_cast<GLuint>(index));
|
|
}
|
|
|
|
void vertex_attrib_pointer_adapter(
|
|
std::uint32_t index,
|
|
std::int32_t component_count,
|
|
std::uint32_t component_type,
|
|
std::uint8_t normalized,
|
|
std::int32_t stride,
|
|
const void* offset) noexcept
|
|
{
|
|
glVertexAttribPointer(
|
|
static_cast<GLuint>(index),
|
|
static_cast<GLint>(component_count),
|
|
static_cast<GLenum>(component_type),
|
|
static_cast<GLboolean>(normalized),
|
|
static_cast<GLsizei>(stride),
|
|
offset);
|
|
}
|
|
|
|
void draw_elements_adapter(
|
|
std::uint32_t mode,
|
|
std::int32_t count,
|
|
std::uint32_t index_type,
|
|
const void* index_offset) noexcept
|
|
{
|
|
glDrawElements(
|
|
static_cast<GLenum>(mode),
|
|
static_cast<GLsizei>(count),
|
|
static_cast<GLenum>(index_type),
|
|
index_offset);
|
|
}
|
|
|
|
void draw_arrays_adapter(std::uint32_t mode, std::int32_t first, std::int32_t count) noexcept
|
|
{
|
|
glDrawArrays(static_cast<GLenum>(mode), static_cast<GLint>(first), static_cast<GLsizei>(count));
|
|
}
|
|
|
|
void activate_texture_adapter(std::uint32_t texture_unit) noexcept
|
|
{
|
|
glActiveTexture(static_cast<GLenum>(texture_unit));
|
|
}
|
|
|
|
void activate_text_texture_unit(std::uint32_t unit_index)
|
|
{
|
|
const auto status = pp::renderer::gl::activate_opengl_texture_unit(
|
|
unit_index,
|
|
pp::renderer::gl::OpenGlActiveTextureDispatch {
|
|
.active_texture = activate_texture_adapter,
|
|
});
|
|
if (!status.ok())
|
|
LOG("Text active texture dispatch failed because: %s", status.message);
|
|
}
|
|
|
|
[[nodiscard]] std::span<const pp::renderer::gl::OpenGlVertexAttribute> text_mesh_vertex_attributes() noexcept
|
|
{
|
|
static const std::array<pp::renderer::gl::OpenGlVertexAttribute, 2> attributes {
|
|
pp::renderer::gl::OpenGlVertexAttribute {
|
|
.index = 0U,
|
|
.component_count = 2,
|
|
.component_type = pp::renderer::gl::vertex_attribute_float_component_type(),
|
|
.normalized = static_cast<std::uint8_t>(pp::renderer::gl::vertex_attribute_not_normalized()),
|
|
.stride = static_cast<std::int32_t>(sizeof(glm::vec4)),
|
|
.offset = 0U,
|
|
},
|
|
pp::renderer::gl::OpenGlVertexAttribute {
|
|
.index = 1U,
|
|
.component_count = 2,
|
|
.component_type = pp::renderer::gl::vertex_attribute_float_component_type(),
|
|
.normalized = static_cast<std::uint8_t>(pp::renderer::gl::vertex_attribute_not_normalized()),
|
|
.stride = static_cast<std::int32_t>(sizeof(glm::vec4)),
|
|
.offset = static_cast<std::uintptr_t>(sizeof(float) * 2),
|
|
},
|
|
};
|
|
return attributes;
|
|
}
|
|
|
|
[[nodiscard]] pp::renderer::gl::OpenGlMeshCreateDispatch text_mesh_create_dispatch() noexcept
|
|
{
|
|
return pp::renderer::gl::OpenGlMeshCreateDispatch {
|
|
.gen_buffers = gen_buffers_adapter,
|
|
.bind_buffer = bind_buffer_adapter,
|
|
.buffer_data = buffer_data_adapter,
|
|
.gen_vertex_arrays = gen_vertex_arrays_adapter,
|
|
.bind_vertex_array = bind_vertex_array_adapter,
|
|
.enable_vertex_attrib_array = enable_vertex_attrib_array_adapter,
|
|
.vertex_attrib_pointer = vertex_attrib_pointer_adapter,
|
|
};
|
|
}
|
|
|
|
[[nodiscard]] pp::renderer::gl::OpenGlBufferUploadDispatch text_buffer_upload_dispatch() noexcept
|
|
{
|
|
return pp::renderer::gl::OpenGlBufferUploadDispatch {
|
|
.bind_buffer = bind_buffer_adapter,
|
|
.buffer_data = buffer_data_adapter,
|
|
};
|
|
}
|
|
|
|
[[nodiscard]] pp::renderer::gl::OpenGlMeshDrawDispatch text_mesh_draw_dispatch() noexcept
|
|
{
|
|
return pp::renderer::gl::OpenGlMeshDrawDispatch {
|
|
.bind_vertex_array = bind_vertex_array_adapter,
|
|
.draw_elements = draw_elements_adapter,
|
|
.draw_arrays = draw_arrays_adapter,
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
std::map<std::string, Font> FontManager::m_fonts;
|
|
Sampler FontManager::m_sampler;
|
|
|
|
Font::Font(Font&& other) noexcept
|
|
{
|
|
w = other.w;
|
|
h = other.h;
|
|
bounds = other.bounds;
|
|
size = other.size;
|
|
scale = other.size;
|
|
font_tex = std::move(other.font_tex);
|
|
path = std::move(other.path);
|
|
id = std::move(other.id);
|
|
chars = std::move(other.chars);
|
|
}
|
|
|
|
void Font::operator=(Font&& other) noexcept
|
|
{
|
|
w = other.w;
|
|
h = other.h;
|
|
bounds = other.bounds;
|
|
size = other.size;
|
|
scale = other.size;
|
|
font_tex = std::move(other.font_tex);
|
|
path = std::move(other.path);
|
|
id = std::move(other.id);
|
|
chars = std::move(other.chars);
|
|
}
|
|
|
|
bool Font::load(const std::string& ttf, int font_size, float font_scale)
|
|
{
|
|
Asset file;
|
|
LOG("Font::load %s", ttf.c_str());
|
|
if (file.open(ttf.c_str()) && file.read_all())
|
|
{
|
|
w = h = 512 * (int)ceilf(font_scale);
|
|
path = ttf;
|
|
scale = font_scale;
|
|
LOG("Font::load loaded");
|
|
auto bitmap = std::make_unique<uint8_t[]>(w*h);
|
|
chars.resize(num_chars);
|
|
//int offset = stbtt_FindMatchingFont(file.m_data, "Arial Bold", STBTT_MACSTYLE_DONTCARE);
|
|
//if (offset < 0)
|
|
// offset = 0;
|
|
stbtt_BakeFontBitmap(file.m_data, 0, (float)font_size*scale, bitmap.get(), w, h, start_char, num_chars, chars.data());
|
|
calc_bounds();
|
|
font_tex.create(w, h, font_atlas_internal_format(), font_atlas_pixel_format(), bitmap.get());
|
|
file.close();
|
|
size = font_size;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Font::change_scale(float scale)
|
|
{
|
|
load(path, size, scale);
|
|
}
|
|
|
|
void Font::calc_bounds()
|
|
{
|
|
glm::vec2 bbmin(FLT_MAX);
|
|
glm::vec2 bbmax(-FLT_MAX);
|
|
for (int i = 0; i < num_chars; i++)
|
|
{
|
|
stbtt_aligned_quad q;
|
|
float x = 0, y = 0;
|
|
stbtt_GetBakedQuad(chars.data(), w, h, i, &x, &y, &q, true);
|
|
bbmin = glm::min(bbmin, { q.x0 / scale, q.y0 / scale });
|
|
bbmax = glm::max(bbmax, { q.x1 / scale, q.y1 / scale });
|
|
}
|
|
bounds = { glm::vec4(bbmin, bbmax) };
|
|
}
|
|
|
|
void FontManager::init()
|
|
{
|
|
m_sampler.create();
|
|
}
|
|
|
|
bool FontManager::load(const std::string& id, const std::string& ttf, int sz, float scale)
|
|
{
|
|
return m_fonts[id].load(ttf, sz, scale);
|
|
}
|
|
|
|
const Font& FontManager::get(const std::string& name, int size, const std::string& weight, bool italic)
|
|
{
|
|
auto id = fmt::format("{}-{}{}-{}", name, weight, italic ? "-italic" : "", size);
|
|
auto it = m_fonts.find(id);
|
|
if (it == m_fonts.end())
|
|
{
|
|
LOG("load font %s", id.c_str());
|
|
auto path = fmt::format("data/fonts/{}-{}{}.ttf", name, weight, italic ? "-italic" : "");
|
|
m_fonts[id].load(path, size, App::I->display_density * App::I->zoom);
|
|
return m_fonts[id];
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
void FontManager::change_scale(float scale)
|
|
{
|
|
for (auto& f : m_fonts)
|
|
f.second.change_scale(scale);
|
|
}
|
|
|
|
std::vector<TextMesh::Token> TextMesh::tokenize(const std::string& s, const Font& f) const noexcept
|
|
{
|
|
std::vector<std::string> parts;
|
|
std::array<char, 10> delims = { ' ', '\n', ',', '.', ':', ';', '?', '!', '\\', '/' };
|
|
std::string tmp;
|
|
bool wrap = false;
|
|
for (char c : s)
|
|
{
|
|
bool is_delim = std::find(delims.begin(), delims.end(), c) != delims.end();
|
|
wrap |= is_delim; // set wrap to notify a delimiter has been reached
|
|
// when a new non-delim char is detected, start a new token
|
|
if (wrap && !is_delim && !tmp.empty())
|
|
{
|
|
parts.push_back(tmp);
|
|
tmp.clear();
|
|
wrap = false;
|
|
}
|
|
tmp.push_back(c);
|
|
}
|
|
// insert last partial token
|
|
if (!tmp.empty())
|
|
parts.push_back(tmp);
|
|
// measure each token length
|
|
std::vector<TextMesh::Token> ret;
|
|
for (auto p : parts)
|
|
{
|
|
float x = 0;
|
|
float y = 0;
|
|
for (char c : p)
|
|
{
|
|
if ((uint8_t)c >= 32 && (uint8_t)c < 256) // visible ascii character
|
|
{
|
|
stbtt_aligned_quad q;
|
|
stbtt_GetBakedQuad((stbtt_bakedchar*)f.chars.data(), f.w, f.h, (uint8_t)c - f.start_char, &x, &y, &q, true);
|
|
}
|
|
}
|
|
ret.emplace_back(p, x);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool TextMesh::create()
|
|
{
|
|
App::I->render_task([this]
|
|
{
|
|
const auto mesh = pp::renderer::gl::create_opengl_mesh_objects(
|
|
pp::renderer::gl::OpenGlMeshUpload {
|
|
.vertex_data = nullptr,
|
|
.vertex_byte_count = 0,
|
|
.index_data = nullptr,
|
|
.index_byte_count = 0,
|
|
.indexed = true,
|
|
.vertex_array_count = 1U,
|
|
.attributes = text_mesh_vertex_attributes(),
|
|
},
|
|
text_mesh_create_dispatch());
|
|
if (mesh.ok()) {
|
|
font_buffers[0] = static_cast<GLuint>(mesh.value().vertex_buffer);
|
|
font_buffers[1] = static_cast<GLuint>(mesh.value().index_buffer);
|
|
font_array = static_cast<GLuint>(mesh.value().vertex_arrays[0]);
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
|
|
void TextMesh::update(const std::string& text, const std::string& font, int size, const std::string& weight, bool italic)
|
|
{
|
|
this->font = font;
|
|
this->size = size;
|
|
this->weight = weight;
|
|
this->italic = italic;
|
|
|
|
auto& f = FontManager::get(font, size, weight, italic);
|
|
float spacing = (f.bounds.w - f.bounds.y);
|
|
float avg_width = f.bounds.z - f.bounds.x;
|
|
cur_box = glm::vec4(0, f.bounds.y, 5, spacing);
|
|
|
|
glm::vec2 bbmin(FLT_MAX);
|
|
glm::vec2 bbmax(-FLT_MAX);
|
|
if (text.empty())
|
|
{
|
|
bbmin = { 0, -spacing * 0.5f };
|
|
bbmax = { 1, +spacing * 0.5f };
|
|
}
|
|
|
|
if (f.chars.size())
|
|
{
|
|
float x = 0;
|
|
float y = 0;
|
|
std::vector<glm::vec4> v;
|
|
std::vector<GLushort> idx;
|
|
|
|
std::vector<Token> parts = max_width > 0 ?
|
|
tokenize(text, f) :
|
|
std::vector<Token>{ Token(text, 0.f) };
|
|
|
|
for (size_t i = 0; i < parts.size(); i++)
|
|
{
|
|
const auto& p = parts[i];
|
|
if (i > 0 && max_width > 0 && (x + p.w) > max_width * f.scale)
|
|
{
|
|
x = 0;
|
|
y += spacing * f.scale;
|
|
}
|
|
for (char c : p.s)
|
|
{
|
|
if (c == '\n')
|
|
{
|
|
x = 0;
|
|
y += spacing * f.scale;
|
|
cur_box = glm::vec4(x, y / f.scale + f.bounds.y, 5, spacing);
|
|
continue;
|
|
}
|
|
stbtt_aligned_quad q;
|
|
float wrap_test_x = x;
|
|
stbtt_GetBakedQuad((stbtt_bakedchar*)f.chars.data(), f.w, f.h, (uint8_t)c - f.start_char, &wrap_test_x, &y, &q, true);
|
|
if (max_width > 0 && q.x1 > max_width * f.scale)
|
|
{
|
|
x = 0;
|
|
y += spacing * f.scale;
|
|
}
|
|
stbtt_GetBakedQuad((stbtt_bakedchar*)f.chars.data(), f.w, f.h, (uint8_t)c - f.start_char, &x, &y, &q, true);
|
|
auto n = (int)v.size();
|
|
v.emplace_back(q.x0 / f.scale, q.y1 / f.scale, q.s0, q.t1);
|
|
v.emplace_back(q.x0 / f.scale, q.y0 / f.scale, q.s0, q.t0);
|
|
v.emplace_back(q.x1 / f.scale, q.y0 / f.scale, q.s1, q.t0);
|
|
v.emplace_back(q.x1 / f.scale, q.y1 / f.scale, q.s1, q.t1);
|
|
idx.push_back(n + 0);
|
|
idx.push_back(n + 1);
|
|
idx.push_back(n + 2);
|
|
idx.push_back(n + 0);
|
|
idx.push_back(n + 2);
|
|
idx.push_back(n + 3);
|
|
bbmin = glm::min(bbmin, glm::floor(xy(f.bounds)));
|
|
bbmax = glm::max(bbmax, glm::ceil(glm::vec2(q.x1 / f.scale, y / f.scale + f.bounds.w)));
|
|
if (max_width > 0 && q.x1 / f.scale + 5 > max_width * f.scale)
|
|
cur_box = glm::vec4(0, (y + spacing * f.scale) / f.scale + f.bounds.y, 5, spacing);
|
|
else
|
|
cur_box = glm::vec4(q.x1 / f.scale + 2, y / f.scale + f.bounds.y, 5, spacing);
|
|
}
|
|
}
|
|
for (auto& vi : v)
|
|
vi -= glm::vec4(xy(f.bounds), 0, 0);
|
|
cur_box -= glm::vec4(xy(f.bounds), 0, 0);
|
|
bb = bbmax - bbmin;
|
|
font_array_count = (int)idx.size();
|
|
App::I->render_task([&]
|
|
{
|
|
(void)pp::renderer::gl::upload_opengl_buffer_data(
|
|
pp::renderer::gl::OpenGlBufferUpload {
|
|
.target = pp::renderer::gl::element_array_buffer_target(),
|
|
.buffer_id = font_buffers[1],
|
|
.data = idx.data(),
|
|
.byte_count = static_cast<std::intptr_t>(idx.size() * sizeof(GLushort)),
|
|
.usage = pp::renderer::gl::static_draw_buffer_usage(),
|
|
},
|
|
text_buffer_upload_dispatch());
|
|
(void)pp::renderer::gl::upload_opengl_buffer_data(
|
|
pp::renderer::gl::OpenGlBufferUpload {
|
|
.target = pp::renderer::gl::array_buffer_target(),
|
|
.buffer_id = font_buffers[0],
|
|
.data = v.data(),
|
|
.byte_count = static_cast<std::intptr_t>(v.size() * sizeof(glm::vec4)),
|
|
.usage = pp::renderer::gl::static_draw_buffer_usage(),
|
|
},
|
|
text_buffer_upload_dispatch());
|
|
});
|
|
}
|
|
}
|
|
|
|
void TextMesh::draw()
|
|
{
|
|
auto& f = FontManager::get(font, size, weight, italic);
|
|
if (f.font_tex.ready())
|
|
{
|
|
activate_text_texture_unit(0U);
|
|
f.font_tex.bind();
|
|
FontManager::m_sampler.bind(0);
|
|
|
|
(void)pp::renderer::gl::draw_opengl_mesh(
|
|
pp::renderer::gl::OpenGlMeshDraw {
|
|
.vertex_array = font_array,
|
|
.mode = pp::renderer::gl::primitive_mode_for_fill_count(3U),
|
|
.count = font_array_count,
|
|
.indexed = true,
|
|
.index_type = pp::renderer::gl::index_type_for_index_size(sizeof(GLushort)),
|
|
.index_offset = nullptr,
|
|
},
|
|
text_mesh_draw_dispatch());
|
|
|
|
f.font_tex.unbind();
|
|
FontManager::m_sampler.unbind();
|
|
}
|
|
}
|