summaryrefslogtreecommitdiff
path: root/src/text.cc
blob: 4a01c8e8a628cac656da8b8196f1d5f776c4fdc9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
#include "text.h"

#include <cstddef>
#include <cstring>

#include "atlas.h"
#include "cglm/types.h"
#include "cglm/vec2.h"
#include "freetype/freetype.h"
#include "freetype/fttypes.h"
#include "geometry.h"
#include "hash.h"
#include "shaders.h"
#include "utils.h"

// Linear filtering for smooth glyph rendering, clamped UVs to prevent atlas
// bleeding
static void wayc_font_context_sampler_desc(struct sg_sampler_desc* desc) {
  wayc_notnull(desc);

  desc->min_filter = SG_FILTER_LINEAR;
  desc->mag_filter = SG_FILTER_LINEAR;
  desc->wrap_u = SG_WRAP_CLAMP_TO_EDGE;
  desc->wrap_v = SG_WRAP_CLAMP_TO_EDGE;
}

static void wayc_font_context_layout(struct sg_vertex_layout_state* layout) {
  wayc_notnull(layout);

  layout->buffers[0].stride = sizeof(text_vertex_s);

  layout->attrs[ATTR_text_in_position].format = SG_VERTEXFORMAT_FLOAT2;
  layout->attrs[ATTR_text_in_position].buffer_index = 0;
  layout->attrs[ATTR_text_in_position].offset = offsetof(text_vertex_s, pos);

  layout->attrs[ATTR_text_in_uv].format = SG_VERTEXFORMAT_FLOAT2;
  layout->attrs[ATTR_text_in_uv].buffer_index = 0;
  layout->attrs[ATTR_text_in_uv].offset = offsetof(text_vertex_s, uv);
}

// Standard alpha blending for transparent text rendering
static void wayc_font_context_color(struct sg_color_target_state* color) {
  wayc_notnull(color);

  struct sg_blend_state blend = {};
  blend.enabled = true;
  blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA;
  blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
  blend.src_factor_alpha = SG_BLENDFACTOR_SRC_ALPHA;
  blend.dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;

  color->blend = blend;
}

static void font_context_pipeline_desc(struct sg_pipeline_desc* desc,
                                       struct sg_shader shader) {
  wayc_notnull(desc);

  struct sg_vertex_layout_state vertex_layout = {};
  wayc_font_context_layout(&vertex_layout);

  struct sg_color_target_state color = {};
  wayc_font_context_color(&color);

  desc->layout = vertex_layout;
  desc->shader = shader;
  desc->index_type = SG_INDEXTYPE_UINT16;

  desc->colors[0] = color;
  desc->color_count = 1;
}

static void wayc_text_quad(struct text_vertex_s* verts, u16* indices, u16 base,
                           const struct glyph_s* glyph, vec2 cursor) {
  f32 x0 = WAYC_X(cursor) + WAYC_X(glyph->bearing);
  f32 y0 = WAYC_Y(cursor) - WAYC_Y(glyph->bearing);
  f32 x1 = x0 + WAYC_X(glyph->size);
  f32 y1 = y0 + WAYC_Y(glyph->size);

  struct quad_s quad;
  wayc_quad_init(&quad, x0, y0, x1, y1);

  vec2 uvs[WAYC_QUAD_NVERTS] = {
      {WAYC_X(glyph->uv0), WAYC_Y(glyph->uv0)},
      {WAYC_X(glyph->uv1), WAYC_Y(glyph->uv0)},
      {WAYC_X(glyph->uv1), WAYC_Y(glyph->uv1)},
      {WAYC_X(glyph->uv0), WAYC_Y(glyph->uv1)},
  };

  for (usize j = 0; j < WAYC_QUAD_NVERTS; j++) {
    glm_vec2_copy(quad.vertices[j], verts[base + j].pos);
    glm_vec2_copy(uvs[j], verts[base + j].uv);
  }

  for (usize j = 0; j < WAYC_QUAD_NINDICES; j++)
    indices[j] = (u16)(base + quad.indices[j]);
}

