Skip to content

Commit

Permalink
Merge pull request #25 from tsoding/freetype
Browse files Browse the repository at this point in the history
FreeType2 Support
  • Loading branch information
rexim committed Jul 11, 2021
2 parents aec872a + b06ac01 commit 6f5335c
Show file tree
Hide file tree
Showing 14 changed files with 2,465 additions and 250 deletions.
50 changes: 26 additions & 24 deletions .github/workflows/ci.yml
Expand Up @@ -42,27 +42,29 @@ jobs:
./build.sh
env:
CC: clang
build-windows-msvc:
runs-on: windows-2019
steps:
- uses: actions/checkout@v1
# this runs vcvarsall for us, so we get the MSVC toolchain in PATH.
- uses: seanmiddleditch/gha-setup-vsdevenv@master
- name: Install dependencies
run: |
./setup_dependencies.bat
- name: build ded
shell: cmd
run: |
./build_msvc.bat
- name: Prepare WindowsBinaries artifacts
shell: cmd
run: |
mkdir winbin
copy /B *.exe winbin
copy /B *.png winbin
- name: Upload WindowsBinaries artifacts
uses: actions/upload-artifact@v2
with:
name: WindowsBinaries
path: ./winbin/
# TODO(#29): build-windows-msvc is broken
# ---
# build-windows-msvc:
# runs-on: windows-2019
# steps:
# - uses: actions/checkout@v1
# # this runs vcvarsall for us, so we get the MSVC toolchain in PATH.
# - uses: seanmiddleditch/gha-setup-vsdevenv@master
# - name: Install dependencies
# run: |
# ./setup_dependencies.bat
# - name: build ded
# shell: cmd
# run: |
# ./build_msvc.bat
# - name: Prepare WindowsBinaries artifacts
# shell: cmd
# run: |
# mkdir winbin
# copy /B *.exe winbin
# copy /B *.png winbin
# - name: Upload WindowsBinaries artifacts
# uses: actions/upload-artifact@v2
# with:
# name: WindowsBinaries
# path: ./winbin/
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -16,3 +16,7 @@ $ ./ded src\main.c
> .\build_msvc.bat
> .\ded.exe src\main.c
```

# Font

Victor Mono: https://rubjo.github.io/victor-mono/
Binary file added VictorMono-Regular.ttf
Binary file not shown.
4 changes: 2 additions & 2 deletions build.sh
Expand Up @@ -3,10 +3,10 @@
set -xe

CC="${CXX:-cc}"
PKGS="sdl2 glew"
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="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"

if [ `uname` = "Darwin" ]; then
CFLAGS+=" -framework OpenGL"
Expand Down
24 changes: 24 additions & 0 deletions shaders/free_glyph.frag
@@ -0,0 +1,24 @@
#version 330 core

uniform sampler2D font;
uniform float time;
uniform vec2 resolution;

in vec2 uv;
in vec2 glyph_uv_pos;
in vec2 glyph_uv_size;
in vec4 glyph_fg_color;
in vec4 glyph_bg_color;

vec3 hsl2rgb(vec3 c) {
vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0);
return c.z + c.y * (rgb-0.5)*(1.0-abs(2.0*c.z-1.0));
}

void main() {
vec2 t = glyph_uv_pos + glyph_uv_size * uv;
vec4 tc = texture(font, t);
vec2 frag_uv = gl_FragCoord.xy / resolution;
vec4 rainbow = vec4(hsl2rgb(vec3((time + frag_uv.x + frag_uv.y), 0.5, 0.5)), 1.0);
gl_FragColor = glyph_bg_color * (1.0 - tc.x) + tc.x * glyph_fg_color * rainbow;
}
31 changes: 31 additions & 0 deletions shaders/free_glyph.vert
@@ -0,0 +1,31 @@
#version 330 core

uniform vec2 resolution;
uniform vec2 camera;

layout(location = 0) in vec2 pos;
layout(location = 1) in vec2 size;
layout(location = 2) in vec2 uv_pos;
layout(location = 3) in vec2 uv_size;
layout(location = 4) in vec4 fg_color;
layout(location = 5) in vec4 bg_color;

out vec2 uv;
out vec2 glyph_uv_pos;
out vec2 glyph_uv_size;
out vec4 glyph_fg_color;
out vec4 glyph_bg_color;

vec2 camera_project(vec2 point)
{
return 2.0 * (point - camera) / resolution;
}

void main() {
uv = vec2(float(gl_VertexID & 1), float((gl_VertexID >> 1) & 1));
gl_Position = vec4(camera_project(uv * size + pos), 0.0, 1.0);
glyph_uv_pos = uv_pos;
glyph_uv_size = uv_size;
glyph_fg_color = fg_color;
glyph_bg_color = bg_color;
}
File renamed without changes.
File renamed without changes.
238 changes: 238 additions & 0 deletions src/free_glyph.c
@@ -0,0 +1,238 @@
#include <assert.h>
#include <stdbool.h>
#include "./free_glyph.h"
#include "./gl_extra.h"

#include "./stb_image_write.h"
#include "./stb_image.h"

typedef struct {
size_t offset;
GLint comps;
GLenum type;
} Attr_Def;

static const Attr_Def glyph_attr_defs[COUNT_FREE_GLYPH_ATTRS] = {
[FREE_GLYPH_ATTR_POS] = {
.offset = offsetof(Free_Glyph, pos),
.comps = 2,
.type = GL_FLOAT
},
[FREE_GLYPH_ATTR_SIZE] = {
.offset = offsetof(Free_Glyph, size),
.comps = 2,
.type = GL_FLOAT
},
[FREE_GLYPH_ATTR_UV_POS] = {
.offset = offsetof(Free_Glyph, uv_pos),
.comps = 2,
.type = GL_FLOAT
},
[FREE_GLYPH_ATTR_UV_SIZE] = {
.offset = offsetof(Free_Glyph, uv_size),
.comps = 2,
.type = GL_FLOAT
},
[FREE_GLYPH_ATTR_FG_COLOR] = {
.offset = offsetof(Free_Glyph, fg_color),
.comps = 4,
.type = GL_FLOAT
},
[FREE_GLYPH_ATTR_BG_COLOR] = {
.offset = offsetof(Free_Glyph, bg_color),
.comps = 4,
.type = GL_FLOAT
},
};
static_assert(COUNT_FREE_GLYPH_ATTRS == 6, "The amount of glyph vertex attributes have changed");


void free_glyph_buffer_init(Free_Glyph_Buffer *fgb,
FT_Face face,
const char *vert_file_path,
const char *frag_file_path)
{
// Init Vertex Attributes
{
glGenVertexArrays(1, &fgb->vao);
glBindVertexArray(fgb->vao);

glGenBuffers(1, &fgb->vbo);
glBindBuffer(GL_ARRAY_BUFFER, fgb->vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(fgb->glyphs), fgb->glyphs, GL_DYNAMIC_DRAW);

for (Free_Glyph_Attr attr = 0; attr < COUNT_FREE_GLYPH_ATTRS; ++attr) {
glEnableVertexAttribArray(attr);
switch (glyph_attr_defs[attr].type) {
case GL_FLOAT:
glVertexAttribPointer(
attr,
glyph_attr_defs[attr].comps,
glyph_attr_defs[attr].type,
GL_FALSE,
sizeof(Free_Glyph),
(void*) glyph_attr_defs[attr].offset);
break;

case GL_INT:
glVertexAttribIPointer(
attr,
glyph_attr_defs[attr].comps,
glyph_attr_defs[attr].type,
sizeof(Free_Glyph),
(void*) glyph_attr_defs[attr].offset);
break;

default:
assert(false && "unreachable");
exit(1);
}
glVertexAttribDivisor(attr, 1);
}
}

// 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);
}

GLuint program = 0;
if (!link_program(vert_shader, frag_shader, &program)) {
exit(1);
}

glUseProgram(program);

fgb->time_uniform = glGetUniformLocation(program, "time");
fgb->resolution_uniform = glGetUniformLocation(program, "resolution");
fgb->camera_uniform = glGetUniformLocation(program, "camera");
}

// Glyph Texture Atlas
{
for (int i = 32; i < 128; ++i) {
if (FT_Load_Char(face, i, FT_LOAD_RENDER)) {
fprintf(stderr, "ERROR: could not load glyph of a character with code %d\n", i);
exit(1);
}

fgb->atlas_width += face->glyph->bitmap.width;
if (fgb->atlas_height < face->glyph->bitmap.rows) {
fgb->atlas_height = face->glyph->bitmap.rows;
}
}

glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &fgb->glyphs_texture);
glBindTexture(GL_TEXTURE_2D, fgb->glyphs_texture);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RED,
(GLsizei) fgb->atlas_width,
(GLsizei) fgb->atlas_height,
0,
GL_RED,
GL_UNSIGNED_BYTE,
NULL);

int x = 0;
for (int i = 32; i < 128; ++i) {
if (FT_Load_Char(face, i, FT_LOAD_RENDER)) {
fprintf(stderr, "ERROR: could not load glyph of a character with code %d\n", i);
exit(1);
}

if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL)) {
fprintf(stderr, "ERROR: could not render glyph of a character with code %d\n", i);
exit(1);
}

fgb->metrics[i].ax = face->glyph->advance.x >> 6;
fgb->metrics[i].ay = face->glyph->advance.y >> 6;
fgb->metrics[i].bw = face->glyph->bitmap.width;
fgb->metrics[i].bh = face->glyph->bitmap.rows;
fgb->metrics[i].bl = face->glyph->bitmap_left;
fgb->metrics[i].bt = face->glyph->bitmap_top;
fgb->metrics[i].tx = (float) x / (float) fgb->atlas_width;

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D(
GL_TEXTURE_2D,
0,
x,
0,
face->glyph->bitmap.width,
face->glyph->bitmap.rows,
GL_RED,
GL_UNSIGNED_BYTE,
face->glyph->bitmap.buffer);
x += face->glyph->bitmap.width;
}
}
}

void free_glyph_buffer_clear(Free_Glyph_Buffer *fgb)
{
fgb->glyphs_count = 0;
}

void free_glyph_buffer_push(Free_Glyph_Buffer *fgb, Free_Glyph glyph)
{
assert(fgb->glyphs_count < FREE_GLYPH_BUFFER_CAP);
fgb->glyphs[fgb->glyphs_count++] = glyph;
}

void free_glyph_buffer_sync(Free_Glyph_Buffer *fgb)
{
glBufferSubData(GL_ARRAY_BUFFER,
0,
fgb->glyphs_count * sizeof(Free_Glyph),
fgb->glyphs);
}

void free_glyph_buffer_draw(Free_Glyph_Buffer *fgb)
{
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei) fgb->glyphs_count);
}

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) {
Glyph_Metric metric = fgb->metrics[(int) text[i]];
float x2 = pos.x + metric.bl;
float y2 = -pos.y - metric.bt;
float w = metric.bw;
float h = metric.bh;

pos.x += metric.ax;
pos.y += metric.ay;

Free_Glyph glyph = {0};
glyph.pos = vec2f(x2, -y2);
glyph.size = vec2f(w, -h);
glyph.uv_pos = vec2f(metric.tx, 0.0f);
glyph.uv_size = vec2f(metric.bw / (float) fgb->atlas_width, metric.bh / (float) fgb->atlas_height);
glyph.fg_color = fg_color;
glyph.bg_color = bg_color;
free_glyph_buffer_push(fgb, glyph);
}
}

void free_glyph_buffer_render_line(Free_Glyph_Buffer *fgb, const char *text, Vec2f pos, Vec4f fg_color, Vec4f bg_color)
{
free_glyph_buffer_render_line_sized(fgb, text, strlen(text), pos, fg_color, bg_color);
}

0 comments on commit 6f5335c

Please sign in to comment.