This commit is contained in:
Alessandro Mauri 2023-02-12 09:34:44 +01:00
parent a32b211d20
commit 5018ccbbb0
13 changed files with 5549 additions and 0 deletions

106
text_rendering/cache.c Normal file
View file

@ -0,0 +1,106 @@
#define _POSIX_C_SOURCE 200809l
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "hash.h"
#include "font.h"
#include "util.h"
static struct hm_ref *hash_table;
static struct font_glyph cache_array[CACHE_SIZE] = {0};
// bitmap size is aligned to word
#define _BSIZE ((CACHE_SIZE+0x3f)&(~0x3f))
static uint64_t bitmap[_BSIZE] = {0};
// bitmap operations
#define B_RESET() memset(bitmap, 0, _BSIZE*sizeof(uint64_t))
#define B_SET(x) bitmap[(x)/_BSIZE] |= 1<<((x)%_BSIZE)
#define B_TEST(x) (bitmap[(x)/_BSIZE]&(1<<((x)%_BSIZE)))
// reset the bitmap every n cycles
#define NCYCLES (CACHE_SIZE/2)
static int cycles = 0;
static inline void set_bit(unsigned int x)
{
// printf("cycles: %d, set: %d\n", cycles, x);
cycles = (cycles+1)%NCYCLES;
if (!cycles) B_RESET();
B_SET(x);
}
void cache_init(void)
{
hash_table = hm_create(CACHE_SIZE);
}
void cache_destroy(void)
{
hm_destroy(hash_table);
}
struct font_glyph * cache_get(unsigned int code)
{
struct hm_entry *r = hm_search(hash_table, code);
// miss
if (!r)
return NULL;
// hit
set_bit((struct font_glyph *)(r->data)-cache_array);
return (struct font_glyph *)(r->data);
}
int cache_insert(struct font_glyph *g)
{
struct font_glyph *spot = NULL;
uint32_t x = 0;
// find an open spot in the cache
// TODO: use __builtin_clz to speed this up
for (; x < CACHE_SIZE; x++) {
// printf("test: %d\n", x);
if (!B_TEST(x))
break;
}
// allocation in cache failed
if (B_TEST(x))
return -1;
set_bit(x);
spot = &cache_array[x];
*spot = *g;
/*
for (int i = 0; i < _BSIZE; i++) {
//print_byte(bitmap[i]);
//print_byte(bitmap[i]>>8);
//print_byte(bitmap[i]>>16);
//print_byte(bitmap[i]>>24);
//print_byte(bitmap[i]>>32);
//print_byte(bitmap[i]>>40);
//print_byte(bitmap[i]>>48);
//print_byte(bitmap[i]>>56);
printf("%lx", bitmap[i]);
}
printf("\n");
*/
struct hm_entry e = { .code = g->codepoint, .data = spot};
if (!hm_insert(hash_table, &e))
return -1;
return 0;
}

10
text_rendering/cache.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef _CACHE_H
#define _CACHE_H
void cache_init(void);
void cache_destroy(void);
struct font_glyph * cache_get(unsigned int code);
int cache_insert(struct font_glyph *g);
#endif

66
text_rendering/font.c Normal file
View file

@ -0,0 +1,66 @@
#define _POSIX_C_SOURCE 200809l
#define STB_TRUETYPE_IMPLEMENTATION
#define STBTT_STATIC
#include <grapheme.h>
#include "stb_truetype.h"
#include "util.h"
#include "font.h"
#define UTF8(c) (c&0x80)
// Generates a cached atlas of font glyphs encoded usign a signed distance field
// https://www.youtube.com/watch?v=1b5hIMqz_wM
// https://github.com/pjako/msdf_c
// this way the texture atlas for the font will be bigger but we save up the space
// needed for rendering the font in multiple sizes
struct font_atlas {
unsigned int glyphs, width, height;
unsigned char *atlas;
unsigned int glyph_max_w, glyph_max_h;
struct {
stbtt_fontinfo info;
float scale;
} stb;
int file_size;
unsigned char *file;
};
// loads a font into memory, storing all the ASCII characters in the atlas
int load_font(struct font_atlas *atlas, const char *path, int height)
{
if (!atlas || !path)
return -1;
dump_file(path, &(atlas->file), &(atlas->file_size));
stbtt_InitFont(&(atlas->stb.info), atlas->file, 0);
atlas->stb.scale = stbtt_ScaleForPixelHeight(&(atlas->stb.info), height);
int ascent, descent, linegap, baseline;
int x0,y0,x1,y1;
stbtt_GetFontVMetrics(&(atlas->stb.info), &ascent, &descent, &linegap);
stbtt_GetFontBoundingBox(&(atlas->stb.info), &x0, &y0, &x1, &y1);
baseline = atlas->stb.scale * -y0;
atlas->glyph_max_w = (atlas->stb.scale*x1) - (atlas->stb.scale*x0);
atlas->glyph_max_h = (baseline+atlas->stb.scale*y1) - (baseline+atlas->stb.scale*y0);
atlas->atlas = emalloc(atlas->glyph_max_w*atlas->glyph_max_h*CACHE_SIZE);
return 0;
}
int free_font(struct font_atlas *atlas)
{
efree(atlas->atlas);
efree(atlas->file);
return 0;
}