enum font_context_error_e wayc_font_context_init(
    struct font_context_s* context) {
  wayc_notnull(context);
  memset(context, 0, sizeof(*context));
  bool success = false;

  FT_Library ft;
  FT_Error fterr = FT_Init_FreeType(&ft);
  if (fterr) return FONT_CONTEXT_ERROR_LOAD_LIBRARY;
  wayc_defer_cond(FT_Done_FreeType(ft), success, true);

  struct sg_shader shader =
      sg_make_shader(text_shader_desc(sg_query_backend()));
  if (sg_query_shader_state(shader) != SG_RESOURCESTATE_VALID)
    return FONT_CONTEXT_ERROR_CREATE_SHADER;
  wayc_defer_cond(sg_destroy_shader(shader), success, true);

  struct sg_sampler_desc sampler_desc = {};
  wayc_font_context_sampler_desc(&sampler_desc);

  struct sg_pipeline_desc pipeline_desc = {};
  font_context_pipeline_desc(&pipeline_desc, shader);

  struct sg_sampler sampler = sg_make_sampler(&sampler_desc);
  if (sg_query_sampler_state(sampler) != SG_RESOURCESTATE_VALID)
    return FONT_CONTEXT_ERROR_CREATE_SAMPLER;

  wayc_defer_cond(sg_destroy_sampler(sampler), success, true);

  struct sg_pipeline pipeline = sg_make_pipeline(&pipeline_desc);
  if (sg_query_pipeline_state(pipeline) != SG_RESOURCESTATE_VALID)
    return FONT_CONTEXT_ERROR_CREATE_PIPELINE;

  context->ft = ft;
  context->sampler = sampler;
  context->pipeline = pipeline;

  success = true;

  return FONT_CONTEXT_ERROR_NONE;
}

void wayc_font_context_deinit(struct font_context_s* context) {
  wayc_notnull(context);

  sg_destroy_pipeline(context->pipeline);
  sg_destroy_sampler(context->sampler);
  FT_Done_FreeType(context->ft);
}

enum font_error_e wayc_font_init(struct font_s* font,
                                 struct font_context_s* context,
                                 const char* path, u32 font_size,
                                 u32 atlas_width, u32 atlas_height) {
  wayc_notnull(font);
  wayc_notnull(context);
  memset(font, 0, sizeof(*font));
  bool success = false;

  FT_Face face;
  FT_Error fterr = FT_New_Face(context->ft, path, 0, &face);
  if (fterr) return FONT_ERROR_LOAD_FACE;
  wayc_defer_cond(FT_Done_Face(face), success, true);

  fterr = FT_Set_Pixel_Sizes(face, 0, font_size);
  if (fterr) return FONT_ERROR_LOAD_FACE;

  struct atlas_s atlas;
  enum atlas_error_e aterr =
      wayc_atlas_init(&atlas, atlas_width, atlas_height, SG_PIXELFORMAT_R8);
  if (aterr) return FONT_ERROR_ATLAS_FAILED;

  font->context = context;
  font->face = face;
  font->atlas = atlas;
  wayc_hashmap_init(&font->cached);

  success = true;
  return FONT_ERROR_NONE;
}

enum font_error_e wayc_font_raster(struct font_s* font, codepoint_t codepoint,
                                   struct glyph_s* glyph) {
  wayc_notnull(font);
  wayc_notnull(glyph);

  // Return cached glyph if already rasterized to avoid redundant FreeType calls
  struct glyph_s got;
  if (wayc_hashmap_get(&font->cached, &codepoint, &got)) {
    *glyph = got;
    return FONT_ERROR_NONE;
  }

  u32 index = FT_Get_Char_Index(font->face, codepoint);
  if (index == 0) return FONT_ERROR_LOAD_GLYPH;

  FT_Error fterr = FT_Load_Glyph(font->face, index, FT_LOAD_RENDER);
  if (fterr) return FONT_ERROR_LOAD_GLYPH;

  FT_GlyphSlot slot = font->face->glyph;
  FT_Bitmap bitmap = slot->bitmap;

  vec2 bearing = {(f32)slot->bitmap_left, (f32)slot->bitmap_top};
  vec2 size = {(f32)bitmap.width, (f32)bitmap.rows};
  f32 advance = (f32)slot->advance.x / WAYC_SCALE;

  // Pack glyph bitmap into atlas texture and get UV coordinates
  vec2 uv0 = {0.0f, 0.0f};
  vec2 uv1 = {0.0f, 0.0f};

  if (bitmap.buffer != nullptr) {
    enum atlas_error_e aterr = wayc_atlas_insert(
        &font->atlas, bitmap.width, bitmap.rows, bitmap.buffer, &uv0, &uv1);
    if (aterr) return FONT_ERROR_ATLAS_FAILED;
  }

  glm_vec2_copy(uv0, got.uv0);
  glm_vec2_copy(uv1, got.uv1);
  glm_vec2_copy(size, got.size);
  glm_vec2_copy(bearing, got.bearing);
  got.advance = advance;

  wayc_hashmap_insert(&font->cached, &codepoint, &got);

  *glyph = got;
  return FONT_ERROR_NONE;
}

void wayc_font_deinit(struct font_s* font) {
  wayc_notnull(font);

  wayc_hashmap_deinit(&font->cached);
  wayc_atlas_deinit(&font->atlas);
  FT_Done_Face(font->face);
}

