bb
This commit is contained in:
parent
a32b211d20
commit
5018ccbbb0
4
RENDERER
Normal file
4
RENDERER
Normal file
@ -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
|
106
text_rendering/cache.c
Normal file
106
text_rendering/cache.c
Normal 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
10
text_rendering/cache.h
Normal 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
66
text_rendering/font.c
Normal 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
40
text_rendering/font.h
Normal 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
76
text_rendering/hash.c
Normal 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
21
text_rendering/hash.h
Normal 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
35
text_rendering/main.c
Normal 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;
|
||||
}
|
5077
text_rendering/stb_truetype.h
Normal file
5077
text_rendering/stb_truetype.h
Normal file
File diff suppressed because it is too large
Load Diff
BIN
text_rendering/test
Executable file
BIN
text_rendering/test
Executable file
Binary file not shown.
98
text_rendering/util.c
Normal file
98
text_rendering/util.c
Normal 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
13
text_rendering/util.h
Normal 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
|
3
ugui.c
3
ugui.c
@ -212,6 +212,9 @@ int crop_rect(ug_rect_t *ra, ug_rect_t *rb)
|
||||
*=============================================================================*/
|
||||
|
||||
|
||||
// TODO: when pushing onto the stack calculate the incremental hash of the whole
|
||||
// stack instead of calculating it at the end, this saves us one last scroll
|
||||
// trought the draw stack
|
||||
static void push_rect_command(ug_ctx_t *ctx, const ug_rect_t *rect, ug_color_t color)
|
||||
{
|
||||
ug_cmd_t *c;
|
||||
|
Loading…
Reference in New Issue
Block a user