1 #include "build.h"
2
3 #if USE_POLYMOST && USE_OPENGL
4
5 #include "polymosttexcache.h"
6 #include "baselayer.h"
7 #include "glbuild.h"
8 #include "hightile_priv.h"
9 #include "polymosttex_priv.h"
10
11 /*
12 PolymostTex Cache file formats
13
14 INDEX (texture.cacheindex):
15 signature "PolymostTexIndx"
16 version CACHEVER
17 ENTRIES...
18 filename char[BMAX_PATH]
19 effects int32
20 flags int32 PTH_CLAMPED
21 offset int32 Offset from the start of the STORAGE file
22 mtime int32 When kplib can return mtimes from ZIPs, this will be used to spot stale entries
23
24 STORAGE (texture.cache):
25 signature "PolymostTexStor"
26 version CACHEVER
27 ENTRIES...
28 tsizx int32 Unpadded dimensions
29 tsizy int32
30 flags int32 PTH_CLAMPED | PTH_HASALPHA
31 format int32 OpenGL compressed format code
32 nmipmaps int32 The number of mipmaps following
33 MIPMAPS...
34 sizx int32 Padded dimensions
35 sizy int32
36 length int32
37 data char[length]
38
39 All multibyte values are little-endian.
40 */
41
42 struct PTCacheIndex_typ {
43 char * filename;
44 int effects;
45 int flags;
46 off_t offset;
47 struct PTCacheIndex_typ * next;
48 };
49 typedef struct PTCacheIndex_typ PTCacheIndex;
50 #define PTCACHEHASHSIZ 512
51 static PTCacheIndex * cachehead[PTCACHEHASHSIZ]; // will be initialized 0 by .bss segment
52
53 static const char * CACHEINDEXFILE = "texture.cacheindex";
54 static const char * CACHESTORAGEFILE = "texture.cache";
55 static const int CACHEVER = 0;
56
57 static int cachedisabled = 0, cachereplace = 0;
58
gethashhead(const char * filename)59 static unsigned int gethashhead(const char * filename)
60 {
61 // implements the djb2 hash, constrained to the hash table size
62 // http://www.cse.yorku.ca/~oz/hash.html
63 unsigned long hash = 5381;
64 int c;
65
66 while ((c = (unsigned char)*filename++)) {
67 hash = ((hash << 5) + hash) ^ c; /* hash * 33 ^ c */
68 }
69
70 return hash & (PTCACHEHASHSIZ-1);
71 }
72
73 /**
74 * Adds an item to the cachehead hash.
75 * @param filename
76 * @param effects
77 * @param flags
78 * @param offset
79 */
ptcache_addhash(const char * filename,int effects,int flags,off_t offset)80 static void ptcache_addhash(const char * filename, int effects, int flags, off_t offset)
81 {
82 unsigned int hash = gethashhead(filename);
83
84 // to reduce memory fragmentation we tack the filename onto the end of the block
85 PTCacheIndex * pci = (PTCacheIndex *) malloc(sizeof(PTCacheIndex) + strlen(filename) + 1);
86
87 pci->filename = (char *) pci + sizeof(PTCacheIndex);
88 strcpy(pci->filename, filename);
89 pci->effects = effects;
90 pci->flags = flags & (PTH_CLAMPED);
91 pci->offset = offset;
92 pci->next = cachehead[hash];
93
94 cachehead[hash] = pci;
95 }
96
97 /**
98 * Locates an item in the cachehead hash.
99 * @param filename
100 * @param effects
101 * @param flags
102 * @return the PTCacheIndex item, or null
103 */
ptcache_findhash(const char * filename,int effects,int flags)104 static PTCacheIndex * ptcache_findhash(const char * filename, int effects, int flags)
105 {
106 PTCacheIndex * pci;
107
108 pci = cachehead[ gethashhead(filename) ];
109 if (!pci) {
110 return 0;
111 }
112
113 flags &= PTH_CLAMPED;
114
115 while (pci) {
116 if (effects == pci->effects &&
117 flags == pci->flags &&
118 strcmp(pci->filename, filename) == 0) {
119 return pci;
120 }
121 pci = pci->next;
122 }
123
124 return 0;
125 }
126
127 /**
128 * Loads the cache index file into memory
129 */
PTCacheLoadIndex(void)130 void PTCacheLoadIndex(void)
131 {
132 FILE * fh = 0;
133 int8_t sig[16];
134 const int8_t indexsig[16] = { 'P','o','l','y','m','o','s','t','T','e','x','I','n','d','x',CACHEVER };
135 const int8_t storagesig[16] = { 'P','o','l','y','m','o','s','t','T','e','x','S','t','o','r',CACHEVER };
136
137 int8_t filename[BMAX_PATH+1];
138 int32_t effects;
139 int32_t flags;
140 int32_t offset;
141 int32_t mtime;
142 PTCacheIndex * pci;
143
144 int total = 0, dups = 0;
145 int haveindex = 0, havestore = 0;
146
147 memset(filename, 0, sizeof(filename));
148
149 // first, check the cache storage file's signature.
150 // we open for reading and writing to test permission
151 fh = fopen(CACHESTORAGEFILE, "r+b");
152 if (fh) {
153 havestore = 1;
154
155 if (fread(sig, 16, 1, fh) != 1 || memcmp(sig, storagesig, 16)) {
156 cachereplace = 1;
157 }
158 fclose(fh);
159 } else {
160 if (errno == ENOENT) {
161 // file doesn't exist, which is fine
162 ;
163 } else {
164 buildprintf("PolymostTexCache: error opening %s, texture cache disabled\n", CACHESTORAGEFILE);
165 cachedisabled = 1;
166 return;
167 }
168 }
169
170 // next, check the index
171 fh = fopen(CACHEINDEXFILE, "r+b");
172 if (fh) {
173 haveindex = 1;
174
175 if (fread(sig, 16, 1, fh) != 1 || memcmp(sig, indexsig, 16)) {
176 cachereplace = 1;
177 }
178 } else {
179 if (errno == ENOENT) {
180 // file doesn't exist, which is fine
181 return;
182 } else {
183 buildprintf("PolymostTexCache: error opening %s, texture cache disabled\n", CACHEINDEXFILE);
184 cachedisabled = 1;
185 return;
186 }
187 }
188
189 // if we're missing either the index or the store, but not both at the same
190 // time, the cache is broken and should be replaced
191 if ((!haveindex || !havestore) && !(!haveindex && !havestore)) {
192 cachereplace = 1;
193 }
194
195 if (cachereplace) {
196 buildprintf("PolymostTexCache: texture cache will be replaced\n");
197 if (fh) {
198 fclose(fh);
199 }
200 return;
201 }
202
203 // now that the index is sitting at the first entry, load everything
204 while (!feof(fh)) {
205 if (fread(filename, BMAX_PATH, 1, fh) != 1 && feof(fh)) {
206 break;
207 }
208 if (fread(&effects, 4, 1, fh) != 1 ||
209 fread(&flags, 4, 1, fh) != 1 ||
210 fread(&offset, 4, 1, fh) != 1 ||
211 fread(&mtime, 4, 1, fh) != 1) {
212 // truncated entry, so throw the whole cache away
213 buildprintf("PolymostTexCache: corrupt texture cache index detected, cache will be replaced\n");
214 cachereplace = 1;
215 PTCacheUnloadIndex();
216 break;
217 }
218
219 effects = B_LITTLE32(effects);
220 flags = B_LITTLE32(flags);
221 offset = B_LITTLE32(offset);
222 mtime = B_LITTLE32(mtime);
223
224 pci = ptcache_findhash((char *) filename, (int) effects, (int) flags);
225 if (pci) {
226 // superseding an old hash entry
227 pci->offset = (off_t) offset;
228 dups++;
229 } else {
230 ptcache_addhash((char *) filename, (int) effects, (int) flags, (off_t) offset);
231 }
232 total++;
233 }
234
235 fclose(fh);
236
237 buildprintf("PolymostTexCache: cache index loaded (%d entries, %d old entries skipped)\n", total, dups);
238 }
239
240 /**
241 * Unloads the cache index from memory
242 */
PTCacheUnloadIndex(void)243 void PTCacheUnloadIndex(void)
244 {
245 PTCacheIndex * pci, * next;
246 int i;
247
248 for (i = 0; i < PTCACHEHASHSIZ; i++) {
249 pci = cachehead[i];
250 while (pci) {
251 next = pci->next;
252 // we needn't free pci->filename since it was alloced with pci
253 free(pci);
254 pci = next;
255 }
256 cachehead[i] = 0;
257 }
258
259 buildprintf("PolymostTexCache: cache index unloaded\n");
260 }
261
262 /**
263 * Does the task of loading a tile from the cache
264 * @param offset the starting offset
265 * @return a PTCacheTile entry fully completed
266 */
ptcache_load(off_t offset)267 static PTCacheTile * ptcache_load(off_t offset)
268 {
269 int32_t tsizx, tsizy;
270 int32_t sizx, sizy;
271 int32_t flags;
272 int32_t format;
273 int32_t nmipmaps, i;
274 int32_t length;
275
276 PTCacheTile * tdef = 0;
277 FILE * fh;
278
279 if (cachereplace) {
280 // cache is in a broken state, so don't try loading
281 return 0;
282 }
283
284 fh = fopen(CACHESTORAGEFILE, "rb");
285 if (!fh) {
286 cachedisabled = 1;
287 buildprintf("PolymostTexCache: error opening %s, texture cache disabled\n", CACHESTORAGEFILE);
288 return 0;
289 }
290
291 fseek(fh, offset, SEEK_SET);
292
293 if (fread(&tsizx, 4, 1, fh) != 1 ||
294 fread(&tsizy, 4, 1, fh) != 1 ||
295 fread(&flags, 4, 1, fh) != 1 ||
296 fread(&format, 4, 1, fh) != 1 ||
297 fread(&nmipmaps, 4, 1, fh) != 1) {
298 // truncated entry, so throw the whole cache away
299 goto fail;
300 }
301
302 tsizx = B_LITTLE32(tsizx);
303 tsizy = B_LITTLE32(tsizy);
304 flags = B_LITTLE32(flags);
305 format = B_LITTLE32(format);
306 nmipmaps = B_LITTLE32(nmipmaps);
307
308 tdef = PTCacheAllocNewTile(nmipmaps);
309 tdef->tsizx = tsizx;
310 tdef->tsizy = tsizy;
311 tdef->flags = flags;
312 tdef->format = format;
313
314 for (i = 0; i < nmipmaps; i++) {
315 if (fread(&sizx, 4, 1, fh) != 1 ||
316 fread(&sizy, 4, 1, fh) != 1 ||
317 fread(&length, 4, 1, fh) != 1) {
318 // truncated entry, so throw the whole cache away
319 goto fail;
320 }
321
322 sizx = B_LITTLE32(sizx);
323 sizy = B_LITTLE32(sizy);
324 length = B_LITTLE32(length);
325
326 tdef->mipmap[i].sizx = sizx;
327 tdef->mipmap[i].sizy = sizy;
328 tdef->mipmap[i].length = length;
329 tdef->mipmap[i].data = (unsigned char *) malloc(length);
330
331 if (fread(tdef->mipmap[i].data, length, 1, fh) != 1) {
332 // truncated data
333 goto fail;
334 }
335 }
336
337 fclose(fh);
338
339 return tdef;
340 fail:
341 cachereplace = 1;
342 buildprintf("PolymostTexCache: corrupt texture cache detected, cache will be replaced\n");
343 PTCacheUnloadIndex();
344 fclose(fh);
345 if (tdef) {
346 PTCacheFreeTile(tdef);
347 }
348 return 0;
349 }
350
351 /**
352 * Loads a tile from the cache.
353 * @param filename the filename
354 * @param effects the effects bits
355 * @param flags the flags bits
356 * @return a PTCacheTile entry fully completed
357 */
PTCacheLoadTile(const char * filename,int effects,int flags)358 PTCacheTile * PTCacheLoadTile(const char * filename, int effects, int flags)
359 {
360 PTCacheIndex * pci;
361 PTCacheTile * tdef;
362
363 if (cachedisabled) {
364 return 0;
365 }
366
367 pci = ptcache_findhash(filename, effects, flags);
368 if (!pci) {
369 return 0;
370 }
371
372 tdef = ptcache_load(pci->offset);
373 if (tdef) {
374 tdef->filename = strdup(filename);
375 tdef->effects = effects;
376 }
377 return tdef;
378 }
379
380 /**
381 * Checks to see if a tile exists in the cache.
382 * @param filename the filename
383 * @param effects the effects bits
384 * @param flags the flags bits
385 * @return !0 if it exists
386 */
PTCacheHasTile(const char * filename,int effects,int flags)387 int PTCacheHasTile(const char * filename, int effects, int flags)
388 {
389 if (cachedisabled) {
390 return 0;
391 }
392
393 return (ptcache_findhash(filename, effects, flags) != 0);
394 }
395
396 /**
397 * Disposes of the resources allocated for a PTCacheTile
398 * @param tdef a PTCacheTile entry
399 */
PTCacheFreeTile(PTCacheTile * tdef)400 void PTCacheFreeTile(PTCacheTile * tdef)
401 {
402 int i;
403
404 if (tdef->filename) {
405 free(tdef->filename);
406 }
407 for (i = 0; i < tdef->nummipmaps; i++) {
408 if (tdef->mipmap[i].data) {
409 free(tdef->mipmap[i].data);
410 }
411 }
412 free(tdef);
413 }
414
415 /**
416 * Allocates the skeleton of a PTCacheTile entry for you to complete.
417 * Memory for filenames and mipmap data should be allocated using malloc()
418 * @param nummipmaps allocate mipmap entries for nummipmaps items
419 * @return a PTCacheTile entry
420 */
PTCacheAllocNewTile(int nummipmaps)421 PTCacheTile * PTCacheAllocNewTile(int nummipmaps)
422 {
423 int size = sizeof(PTCacheTile) + (nummipmaps-1) * sizeof(PTCacheTileMip);
424 PTCacheTile * tdef;
425
426 tdef = (PTCacheTile *) malloc(size);
427 memset(tdef, 0, size);
428
429 tdef->nummipmaps = nummipmaps;
430
431 return tdef;
432 }
433
434 /**
435 * Stores a PTCacheTile into the cache.
436 * @param tdef a PTCacheTile entry fully completed
437 * @return !0 on success
438 */
PTCacheWriteTile(PTCacheTile * tdef)439 int PTCacheWriteTile(PTCacheTile * tdef)
440 {
441 long i;
442 PTCacheIndex * pci;
443
444 FILE * fh;
445 off_t offset;
446 char createmode[] = "ab";
447
448 if (cachedisabled) {
449 return 0;
450 }
451
452 if (cachereplace) {
453 createmode[0] = 'w';
454 cachereplace = 0;
455 }
456
457 // 1. write the tile data to the storage file
458 fh = fopen(CACHESTORAGEFILE, createmode);
459 if (!fh) {
460 cachedisabled = 1;
461 buildprintf("PolymostTexCache: error opening %s, texture cache disabled\n", CACHESTORAGEFILE);
462 return 0;
463 }
464
465 // apparently opening in append doesn't actually put the
466 // file pointer at the end of the file like you would
467 // imagine, so the ftell doesn't return the length of the
468 // file like you'd expect it should
469 fseek(fh, 0, SEEK_END);
470 offset = ftell(fh);
471
472 if (offset == 0) {
473 // new file
474 const int8_t storagesig[16] = { 'P','o','l','y','m','o','s','t','T','e','x','S','t','o','r',CACHEVER };
475 if (fwrite(storagesig, 16, 1, fh) != 1) {
476 goto fail;
477 }
478
479 offset = 16;
480 }
481
482 {
483 int32_t tsizx, tsizy;
484 int32_t format, flags, nmipmaps;
485
486 tsizx = B_LITTLE32(tdef->tsizx);
487 tsizy = B_LITTLE32(tdef->tsizy);
488 flags = tdef->flags & (PTH_CLAMPED | PTH_HASALPHA);
489 flags = B_LITTLE32(flags);
490 format = B_LITTLE32(tdef->format);
491 nmipmaps = B_LITTLE32(tdef->nummipmaps);
492
493 if (fwrite(&tsizx, 4, 1, fh) != 1 ||
494 fwrite(&tsizy, 4, 1, fh) != 1 ||
495 fwrite(&flags, 4, 1, fh) != 1 ||
496 fwrite(&format, 4, 1, fh) != 1 ||
497 fwrite(&nmipmaps, 4, 1, fh) != 1) {
498 goto fail;
499 }
500 }
501
502 for (i = 0; i < tdef->nummipmaps; i++) {
503 int32_t sizx, sizy;
504 int32_t length;
505
506 sizx = B_LITTLE32(tdef->mipmap[i].sizx);
507 sizy = B_LITTLE32(tdef->mipmap[i].sizy);
508 length = B_LITTLE32(tdef->mipmap[i].length);
509
510 if (fwrite(&sizx, 4, 1, fh) != 1 ||
511 fwrite(&sizy, 4, 1, fh) != 1 ||
512 fwrite(&length, 4, 1, fh) != 1) {
513 goto fail;
514 }
515
516 if (fwrite(tdef->mipmap[i].data, tdef->mipmap[i].length, 1, fh) != 1) {
517 // truncated data
518 goto fail;
519 }
520 }
521
522 fclose(fh);
523
524 // 2. append to the index
525 fh = fopen(CACHEINDEXFILE, createmode);
526 if (!fh) {
527 cachedisabled = 1;
528 buildprintf("PolymostTexCache: error opening %s, texture cache disabled\n", CACHEINDEXFILE);
529 return 0;
530 }
531
532 fseek(fh, 0, SEEK_END);
533 if (ftell(fh) == 0) {
534 // new file
535 const int8_t indexsig[16] = { 'P','o','l','y','m','o','s','t','T','e','x','I','n','d','x',CACHEVER };
536 if (fwrite(indexsig, 16, 1, fh) != 1) {
537 goto fail;
538 }
539 }
540
541 {
542 int8_t filename[BMAX_PATH];
543 int32_t effects, offsett, flags, mtime;
544
545 strncpy((char *) filename, tdef->filename, sizeof(filename)-1);
546 filename[sizeof(filename)-1] = 0;
547 effects = B_LITTLE32(tdef->effects);
548 flags = tdef->flags & (PTH_CLAMPED); // we don't want the informational flags in the index
549 flags = B_LITTLE32(flags);
550 offsett = B_LITTLE32(offset);
551 mtime = 0;
552
553 if (fwrite(filename, sizeof(filename), 1, fh) != 1 ||
554 fwrite(&effects, 4, 1, fh) != 1 ||
555 fwrite(&flags, 4, 1, fh) != 1 ||
556 fwrite(&offsett, 4, 1, fh) != 1 ||
557 fwrite(&mtime, 4, 1, fh) != 1) {
558 goto fail;
559 }
560 }
561
562 fclose(fh);
563
564 // stow the data into the index in memory
565 pci = ptcache_findhash(tdef->filename, tdef->effects, tdef->flags);
566 if (pci) {
567 // superseding an old hash entry
568 pci->offset = offset;
569 } else {
570 ptcache_addhash(tdef->filename, tdef->effects, tdef->flags, offset);
571 }
572
573 return 1;
574 fail:
575 cachedisabled = 1;
576 buildprintf("PolymostTexCache: error writing to cache, texture cache disabled\n");
577 if (fh) fclose(fh);
578 return 0;
579 }
580
581 /**
582 * Forces the cache to be rebuilt.
583 */
PTCacheForceRebuild(void)584 void PTCacheForceRebuild(void)
585 {
586 PTCacheUnloadIndex();
587 cachedisabled = 0;
588 cachereplace = 1;
589 }
590
591 #endif //USE_OPENGL
592