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