diff --git a/assets/fonts/hack/Hack-Bold.ttf b/assets/fonts/hack/Hack-Bold.ttf new file mode 100644 index 0000000..7ff4975 Binary files /dev/null and b/assets/fonts/hack/Hack-Bold.ttf differ diff --git a/assets/fonts/hack/Hack-BoldItalic.ttf b/assets/fonts/hack/Hack-BoldItalic.ttf new file mode 100644 index 0000000..3b137d9 Binary files /dev/null and b/assets/fonts/hack/Hack-BoldItalic.ttf differ diff --git a/assets/fonts/hack/Hack-Italic.ttf b/assets/fonts/hack/Hack-Italic.ttf new file mode 100644 index 0000000..d26198a Binary files /dev/null and b/assets/fonts/hack/Hack-Italic.ttf differ diff --git a/assets/fonts/hack/Hack-Regular.ttf b/assets/fonts/hack/Hack-Regular.ttf new file mode 100644 index 0000000..92a90cb Binary files /dev/null and b/assets/fonts/hack/Hack-Regular.ttf differ diff --git a/assets/shaders/text.frag b/assets/shaders/text.frag new file mode 100644 index 0000000..a4a5658 --- /dev/null +++ b/assets/shaders/text.frag @@ -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; +} diff --git a/assets/shaders/text.vert b/assets/shaders/text.vert new file mode 100644 index 0000000..73b96d1 --- /dev/null +++ b/assets/shaders/text.vert @@ -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; +} diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 60047c4..9763995 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -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) diff --git a/client/src/gfx/shader.cpp b/client/src/gfx/shader.cpp new file mode 100644 index 0000000..dbbdb53 --- /dev/null +++ b/client/src/gfx/shader.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#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); + } + } +} + diff --git a/client/src/gfx/shader.h b/client/src/gfx/shader.h new file mode 100644 index 0000000..402999c --- /dev/null +++ b/client/src/gfx/shader.h @@ -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); +}; diff --git a/client/src/gfx/txt_renderer.cpp b/client/src/gfx/txt_renderer.cpp new file mode 100644 index 0000000..9b48579 --- /dev/null +++ b/client/src/gfx/txt_renderer.cpp @@ -0,0 +1,141 @@ +#include +#include +#include +#include + +#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(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); +} diff --git a/client/src/gfx/txt_renderer.h b/client/src/gfx/txt_renderer.h new file mode 100644 index 0000000..531585e --- /dev/null +++ b/client/src/gfx/txt_renderer.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include FT_FREETYPE_H +#include + +#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 _chars; + float _projecton[16]; +}; diff --git a/client/src/main.cpp b/client/src/main.cpp index fd1dcb4..8ea7680 100644 --- a/client/src/main.cpp +++ b/client/src/main.cpp @@ -1,11 +1,10 @@ #include +#include "gfx/txt_renderer.h" #include #include -#include -#include -#include -#include +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();