[Add] Text rendering.

This commit is contained in:
Ritchie Cunningham 2025-09-20 03:16:59 +01:00
parent c3ebd0e501
commit f86015736d
12 changed files with 318 additions and 9 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

11
assets/shaders/text.frag Normal file
View File

@ -0,0 +1,11 @@
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D text;
uniform vec3 textColor;
void main() {
vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
color = vec4(textColor, 1.0) * sampled;
}

11
assets/shaders/text.vert Normal file
View File

@ -0,0 +1,11 @@
#version 330 core
layout(location = 0) in vec4 vertex;
out vec2 TexCoords;
uniform mat4 projection;
void main() {
gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
TexCoords = vertex.zw;
}

View File

@ -7,5 +7,6 @@ add_executable(bettolac
find_package(SDL3 REQUIRED)
find_package(GLEW REQUIRED)
find_package(OpenGL REQUIRED)
find_package(Freetype REQUIRED)
target_link_libraries(bettolac PRIVATE bettola SDL3::SDL3 GLEW::glew OpenGL::GL)
target_link_libraries(bettolac PRIVATE bettola SDL3::SDL3 GLEW::glew OpenGL::GL Freetype::Freetype)

88
client/src/gfx/shader.cpp Normal file
View File

@ -0,0 +1,88 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <GL/glew.h>
#include "shader.h"
/* Read whole file. */
char* read_file(const char* path) {
FILE* file = fopen(path, "rb");
if(!file) {
printf("Failed to open file: %s\n", path);
return NULL;
}
fseek(file, 0, SEEK_END);
long len = ftell(file);
fseek(file, 0, SEEK_SET);
char* buffer = (char*)malloc(len+1);
fread(buffer, 1, len, file);
fclose(file);
buffer[len] = '\0';
return buffer;
}
Shader::Shader(const char* vert_path, const char* frag_path) {
char* vert_source = read_file(vert_path);
char* frag_source = read_file(frag_path);
if(!vert_source || !frag_source) {
printf("Failed to read shader files.\n");
return;
}
unsigned int vert, frag;
vert = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vert, 1, &vert_source, NULL);
glCompileShader(vert);
_check_compile_errors(vert, "VERTEX");
frag = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(frag, 1, &frag_source, NULL);
glCompileShader(frag);
_check_compile_errors(frag, "FRAGMENT");
id = glCreateProgram();
glAttachShader(id, vert);
glAttachShader(id, frag);
glLinkProgram(id);
_check_compile_errors(id, "PROGRAM");
glDeleteShader(vert);
glDeleteShader(frag);
free(vert_source);
free(frag_source);
}
void Shader::use(void) {
glUseProgram(id);
}
void Shader::set_mat4(const char* name, const float* value) {
glUniformMatrix4fv(glGetUniformLocation(id, name), 1, GL_FALSE, value);
}
void Shader::set_vec3(const char* name, float v1, float v2, float v3) {
glUniform3f(glGetUniformLocation(id, name), v1, v2, v3);
}
void Shader::set_int(const char* name, int value) {
glUniform1i(glGetUniformLocation(id, name), value);
}
void Shader::_check_compile_errors(unsigned int shader_id, const char* type) {
int success;
char info_log[1024];
if(strcmp(type, "PROGRAM") != 0) {
glGetShaderiv(shader_id, GL_COMPILE_STATUS, &success);
if(!success) {
glGetShaderInfoLog(shader_id, 1024, NULL, info_log);
printf("Shader compilation error (%s):\n%s\n", type, info_log);
}
} else {
glGetProgramiv(shader_id, GL_LINK_STATUS, &success);
if(!success) {
glGetProgramInfoLog(shader_id, 1024, NULL, info_log);
printf("Shader linking error (%s):\n%s\n", type, info_log);
}
}
}

16
client/src/gfx/shader.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
class Shader {
public:
unsigned int id;
Shader(const char* vertex_path, const char* fragment_path);
void use(void);
void set_mat4(const char* name, const float* value);
void set_vec3(const char* name, float v1, float v2, float v3);
void set_int(const char* name, int value);
private:
void _check_compile_errors(unsigned int shader, const char* type);
};

View File

