-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathrunara.c
More file actions
2039 lines (1680 loc) · 56.1 KB
/
Copy pathrunara.c
File metadata and controls
2039 lines (1680 loc) · 56.1 KB
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
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#define _POSIX_C_SOURCE 200809L
#include <string.h>
#include <float.h>
#include <fontconfig/fontconfig.h>
#include <cglm/mat4.h>
#include <cglm/types-struct.h>
#include <ctype.h>
#include "include/runara/runara.h"
#include "vendor/glad/include/glad/glad.h"
#include <math.h>
#define STB_IMAGE_IMPLEMENTATION
#include "vendor/stb_image/stb_image.h"
#define LINESKY_IMPLEMENTATION
#define LINESKY_STRIP_STRUCTURES
#include <linesky.h>
#include <locale.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#ifdef _WIN32
#define HOMEDIR "USERPROFILE"
#else
#define HOMEDIR (char*)"HOME"
#endif
#define MAX(a, b) a > b ? a : b
#define MIN(a, b) a < b ? a : b
#define RN_TRACE(...) { printf("runara: [TRACE]: "); printf(__VA_ARGS__); printf("\n"); }
#define RN_INFO(...) { printf("runara: [INFO]: "); printf(__VA_ARGS__); printf("\n"); }
#define RN_WARN(...) { printf("runara: [WARN]: "); printf(__VA_ARGS__); printf("\n"); }
#define RN_ERROR(...) { fprintf(stderr, "runara: [ERROR]: "); printf(__VA_ARGS__); printf("\n"); }
static uint32_t shader_create(GLenum type, const char* src);
static RnShader shader_prg_create(const char* vert_src, const char* frag_src);
static void shader_set_mat(RnShader prg, const char* name, mat4 mat);
static void set_projection_matrix(RnState* state);
static void renderer_init(RnState* state);
static void renderer_flush(RnState* state);
static void renderer_begin(RnState* state);
static void create_font_atlas(RnFont* font);
static RnGlyph* get_glyph_from_codepoint(RnGlyphCache cache, RnFont font, uint64_t codepoint);
static RnGlyph load_glyph_from_codepoint(RnFont* font, uint64_t codepoint, bool colored);
static RnGlyph load_colr_glyph_from_codepoint(RnFont* font, uint64_t codepoint);
static RnGlyph get_glyph_from_cache(RnGlyphCache* cache, RnFont* font, uint64_t codepoint);
static RnHarfbuzzText* get_hb_text_from_str(RnHarfbuzzCache cache, RnFont font, const char* str);
static RnHarfbuzzText* load_hb_text_from_str(RnFont font, const char* str);
static RnHarfbuzzText* get_hb_text_from_cache(RnHarfbuzzCache* cache, RnFont font, const char* str);
static uint64_t djb2_hash(const unsigned char *str);
// --- Static Functions ---
/* This function creates an OpenGL shader unit */
uint32_t
shader_create(GLenum type, const char* src) {
// Create & compile the shader source with OpenGL
uint32_t shader = glCreateShader(type);
glShaderSource(shader, 1, &src, NULL);
glCompileShader(shader);
// Check for compilation errors
int32_t compiled;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if(!compiled) {
RN_ERROR("Failed to compile %s shader.", type == GL_VERTEX_SHADER ? "vertex" : "fragment");
char info[512];
glGetShaderInfoLog(shader, 512, NULL, info);
RN_INFO("%s", info);
glDeleteShader(shader);
}
return shader;
}
/* This function creates an OpenGL shader program consisting of
* a vertex- & fragment shader.
* */
RnShader
shader_prg_create(const char* vert_src, const char* frag_src) {
// Creating vertex & fragment shader with the shader API
uint32_t vertex_shader = shader_create(GL_VERTEX_SHADER, vert_src);
uint32_t fragment_shader = shader_create(GL_FRAGMENT_SHADER, frag_src);
// Creating & linking the shader program with OpenGL
RnShader prg;
prg.id = glCreateProgram();
glAttachShader(prg.id, vertex_shader);
glAttachShader(prg.id, fragment_shader);
glLinkProgram(prg.id);
// Checking for linking errors
int32_t linked;
glGetProgramiv(prg.id, GL_LINK_STATUS, &linked);
if(!linked) {
RN_ERROR("Failed to link shader program.");
char info[512];
glGetProgramInfoLog(prg.id, 512, NULL, info);
RN_INFO("%s", info);
// Cleanup the shaders & the program
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
glDeleteProgram(prg.id);
return prg;
}
// Delete the shaders after
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
return prg;
}
static uint32_t create_compute_program(const char* src) {
GLuint cs = glCreateShader(GL_COMPUTE_SHADER);
glShaderSource(cs, 1, &src, NULL);
glCompileShader(cs);
GLint ok=0; glGetShaderiv(cs, GL_COMPILE_STATUS, &ok);
if(!ok){
GLint len=0;
glGetShaderiv(cs, GL_INFO_LOG_LENGTH, &len);
char* log=(char*)malloc(len?len:1);
if(len)
glGetShaderInfoLog(cs,len,NULL,log);
}
GLuint prog = glCreateProgram();
glAttachShader(prog, cs);
glLinkProgram(prog);
glDeleteShader(cs);
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
if(!ok){
GLint len=0;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);
char* log=(char*)malloc(len?len:1);
if(len)
glGetProgramInfoLog(prog,len,NULL,log);
fprintf(stderr,"[Compute] link error:\n%s\n", log); free(log);
}
return prog;
}
void
shader_set_mat(RnShader prg, const char* name, mat4 mat) {
glUniformMatrix4fv(glGetUniformLocation(prg.id, name), 1, GL_FALSE, mat[0]);
}
/* This function uploads the orthographic projection
* matrix that is used to crete the pixel space
* in which objects are rendered.
* */
void
set_projection_matrix(RnState* state) {
mat4 orthoMatrix = GLM_MAT4_IDENTITY_INIT;
glm_ortho(0.0f, (float)state->render.render_w,
(float)state->render.render_h, 0.0f,
-1.0f, 1.0f,
orthoMatrix);
// Upload the matrix to the shader
shader_set_mat(state->render.shader, "u_proj", orthoMatrix);
}
/* This function sets up OpenGL buffer object and shaders
* and sets up the state to use the batch rendering pipeline.
* */
void
renderer_init(RnState* state) {
// OpenGL Setup
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
// Allocate memory for vertices
state->render.n_instances = 0;
state->render.instances = (RnInstance*)calloc(RN_MAX_RENDER_BATCH, sizeof(RnInstance) );
glGenVertexArrays(1, &state->render.vao);
glBindVertexArray(state->render.vao);
RnVertex quad_vertices[4] = {
{{0.0f, 0.0f}, {0.0f, 0.0f}},
{{1.0f, 0.0f}, {1.0f, 0.0f}},
{{1.0f, 1.0f}, {1.0f, 1.0f}},
{{0.0f, 1.0f}, {0.0f, 1.0f}},
};
uint32_t quad_indices[6] = {0, 1, 2, 2, 3, 0};
glGenBuffers(1, &state->render.vbo_static);
glBindBuffer(GL_ARRAY_BUFFER, state->render.vbo_static);
glBufferData(GL_ARRAY_BUFFER, sizeof(quad_vertices), quad_vertices, GL_STATIC_DRAW);
glGenBuffers(1, &state->render.ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, state->render.ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(quad_indices), quad_indices, GL_STATIC_DRAW);
// --- Vertex layout for static quad (binding 0) ---
glEnableVertexAttribArray(0); // a_local_pos
glVertexAttribPointer(
0, 2, GL_FLOAT, GL_FALSE, sizeof(RnVertex), (void*)offsetof(RnVertex, pos));
glEnableVertexAttribArray(1); // a_texcoord
glVertexAttribPointer(
1, 2, GL_FLOAT, GL_FALSE, sizeof(RnVertex), (void*)offsetof(RnVertex, texcoord));
glGenBuffers(1, &state->render.vbo_instances);
glBindBuffer(GL_ARRAY_BUFFER, state->render.vbo_instances);
glBufferData(GL_ARRAY_BUFFER, sizeof(RnInstance) * RN_MAX_RENDER_BATCH, NULL, GL_DYNAMIC_DRAW);
GLsizei stride = sizeof(RnInstance);
uintptr_t offset = 0;
// i_pos : vec2
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, stride, (void*)offset);
glVertexAttribDivisor(2, 1);
offset += sizeof(float) * 2;
// i_size : vec2
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, stride, (void*)offset);
glVertexAttribDivisor(3, 1);
offset += sizeof(float) * 2;
// i_rotation : float
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, stride, (void*)offset);
glVertexAttribDivisor(4, 1);
offset += sizeof(float) * 1;
// i_color : vec4 (u8 normalized)
glEnableVertexAttribArray(5);
glVertexAttribPointer(5, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, (void*)offset);
glVertexAttribDivisor(5, 1);
offset += sizeof(uint8_t) * 4;
// i_tex_index : uint (integer attribute)
glEnableVertexAttribArray(6);
glVertexAttribIPointer(6, 1, GL_UNSIGNED_BYTE, stride, (void*)offset);
glVertexAttribDivisor(6, 1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
/* Shader source code*/
// Vertex shader
const char* vert_src =
"#version 460 core\n"
"layout(location = 0) in vec2 a_local_pos;\n"
"layout(location = 1) in vec2 a_texcoord;\n"
"\n"
"layout(location = 2) in vec2 i_pos;\n"
"layout(location = 3) in vec2 i_size;\n"
"layout(location = 4) in float i_rotation;\n"
"layout(location = 5) in vec4 i_color;\n"
"layout(location = 6) in int i_tex_index;\n"
"\n"
"uniform mat4 u_proj;\n"
"\n"
"out vec2 v_texcoord;\n"
"out vec4 v_color;\n"
"flat out int v_tex_index;\n"
"\n"
"void main()\n"
"{\n"
" // Rotation 2x2\n"
" float c = cos(i_rotation);\n"
" float s = sin(i_rotation);\n"
" mat2 rot = mat2(c, -s, s, c);\n"
"\n"
" // Transform\n"
" vec2 world = i_pos + rot * (a_local_pos * i_size);\n"
"\n"
" v_texcoord = a_texcoord;\n"
" v_color = i_color;\n"
" v_tex_index = i_tex_index;\n"
" gl_Position = u_proj * vec4(world, 0.0, 1.0);\n"
"}\n";
const char* frag_src =
"#version 460 core\n"
"out vec4 o_color;\n"
"\n"
"in vec4 v_color;\n"
"flat in int v_tex_index;\n"
"in vec2 v_texcoord;\n"
"\n"
"uniform sampler2D u_textures[32];\n"
"\n"
"void main()\n"
"{\n"
" vec4 col = v_color;\n"
" if (v_tex_index != 0) {\n"
" int idx = clamp(v_tex_index, 0, 31);\n"
" col *= texture(u_textures[idx - 1], v_texcoord);\n"
" }\n"
" o_color = col;\n"
"}\n";
// Creating the shader program with the source code of the
// vertex- and fragment shader
state->render.shader = shader_prg_create(vert_src, frag_src);
// initializing vertex position data
state->render.vert_pos[0] = (vec4s){-0.5f, -0.5f, 0.0f, 1.0f};
state->render.vert_pos[1] = (vec4s){0.5f, -0.5f, 0.0f, 1.0f};
state->render.vert_pos[2] = (vec4s){0.5f, 0.5f, 0.0f, 1.0f};
state->render.vert_pos[3] = (vec4s){-0.5f, 0.5f, 0.0f, 1.0f};
// Populating the textures array in the shader with texture IDs
int32_t tex_slots[RN_MAX_TEX_COUNT_BATCH];
for(uint32_t i = 0; i < RN_MAX_TEX_COUNT_BATCH; i++) {
tex_slots[i] = i;
}
// Upload the texture array (sampler2D array) to the shader
glUseProgram(state->render.shader.id);
glBindVertexArray(state->render.vao);
set_projection_matrix(state);
glUniform1iv(glGetUniformLocation(state->render.shader.id, "u_textures"), RN_MAX_TEX_COUNT_BATCH, tex_slots);
}
/* This function renders every vertex in the current batch */
void
renderer_flush(RnState* state) {
if(state->render.n_instances <= 0) return;
glBindBuffer(GL_ARRAY_BUFFER, state->render.vbo_instances);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(RnInstance) * state->render.n_instances, state->render.instances);
// Bind used texture slots
for(uint32_t i = 0; i < state->render.tex_count; i++) {
glBindTextureUnit(i, state->render.textures[i].id);
}
glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0, state->render.n_instances);
state->drawcalls++;
}
/* This function begins a new batch within the
* renderer
* */
void renderer_begin(RnState* state) {
// Resetting all the
state->render.n_instances = 0;
state->render.tex_index = 0;
state->render.tex_count = 0;
}
/* This function creates the atlas texture of
* a given font with OpenGL
* */
void create_font_atlas(RnFont* font) {
glGenTextures(1, &font->atlas_id);
glBindTexture(GL_TEXTURE_2D, font->atlas_id);
int32_t filter_mode = font->filter_mode == RN_TEX_FILTER_LINEAR ?
GL_LINEAR : GL_NEAREST;
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, filter_mode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter_mode);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA,
font->atlas_w,
font->atlas_h,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
NULL);
// Generate mipmaps
glGenerateMipmap(GL_TEXTURE_2D);
}
RnGlyph* get_glyph_from_codepoint(RnGlyphCache cache, RnFont font, uint64_t codepoint) {
for(uint32_t i = 0; i < cache.len; i++) {
if(cache.data[i].codepoint == codepoint
&& cache.data[i].font_id == font.id) {
return &cache.data[i];
}
}
return NULL;
}
RnGlyph load_colr_glyph_from_codepoint(RnFont* font, uint64_t codepoint) {
RnGlyph glyph = {0};
FT_UInt glyph_index = codepoint;
if (FT_Load_Glyph(font->face, glyph_index, FT_LOAD_COLOR)) {
RN_ERROR("Failed to load glyph index '%u'.", glyph_index);
return glyph;
}
FT_GlyphSlot slot = font->face->glyph;
if (slot->format == FT_GLYPH_FORMAT_BITMAP && slot->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
return load_glyph_from_codepoint(font, codepoint, true);
}
FT_LayerIterator layer_iterator = {0};
FT_UInt layer_glyph_index;
FT_UInt layer_color_index;
layer_iterator.p = NULL;
FT_Bool has_layers = FT_Get_Color_Glyph_Layer(
font->face,
glyph_index,
&layer_glyph_index,
&layer_color_index,
&layer_iterator
);
if (!has_layers) {
return load_glyph_from_codepoint(font, codepoint, false);
}
// Select default palette (palette 0)
FT_Color* palette = NULL;
if (FT_Palette_Select(font->face, 0, &palette)) {
palette = NULL; // fallback: no palette
}
int canvas_size = font->selected_strike_size;
unsigned char* rgba_data = calloc(canvas_size * canvas_size * 4, 1);
if (!rgba_data) {
RN_ERROR("Failed to allocate RGBA canvas.");
exit(EXIT_FAILURE);
}
int min_x = 9999, min_y = 9999;
int max_x = -9999, max_y = -9999;
FT_LayerIterator measure_iterator = {0};
measure_iterator.p = NULL;
FT_UInt measure_glyph_index, measure_color_index;
// Get bounding box
if (FT_Get_Color_Glyph_Layer(font->face, glyph_index, &measure_glyph_index, &measure_color_index, &measure_iterator)) {
do {
if (FT_Load_Glyph(font->face, measure_glyph_index, FT_LOAD_RENDER)) {
continue;
}
FT_GlyphSlot slot = font->face->glyph;
if (slot->format != FT_GLYPH_FORMAT_BITMAP)
continue;
int glyph_min_x = slot->bitmap_left;
int glyph_min_y = -slot->bitmap_top + slot->bitmap.rows;
int glyph_max_x = glyph_min_x + slot->bitmap.width;
int glyph_max_y = glyph_min_y + slot->bitmap.rows;
if (glyph_min_x < min_x) min_x = glyph_min_x;
if (glyph_min_y < min_y) min_y = glyph_min_y;
if (glyph_max_x > max_x) max_x = glyph_max_x;
if (glyph_max_y > max_y) max_y = glyph_max_y;
} while (FT_Get_Color_Glyph_Layer(font->face, glyph_index, &measure_glyph_index, &measure_color_index, &measure_iterator));
}
if (min_x > max_x || min_y > max_y) {
free(rgba_data);
RN_ERROR("Invalid bounding box for COLR glyph.");
return glyph;
}
int glyph_width = max_x - min_x;
int glyph_height = max_y - min_y;
// Composite layers
layer_iterator.p = NULL;
if (FT_Get_Color_Glyph_Layer(font->face, glyph_index, &layer_glyph_index, &layer_color_index, &layer_iterator)) {
do {
if (FT_Load_Glyph(font->face, layer_glyph_index, FT_LOAD_RENDER)) {
continue;
}
FT_GlyphSlot slot = font->face->glyph;
if (slot->format != FT_GLYPH_FORMAT_BITMAP)
continue;
FT_Color layer_color;
if (layer_color_index == 0xFFFF || !palette) {
layer_color.red = 0x00;
layer_color.green = 0x00;
layer_color.blue = 0x00;
layer_color.alpha = 0xFF;
} else {
layer_color = palette[layer_color_index];
}
for (uint32_t y = 0; y < slot->bitmap.rows; y++) {
for (uint32_t x = 0; x < slot->bitmap.width; x++) {
unsigned char coverage = slot->bitmap.buffer[y * slot->bitmap.pitch + x];
if (coverage == 0)
continue;
int dst_x = (slot->bitmap_left + x) - min_x;
int dst_y = (glyph_height - (slot->bitmap_top - y)) - min_y;
if (dst_x < 0 || dst_x >= canvas_size || dst_y < 0 || dst_y >= canvas_size)
continue;
unsigned char* pixel = &rgba_data[(dst_y * canvas_size + dst_x) * 4];
unsigned char src_r = (layer_color.red * coverage) >> 8;
unsigned char src_g = (layer_color.green * coverage) >> 8;
unsigned char src_b = (layer_color.blue * coverage) >> 8;
unsigned char src_a = (layer_color.alpha * coverage) >> 8;
pixel[0] = src_r;
pixel[1] = src_g;
pixel[2] = src_b;
pixel[3] = src_a;
}
}
} while (FT_Get_Color_Glyph_Layer(font->face, glyph_index, &layer_glyph_index, &layer_color_index, &layer_iterator));
}
glBindTexture(GL_TEXTURE_2D, font->atlas_id);
if (font->atlas_x + glyph_width >= font->atlas_w) {
font->atlas_x = 0;
font->atlas_y += font->atlas_row_h;
font->atlas_row_h = 0;
}
if (font->atlas_y + glyph_height >= font->atlas_h) {
RN_ERROR("Font atlas overflow (vertical). Not handled yet.");
free(rgba_data);
return glyph;
}
glTexSubImage2D(
GL_TEXTURE_2D,
0,
font->atlas_x,
font->atlas_y,
glyph_width,
glyph_height,
GL_RGBA,
GL_UNSIGNED_BYTE,
rgba_data
);
glGenerateMipmap(GL_TEXTURE_2D);
float scale = 1.0f;
if (font->selected_strike_size)
scale = ((float)font->size / (float)font->selected_strike_size);
// Unscaled UVs (stay relative to atlas)
glyph.u0 = (float)font->atlas_x / (float)font->atlas_w;
glyph.v0 = (float)font->atlas_y / (float)font->atlas_h;
glyph.u1 = (float)(font->atlas_x + glyph_width) / (float)font->atlas_w;
glyph.v1 = (float)(font->atlas_y + glyph_height - 1) / (float)font->atlas_h;
glyph.width = glyph_width * scale;
glyph.height = glyph_height * scale;
glyph.glyph_top = (float)slot->bitmap_top;
glyph.glyph_bottom = (float)((int)slot->bitmap_top - (int)slot->bitmap.rows);
glyph.bearing_x = min_x * scale;
glyph.bearing_y = -min_y * scale;
glyph.advance = (font->face->glyph->advance.x / 64.0f) * scale; // remember divide by 64!
glyph.font_id = font->id;
glyph.codepoint = codepoint;
font->atlas_x += glyph_width + 1;
font->atlas_row_h = (font->atlas_row_h > (uint32_t)glyph_height) ? font->atlas_row_h : (uint32_t)glyph_height;
// Cleanup
free(rgba_data);
// Return the glyph
return glyph;
}
/* This function loads a glyph's bitmap from a given glyph index from a font.
* The bitmap is uploade to the texture atlas of the font. If the glyph does
* not fit onto the atlas, the atlas texture is resize. Glyphs are padded
* within the atlas
* */
RnGlyph
load_glyph_from_codepoint(RnFont* font, uint64_t codepoint, bool colored) {
RnGlyph glyph;
// Load the glyph with freetype
uint32_t flags = colored ? FT_LOAD_RENDER | FT_LOAD_COLOR : FT_LOAD_RENDER;
if (FT_Load_Glyph(font->face, codepoint, flags)) {
RN_ERROR("Failed to load glyph of character with codepoint '%lu'.", codepoint);
return glyph;
}
// Retrieving glyph information
FT_GlyphSlot slot = font->face->glyph;
int32_t width, height;
int bpp = 4;
int padding = 1;
int old_width = slot->bitmap.width;
int old_height = slot->bitmap.rows;
width = old_width + padding * 2.0f;
height = old_height + padding * 2.0f;
// Allocate memory for RGBA data with padding
unsigned char* rgba_data = (unsigned char*)malloc(width * height * bpp);
if (rgba_data == NULL) {
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
// Initialize the buffer with transparent color (RGBA = 0, 0, 0, 0)
memset(rgba_data, 0, width * height * bpp);
if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY || !colored) {
// Grayscale glyph (normal text)
for (int y = 0; y < old_height; y++) {
for (int x = 0; x < old_width; x++) {
unsigned char* src_pixel = &slot->bitmap.buffer[y * slot->bitmap.pitch + x];
unsigned char* dst_pixel = &rgba_data[((y + padding) * width + (x + padding)) * bpp];
unsigned char gray = *src_pixel;
dst_pixel[0] = gray; // R
dst_pixel[1] = gray; // G
dst_pixel[2] = gray; // B
dst_pixel[3] = gray; // A (coverage)
}
}
}
else if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
// Color bitmap glyph (emoji)
for (int y = 0; y < old_height; y++) {
for (int x = 0; x < old_width; x++) {
unsigned char* src_pixel = &slot->bitmap.buffer[(y * slot->bitmap.pitch) + (x * 4)];
unsigned char* dst_pixel = &rgba_data[((y + padding) * width + (x + padding)) * bpp];
dst_pixel[0] = src_pixel[2]; // R
dst_pixel[1] = src_pixel[1]; // G
dst_pixel[2] = src_pixel[0]; // B
dst_pixel[3] = src_pixel[3]; // A
}
}
}
else {
RN_ERROR("Unsupported pixel mode: %d", slot->bitmap.pixel_mode);
}
// When the atlas overflows on the X, advance
// one line down
if (font->atlas_x + width > font->atlas_w) {
font->atlas_x = 0;
font->atlas_y += font->atlas_row_h;
font->atlas_row_h = 0;
}
// Resize the atlas if it overflows on the Y
if (font->atlas_y + height > font->atlas_h) {
int new_w = font->atlas_w * 2;
int new_h = font->atlas_h * 2 + 1;
uint32_t new_id;
// Create new texture
glGenTextures(1, &new_id);
glBindTexture(GL_TEXTURE_2D, new_id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, new_w, new_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
// Copy old texture data
glBindTexture(GL_TEXTURE_2D, font->atlas_id);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, font->atlas_w, font->atlas_h);
// Delete old texture
glDeleteTextures(1, &font->atlas_id);
// Set new values
font->atlas_id = new_id;
font->atlas_w = new_w;
font->atlas_h = new_h;
// Bind the new texture
glBindTexture(GL_TEXTURE_2D, font->atlas_id);
// Initialize new texture data
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA,
font->atlas_w,
font->atlas_h,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
NULL);
}
// Bind the texture atlas
glBindTexture(GL_TEXTURE_2D, font->atlas_id);
// Set texture attributes
int32_t filter_mode = font->filter_mode == RN_TEX_FILTER_LINEAR ?
GL_LINEAR : GL_NEAREST;
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, filter_mode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter_mode);
// Upload the glyph's bitmap to the atlas
glTexSubImage2D(
GL_TEXTURE_2D,
0,
font->atlas_x,
font->atlas_y,
width,
height,
GL_RGBA,
GL_UNSIGNED_BYTE,
rgba_data);
glGenerateMipmap(GL_TEXTURE_2D);
/* Set glyph attributes */
float scale = 1.0f;
if (font->selected_strike_size)
scale = ((float)font->size / (float)font->selected_strike_size);
glyph.width = slot->bitmap.width * scale;
glyph.height = slot->bitmap.rows * scale;
glyph.glyph_top = (float)slot->bitmap_top;
glyph.glyph_bottom = (float)((int)slot->bitmap_top - (int)slot->bitmap.rows);
glyph.bearing_x = slot->bitmap_left * scale;
glyph.bearing_y = slot->bitmap_top * scale;
glyph.advance = (slot->advance.x / 64.0f) * scale;
glyph.ascender = (slot->metrics.horiBearingY >> 6) * scale;
glyph.descender = ((slot->metrics.horiBearingY - slot->metrics.height) / 64.0f) * scale;
glyph.codepoint = codepoint;
glyph.font_id = font->id;
glyph.u0 = (float)(font->atlas_x + padding) / (float)font->atlas_w;
glyph.v0 = (float)(font->atlas_y + padding) / (float)font->atlas_h;
glyph.u1 = (float)(font->atlas_x + width) / (float)font->atlas_w;
glyph.v1 = (float)(font->atlas_y + height) / (float)font->atlas_h;
font->atlas_x += width + 1;
font->atlas_row_h = (font->atlas_row_h > height) ? font->atlas_row_h : height;
// Cleanup
free(rgba_data);
// Return final glyph
return glyph;
// Free allocated memory
free(rgba_data);
return glyph;
}
RnGlyph get_glyph_from_cache(RnGlyphCache* cache, RnFont* font, uint64_t codepoint) {
RnGlyph* glyph = get_glyph_from_codepoint(*cache, *font, codepoint);
if(glyph) {
return *glyph;
}
RnGlyph new_glyph = load_colr_glyph_from_codepoint(font, codepoint);
DA_PUSH(cache, new_glyph);
return new_glyph;
}
RnHarfbuzzText* get_hb_text_from_str(RnHarfbuzzCache cache, RnFont font, const char* str) {
uint64_t hash = djb2_hash((unsigned char*)str);
for(uint32_t i = 0; i < cache.len; i++) {
if(cache.data[i]->hash == hash &&
cache.data[i]->font_id == font.id) {
return cache.data[i];
}
}
return NULL;
}
/*
* This function loads the
* text rendering information for a given string
* with harfbuzz */
RnHarfbuzzText*
load_hb_text_from_str(RnFont font, const char* str) {
RnHarfbuzzText* text = malloc(sizeof(*text));
text->words = NULL;
text->nwords = 0;
// Create a HarfBuzz buffer and add text
text->buf = hb_buffer_create();
hb_buffer_add_utf8(text->buf, str, -1, 0, -1);
// Shape the text
hb_buffer_guess_segment_properties(text->buf);
hb_shape(font.hb_font, text->buf, NULL, 0);
int32_t len;
// Retrieve glyph information and positions
text->glyph_info = hb_buffer_get_glyph_infos(text->buf, &text->glyph_count);
text->glyph_pos = hb_buffer_get_glyph_positions(text->buf, &text->glyph_count);
// Generate a hash for the text
text->hash = djb2_hash((const unsigned char*)str);
// Set font ID for the harfbuzz text
text->font_id = font.id;
// Set rendered string of the text
text->str = malloc(strlen(str) + 1);
strcpy(text->str, str);
text->highest_bearing = 0.0f;
return text;
}
RnHarfbuzzText* get_hb_text_from_cache(RnHarfbuzzCache* cache, RnFont font, const char* str) {
RnHarfbuzzText* text = get_hb_text_from_str(*cache, font, str);
if(text) {
return text;
}
RnHarfbuzzText* new_text = load_hb_text_from_str(font, str);
DA_PUSH(cache, new_text);
return new_text;
}
/*
* Returns the DJB2 hash of a given string
* */
uint64_t
djb2_hash(const unsigned char *str) {
unsigned long hash = 5381;
int c;
while ((c = *str++)) {
hash = ((hash << 5) + hash) + c; // hash * 33 + c
}
return hash;
}
// ===========================================================
// ----------------Public API Functions ----------------------
// ===========================================================
RnState*
rn_init(uint32_t render_w, uint32_t render_h, RnGLLoader loader) {
RnState* state = malloc(sizeof(*state));
// Set locale to ensure that unicode is working
setlocale(LC_ALL, "");
// Load OpenGL functions with glad
if(loader && !gladLoadGLLoader((GLADloadproc)loader)) {
RN_ERROR("Failed to initialize Glad.");
return state;
}
// Set default state
state->render.render_w = render_w;
state->render.render_h = render_h;
state->render.tex_count = 0;
state->drawcalls = 0;
state->cull_start = (vec2s){-1, -1};
state->cull_end = (vec2s){-1, -1};
// Initializing the renderer
renderer_init(state);
// Initializing FreeType
if(FT_Init_FreeType(&state->ft) != 0) {
RN_ERROR("Failed to initialize FreeType.");
return state;
}
state->glyph_cache = (RnGlyphCache)DA_INIT;
state->hb_cache = (RnHarfbuzzCache)DA_INIT;
state->init = true;
return state;
}
void
rn_terminate(RnState* state) {
// Free glyph- & harfbuzz-caches
DA_FREE(&state->glyph_cache);
DA_FREE(&state->hb_cache);
// Terminate freetype
FT_Done_FreeType(state->ft);
free(state);
}
void
rn_resize_display(RnState* state, uint32_t render_w, uint32_t render_h) {
// Set render dimensions
state->render.render_w = render_w;
state->render.render_h = render_h;
// Send the dimension chnage to OpenGL
glViewport(0, 0, render_w, render_h);
set_projection_matrix(state);
}
RnTexture
rn_load_texture(const char* filepath) {
return rn_load_texture_ex(filepath, false, RN_TEX_FILTER_LINEAR);
}
void
rn_load_texture_base_types(
const char* filepath,
uint32_t* o_tex_id,
uint32_t* o_tex_width,
uint32_t* o_tex_height,
uint32_t filter) {
int width, height, channels;
// Load image data with stb_image
unsigned char* image = stbi_load(filepath, &width, &height, &channels, STBI_rgb_alpha);
if (!image) {
RN_ERROR("Failed to load texture at '%s'.", filepath);
return;
}
// Create OpenGL texture
glGenTextures(1, o_tex_id);
glBindTexture(GL_TEXTURE_2D, *o_tex_id);
// Set texture parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
switch(filter) {
case RN_TEX_FILTER_LINEAR:
glTextureParameteri(*o_tex_id, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTextureParameteri(*o_tex_id, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
break;
case RN_TEX_FILTER_NEAREST:
glTextureParameteri(*o_tex_id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTextureParameteri(*o_tex_id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
break;
}
// Load texture data
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
// Free image data CPU side
stbi_image_free(image);
*o_tex_width = width;
*o_tex_height = height;
}
RnTexture
rn_load_texture_ex(const char* filepath, bool flip, RnTextureFiltering filter) {
RnTexture tex;
int width, height, channels;
stbi_set_flip_vertically_on_load(flip);
// Load image data with stb_image
unsigned char* image = stbi_load(filepath, &width, &height, &channels, STBI_rgb_alpha);
if (!image) {
RN_ERROR("Failed to load texture at '%s'.", filepath);
return tex;
}