40
text_rendering/font.h Normal file
View file

@ -0,0 +1,40 @@
#ifndef _FONT_H
#define _FONT_H
#define CACHE_SIZE 512
/* width and height of a glyph contain the kering advance
* (u,v)
* +----------*---+ -
* | .ii. | | ^
* | @@@@@@. |<->| |
* | V@Mio@@o |adv| |
* | :i. V@V | | |
* | :oM@@M | | |
* | :@@@MM@M | | |
* | @@o o@M | | |
* |:@@. M@M | | |
* | @@@o@@@@ | | |
* | :M@@V:@@.| | v
* +----------*---+ -
* |<------------->|
* w
*/
struct font_glyph {
unsigned int codepoint;
unsigned int u, v, w, h;
};
struct font_atlas;
int load_font(struct font_atlas *atlas, const char *path, int height);
int free_font(struct font_atlas *atlas);
void cache_init(void);
void cache_destroy(void);
struct font_glyph * cache_get(unsigned int code);
int cache_insert(struct font_glyph *g);
#endif

76
text_rendering/hash.c Normal file
View file

@ -0,0 +1,76 @@
#define _POSIX_C_SOURCE 200809l
#define _DEFAULT_SOURCE
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "hash.h"
#define MAXSIZE 4096
static unsigned int hash(unsigned int code)
{
// identity map the ascii range
if (code < 128) return code;
return (uint32_t)((uint64_t)(code*2654435761)>>32);
}
struct hm_ref * hm_create(unsigned int size)
{
if (!size || size > MAXSIZE)
return NULL;
// round to the greater power of two
size = 1<<__builtin_clz(size);
// FIXME: check for intger overflow here
struct hm_ref *h = malloc(sizeof(struct hm_ref)+sizeof(struct hm_entry)*size);
if (h) {
h->items = 0;
h->size = size;
memset(h->bucket, 0, sizeof(struct hm_ref)*size);
}
return h;
}
void hm_destroy(struct hm_ref *hm)
{
free(hm);
}
static struct hm_entry * lookup(struct hm_ref *hm, unsigned int code)
{
// fast modulo operation for power-of-2 size
unsigned int mask = hm->size - 1;
unsigned int i = hash(code);
for (unsigned int j = 1; ; i += j++) {
if (!hm->bucket[i&mask].code || hm->bucket[i].code == code)
return &(hm->bucket[i&mask]);
}
return NULL;
}
struct hm_entry * hm_search(struct hm_ref *hm, unsigned int code)
{
struct hm_entry *r = lookup(hm, code);
if (r) {
if (r->code == code)
return r;
return NULL;
}
return r;
}
struct hm_entry * hm_insert(struct hm_ref *hm, struct hm_entry *entry)
{
struct hm_entry *r = lookup(hm, entry->code);
if (r) *r = *entry;
return r;
}

21
text_rendering/hash.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef _HASH_H
#define _HASH_H
struct hm_entry {
unsigned long code;
void *data;
};
struct hm_ref {
unsigned int items, size;
struct hm_entry bucket[];
};
struct hm_ref * hm_create(unsigned int size);
void hm_destroy(struct hm_ref *hm);
struct hm_entry * hm_search(struct hm_ref *hm, unsigned int code);
struct hm_entry * hm_insert(struct hm_ref *hm, struct hm_entry *entry);
#endif

35
text_rendering/main.c Normal file
View file

