master
parent
a32b211d20
commit
5018ccbbb0
@ -0,0 +1,4 @@ |
|||||||
|
Divide up the OpenGL renderer into three parts, one part that draws simple shapes, |
||||||
|
another that only draws text and one that is responsible for drawing icons and |
||||||
|
mapping sprites in general, this way all the texture atlases are separate and |
||||||
|
everything is done within 3 draw calls |
@ -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; |
||||||
|
} |
@ -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 |
@ -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; |
||||||
|
} |
@ -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 |
@ -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; |
||||||
|
} |
@ -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 |
@ -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
Binary file not shown.
@ -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]); |
||||||
|
} |
@ -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 |
Loading…
Reference in new issue