bool wayc_text_mesh_init(struct text_mesh_s* mesh, const char* text,
                         usize text_len, struct font_s* font, vec2 position) {
  wayc_notnull(mesh);
  wayc_notnull(text);
  wayc_notnull(font);
  memset(mesh, 0, sizeof(*mesh));

  bool success = false;

  usize nverts = text_len * WAYC_QUAD_NVERTS;
  usize nindices = text_len * WAYC_QUAD_NINDICES;

  usize bytes_verts = nverts * sizeof(struct text_vertex_s);
  usize bytes_indices = nindices * sizeof(u16);

  struct text_vertex_s* verts = (struct text_vertex_s*)mi_malloc(bytes_verts);
  wayc_defer_cond(mi_free(verts), success, true);

  u16* indices = (u16*)mi_malloc(bytes_indices);
  wayc_defer_cond(mi_free(indices), success, true);

  // Build quads for each character, advancing cursor by glyph width
  vec2 cursor;
  glm_vec2_copy(position, cursor);
  for (usize i = 0; i < text_len; i++) {
    codepoint_t codepoint = (codepoint_t)text[i];  // TODO: handle UTF-8

    struct glyph_s glyph;
    enum font_error_e err = wayc_font_raster(font, codepoint, &glyph);
    if (err != FONT_ERROR_NONE) return false;

    u16 base = (u16)(i * WAYC_QUAD_NVERTS);
    wayc_text_quad(verts, &indices[i * WAYC_QUAD_NINDICES], base, &glyph,
                   cursor);
    WAYC_X(cursor) += glyph.advance;
  }

  mesh->vertices = verts;
  mesh->vertex_count = nverts;
  mesh->indices = indices;
  mesh->index_count = nindices;

  success = true;
  return true;
}

void wayc_text_mesh_deinit(struct text_mesh_s* mesh) {
  wayc_notnull(mesh);

  if (mesh->vertices == nullptr || mesh->indices == nullptr) return;

  mi_free(mesh->vertices);
  mi_free(mesh->indices);

  mesh->vertices = nullptr;
  mesh->indices = nullptr;
}

static inline void wayc_text_buf_desc(struct sg_buffer_desc* vbo,
                                      struct sg_buffer_desc* ebo) {
  wayc_notnull(vbo);
  wayc_notnull(ebo);

  struct sg_buffer_usage usage = {};
  usage.vertex_buffer = true;
  usage.dynamic_update = false;
  usage.stream_update = true;
  usage.immutable = false;

  vbo->usage = usage;
  usage.vertex_buffer = false;
  usage.index_buffer = true;
  ebo->usage = usage;
}

bool wayc_text_group_init(struct text_group_s* group, struct font_s* font,
                          usize glyph_capacity) {
  wayc_notnull(group);
  wayc_notnull(font);
  memset(group, 0, sizeof(*group));

  bool success = false;

  usize nverts = glyph_capacity * WAYC_QUAD_NVERTS;
  usize nindices = glyph_capacity * WAYC_QUAD_NINDICES;

  usize verts_bytes = nverts * sizeof(struct text_vertex_s);
  usize indices_bytes = nindices * sizeof(u16);

  struct sg_buffer_desc vbo_desc = {};
  struct sg_buffer_desc ebo_desc = {};
  wayc_text_buf_desc(&vbo_desc, &ebo_desc);

  vbo_desc.size = verts_bytes;
  ebo_desc.size = indices_bytes;

  struct sg_buffer vbuf = sg_make_buffer(&vbo_desc);
  if (sg_query_buffer_state(vbuf) != SG_RESOURCESTATE_VALID) return false;
  wayc_defer_cond(sg_destroy_buffer(vbuf), success, true);

  struct sg_buffer ebuf = sg_make_buffer(&ebo_desc);
  if (sg_query_buffer_state(ebuf) != SG_RESOURCESTATE_VALID) return false;
  wayc_defer_cond(sg_destroy_buffer(ebuf), success, true);

  group->vbuf = vbuf;
  group->ebuf = ebuf;
  group->glyph_capacity = glyph_capacity;
  group->font = font;

  success = true;
  return success;
}

void wayc_text_group_begin(struct text_group_s* group) {
  wayc_notnull(group);

  group->index_count = 0;
  group->glyph_count = 0;
}

void wayc_text_group_end(struct text_group_s* group,
                         struct font_context_s* context) {
  wayc_notnull(group);
  wayc_notnull(context);
}

void wayc_text_group_deinit(struct text_group_s* group) {
  wayc_notnull(group);

  sg_destroy_buffer(group->vbuf);
  sg_destroy_buffer(group->ebuf);
}