1 #ifdef USE_OPENGL
2 
3 #include "baselayer.h"
4 #include "build.h"
5 #include "lz4.h"
6 #include "hightile.h"
7 #include "polymost.h"
8 #include "texcache.h"
9 #include "dxtfilter.h"
10 #include "scriptfile.h"
11 #include "xxhash.h"
12 #include "kplib.h"
13 
14 #include "vfs.h"
15 
16 #include <fcntl.h>
17 #ifdef _WIN32
18 # include <io.h>
19 #else
20 # include <unistd.h>
21 #endif
22 #include <sys/stat.h>
23 
24 #define CLEAR_GL_ERRORS() while(glGetError() != GL_NO_ERROR) { }
25 #define TEXCACHE_FREEBUFS() { Xfree(pic), Xfree(packbuf), Xfree(midbuf); }
26 
27 globaltexcache texcache;
28 
29 char TEXCACHEFILE[BMAX_PATH] = "texturecache";
30 
31 static const char *texcache_errors[TEXCACHEERRORS] = {
32     "no error",
33     "out of memory!",
34     "read too few bytes from cache file",
35     "dedxtfilter failed",
36     "glCompressedTexImage2DARB failed",
37     "glGetTexLevelParameteriv failed",
38 };
39 
40 void (*gloadtile_n64)(int32_t dapic, int32_t dapal, int32_t tintpalnum, int32_t dashade, int32_t dameth, pthtyp *pth, int32_t doalloc) = nullptr;
41 
texcache_tryart(int32_t const dapicnum,int32_t const dapalnum,int32_t const dashade,int32_t dameth)42 static pthtyp *texcache_tryart(int32_t const dapicnum, int32_t const dapalnum, int32_t const dashade, int32_t dameth)
43 {
44     const int32_t j = dapicnum&(GLTEXCACHEADSIZ-1);
45     pthtyp *pth;
46     int32_t tintpalnum = -1;
47     int32_t searchpalnum = dapalnum;
48     polytintflags_t const tintflags = hictinting[dapalnum].f;
49 
50     if (tintflags & (HICTINT_USEONART|HICTINT_ALWAYSUSEART))
51     {
52         tintpalnum = dapalnum;
53         dameth &= ~DAMETH_INDEXED;
54         if (!(tintflags & HICTINT_APPLYOVERPALSWAP))
55             searchpalnum = 0;
56     }
57 
58     if (dameth & DAMETH_N64)
59     {
60         // load from art
61         for (pth=texcache.list[j]; pth; pth=pth->next)
62             if (pth->picnum == dapicnum && (pth->flags & PTH_N64)
63                 && (pth->flags & (PTH_CLAMPED | PTH_N64_INTENSIVITY | PTH_N64_SCALED)) == (TO_PTH_CLAMPED(dameth) | TO_PTH_N64_INTENSIVITY(dameth) | TO_PTH_N64_SCALED(dameth)))
64             {
65                 if (pth->flags & PTH_INVALIDATED)
66                 {
67                     pth->flags &= ~PTH_INVALIDATED;
68                     gloadtile_n64(dapicnum, searchpalnum, tintpalnum, dashade, dameth, pth, 0);
69                     pth->palnum = dapalnum;
70                 }
71 
72                 return pth;
73             }
74 
75         pth = (pthtyp *)Xcalloc(1,sizeof(pthtyp));
76 
77         gloadtile_n64(dapicnum, searchpalnum, tintpalnum, dashade, dameth, pth, 1);
78 
79         pth->palnum = dapalnum;
80         pth->next = texcache.list[j];
81         texcache.list[j] = pth;
82 
83         return pth;
84     }
85 
86     // load from art
87     for (pth=texcache.list[j]; pth; pth=pth->next)
88         if (pth->picnum == dapicnum &&
89             (dameth & DAMETH_INDEXED ? (pth->flags & PTH_INDEXED) &&
90                                        (pth->flags & PTH_CLAMPED) == TO_PTH_CLAMPED(dameth) :
91                  (pth->palnum == dapalnum && pth->shade == dashade &&
92                  !(pth->flags & PTH_INDEXED) &&
93                  (pth->flags & (PTH_CLAMPED | PTH_HIGHTILE | PTH_NOTRANSFIX)) ==
94                      (TO_PTH_CLAMPED(dameth) | TO_PTH_NOTRANSFIX(dameth)) &&
95                  polymost_want_npotytex(dameth, tilesiz[dapicnum].y) == !!(pth->flags&PTH_NPOTWALL))))
96         {
97             if (pth->flags & PTH_INVALIDATED)
98             {
99                 pth->flags &= ~PTH_INVALIDATED;
100                 gloadtile_art(dapicnum, searchpalnum, tintpalnum, dashade, dameth, pth, 0);
101                 pth->palnum = dapalnum;
102             }
103 
104             return pth;
105         }
106 
107     pth = (pthtyp *)Xcalloc(1,sizeof(pthtyp));
108 
109     gloadtile_art(dapicnum, searchpalnum, tintpalnum, dashade, dameth, pth, 1);
110 
111     pth->palnum = dapalnum;
112     pth->next = texcache.list[j];
113     texcache.list[j] = pth;
114 
115     return pth;
116 }
117 
texcache_fetchmulti(pthtyp * pth,hicreplctyp * si,int32_t dapicnum,int32_t dameth)118 pthtyp *texcache_fetchmulti(pthtyp *pth, hicreplctyp *si, int32_t dapicnum, int32_t dameth)
119 {
120     const int32_t j = dapicnum&(GLTEXCACHEADSIZ-1);
121     int32_t i;
122 
123     for (i = 0; i <= (GLTEXCACHEADSIZ - 1); i++)
124     {
125         const pthtyp *pth2;
126 
127         for (pth2=texcache.list[i]; pth2; pth2=pth2->next)
128         {
129             if (pth2->hicr && pth2->hicr->filename && si->filename && filnamcmp(pth2->hicr->filename, si->filename) == 0)
130             {
131                 Bmemcpy(pth, pth2, sizeof(pthtyp));
132                 pth->picnum = dapicnum;
133                 pth->flags = TO_PTH_CLAMPED(dameth) | TO_PTH_NOTRANSFIX(dameth) |
134                              PTH_HIGHTILE | (drawingskybox>0)*PTH_SKYBOX;
135                 if (pth2->flags & PTH_HASALPHA)
136                     pth->flags |= PTH_HASALPHA;
137                 pth->hicr = si;
138 
139                 pth->next = texcache.list[j];
140                 texcache.list[j] = pth;
141 
142                 return pth;
143             }
144         }
145     }
146 
147     return NULL;
148 }
149 
150 // <dashade>: ignored if not in Polymost+r_usetileshades
texcache_fetch(int32_t dapicnum,int32_t dapalnum,int32_t dashade,int32_t dameth)151 pthtyp *texcache_fetch(int32_t dapicnum, int32_t dapalnum, int32_t dashade, int32_t dameth)
152 {
153     const int32_t j = dapicnum & (GLTEXCACHEADSIZ - 1);
154     hicreplctyp *si = usehightile ? hicfindsubst(dapicnum, dapalnum, hictinting[dapalnum].f & HICTINT_ALWAYSUSEART) : NULL;
155 
156     if (drawingskybox && usehightile)
157         if ((si = hicfindskybox(dapicnum, dapalnum)) == NULL)
158             return NULL;
159 
160     if (!polymost_usetileshades() || videoGetRenderMode() != REND_POLYMOST)
161         dashade = 0;
162 
163     if (!si)
164     {
165         return (dapalnum >= (MAXPALOOKUPS - RESERVEDPALS) || hicprecaching) ?
166                 NULL : texcache_tryart(dapicnum, dapalnum, dashade, dameth);
167     }
168 
169     /* if palette > 0 && replacement found
170      *    no effects are applied to the texture
171      * else if palette > 0 && no replacement found
172      *    effects are applied to the palette 0 texture if it exists
173      */
174 
175     polytintflags_t const tintflags = hictinting[dapalnum].f;
176 
177     const int32_t checktintpal = (tintflags & HICTINT_APPLYOVERALTPAL) ? 0 : si->palnum;
178     const int32_t checkcachepal = ((tintflags & HICTINT_IN_MEMORY) || ((tintflags & HICTINT_APPLYOVERALTPAL) && si->palnum > 0)) ? dapalnum : si->palnum;
179 
180     // load a replacement
181     for (pthtyp *pth = texcache.list[j]; pth; pth = pth->next)
182     {
183         if (pth->picnum == dapicnum && pth->palnum == checkcachepal && (checktintpal > 0 ? 1 : (pth->effects == tintflags))
184             && (pth->flags & (PTH_CLAMPED | PTH_HIGHTILE | PTH_SKYBOX | PTH_NOTRANSFIX))
185                == (TO_PTH_CLAMPED(dameth) | TO_PTH_NOTRANSFIX(dameth) | PTH_HIGHTILE | (drawingskybox > 0) * PTH_SKYBOX)
186             && (drawingskybox > 0 ? (pth->skyface == drawingskybox) : 1))
187         {
188             if (pth->flags & PTH_INVALIDATED)
189             {
190                 pth->flags &= ~PTH_INVALIDATED;
191 
192                 int32_t tilestat = gloadtile_hi(dapicnum, dapalnum, drawingskybox, si, dameth, pth, 0,
193                                         (checktintpal > 0) ? 0 : tintflags);  // reload tile
194 
195                 if (!tilestat)
196                     continue;
197 
198                 if (tilestat == -2)  // bad filename
199                     hicclearsubst(dapicnum, dapalnum);
200 
201                 return (drawingskybox || hicprecaching) ? NULL : texcache_tryart(dapicnum, dapalnum, dashade, dameth);
202             }
203 
204             return pth;
205         }
206     }
207 
208     pthtyp *pth = (pthtyp *)Xcalloc(1, sizeof(pthtyp));
209 
210     // possibly fetch an already loaded multitexture :_)
211     if (dapalnum == DETAILPAL && texcache_fetchmulti(pth, si, dapicnum, dameth))
212         return pth;
213 
214     int32_t tilestat =
215     gloadtile_hi(dapicnum, dapalnum, drawingskybox, si, dameth, pth, 1, (checktintpal > 0) ? 0 : tintflags);
216 
217     if (!tilestat)
218     {
219         pth->next = texcache.list[j];
220         pth->palnum = checkcachepal;
221         texcache.list[j] = pth;
222         return pth;
223     }
224 
225     if (tilestat == -2)  // bad filename
226         hicclearsubst(dapicnum, dapalnum);
227 
228     Xfree(pth);
229 
230     return (drawingskybox || hicprecaching) ? NULL : texcache_tryart(dapicnum, dapalnum, dashade, dameth);
231 }
232 
texcache_closefiles(void)233 static void texcache_closefiles(void)
234 {
235     if (texcache.handle != -1)
236     {
237         Bclose(texcache.handle);
238         texcache.handle = -1;
239     }
240     MAYBE_FCLOSE_AND_NULL(texcache.index);
241 }
242 
texcache_freeptrs(void)243 void texcache_freeptrs(void)
244 {
245     texcache.entrybufsiz = 0;
246 
247     if (!texcache.entries)
248         return;
249 
250     for (bssize_t i = 0; i < texcache.numentries; i++)
251         if (texcache.entries[i])
252         {
253             for (bssize_t ii = texcache.numentries - 1; ii >= 0; ii--)
254                 if (i != ii && texcache.entries[ii] == texcache.entries[i])
255                 {
256                     /*OSD_Printf("removing duplicate cacheptr %d\n",ii);*/
257                     texcache.entries[ii] = NULL;
258                 }
259 
260             DO_FREE_AND_NULL(texcache.entries[i]->name);
261             DO_FREE_AND_NULL(texcache.entries[i]);
262         }
263 
264     DO_FREE_AND_NULL(texcache.entries);
265 }
266 
texcache_clearmemcache(void)267 static inline void texcache_clearmemcache(void)
268 {
269     DO_FREE_AND_NULL(texcache.buf);
270     texcache.memsize = -1;
271 }
272 
texcache_syncmemcache(void)273 void texcache_syncmemcache(void)
274 {
275     int32_t len = buildvfs_length(texcache.handle);
276 
277     if (!texcache.buf || texcache.handle == -1 || len <= (int32_t)texcache.memsize)
278         return;
279 
280     texcache.buf = (uint8_t *)Xrealloc(texcache.buf, len);
281 
282     if (!texcache.buf)
283     {
284         texcache_clearmemcache();
285         initprintf("Failed syncing memcache!\n");
286         glusememcache = 0;
287     }
288     else
289     {
290         initprintf("Syncing memcache\n");
291         Blseek(texcache.handle, texcache.memsize, BSEEK_SET);
292         if (Bread(texcache.handle, texcache.buf + texcache.memsize, len - texcache.memsize) != (bssize_t)(len-texcache.memsize))
293         {
294             initprintf("Failed reading texcache into memcache!\n");
295             texcache_clearmemcache();
296             glusememcache = 0;
297         }
298         else
299             texcache.memsize = len;
300     }
301 }
302 
texcache_init(void)303 void texcache_init(void)
304 {
305     if (!texcache.index)
306         texcache.handle = -1;
307 
308     texcache_closefiles();
309     texcache_clearmemcache();
310     texcache_freeptrs();
311 
312     texcache.current = texcache.first = (texcacheindex *)Xcalloc(1, sizeof(texcacheindex));
313     texcache.numentries = 0;
314 
315     //    Bmemset(&firstcacheindex, 0, sizeof(texcacheindex));
316     //    Bmemset(&cacheptrs[0], 0, sizeof(cacheptrs));
317 
318     texcache.hashes.size = TEXCACHEHASHSIZE;
319     hash_init(&texcache.hashes);
320 }
321 
texcache_deletefiles(void)322 static void texcache_deletefiles(void)
323 {
324     unlink(TEXCACHEFILE);
325     Bstrcpy(ptempbuf, TEXCACHEFILE);
326     Bstrcat(ptempbuf, ".index");
327     unlink(ptempbuf);
328 }
329 
texcache_enabled(void)330 int32_t texcache_enabled(void)
331 {
332 #if defined EDUKE32_GLES || !defined USE_GLEXT
333     return 0;
334 #else
335     if (!glinfo.texcompr || !glusetexcompr || !glusetexcache)
336         return 0;
337 
338     if (!texcache.index || texcache.handle < 0)
339     {
340         OSD_Printf("Warning: no active cache!\n");
341         return 0;
342     }
343 
344     return 1;
345 #endif
346 }
347 
texcache_openfiles(void)348 void texcache_openfiles(void)
349 {
350     Bstrcpy(ptempbuf, TEXCACHEFILE);
351     Bstrcat(ptempbuf, ".index");
352 
353     bool const texcache_exists = buildvfs_exists(ptempbuf);
354     texcache.index      = buildvfs_fopen_append(ptempbuf);
355     texcache.handle = Bopen(TEXCACHEFILE, BO_BINARY | BO_CREAT | BO_APPEND | BO_RDWR, BS_IREAD | BS_IWRITE);
356 
357     if (!texcache.index || texcache.handle == -1)
358     {
359         initprintf("Unable to open cache file \"%s\" or \"%s\": %s\n", TEXCACHEFILE, ptempbuf, strerror(errno));
360         texcache_closefiles();
361         glusetexcache = 0;
362         return;
363     }
364 
365     if (!texcache_exists)
366     {
367         buildvfs_fputstr(texcache.index, "// automatically generated by the engine, DO NOT MODIFY!\n");
368     }
369 
370     initprintf("Opened \"%s\" as cache file\n", TEXCACHEFILE);
371 }
372 
373 
texcache_checkgarbage(void)374 void texcache_checkgarbage(void)
375 {
376     if (!texcache_enabled())
377         return;
378 
379     texcache.current = texcache.first;
380 
381     int32_t bytes = 0;
382 
383     while (texcache.current->next)
384     {
385         bytes += texcache.current->len;
386         texcache.current = texcache.current->next;
387     }
388 
389     bytes = Blseek(texcache.handle, 0, BSEEK_END)-bytes;
390 
391     if (bytes)
392         initprintf("Cache contains %d bytes of garbage data\n", bytes);
393 }
394 
texcache_invalidate(void)395 void texcache_invalidate(void)
396 {
397 #ifdef DEBUGGINGAIDS
398     OSD_Printf("texcache_invalidate()\n");
399 #endif
400     r_downsizevar = r_downsize; // update the cvar representation when the menu changes r_downsize
401 
402     polymost_glreset();
403 
404     texcache_init();
405     texcache_deletefiles();
406     texcache_openfiles();
407 }
408 
texcache_loadoffsets(void)409 int texcache_loadoffsets(void)
410 {
411     Bstrcpy(ptempbuf, TEXCACHEFILE);
412     Bstrcat(ptempbuf, ".index");
413     scriptfile *script = scriptfile_fromfile(ptempbuf);
414 
415     if (!script) return -1;
416 
417     int32_t foffset, fsize;
418     char *fname;
419 
420     while (!scriptfile_eof(script))
421     {
422         if (scriptfile_getstring(script, &fname)) break;	// hashed filename
423         if (scriptfile_getnumber(script, &foffset)) break;	// offset in cache
424         if (scriptfile_getnumber(script, &fsize)) break;	// size
425 
426         int const i = hash_find(&texcache.hashes,fname);
427 
428         if (i > -1)
429         {
430             // update an existing entry
431             texcacheindex *t = texcache.entries[i];
432             t->offset = foffset;
433             t->len = fsize;
434             /*initprintf("%s %d got a match for %s offset %d\n",__FILE__, __LINE__, fname,foffset);*/
435         }
436         else
437         {
438             texcacheindex * const index = texcache.current;
439 
440             index->name   = Xstrdup(fname);
441             index->offset = foffset;
442             index->len    = fsize;
443             index->next   = (texcacheindex *) Xcalloc(1, sizeof(texcacheindex));
444             hash_add(&texcache.hashes, fname, texcache.numentries, 1);
445             if (++texcache.numentries > texcache.entrybufsiz)
446             {
447                 texcache.entrybufsiz += 512;
448                 texcache.entries = (texcacheindex **) Xrealloc(texcache.entries, sizeof(intptr_t) * texcache.entrybufsiz);
449             }
450             texcache.entries[texcache.numentries-1] = texcache.current;
451             texcache.current = index->next;
452         }
453     }
454 
455     scriptfile_close(script);
456     return 0;
457 }
458 
459 // Read from on-disk texcache or its in-memory cache.
texcache_readdata(void * outBuf,int32_t len)460 int texcache_readdata(void *outBuf, int32_t len)
461 {
462     const int32_t ofilepos = texcache.pos;
463 
464     texcache.pos += len;
465 
466     if (texcache.buf && texcache.memsize >= ofilepos + len)
467     {
468         //        initprintf("using memcache!\n");
469         Bmemcpy(outBuf, texcache.buf + ofilepos, len);
470         return 0;
471     }
472 
473     if (Blseek(texcache.handle, ofilepos, BSEEK_SET) != ofilepos ||
474         Bread(texcache.handle, outBuf, len) < len)
475         return 1;
476 
477     return 0;
478 }
479 
texcache_calcid(char * outbuf,const char * filename,const int32_t len,const int32_t dameth,const char effect)480 char const * texcache_calcid(char *outbuf, const char *filename, const int32_t len, const int32_t dameth, const char effect)
481 {
482     // Assert that BMAX_PATH is a multiple of 4 so that struct texcacheid_t
483     // gets no padding inserted by the compiler.
484     EDUKE32_STATIC_ASSERT((BMAX_PATH & 3) == 0);
485 
486     struct texcacheid_t {
487         int32_t len, method;
488         char effect, name[BMAX_PATH+3];  // +3: pad to a multiple of 4
489     } id = { len, dameth, effect, "" };
490 
491     Bstrcpy(id.name, filename);
492 
493     size_t const fnlen = Bstrlen(filename);
494     size_t idlen = Bstrlen(id.name);
495     while (idlen < BMAX_PATH - fnlen)
496     {
497         Bstrcat(id.name, filename);
498         idlen += fnlen;
499     }
500 
501     Bsprintf(outbuf, "%08x%08x%08x",
502              XXH32((uint8_t *)id.name, fnlen, TEXCACHEMAGIC[3]),
503              XXH32((uint8_t *)id.name, Bstrlen(id.name), TEXCACHEMAGIC[3]),
504              XXH32((uint8_t *)&id, sizeof(struct texcacheid_t), TEXCACHEMAGIC[3]));
505 
506     return outbuf;
507 }
508 
509 #define FAIL(x) { err = x; goto failure; }
510 
511 // returns 1 on success
texcache_readtexheader(char const * cacheid,texcacheheader * head,int32_t modelp)512 int texcache_readtexheader(char const * cacheid, texcacheheader *head, int32_t modelp)
513 {
514     if (!texcache_enabled())
515         return 0;
516 
517     int32_t i = hash_find(&texcache.hashes, cacheid);
518 
519     if (i < 0 || !texcache.entries[i])
520         return 0;  // didn't find it
521 
522     texcache.pos = texcache.entries[i]->offset;
523 //    initprintf("%s %d got a match for %s offset %d\n",__FILE__, __LINE__, cachefn,offset);
524 
525     int err = 0;
526 
527     if (texcache_readdata(head, sizeof(texcacheheader)))
528         FAIL(0);
529 
530     if (Bmemcmp(head->magic, TEXCACHEMAGIC, 4))
531         FAIL(1);
532 
533     // native (little-endian) -> internal
534     head->xdim    = B_LITTLE32(head->xdim);
535     head->ydim    = B_LITTLE32(head->ydim);
536     head->flags   = B_LITTLE32(head->flags);
537     head->quality = B_LITTLE32(head->quality);
538 
539     if (modelp && head->quality != r_downsize)
540         FAIL(2);
541     if ((head->flags & CACHEAD_COMPRESSED) && glusetexcache != 2)
542         FAIL(3);
543     if (!(head->flags & CACHEAD_COMPRESSED) && glusetexcache == 2)
544         FAIL(4);
545 
546     // handle nodownsize
547     if (!modelp && !(head->flags & CACHEAD_NODOWNSIZE) && head->quality != r_downsize)
548         return 0;
549 
550     if (gltexmaxsize && (head->xdim > (1<<gltexmaxsize) || head->ydim > (1<<gltexmaxsize)))
551         FAIL(5);
552     if (!glinfo.texnpot && (head->flags & CACHEAD_NONPOW2))
553         FAIL(6);
554 
555     return 1;
556 
557 failure:
558     {
559         static const char *error_msgs[] = {
560             "failed reading texture cache header",  // 0
561             "header magic string doesn't match",  // 1
562             "r_downsize doesn't match",  // 2  (skins only)
563             "compression doesn't match: cache contains compressed tex",  // 3
564             "compression doesn't match: cache contains uncompressed tex",  // 4
565             "texture in cache exceeds maximum supported size",  // 5
566             "texture in cache has non-power-of-two size, unsupported",  // 6
567         };
568 
569         initprintf("%s cache miss: %s\n", modelp?"Skin":"Texture", error_msgs[err]);
570     }
571 
572     return 0;
573 }
574 
575 #undef READTEXHEADER_FAILURE
576 
577 #if defined USE_GLEXT && !defined EDUKE32_GLES
578 
texcache_prewritetex(texcacheheader * head)579 void texcache_prewritetex(texcacheheader *head)
580 {
581     Bmemcpy(head->magic, TEXCACHEMAGIC, 4);   // sizes are set by caller
582 
583     if (glusetexcache == 2)
584         head->flags |= CACHEAD_COMPRESSED;
585 
586     // native -> external (little-endian)
587     head->xdim    = B_LITTLE32(head->xdim);
588     head->ydim    = B_LITTLE32(head->ydim);
589     head->flags   = B_LITTLE32(head->flags);
590     head->quality = B_LITTLE32(head->quality);
591 }
592 
593 #define WRITEX_FAIL_ON_ERROR() if (glGetError() != GL_NO_ERROR) goto failure
594 
texcache_writetex_fromdriver(char const * const cacheid,texcacheheader * head)595 void texcache_writetex_fromdriver(char const * const cacheid, texcacheheader *head)
596 {
597     if (!texcache_enabled()) return;
598 
599     GLint gi = GL_FALSE;
600     glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &gi);
601     if (gi != GL_TRUE)
602     {
603         static GLint glGetTexLevelParameterivOK = GL_TRUE;
604         if (glGetTexLevelParameterivOK == GL_TRUE)
605         {
606             OSD_Printf("Error: glGetTexLevelParameteriv returned GL_FALSE!\n");
607             glGetTexLevelParameterivOK = GL_FALSE;
608         }
609         return;
610     }
611 
612     texcache_prewritetex(head);
613     Blseek(texcache.handle, 0, BSEEK_END);
614     size_t const offset = Blseek(texcache.handle, 0, BSEEK_CUR);
615 
616     texcachepicture pict;
617 
618     char *pic     = nullptr;
619     char *packbuf = nullptr;
620     void *midbuf  = nullptr;
621     size_t alloclen = 0;
622 
623     //    OSD_Printf("Caching %s, offset 0x%x\n", cachefn, offset);
624     if (Bwrite(texcache.handle, head, sizeof(texcacheheader)) != sizeof(texcacheheader)) goto failure;
625 
626     CLEAR_GL_ERRORS();
627 
628     for (int level = 0, padx = 0, pady = 0; level == 0 || (padx > 1 || pady > 1); ++level)
629     {
630         glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_COMPRESSED, &gi);
631         WRITEX_FAIL_ON_ERROR();
632         if (gi != GL_TRUE)
633             goto failure;  // an uncompressed mipmap
634 
635         glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_INTERNAL_FORMAT, &gi);
636         WRITEX_FAIL_ON_ERROR();
637 
638 #if defined __APPLE__ && defined POLYMER
639         if (pr_ati_textureformat_one && gi == 1) gi = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
640 #endif
641         // native -> external (little endian)
642         pict.format = B_LITTLE32(gi);
643 
644         glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &gi);
645         WRITEX_FAIL_ON_ERROR();
646         padx      = gi;
647         pict.xdim = B_LITTLE32(gi);
648 
649         glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &gi);
650         WRITEX_FAIL_ON_ERROR();
651         pady      = gi;
652         pict.ydim = B_LITTLE32(gi);
653 
654         glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_BORDER, &gi);
655         WRITEX_FAIL_ON_ERROR();
656         pict.border = B_LITTLE32(gi);
657 
658         glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_DEPTH, &gi);
659         WRITEX_FAIL_ON_ERROR();
660         pict.depth = B_LITTLE32(gi);
661 
662         glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &gi);
663         WRITEX_FAIL_ON_ERROR();
664         uint32_t miplen = gi;
665         pict.size       = B_LITTLE32(gi);
666 
667         if (alloclen < miplen)
668         {
669             alloclen = miplen;
670             pic      = (char *)Xrealloc(pic, miplen);
671             packbuf  = (char *)Xrealloc(packbuf, miplen);
672             midbuf   = (void *)Xrealloc(midbuf, miplen);
673         }
674 
675         glGetCompressedTexImage(GL_TEXTURE_2D, level, pic);
676         WRITEX_FAIL_ON_ERROR();
677 
678         if (Bwrite(texcache.handle, &pict, sizeof(texcachepicture)) != sizeof(texcachepicture)) goto failure;
679         if (dxtfilter(texcache.handle, &pict, pic, midbuf, packbuf, miplen)) goto failure;
680     }
681 
682     texcache_postwritetex(cacheid, offset);
683     TEXCACHE_FREEBUFS();
684     return;
685 
686 failure:
687     initprintf("ERROR: cache failure!\n");
688     texcache.current->offset = 0;
689     Xfree(texcache.current->name);
690     TEXCACHE_FREEBUFS();
691 }
692 
693 #undef WRITEX_FAIL_ON_ERROR
694 
texcache_postwritetex(char const * const cacheid,int32_t const offset)695 void texcache_postwritetex(char const * const cacheid, int32_t const offset)
696 {
697     int32_t const i = hash_find(&texcache.hashes, cacheid);
698 
699     texcacheindex *t;
700 
701     if (i > -1)
702     {
703         // update an existing entry
704         t = texcache.entries[i];
705 
706         t->offset = offset;
707         t->len    = Blseek(texcache.handle, 0, BSEEK_CUR) - t->offset;
708         /*initprintf("%s %d got a match for %s offset %d\n",__FILE__, __LINE__, cachefn,offset);*/
709     }
710     else
711     {
712         t = texcache.current;
713 
714         Xfree(t->name);
715         t->name   = Xstrdup(cacheid);
716         t->offset = offset;
717         t->len    = Blseek(texcache.handle, 0, BSEEK_CUR) - t->offset;
718         t->next   = (texcacheindex *)Xcalloc(1, sizeof(texcacheindex));
719 
720         hash_add(&texcache.hashes, cacheid, texcache.numentries, 0);
721 
722         if (++texcache.numentries > texcache.entrybufsiz)
723         {
724             texcache.entrybufsiz += 512;
725             texcache.entries = (texcacheindex **)Xrealloc(texcache.entries, sizeof(intptr_t) * texcache.entrybufsiz);
726         }
727 
728         texcache.entries[texcache.numentries - 1] = t;
729         texcache.current = t->next;
730     }
731 
732     if (texcache.index)
733     {
734         char buf[64];
735         buildvfs_fputstrptr(texcache.index, t->name);
736         snprintf(buf, sizeof(buf), " %d %d\n", t->offset, t->len);
737         buildvfs_fputstrptr(texcache.index, buf);
738     }
739     else
740         OSD_Printf("wtf?\n");
741 }
742 
743 #endif
744 
texcache_setuptexture(int32_t * doalloc,GLuint * glpic)745 static void texcache_setuptexture(int32_t *doalloc, GLuint *glpic)
746 {
747     if (*doalloc&1)
748     {
749         glGenTextures(1, glpic);  //# of textures (make OpenGL allocate structure)
750         *doalloc |= 2;	// prevents glGenTextures being called again if we fail in here
751     }
752 
753     glBindTexture(GL_TEXTURE_2D, *glpic);
754 }
755 
texcache_loadmips(const texcacheheader * head,GLenum * glerr)756 static int32_t texcache_loadmips(const texcacheheader *head, GLenum *glerr)
757 {
758     texcachepicture pict;
759 
760     char *pic     = nullptr;
761     char *packbuf = nullptr;
762     void *midbuf  = nullptr;
763 
764     int32_t alloclen=0;
765 
766 #if !defined USE_GLEXT && defined EDUKE32_GLES
767     UNREFERENCED_PARAMETER(glerr);
768     UNREFERENCED_PARAMETER(head);
769 #endif
770 
771     for (bssize_t level = 0; level==0 || (pict.xdim > 1 || pict.ydim > 1); level++)
772     {
773         if (texcache_readdata(&pict, sizeof(texcachepicture)))
774         {
775             TEXCACHE_FREEBUFS();
776             return TEXCACHERR_BUFFERUNDERRUN;
777         }
778 
779         // external (little endian) -> native
780         pict.size   = B_LITTLE32(pict.size);
781         pict.format = B_LITTLE32(pict.format);
782         pict.xdim   = B_LITTLE32(pict.xdim);
783         pict.ydim   = B_LITTLE32(pict.ydim);
784         pict.border = B_LITTLE32(pict.border);
785         pict.depth  = B_LITTLE32(pict.depth);
786 
787         if (alloclen < pict.size)
788         {
789             alloclen = pict.size;
790             pic      = (char *)Xrealloc(pic, pict.size);
791             packbuf  = (char *)Xrealloc(packbuf, pict.size + 16);
792             midbuf   = (void *)Xrealloc(midbuf, pict.size);
793         }
794 
795 #if defined USE_GLEXT && !defined EDUKE32_GLES
796         if (dedxtfilter(texcache.handle, &pict, pic, midbuf, packbuf, (head->flags & CACHEAD_COMPRESSED) != 0))
797         {
798             TEXCACHE_FREEBUFS();
799             return TEXCACHERR_DEDXT;
800         }
801 
802         glCompressedTexImage2D(GL_TEXTURE_2D, level, pict.format, pict.xdim, pict.ydim, pict.border, pict.size, pic);
803         if ((*glerr=glGetError()) != GL_NO_ERROR)
804         {
805             TEXCACHE_FREEBUFS();
806             return TEXCACHERR_COMPTEX;
807         }
808 
809         GLint format;
810         glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_INTERNAL_FORMAT, &format);
811         if ((*glerr = glGetError()) != GL_NO_ERROR)
812         {
813             TEXCACHE_FREEBUFS();
814             return TEXCACHERR_GETTEXLEVEL;
815         }
816 
817         if (pict.format != format)
818         {
819             OSD_Printf("gloadtile_cached: invalid texture cache file format %d %d\n", pict.format, format);
820             TEXCACHE_FREEBUFS();
821             return -1;
822         }
823 #endif
824     }
825 
826     TEXCACHE_FREEBUFS();
827     return 0;
828 }
829 
texcache_loadskin(const texcacheheader * head,int32_t * doalloc,GLuint * glpic,vec2_t * siz)830 int32_t texcache_loadskin(const texcacheheader *head, int32_t *doalloc, GLuint *glpic, vec2_t *siz)
831 {
832     int32_t err=0;
833     GLenum glerr=GL_NO_ERROR;
834 
835     texcache_setuptexture(doalloc, glpic);
836 
837     siz->x = head->xdim;
838     siz->y = head->ydim;
839 
840     CLEAR_GL_ERRORS();
841 
842     if ((err = texcache_loadmips(head, &glerr)))
843     {
844         if (err > 0)
845             initprintf("texcache_loadskin: %s  (glerr=%x)\n", texcache_errors[err], glerr);
846 
847         return -1;
848     }
849 
850     return 0;
851 }
852 
texcache_loadtile(const texcacheheader * head,int32_t * doalloc,pthtyp * pth)853 int32_t texcache_loadtile(const texcacheheader *head, int32_t *doalloc, pthtyp *pth)
854 {
855     int32_t err   = 0;
856     GLenum  glerr = GL_NO_ERROR;
857 
858     texcache_setuptexture(doalloc, &pth->glpic);
859 
860     pth->siz.x = head->xdim;
861     pth->siz.y = head->ydim;
862 
863     CLEAR_GL_ERRORS();
864 
865     if ((err = texcache_loadmips(head, &glerr)))
866     {
867         if (err > 0)
868             initprintf("texcache_loadtile: %s  (glerr=%x)\n", texcache_errors[err], glerr);
869 
870         return -1;
871     }
872 
873     return 0;
874 }
875 
texcache_setupmemcache(void)876 void texcache_setupmemcache(void)
877 {
878     if (!glusememcache || !texcache_enabled())
879         return;
880 
881     texcache.memsize = buildvfs_length(texcache.handle);
882 
883     if (texcache.memsize <= 0)
884         return;
885 
886     texcache.buf = (uint8_t *)Xrealloc(texcache.buf, texcache.memsize);
887 
888     if (!texcache.buf)
889     {
890         initprintf("Failed allocating %d bytes for memcache!\n", (int)texcache.memsize);
891         texcache_clearmemcache();
892         glusememcache = 0;
893         return;
894     }
895 
896     if (Bread(texcache.handle, texcache.buf, texcache.memsize) != (bssize_t)texcache.memsize)
897     {
898         initprintf("Failed reading texcache into memcache!\n");
899         texcache_clearmemcache();
900         glusememcache = 0;
901     }
902 }
903 
904 #endif
905