@ -0,0 +1,141 @@
#include <cstdio>
#include <GL/glew.h>
#include <SDL3/SDL_render.h>
#include <freetype/freetype.h>
#include "txt_renderer.h"
/* Not sure we'll need a whole math lib? Basic ortho for now. */
void ortho(float* mat, float left, float right, float bottom, float top,
float near, float far) {
mat[0] = 2.0f / (right - left);
mat[4] = 0.0f;
mat[8] = 0.0f;
mat[12] = -(right + left) / (right - left);
mat[1] = 0.0f;
mat[5] = 2.0f / (top - bottom);
mat[9] = 0.0f;
mat[13] = -(top + bottom) / (top - bottom);
mat[2] = 0.0f;
mat[6] = 0.0f;
mat[10] = -2.0f / (far - near);
mat[14] = -(far + near) / (far - near);
mat[3] = 0.0f;
mat[7] = 0.0f;
mat[11] = 0.0f;
mat[15] = 1.0f;
}
TextRenderer::TextRenderer(unsigned int screen_width, unsigned int screen_height) {
/* Create projection matrix. */
ortho(_projecton, 0.0f, (float)screen_width, 0.0f, (float)screen_height, -1.0f, 1.0f);
/* Load shader. */
_txt_shader = new Shader("assets/shaders/text.vert",
"assets/shaders/text.frag");
_txt_shader->use();
_txt_shader->set_mat4("projection", _projecton);
/* Configure VAO/VBO for texture coords. */
glGenVertexArrays(1, &_vao);
glGenBuffers(1, &_vbo);
glBindVertexArray(_vao);
glBindBuffer(GL_ARRAY_BUFFER, _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);
}
TextRenderer::~TextRenderer(void) {
delete _txt_shader;
}
void TextRenderer::load_font(const char* font_path, unsigned int font_size) {
_chars.clear();
FT_Library ft;
if(FT_Init_FreeType(&ft)) {
printf("Could not init FreeType Library\n");
return;
}
FT_Face face;
if(FT_New_Face(ft, font_path, 0, &face)) {
printf("Failed to load font: %s\n", font_path);
return;
}
FT_Set_Pixel_Sizes(face, 0, font_size);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
for(unsigned char c = 0; c < 128; c++) {
if(FT_Load_Char(face, c, FT_LOAD_RENDER)) {
printf("Failed to load Glyph for char %c\n", c);
continue;
}
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, face->glyph->bitmap.width,
face->glyph->bitmap.rows, 0, GL_RED, GL_UNSIGNED_BYTE,
face->glyph->bitmap.buffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
character ch = {
texture,
{ (int)face->glyph->bitmap.width, (int)face->glyph->bitmap.rows },
{ face->glyph->bitmap_left, face->glyph->bitmap_top },
(unsigned int)face->glyph->advance.x
};
_chars.insert(std::pair<char, character>(c, ch));
}
glBindTexture(GL_TEXTURE_2D, 0);
FT_Done_Face(face);
FT_Done_FreeType(ft);
}
void TextRenderer::render_text(const char* text, float x, float y, float scale,
float color[3]) {
_txt_shader->use();
_txt_shader->set_vec3("textColor", color[0], color[1], color[2]);
glActiveTexture(GL_TEXTURE0);
glBindVertexArray(_vao);
for(const char* p = text; *p; p++) {
character ch = _chars[*p];
float xpos = x + ch.bearing[0] * scale;
float ypos = y - (ch.size[1] - ch.bearing[1]) * scale;
float w = ch.size[0] * scale;
float h = ch.size[1] * scale;
float vertices[6][4] = {
{ xpos, ypos + h, 0.0f, 0.0f },
{ xpos, ypos, 0.0f, 1.0f },
{ xpos + w, ypos, 1.0f, 1.0f },
{ xpos, ypos + h, 0.0f, 0.0f },
{ xpos + w, ypos, 1.0f, 1.0f },
{ xpos + w, ypos + h, 1.0f, 0.0f }
};
glBindTexture(GL_TEXTURE_2D, ch.texture_id);
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDrawArrays(GL_TRIANGLES, 0, 6);
x += (ch.advance >> 6) * scale;
}
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}

View File

@ -0,0 +1,30 @@
#pragma once
#include <ft2build.h>
#include FT_FREETYPE_H
#include <map>
#include "shader.h"
/* State of a single charactrer glyph. */
struct character {
unsigned int texture_id;
int size[2];
int bearing[2];
unsigned int advance;
};
class TextRenderer {
public:
TextRenderer(unsigned int screen_width, unsigned int screen_height);
~TextRenderer(void);
void load_font(const char* font_path, unsigned int font_size);
void render_text(const char* text, float x, float y, float scale, float color[3]);
private:
Shader* _txt_shader;
unsigned int _vao, _vbo;
std::map<char, character> _chars;
float _projecton[16];
};

View File

@ -1,11 +1,10 @@
#include <cstdio>
#include "gfx/txt_renderer.h"
#include <GL/glew.h>
#include <SDL3/SDL.h>
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_oldnames.h>
#include <SDL3/SDL_video.h>
const int SCREEN_WIDTH = 1280;
const int SCREEN_HEIGHT = 720;
int main(int argc, char** argv) {
/* Init SDL. */
if(!SDL_Init(SDL_INIT_VIDEO)) {
@ -21,8 +20,8 @@ int main(int argc, char** argv) {
/* Create a window. */
SDL_Window* window = SDL_CreateWindow(
"Bettola Client",
1280,
720,
SCREEN_WIDTH,
SCREEN_HEIGHT,
SDL_WINDOW_OPENGL
);
@ -46,7 +45,13 @@ int main(int argc, char** argv) {
return 1;
}
printf("SDL/OpenGL initialisation succes.\n");
/* Configure OpenGL. */
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
/* Init text renderer. */
TextRenderer* txt_render_instance = new TextRenderer(SCREEN_WIDTH, SCREEN_HEIGHT);
txt_render_instance->load_font("assets/fonts/hack/Hack-Regular.ttf", 24);
bool running = true;
while(running) {
@ -58,15 +63,21 @@ int main(int argc, char** argv) {
}
}
/* Rendering. */
/* Clear screen. */
glClearColor(0.1f, 0.1f, 0.1, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
/* Render text. */
float white[] = { 1.0f, 1.0f, 1.0f };
txt_render_instance->render_text("Hello World", 25.0f, SCREEN_HEIGHT - 50.0f,
1.0f, white);
/* It's really odd to call it SwapWindow now, rather than SwapBuffer. */
SDL_GL_SwapWindow(window);
}
/* Cleanup. */
delete txt_render_instance;
SDL_GL_DestroyContext(context);
SDL_DestroyWindow(window);
SDL_Quit();