uui/src/uui/opengl/font_manager.cpp
2022-10-03 16:06:32 +09:00

318 lines
10 KiB
C++

#include "uui/opengl/font_manager.hpp"
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include "uui/config.hpp"
#include "uui/opengl/application.hpp"
#include "uui/opengl/gl_utils.hpp"
#include "uui/shader.hpp"
uui::GlFontManager::GlFontManager()
{
initialized = false;
}
uui::GlFontManager::~GlFontManager()
{
if(initialized)
deinit();
}
void uui::GlFontManager::init()
{
if(initialized)
throw std::runtime_error("GlFontManager error : calling init while initialized");
if constexpr(uui::GlApplication::debug_mode)
std::cout << "Init GlFontManager" << std::endl;
if(FT_Init_FreeType(&ft))
throw std::runtime_error("ERROR::FREETYPE: Could not init FreeType Library");
gl_program = glCreateProgram();
// Vertex shader compilation
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
{
auto vertex_src = readFile("bin/uui/opengl/shaders/text.vert");
auto vertex_str = std::string(vertex_src.begin(), vertex_src.end());
auto vertex_code = vertex_str.c_str();
glShaderSource(vertex_shader, 1, &vertex_code, NULL);
glCompileShader(vertex_shader);
if constexpr(uui::GlApplication::debug_mode)
{
int success;
char info[512];
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
if(!success)
{
glGetShaderInfoLog(vertex_shader, 512, NULL, info);
std::cout << "GlBox vertex shader creation error : " << info << std::endl;
}
}
}
// Fragment shader compilation
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
{
auto fragment_src = readFile("bin/uui/opengl/shaders/text.frag");
auto fragment_str = std::string(fragment_src.begin(), fragment_src.end());
auto fragment_code = fragment_str.c_str();
glShaderSource(fragment_shader, 1, &fragment_code, NULL);
glCompileShader(fragment_shader);
if constexpr(uui::GlApplication::debug_mode)
{
int success;
char info[512];
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
if(!success)
{
glGetShaderInfoLog(fragment_shader, 512, NULL, info);
std::cout << "GlBox fragment shader creation error : " << info << std::endl;
}
}
}
// Linking shader program
glAttachShader(gl_program, vertex_shader);
if constexpr(uui::GlApplication::debug_mode)
_glCheckError();
glAttachShader(gl_program, fragment_shader);
if constexpr(uui::GlApplication::debug_mode)
_glCheckError();
glLinkProgram(gl_program);
if constexpr(uui::GlApplication::debug_mode)
{
int success;
char info[512];
glGetProgramiv(gl_program, GL_LINK_STATUS, &success);
if(!success)
{
glGetProgramInfoLog(gl_program, 512, NULL, info);
std::cout << "GlBox error : " << info << std::endl;
}
}
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
// init_font("/usr/share/fonts/noto/NotoSans-Regular.ttf", 32);
init_font("FreeSans.ttf", 32);
// Initialize VAO/VBO for rendering
glGenVertexArrays(1, &gl_vao);
glGenBuffers(1, &gl_vbo);
glBindVertexArray(gl_vao);
glBindBuffer(GL_ARRAY_BUFFER, gl_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
initialized = true;
}
void uui::GlFontManager::deinit()
{
if(!initialized)
throw std::runtime_error("GlFontManager error : calling deinit while not initialized");
if constexpr(uui::GlApplication::debug_mode)
std::cout << "Deinit GlFontManager" << std::endl;
for(const auto& font: fonts) glDeleteTextures(1, &font.atlas_texture);
glDeleteBuffers(1, &gl_vbo);
glDeleteBuffers(1, &gl_vao);
glDeleteProgram(gl_program);
FT_Done_FreeType(ft);
initialized = false;
}
const uui::GlFontManager::Font& uui::GlFontManager::init_font(const std::string& font_path, std::size_t font_size)
{
fonts.emplace_back();
auto& font = fonts.back();
FT_Face face;
if(FT_New_Face(ft, font_path.c_str(), 0, &face))
throw std::runtime_error("ERROR::FREETYPE: Failed to load font");
FT_Set_Pixel_Sizes(face, 0, font_size);
FT_GlyphSlot glyph = face->glyph;
unsigned int row_width = 0;
unsigned int row_height = 0;
memset(font.char_infos, 0, sizeof(font.char_infos));
// Find minimum size for a texture holding all visible ASCII characters
for(int i = 32; i < 256; i++)
{
if(FT_Load_Char(face, i, FT_LOAD_RENDER))
{
std::cout << "GlFontManager warning : loading character " << i << " failed!" << std::endl;
continue;
}
if(row_width + glyph->bitmap.width + 1 >= ATLAS_MAX_WIDTH)
{
font.atlas_width = std::max(font.atlas_width, row_width);
font.atlas_height += row_height;
row_width = 0;
row_height = 0;
}
row_width += glyph->bitmap.width + 1;
row_height = std::max(row_height, glyph->bitmap.rows);
}
font.atlas_width = std::max(font.atlas_width, row_width);
font.atlas_height += row_height;
glLinkProgram(gl_program);
// Create a texture that will be used to hold all ASCII glyphs
glActiveTexture(Config::FONT_TEXTURE_UNIT);
glGenTextures(1, &font.atlas_texture);
glBindTexture(GL_TEXTURE_2D, font.atlas_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, font.atlas_width, font.atlas_height, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
// We require 1 byte alignment when uploading texture data
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Clamping to edges is important to prevent artifacts when scaling
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Linear filtering usually looks best for text
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Paste all glyph bitmaps into the texture, remembering the offset
int offset_x = 0;
int offset_y = 0;
row_height = 0;
for(int i = 32; i < 256; i++)
{
if(FT_Load_Char(face, i, FT_LOAD_RENDER))
{
std::cout << "GlFontManager warning : loading character " << i << " failed!" << std::endl;
continue;
}
if(offset_x + glyph->bitmap.width + 1 >= ATLAS_MAX_WIDTH)
{
offset_y += row_height;
row_height = 0;
offset_x = 0;
}
glTexSubImage2D(
GL_TEXTURE_2D, 0, offset_x, offset_y, glyph->bitmap.width, glyph->bitmap.rows, GL_RED, GL_UNSIGNED_BYTE,
glyph->bitmap.buffer);
font.char_infos[i].advance_x = glyph->advance.x >> 6;
font.char_infos[i].advance_y = glyph->advance.y >> 6;
font.char_infos[i].bitmap_width = glyph->bitmap.width;
font.char_infos[i].bitmap_height = glyph->bitmap.rows;
font.char_infos[i].bitmap_left = glyph->bitmap_left;
font.char_infos[i].bitmap_top = glyph->bitmap_top;
font.char_infos[i].texture_x = offset_x / (float)font.atlas_width;
font.char_infos[i].texture_y = offset_y / (float)font.atlas_height;
row_height = std::max(row_height, glyph->bitmap.rows);
offset_x += glyph->bitmap.width + 1;
}
FT_Done_Face(face);
return font;
}
std::tuple<int, int> uui::GlFontManager::get_text_size(
const std::string& text, uui::GlFontManager::Font& font, float scale_x, float scale_y)
{
float width = 0.0f;
float height = 0.0f;
for(auto current_char: text)
{
const auto& char_info = font.char_infos[static_cast<std::size_t>(current_char)];
width += char_info.advance_x * scale_x;
// height = std::max(height, char_info.bitmap_top + char_info.bitmap_height * scale_y);
height = std::max(height, roundf(char_info.bitmap_height * scale_y));
}
return {static_cast<int>(width), static_cast<int>(height)};
}
void uui::GlFontManager::render_text(
const std::string& text, uui::GlFontManager::Font& font, float x, float y, float scale_x, float scale_y,
const std::tuple<float, float, float, float>& text_color)
{
GLuint gl_prev_program;
glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)(&gl_prev_program));
glUseProgram(gl_program);
glActiveTexture(Config::FONT_TEXTURE_UNIT);
auto [color_r, color_g, color_b, color_a] = text_color;
glUniform4f(glGetUniformLocation(gl_program, "textColor"), color_r, color_g, color_b, color_a);
glBindVertexArray(gl_vao);
// Use the texture containing the atlas
glBindTexture(GL_TEXTURE_2D, font.atlas_texture);
std::vector<float> vertices;
vertices.resize(24 * text.size());
// vertices.resize(text.size());
int char_count = 0;
// Loop through all characters
float out_base_x = x;
float out_base_y = y;
for(auto current_char: text)
{
const auto& char_info = font.char_infos[static_cast<std::size_t>(current_char)];
// Calculate the vertex and texture coordinates
float char_x = out_base_x + char_info.bitmap_left * scale_x;
float char_y = out_base_y - char_info.bitmap_top * scale_y;
float w = char_info.bitmap_width * scale_x;
float h = char_info.bitmap_height * scale_y;
// Advance the cursor to the start of the next character
out_base_x += char_info.advance_x * scale_x;
out_base_y += char_info.advance_y * scale_y;
// Skip glyphs that have no pixels
if(!w || !h)
continue;
// clang-format off
float current_vertex[24] =
{
char_x, char_y, char_info.texture_x, char_info.texture_y, // top_left
char_x + w, char_y, char_info.texture_x + char_info.bitmap_width / font.atlas_width, char_info.texture_y, // top-right
char_x, char_y + h, char_info.texture_x, char_info.texture_y + char_info.bitmap_height / font.atlas_height, // bottom-right
char_x + w, char_y, char_info.texture_x + char_info.bitmap_width / font.atlas_width, char_info.texture_y, // top-right
char_x, char_y + h, char_info.texture_x, char_info.texture_y + char_info.bitmap_height / font.atlas_height, // bottom-left
char_x + w, char_y + h, char_info.texture_x + char_info.bitmap_width / font.atlas_width,
char_info.texture_y + char_info.bitmap_height / font.atlas_height // bottom-right
};
std::memcpy(vertices.data() + (char_count * 24), current_vertex, 24 * sizeof(float));
// clang-format on
char_count += 1;
}
// Draw all the character on the screen in one go
glBindBuffer(GL_ARRAY_BUFFER, gl_vbo);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_DYNAMIC_DRAW);
glDrawArrays(GL_TRIANGLES, 0, vertices.size());
// glDisableVertexAttribArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindVertexArray(0);
glUseProgram(gl_prev_program);
}