diff --git a/build.sh b/build.sh index ff1c7d9e..6d7c4898 100755 --- a/build.sh +++ b/build.sh @@ -6,7 +6,7 @@ CC="${CXX:-cc}" PKGS="sdl2 glew freetype2" CFLAGS="-Wall -Wextra -std=c11 -pedantic -ggdb" LIBS=-lm -SRC="src/main.c src/la.c src/editor.c src/sdl_extra.c src/file.c src/gl_extra.c src/tile_glyph.c src/free_glyph.c" +SRC="src/main.c src/la.c src/editor.c src/sdl_extra.c src/file.c src/gl_extra.c src/tile_glyph.c src/free_glyph.c src/cursor_renderer.c" if [ `uname` = "Darwin" ]; then CFLAGS+=" -framework OpenGL" diff --git a/shaders/cursor.frag b/shaders/cursor.frag new file mode 100644 index 00000000..53022e12 --- /dev/null +++ b/shaders/cursor.frag @@ -0,0 +1,14 @@ +#version 330 core + +#define PERIOD 1.0 +#define BLINK_THRESHOLD 0.5 + +uniform float time; +uniform float last_stroke; + +void main() { + float t = time - last_stroke; + float threshold = float(t < BLINK_THRESHOLD); + float blink = mod(floor(t / PERIOD), 2); + gl_FragColor = vec4(1.0) * min(threshold + blink, 1.0); +} diff --git a/shaders/cursor.vert b/shaders/cursor.vert new file mode 100644 index 00000000..9deb2012 --- /dev/null +++ b/shaders/cursor.vert @@ -0,0 +1,23 @@ +#version 330 core + +uniform vec2 resolution; +uniform vec2 camera; +uniform vec2 pos; +uniform float height; + +#define WIDTH 5.0 + +out vec2 uv; + +vec2 project_point(vec2 point) +{ + return 2.0 * (point - camera) / resolution; +} + +void main() { + uv = vec2(float(gl_VertexID & 1), float((gl_VertexID >> 1) & 1)); + gl_Position = vec4( + project_point(uv * vec2(WIDTH, height) + pos), + 0.0, + 1.0); +} diff --git a/src/cursor_renderer.c b/src/cursor_renderer.c new file mode 100644 index 00000000..b9b35230 --- /dev/null +++ b/src/cursor_renderer.c @@ -0,0 +1,48 @@ +#include +#include "./gl_extra.h" +#include "./cursor_renderer.h" + +void cursor_renderer_init(Cursor_Renderer *cr, + const char *vert_file_path, + const char *frag_file_path) +{ + // Init Shaders + { + GLuint vert_shader = 0; + if (!compile_shader_file(vert_file_path, GL_VERTEX_SHADER, &vert_shader)) { + exit(1); + } + GLuint frag_shader = 0; + if (!compile_shader_file(frag_file_path, GL_FRAGMENT_SHADER, &frag_shader)) { + exit(1); + } + + if (!link_program(vert_shader, frag_shader, &cr->program)) { + exit(1); + } + + glUseProgram(cr->program); + + cr->time_uniform = glGetUniformLocation(cr->program, "time"); + cr->resolution_uniform = glGetUniformLocation(cr->program, "resolution"); + cr->camera_uniform = glGetUniformLocation(cr->program, "camera"); + cr->pos_uniform = glGetUniformLocation(cr->program, "pos"); + cr->height_uniform = glGetUniformLocation(cr->program, "height"); + cr->last_stroke_uniform = glGetUniformLocation(cr->program, "last_stroke"); + } +} + +void cursor_renderer_use(const Cursor_Renderer *cr) +{ + glUseProgram(cr->program); +} + +void cursor_renderer_move_to(const Cursor_Renderer *cr, Vec2f pos) +{ + glUniform2f(cr->pos_uniform, pos.x, pos.y); +} + +void cursor_renderer_draw() +{ + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} diff --git a/src/cursor_renderer.h b/src/cursor_renderer.h new file mode 100644 index 00000000..2ce55f51 --- /dev/null +++ b/src/cursor_renderer.h @@ -0,0 +1,31 @@ +#ifndef CURSOR_RENDERER_H_ +#define CURSOR_RENDERER_H_ + +#define GLEW_STATIC +#include + +#define GL_GLEXT_PROTOTYPES +#include + +#include "./la.h" + +typedef struct { + GLuint program; + + GLint time_uniform; + GLint resolution_uniform; + GLint camera_uniform; + GLint pos_uniform; + GLint height_uniform; + GLint last_stroke_uniform; +} Cursor_Renderer; + +void cursor_renderer_init(Cursor_Renderer *cr, + const char *vert_file_path, + const char *frag_file_path); + +void cursor_renderer_use(const Cursor_Renderer *cr); +void cursor_renderer_move_to(const Cursor_Renderer *cr, Vec2f pos); +void cursor_renderer_draw(void); + +#endif // CURSOR_RENDERER_H_ diff --git a/src/free_glyph.c b/src/free_glyph.c index 7276cbaf..f7f9a2af 100644 --- a/src/free_glyph.c +++ b/src/free_glyph.c @@ -102,16 +102,15 @@ void free_glyph_buffer_init(Free_Glyph_Buffer *fgb, exit(1); } - GLuint program = 0; - if (!link_program(vert_shader, frag_shader, &program)) { + if (!link_program(vert_shader, frag_shader, &fgb->program)) { exit(1); } - glUseProgram(program); + glUseProgram(fgb->program); - fgb->time_uniform = glGetUniformLocation(program, "time"); - fgb->resolution_uniform = glGetUniformLocation(program, "resolution"); - fgb->camera_uniform = glGetUniformLocation(program, "camera"); + fgb->time_uniform = glGetUniformLocation(fgb->program, "time"); + fgb->resolution_uniform = glGetUniformLocation(fgb->program, "resolution"); + fgb->camera_uniform = glGetUniformLocation(fgb->program, "camera"); } // Glyph Texture Atlas @@ -185,6 +184,13 @@ void free_glyph_buffer_init(Free_Glyph_Buffer *fgb, } } +void free_glyph_buffer_use(const Free_Glyph_Buffer *fgb) +{ + glBindVertexArray(fgb->vao); + glBindBuffer(GL_ARRAY_BUFFER, fgb->vbo); + glUseProgram(fgb->program); +} + void free_glyph_buffer_clear(Free_Glyph_Buffer *fgb) { fgb->glyphs_count = 0; @@ -209,6 +215,21 @@ void free_glyph_buffer_draw(Free_Glyph_Buffer *fgb) glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei) fgb->glyphs_count); } +float free_glyph_buffer_cursor_pos(const Free_Glyph_Buffer *fgb, const char *text, size_t text_size, Vec2f pos, size_t col) +{ + for (size_t i = 0; i < text_size; ++i) { + if (i == col) { + return pos.x; + } + + Glyph_Metric metric = fgb->metrics[(int) text[i]]; + pos.x += metric.ax; + pos.y += metric.ay; + } + + return pos.x; +} + void free_glyph_buffer_render_line_sized(Free_Glyph_Buffer *fgb, const char *text, size_t text_size, Vec2f pos, Vec4f fg_color, Vec4f bg_color) { for (size_t i = 0; i < text_size; ++i) { diff --git a/src/free_glyph.h b/src/free_glyph.h index 7ab228db..6d69d8c8 100644 --- a/src/free_glyph.h +++ b/src/free_glyph.h @@ -52,6 +52,7 @@ typedef struct { typedef struct { GLuint vao; GLuint vbo; + GLuint program; FT_UInt atlas_width; FT_UInt atlas_height; @@ -72,11 +73,14 @@ void free_glyph_buffer_init(Free_Glyph_Buffer *fgb, FT_Face face, const char *vert_file_path, const char *frag_file_path); +void free_glyph_buffer_use(const Free_Glyph_Buffer *fgb); void free_glyph_buffer_clear(Free_Glyph_Buffer *fgb); void free_glyph_buffer_push(Free_Glyph_Buffer *fgb, Free_Glyph glyph); void free_glyph_buffer_sync(Free_Glyph_Buffer *fgb); void free_glyph_buffer_draw(Free_Glyph_Buffer *fgb); +float free_glyph_buffer_cursor_pos(const Free_Glyph_Buffer *fgb, const char *text, size_t text_size, Vec2f pos, size_t col); + void free_glyph_buffer_render_line_sized(Free_Glyph_Buffer *fgb, const char *text, size_t text_size, Vec2f pos, Vec4f fg_color, Vec4f bg_color); void free_glyph_buffer_render_line(Free_Glyph_Buffer *fgb, const char *text, Vec2f pos, Vec4f fg_color, Vec4f bg_color); diff --git a/src/main.c b/src/main.c index 1db5320c..f0f8d622 100644 --- a/src/main.c +++ b/src/main.c @@ -26,6 +26,7 @@ #include "./gl_extra.h" #include "./tile_glyph.h" #include "./free_glyph.h" +#include "./cursor_renderer.h" #define SCREEN_WIDTH 800 #define SCREEN_HEIGHT 600 @@ -80,6 +81,7 @@ void gl_render_cursor(Tile_Glyph_Buffer *tgb) static Tile_Glyph_Buffer tgb = {0}; #else static Free_Glyph_Buffer fgb = {0}; +static Cursor_Renderer cr = {0}; #endif void render_editor_into_tgb(SDL_Window *window, Tile_Glyph_Buffer *tgb, Editor *editor) @@ -119,31 +121,51 @@ void render_editor_into_tgb(SDL_Window *window, Tile_Glyph_Buffer *tgb, Editor * // TODO(#27): Free_Glyph renderer does not support cursor // TODO(#28): Camera location is broken in Free_Glyph Renderer -void render_editor_into_fgb(SDL_Window *window, Free_Glyph_Buffer *fgb, Editor *editor) +void render_editor_into_fgb(SDL_Window *window, Free_Glyph_Buffer *fgb, Cursor_Renderer *cr, Editor *editor) { + int w, h; + SDL_GetWindowSize(window, &w, &h); + + free_glyph_buffer_use(fgb); { - int w, h; - SDL_GetWindowSize(window, &w, &h); glUniform2f(fgb->resolution_uniform, (float) w, (float) h); - } + glUniform1f(fgb->time_uniform, (float) SDL_GetTicks() / 1000.0f); + glUniform2f(fgb->camera_uniform, camera_pos.x, camera_pos.y); + + free_glyph_buffer_clear(fgb); - glUniform1f(fgb->time_uniform, (float) SDL_GetTicks() / 1000.0f); - glUniform2f(fgb->camera_uniform, camera_pos.x, camera_pos.y); + { + for (size_t row = 0; row < editor->size; ++row) { + const Line *line = editor->lines + row; + free_glyph_buffer_render_line_sized( + fgb, line->chars, line->size, + vec2f(0, -(float)row * FREE_GLYPH_FONT_SIZE), + vec4fs(1.0f), vec4fs(0.0f)); + } + } - free_glyph_buffer_clear(fgb); + free_glyph_buffer_sync(fgb); + free_glyph_buffer_draw(fgb); + } + cursor_renderer_use(cr); { - for (size_t row = 0; row < editor->size; ++row) { - const Line *line = editor->lines + row; - free_glyph_buffer_render_line_sized( - fgb, line->chars, line->size, - vec2f(0, -(float)row * FREE_GLYPH_FONT_SIZE), - vec4fs(1.0f), vec4fs(0.0f)); + glUniform2f(cr->resolution_uniform, (float) w, (float) h); + glUniform1f(cr->time_uniform, (float) SDL_GetTicks() / 1000.0f); + glUniform2f(cr->camera_uniform, camera_pos.x, camera_pos.y); + glUniform1f(cr->height_uniform, FREE_GLYPH_FONT_SIZE); + + float y = -(float) editor->cursor_row * FREE_GLYPH_FONT_SIZE; + + float x = 0.0f; + if (editor->cursor_row < editor->size) { + Line *line = &editor->lines[editor->cursor_row]; + x = free_glyph_buffer_cursor_pos(fgb, line->chars, line->size, vec2f(0.0, y), editor->cursor_col); } - } - free_glyph_buffer_sync(fgb); - free_glyph_buffer_draw(fgb); + cursor_renderer_move_to(cr, vec2f(x, y)); + cursor_renderer_draw(); + } } int main(int argc, char **argv) @@ -246,6 +268,9 @@ int main(int argc, char **argv) face, "./shaders/free_glyph.vert", "./shaders/free_glyph.frag"); + cursor_renderer_init(&cr, + "./shaders/cursor.vert", + "./shaders/cursor.frag"); #endif bool quit = false; @@ -263,6 +288,10 @@ int main(int argc, char **argv) switch (event.key.keysym.sym) { case SDLK_BACKSPACE: { editor_backspace(&editor); +#ifndef TILE_GLYPH_RENDER + cursor_renderer_use(&cr); + glUniform1f(cr.last_stroke_uniform, (float) SDL_GetTicks() / 1000.0f); +#endif } break; @@ -275,11 +304,19 @@ int main(int argc, char **argv) case SDLK_RETURN: { editor_insert_new_line(&editor); +#ifndef TILE_GLYPH_RENDER + cursor_renderer_use(&cr); + glUniform1f(cr.last_stroke_uniform, (float) SDL_GetTicks() / 1000.0f); +#endif } break; case SDLK_DELETE: { editor_delete(&editor); +#ifndef TILE_GLYPH_RENDER + cursor_renderer_use(&cr); + glUniform1f(cr.last_stroke_uniform, (float) SDL_GetTicks() / 1000.0f); +#endif } break; @@ -287,23 +324,40 @@ int main(int argc, char **argv) if (editor.cursor_row > 0) { editor.cursor_row -= 1; } +#ifndef TILE_GLYPH_RENDER + cursor_renderer_use(&cr); + glUniform1f(cr.last_stroke_uniform, (float) SDL_GetTicks() / 1000.0f); +#endif } break; case SDLK_DOWN: { editor.cursor_row += 1; +#ifndef TILE_GLYPH_RENDER + cursor_renderer_use(&cr); + glUniform1f(cr.last_stroke_uniform, (float) SDL_GetTicks() / 1000.0f); +#endif } break; case SDLK_LEFT: { if (editor.cursor_col > 0) { editor.cursor_col -= 1; +#ifndef TILE_GLYPH_RENDER + cursor_renderer_use(&cr); + glUniform1f(cr.last_stroke_uniform, (float) SDL_GetTicks() / 1000.0f); +#endif } } break; case SDLK_RIGHT: { editor.cursor_col += 1; +#ifndef TILE_GLYPH_RENDER + cursor_renderer_use(&cr); + glUniform1f(cr.last_stroke_uniform, (float) SDL_GetTicks() / 1000.0f); +#endif + } break; } @@ -312,6 +366,10 @@ int main(int argc, char **argv) case SDL_TEXTINPUT: { editor_insert_text_before_cursor(&editor, event.text.text); +#ifndef TILE_GLYPH_RENDER + cursor_renderer_use(&cr); + glUniform1f(cr.last_stroke_uniform, (float) SDL_GetTicks() / 1000.0f); +#endif } break; @@ -360,7 +418,7 @@ int main(int argc, char **argv) #ifdef TILE_GLYPH_RENDER render_editor_into_tgb(window, &tgb, &editor); #else - render_editor_into_fgb(window, &fgb, &editor); + render_editor_into_fgb(window, &fgb, &cr, &editor); #endif // TILE_GLYPH_RENDER SDL_GL_SwapWindow(window);