#include "fitz-base.h" #include "fitz-world.h" #include "fitz-draw.h" typedef struct fz_hash_s fz_hash; typedef struct fz_key_s fz_key; typedef struct fz_val_s fz_val; struct fz_glyphcache_s { int slots; int size; fz_hash *hash; fz_val *lru; unsigned char *buffer; int load; int used; }; struct fz_key_s { void *fid; int a, b; int c, d; unsigned short cid; unsigned char e, f; }; struct fz_hash_s { fz_key key; fz_val *val; }; struct fz_val_s { fz_hash *ent; unsigned char *samples; short w, h, x, y; int uses; }; static unsigned int hashkey(fz_key *key) { unsigned char *s = (unsigned char*)key; unsigned int hash = 0; unsigned int i; for (i = 0; i < sizeof(fz_key); i++) { hash += s[i]; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash; } fz_error * fz_newglyphcache(fz_glyphcache **arenap, int slots, int size) { fz_glyphcache *arena; arena = *arenap = fz_malloc(sizeof(fz_glyphcache)); if (!arena) return fz_outofmem; arena->slots = slots; arena->size = size; arena->hash = nil; arena->lru = nil; arena->buffer = nil; arena->hash = fz_malloc(sizeof(fz_hash) * slots); if (!arena->hash) goto cleanup; arena->lru = fz_malloc(sizeof(fz_val) * slots); if (!arena->lru) goto cleanup; arena->buffer = fz_malloc(size); if (!arena->buffer) goto cleanup; memset(arena->hash, 0, sizeof(fz_hash) * slots); memset(arena->lru, 0, sizeof(fz_val) * slots); memset(arena->buffer, 0, size); arena->load = 0; arena->used = 0; return nil; cleanup: fz_free(arena->hash); fz_free(arena->lru); fz_free(arena->buffer); fz_free(arena); return fz_outofmem; } void fz_dropglyphcache(fz_glyphcache *arena) { fz_free(arena->hash); fz_free(arena->lru); fz_free(arena->buffer); fz_free(arena); } static int hokay = 0; static int hcoll = 0; static int hdist = 0; static int coos = 0; static int covf = 0; static int ghits = 0; static int gmisses = 0; static fz_val * hashfind(fz_glyphcache *arena, fz_key *key) { fz_hash *tab = arena->hash; int pos = hashkey(key) % arena->slots; while (1) { if (!tab[pos].val) return nil; if (memcmp(key, &tab[pos].key, sizeof (fz_key)) == 0) return tab[pos].val; pos = pos + 1; if (pos == arena->slots) pos = 0; } } static void hashinsert(fz_glyphcache *arena, fz_key *key, fz_val *val) { fz_hash *tab = arena->hash; int pos = hashkey(key) % arena->slots; int didcoll = 0; while (1) { if (!tab[pos].val) { tab[pos].key = *key; tab[pos].val = val; tab[pos].val->ent = &tab[pos]; if (didcoll) hcoll ++; else hokay ++; hdist += didcoll; return; } pos = pos + 1; if (pos == arena->slots) pos = 0; didcoll ++; } } static void hashremove(fz_glyphcache *arena, fz_key *key) { fz_hash *tab = arena->hash; unsigned int pos = hashkey(key) % arena->slots; unsigned int hole; unsigned int look; unsigned int code; while (1) { if (!tab[pos].val) return; /* boo hoo! tried to remove non-existant key */ if (memcmp(key, &tab[pos].key, sizeof (fz_key)) == 0) { tab[pos].val = nil; hole = pos; look = hole + 1; if (look == arena->slots) look = 0; while (tab[look].val) { code = (hashkey(&tab[look].key) % arena->slots); if ((code <= hole && hole < look) || (look < code && code <= hole) || (hole < look && look < code)) { tab[hole] = tab[look]; tab[hole].val->ent = &tab[hole]; tab[look].val = nil; hole = look; } look = look + 1; if (look == arena->slots) look = 0; } return; } pos = pos + 1; if (pos == arena->slots) pos = 0; } } void fz_debugglyphcache(fz_glyphcache *arena) { printf("cache load %d / %d (%d / %d bytes)\n", arena->load, arena->slots, arena->used, arena->size); printf("no-colliders: %d colliders: %d\n", hokay, hcoll); printf("avg dist: %d / %d: %g\n", hdist, hcoll, (double)hdist / hcoll); printf("out-of-space evicts: %d\n", coos); printf("out-of-hash evicts: %d\n", covf); printf("hits = %d misses = %d ratio = %g\n", ghits, gmisses, (float)ghits / (ghits + gmisses)); /* int i; for (i = 0; i < arena->slots; i++) { if (!arena->hash[i].val) printf("glyph % 4d: empty\n", i); else { fz_key *k = &arena->hash[i].key; fz_val *b = arena->hash[i].val; printf("glyph % 4d: %p %d [%g %g %g %g + %d %d] " "-> [%dx%d %d,%d]\n", i, k->fid, k->cid, k->a / 65536.0, k->b / 65536.0, k->c / 65536.0, k->d / 65536.0, k->e, k->f, b->w, b->h, b->x, b->y); } } for (i = 0; i < arena->load; i++) printf("lru %04d: glyph %d (%d)\n", i, arena->lru[i].ent - arena->hash, arena->lru[i].uses); */ } static void bubble(fz_glyphcache *arena, int i) { fz_val tmp; if (i == 0 || arena->load < 2) return; tmp = arena->lru[i - 1]; arena->lru[i - 1] = arena->lru[i]; arena->lru[i] = tmp; arena->lru[i - 1].ent->val = &arena->lru[i - 1]; arena->lru[i].ent->val = &arena->lru[i]; } static void evictlast(fz_glyphcache *arena) { fz_val *lru = arena->lru; unsigned char *s, *e; int i, k; fz_key key; if (arena->load == 0) return; k = arena->load - 1; s = lru[k].samples; e = s + lru[k].w * lru[k].h; /* pack buffer to fill hole */ memmove(s, e, arena->buffer + arena->used - e); memset(arena->buffer + arena->used - (e - s), 0, e - s); arena->used -= e - s; /* update lru pointers */ for (i = 0; i < k; i++) /* XXX this is DOG slow! XXX */ if (lru[i].samples >= e) lru[i].samples -= e - s; /* remove hash entry */ key = lru[k].ent->key; hashremove(arena, &key); arena->load --; } static void evictall(fz_glyphcache *arena) { //printf("zap!\n"); memset(arena->hash, 0, sizeof(fz_hash) * arena->slots); memset(arena->lru, 0, sizeof(fz_val) * arena->slots); memset(arena->buffer, 0, arena->size); arena->load = 0; arena->used = 0; } fz_error * fz_renderglyph(fz_glyphcache *arena, fz_glyph *glyph, fz_font *font, int cid, fz_matrix ctm) { fz_error *error; fz_key key; fz_val *val; int size; key.fid = font; key.cid = cid; key.a = ctm.a * 65536; key.b = ctm.b * 65536; key.c = ctm.c * 65536; key.d = ctm.d * 65536; key.e = (ctm.e - fz_floor(ctm.e)) * 256; key.f = (ctm.f - fz_floor(ctm.f)) * 256; val = hashfind(arena, &key); if (val) { val->uses ++; glyph->w = val->w; glyph->h = val->h; glyph->x = val->x; glyph->y = val->y; glyph->samples = val->samples; bubble(arena, val - arena->lru); ghits++; return nil; } gmisses++; ctm.e = fz_floor(ctm.e) + key.e / 256.0; ctm.f = fz_floor(ctm.f) + key.f / 256.0; error = font->render(glyph, font, cid, ctm); if (error) return error; size = glyph->w * glyph->h; if (size > arena->size / 6) return nil; while (arena->load > arena->slots * 75 / 100) { covf ++; evictall(arena); } while (arena->used + size >= arena->size) { coos ++; evictall(arena); } val = &arena->lru[arena->load++]; val->uses = 0; val->w = glyph->w; val->h = glyph->h; val->x = glyph->x; val->y = glyph->y; val->samples = arena->buffer + arena->used; arena->used += size; memcpy(val->samples, glyph->samples, glyph->w * glyph->h); glyph->samples = val->samples; hashinsert(arena, &key, val); return nil; }