@ -0,0 +1,35 @@
#include <stdlib.h>
#include <stdio.h>
#include <grapheme.h>
#include "cache.h"
#include "font.h"
int main(void)
{
cache_init();
struct font_glyph *g, b;
b.codepoint = 'a';
b.u = b.v = 10;
b.w = b.h = 20;
g = cache_get('a');
if (!g) printf("no element\n");
g = cache_get(0xa3c0);
if (!g) printf("not present\n");
const char *s = "κόσμε ciao mamma @à``²`²aas³³²";
size_t ret, off;
uint_least32_t cp;
for (off = 0; (ret = grapheme_decode_utf8(s+off, SIZE_MAX, &cp)) > 0 && cp != 0; off += ret) {
printf("%.*s (%d) -> %d\n", (int)ret, s+off, (int)ret, cp);
b.codepoint = cp;
if (cache_insert(&b)) printf("failed insert %d\n", b.codepoint);
if ((g = cache_get(cp))) printf("got %d\n", g->codepoint);
}
cache_destroy();
return 0;
}

File diff suppressed because it is too large Load diff

BIN
text_rendering/test Executable file

Binary file not shown.

98
text_rendering/util.c Normal file
View file

@ -0,0 +1,98 @@
#define _POSIX_C_SOURCE 200809l
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <err.h>
#include "util.h"
const char *bit_rep[16] = {
[ 0] = "0000", [ 1] = "0001", [ 2] = "0010", [ 3] = "0011",
[ 4] = "0100", [ 5] = "0101", [ 6] = "0110", [ 7] = "0111",
[ 8] = "1000", [ 9] = "1001", [10] = "1010", [11] = "1011",
[12] = "1100", [13] = "1101", [14] = "1110", [15] = "1111",
};
void * emalloc(unsigned long int size)
{
void *r = malloc(size);
if (!r)
err(EXIT_FAILURE, "malloc() of size %ld", size);
return r;
}
void * ecalloc(unsigned long int nmemb, unsigned long int size)
{
void *r = calloc(nmemb, size);
if (!r)
err(EXIT_FAILURE, "calloc() of size %ld, nmemb %ld", size, nmemb);
return r;
}
void efree(void *ptr)
{
if (ptr)
free(ptr);
}
void map_file(const unsigned char **str, int *size, const char *path)
{
if (!path) {
errno = EINVAL;
err(EXIT_FAILURE, "NULL filename");
}
FILE *fp = fopen(path, "r");
if (!fp)
err(EXIT_FAILURE, "Cannot open file %s", path);
*size = lseek(fileno(fp), 0, SEEK_END);
if (*size == (off_t)-1)
err(EXIT_FAILURE, "lseek() failed");
*str = mmap(0, *size, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
if (*str == (void*)-1)
err(EXIT_FAILURE, "mmap() failed");
if (fclose(fp))
err(EXIT_FAILURE, "Error closing file");
}
void dump_file(const char *path, unsigned char **buf, int *buf_len)
{
if (!path) {
errno = EINVAL;
err(EXIT_FAILURE, "NULL filename");
}
if (!buf) {
errno = EINVAL;
err(EXIT_FAILURE, "No buffer specified");
}
if (!buf_len) {
errno = EINVAL;
err(EXIT_FAILURE, "Nowhere to store buffer size");
}
FILE *fp = fopen(path, "r");
if (!fp)
err(EXIT_FAILURE, "Cannot open file %s", path);
*buf_len = lseek(fileno(fp), 0, SEEK_END);
if (*buf_len == (off_t)-1)
err(EXIT_FAILURE, "lseek() failed");
*buf = emalloc(*buf_len);
*buf_len = fread(*buf, 1, *buf_len, fp);
if (fclose(fp))
err(EXIT_FAILURE, "Error closing file");
}
void print_byte(unsigned char byte)
{
printf("%s%s", bit_rep[byte >> 4], bit_rep[byte & 0x0F]);
}

13
text_rendering/util.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef _UTIL_H
#define _UTIL_H
void * emalloc(unsigned long int size);
void * ecalloc(unsigned long int nmemb, unsigned long int size);
void efree(void *ptr);
void map_file(const unsigned char **str, int *size, const char *path);
void dump_file(const char *path, unsigned char **buf, int *buf_len);
void print_byte(unsigned char byte);
#endif