Glide Plugin GLES2 port from mupen64plus-ae, but with special FrameSkip code
[mupen64plus-pandora.git] / source / gles2glide64 / src / GlideHQ / TxCache.cpp
1 /*
2  * Texture Filtering
3  * Version:  1.0
4  *
5  * Copyright (C) 2007  Hiroshi Morii   All Rights Reserved.
6  * Email koolsmoky(at)users.sourceforge.net
7  * Web   http://www.3dfxzone.it/koolsmoky
8  *
9  * this is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2, or (at your option)
12  * any later version.
13  *
14  * this is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with GNU Make; see the file COPYING.  If not, write to
21  * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
22  */
23
24 #ifdef __MSC__
25 #pragma warning(disable: 4786)
26 #endif
27
28 #include <boost/filesystem.hpp>
29 #include <zlib.h>
30 #include "TxCache.h"
31 #include "TxDbg.h"
32 #include "../Glide64/m64p.h"
33 #include "../Glide64/Gfx_1.3.h"
34
35 TxCache::~TxCache()
36 {
37   /* free memory, clean up, etc */
38   clear();
39
40   delete _txUtil;
41 }
42
43 TxCache::TxCache(int options, int cachesize, const wchar_t *datapath,
44                  const wchar_t *cachepath, const wchar_t *ident,
45                  dispInfoFuncExt callback)
46 {
47   _txUtil = new TxUtil();
48
49   _options = options;
50   _cacheSize = cachesize;
51   _callback = callback;
52   _totalSize = 0;
53
54   /* save path name */
55   if (datapath)
56     _datapath.assign(datapath);
57   if (cachepath)
58     _cachepath.assign(cachepath);
59
60   /* save ROM name */
61   if (ident)
62     _ident.assign(ident);
63
64   /* zlib memory buffers to (de)compress hires textures */
65   if (_options & (GZ_TEXCACHE|GZ_HIRESTEXCACHE)) {
66     _gzdest0   = TxMemBuf::getInstance()->get(0);
67     _gzdest1   = TxMemBuf::getInstance()->get(1);
68     _gzdestLen = (TxMemBuf::getInstance()->size_of(0) < TxMemBuf::getInstance()->size_of(1)) ?
69                   TxMemBuf::getInstance()->size_of(0) : TxMemBuf::getInstance()->size_of(1);
70
71     if (!_gzdest0 || !_gzdest1 || !_gzdestLen) {
72       _options &= ~(GZ_TEXCACHE|GZ_HIRESTEXCACHE);
73       _gzdest0 = NULL;
74       _gzdest1 = NULL;
75       _gzdestLen = 0;
76     }
77   }
78 }
79
80 boolean
81 TxCache::add(uint64 checksum, GHQTexInfo *info, int dataSize)
82 {
83   /* NOTE: dataSize must be provided if info->data is zlib compressed. */
84
85   if (!checksum || !info->data) return 0;
86
87   uint8 *dest = info->data;
88   uint16 format = info->format;
89
90   if (!dataSize) {
91     dataSize = _txUtil->sizeofTx(info->width, info->height, info->format);
92
93     if (!dataSize) return 0;
94
95     if (_options & (GZ_TEXCACHE|GZ_HIRESTEXCACHE)) {
96       /* zlib compress it. compression level:1 (best speed) */
97       uLongf destLen = _gzdestLen;
98       dest = (dest == _gzdest0) ? _gzdest1 : _gzdest0;
99       if (compress2(dest, &destLen, info->data, dataSize, 1) != Z_OK) {
100         dest = info->data;
101         DBG_INFO(80, L"Error: zlib compression failed!\n");
102       } else {
103         DBG_INFO(80, L"zlib compressed: %.02fkb->%.02fkb\n", (float)dataSize/1000, (float)destLen/1000);
104         dataSize = destLen;
105         format |= GR_TEXFMT_GZ;
106       }
107     }
108   }
109
110   /* if cache size exceeds limit, remove old cache */
111   if (_cacheSize > 0) {
112     _totalSize += dataSize;
113     if ((_totalSize > _cacheSize) && !_cachelist.empty()) {
114       /* _cachelist is arranged so that frequently used textures are in the back */
115       std::list<uint64>::iterator itList = _cachelist.begin();
116       while (itList != _cachelist.end()) {
117         /* find it in _cache */
118         std::map<uint64, TXCACHE*>::iterator itMap = _cache.find(*itList);
119         if (itMap != _cache.end()) {
120           /* yep we have it. remove it. */
121           _totalSize -= (*itMap).second->size;
122           free((*itMap).second->info.data);
123           delete (*itMap).second;
124           _cache.erase(itMap);
125         }
126         itList++;
127
128         /* check if memory cache has enough space */
129         if (_totalSize <= _cacheSize)
130           break;
131       }
132       /* remove from _cachelist */
133       _cachelist.erase(_cachelist.begin(), itList);
134
135       DBG_INFO(80, L"+++++++++\n");
136     }
137     _totalSize -= dataSize;
138   }
139
140   /* cache it */
141   uint8 *tmpdata = (uint8*)malloc(dataSize);
142   if (tmpdata) {
143     TXCACHE *txCache = new TXCACHE;
144     if (txCache) {
145       /* we can directly write as we filter, but for now we get away
146        * with doing memcpy after all the filtering is done.
147        */
148       memcpy(tmpdata, dest, dataSize);
149
150       /* copy it */
151       memcpy(&txCache->info, info, sizeof(GHQTexInfo));
152       txCache->info.data = tmpdata;
153       txCache->info.format = format;
154       txCache->size = dataSize;
155
156       /* add to cache */
157       if (_cacheSize > 0) {
158         _cachelist.push_back(checksum);
159         txCache->it = --(_cachelist.end());
160       }
161       /* _cache[checksum] = txCache; */
162       _cache.insert(std::map<uint64, TXCACHE*>::value_type(checksum, txCache));
163
164 #ifdef DEBUG
165       DBG_INFO(80, L"[%5d] added!! crc:%08X %08X %d x %d gfmt:%x total:%.02fmb\n",
166                _cache.size(), (uint32)(checksum >> 32), (uint32)(checksum & 0xffffffff),
167                info->width, info->height, info->format, (float)_totalSize/1000000);
168
169       DBG_INFO(80, L"smalllodlog2:%d largelodlog2:%d aspectratiolog2:%d\n",
170                txCache->info.smallLodLog2, txCache->info.largeLodLog2, txCache->info.aspectRatioLog2);
171
172       if (info->tiles) {
173         DBG_INFO(80, L"tiles:%d un-tiled size:%d x %d\n", info->tiles, info->untiled_width, info->untiled_height);
174       }
175
176       if (_cacheSize > 0) {
177         DBG_INFO(80, L"cache max config:%.02fmb\n", (float)_cacheSize/1000000);
178
179         if (_cache.size() != _cachelist.size()) {
180           DBG_INFO(80, L"Error: cache/cachelist mismatch! (%d/%d)\n", _cache.size(), _cachelist.size());
181         }
182       }
183 #endif
184
185       /* total cache size */
186       _totalSize += dataSize;
187
188       return 1;
189     }
190     free(tmpdata);
191   }
192
193   return 0;
194 }
195
196 boolean
197 TxCache::get(uint64 checksum, GHQTexInfo *info)
198 {
199   if (!checksum || _cache.empty()) return 0;
200
201   /* find a match in cache */
202   std::map<uint64, TXCACHE*>::iterator itMap = _cache.find(checksum);
203   if (itMap != _cache.end()) {
204     /* yep, we've got it. */
205     memcpy(info, &(((*itMap).second)->info), sizeof(GHQTexInfo));
206
207     /* push it to the back of the list */
208     if (_cacheSize > 0) {
209       _cachelist.erase(((*itMap).second)->it);
210       _cachelist.push_back(checksum);
211       ((*itMap).second)->it = --(_cachelist.end());
212     }
213
214     /* zlib decompress it */
215     if (info->format & GR_TEXFMT_GZ) {
216       uLongf destLen = _gzdestLen;
217       uint8 *dest = (_gzdest0 == info->data) ? _gzdest1 : _gzdest0;
218       if (uncompress(dest, &destLen, info->data, ((*itMap).second)->size) != Z_OK) {
219         DBG_INFO(80, L"Error: zlib decompression failed!\n");
220         return 0;
221       }
222       info->data = dest;
223       info->format &= ~GR_TEXFMT_GZ;
224       DBG_INFO(80, L"zlib decompressed: %.02fkb->%.02fkb\n", (float)(((*itMap).second)->size)/1000, (float)destLen/1000);
225     }
226
227     return 1;
228   }
229
230   return 0;
231 }
232
233 boolean
234 TxCache::save(const wchar_t *path, const wchar_t *filename, int config)
235 {
236   if (!_cache.empty()) {
237     /* dump cache to disk */
238     char cbuf[MAX_PATH];
239
240     boost::filesystem::wpath cachepath(path);
241     boost::filesystem::create_directory(cachepath);
242
243     /* Ugly hack to enable fopen/gzopen in Win9x */
244 #ifdef BOOST_WINDOWS_API
245     wchar_t curpath[MAX_PATH];
246     GETCWD(MAX_PATH, curpath);
247     CHDIR(cachepath.wstring().c_str());
248 #else
249     char curpath[MAX_PATH];
250     wcstombs(cbuf, cachepath.wstring().c_str(), MAX_PATH);
251     if (GETCWD(MAX_PATH, curpath) == NULL)
252         ERRLOG("Error while retrieving working directory!");
253     if (CHDIR(cbuf) != 0)
254         ERRLOG("Error while changing current directory to '%s'!", cbuf);
255 #endif
256
257     wcstombs(cbuf, filename, MAX_PATH);
258
259     gzFile gzfp = gzopen(cbuf, "wb1");
260     DBG_INFO(80, L"gzfp:%x file:%ls\n", gzfp, filename);
261     if (gzfp) {
262       /* write header to determine config match */
263       gzwrite(gzfp, &config, 4);
264
265       std::map<uint64, TXCACHE*>::iterator itMap = _cache.begin();
266       while (itMap != _cache.end()) {
267         uint8 *dest    = (*itMap).second->info.data;
268         uint32 destLen = (*itMap).second->size;
269         uint16 format  = (*itMap).second->info.format;
270
271         /* to keep things simple, we save the texture data in a zlib uncompressed state. */
272         /* sigh... for those who cannot wait the extra few seconds. changed to keep
273          * texture data in a zlib compressed state. if the GZ_TEXCACHE or GZ_HIRESTEXCACHE
274          * option is toggled, the cache will need to be rebuilt.
275          */
276         /*if (format & GR_TEXFMT_GZ) {
277           dest = _gzdest0;
278           destLen = _gzdestLen;
279           if (dest && destLen) {
280             if (uncompress(dest, &destLen, (*itMap).second->info.data, (*itMap).second->size) != Z_OK) {
281               dest = NULL;
282               destLen = 0;
283             }
284             format &= ~GR_TEXFMT_GZ;
285           }
286         }*/
287
288         if (dest && destLen) {
289           /* texture checksum */
290           gzwrite(gzfp, &((*itMap).first), 8);
291
292           /* other texture info */
293           gzwrite(gzfp, &((*itMap).second->info.width), 4);
294           gzwrite(gzfp, &((*itMap).second->info.height), 4);
295           gzwrite(gzfp, &format, 2);
296
297           gzwrite(gzfp, &((*itMap).second->info.smallLodLog2), 4);
298           gzwrite(gzfp, &((*itMap).second->info.largeLodLog2), 4);
299           gzwrite(gzfp, &((*itMap).second->info.aspectRatioLog2), 4);
300
301           gzwrite(gzfp, &((*itMap).second->info.tiles), 4);
302           gzwrite(gzfp, &((*itMap).second->info.untiled_width), 4);
303           gzwrite(gzfp, &((*itMap).second->info.untiled_height), 4);
304
305           gzwrite(gzfp, &((*itMap).second->info.is_hires_tex), 1);
306
307           gzwrite(gzfp, &destLen, 4);
308           gzwrite(gzfp, dest, destLen);
309         }
310
311         itMap++;
312
313         /* not ready yet */
314         /*if (_callback)
315           (*_callback)(L"Total textures saved to HDD: %d\n", std::distance(itMap, _cache.begin()));*/
316       }
317       gzclose(gzfp);
318     }
319
320     if (CHDIR(curpath) != 0)
321         ERRLOG("Error while changing current directory back to original path of '%s'!", curpath);
322   }
323
324   return _cache.empty();
325 }
326
327 boolean
328 TxCache::load(const wchar_t *path, const wchar_t *filename, int config)
329 {
330   /* find it on disk */
331   char cbuf[MAX_PATH];
332
333   boost::filesystem::wpath cachepath(path);
334
335 #ifdef BOOST_WINDOWS_API
336   wchar_t curpath[MAX_PATH];
337   GETCWD(MAX_PATH, curpath);
338   CHDIR(cachepath.wstring().c_str());
339 #else
340   char curpath[MAX_PATH];
341   wcstombs(cbuf, cachepath.wstring().c_str(), MAX_PATH);
342   if (GETCWD(MAX_PATH, curpath) == NULL)
343       ERRLOG("Error while retrieving working directory!");
344   if (CHDIR(cbuf) != 0)
345       ERRLOG("Error while changing current directory to '%s'!", cbuf);
346 #endif
347
348   wcstombs(cbuf, filename, MAX_PATH);
349
350   gzFile gzfp = gzopen(cbuf, "rb");
351   DBG_INFO(80, L"gzfp:%x file:%ls\n", gzfp, filename);
352   if (gzfp) {
353     /* yep, we have it. load it into memory cache. */
354     int dataSize;
355     uint64 checksum;
356     GHQTexInfo tmpInfo;
357     int tmpconfig;
358     /* read header to determine config match */
359     gzread(gzfp, &tmpconfig, 4);
360
361     if (tmpconfig == config) {
362       do {
363         memset(&tmpInfo, 0, sizeof(GHQTexInfo));
364
365         gzread(gzfp, &checksum, 8);
366
367         gzread(gzfp, &tmpInfo.width, 4);
368         gzread(gzfp, &tmpInfo.height, 4);
369         gzread(gzfp, &tmpInfo.format, 2);
370
371         gzread(gzfp, &tmpInfo.smallLodLog2, 4);
372         gzread(gzfp, &tmpInfo.largeLodLog2, 4);
373         gzread(gzfp, &tmpInfo.aspectRatioLog2, 4);
374
375         gzread(gzfp, &tmpInfo.tiles, 4);
376         gzread(gzfp, &tmpInfo.untiled_width, 4);
377         gzread(gzfp, &tmpInfo.untiled_height, 4);
378
379         gzread(gzfp, &tmpInfo.is_hires_tex, 1);
380
381         gzread(gzfp, &dataSize, 4);
382
383         tmpInfo.data = (uint8*)malloc(dataSize);
384         if (tmpInfo.data) {
385           gzread(gzfp, tmpInfo.data, dataSize);
386
387           /* add to memory cache */
388           add(checksum, &tmpInfo, (tmpInfo.format & GR_TEXFMT_GZ) ? dataSize : 0);
389
390           free(tmpInfo.data);
391         } else {
392           gzseek(gzfp, dataSize, SEEK_CUR);
393         }
394
395         /* skip in between to prevent the loop from being tied down to vsync */
396         if (_callback && (!(_cache.size() % 100) || gzeof(gzfp)))
397           (*_callback)(L"[%d] total mem:%.02fmb - %ls\n", _cache.size(), (float)_totalSize/1000000, filename);
398
399       } while (!gzeof(gzfp));
400       gzclose(gzfp);
401     } else {
402       if ((tmpconfig & HIRESTEXTURES_MASK) != (config & HIRESTEXTURES_MASK)) {
403         const char *conf_str;
404         if ((tmpconfig & HIRESTEXTURES_MASK) == NO_HIRESTEXTURES)
405           conf_str = "0";
406         else if ((tmpconfig & HIRESTEXTURES_MASK) == RICE_HIRESTEXTURES)
407           conf_str = "1";
408         else
409           conf_str = "set to an unsupported format";
410
411         WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_hirs must be %s", conf_str);
412       }
413       if ((tmpconfig & COMPRESS_HIRESTEX) != (config & COMPRESS_HIRESTEX))
414         WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_hirs_cmpr must be %s", (tmpconfig & COMPRESS_HIRESTEX) ? "True" : "False");
415       if ((tmpconfig & COMPRESSION_MASK) != (config & COMPRESSION_MASK) && (tmpconfig & COMPRESS_HIRESTEX)) {
416         const char *conf_str;
417         if ((tmpconfig & COMPRESSION_MASK) == FXT1_COMPRESSION)
418           conf_str = "1";
419         else if ((tmpconfig & COMPRESSION_MASK) == S3TC_COMPRESSION)
420           conf_str = "0";
421         else
422           conf_str = "set to an unsupported format";
423
424         WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_cmpr must be %s", conf_str);
425       }
426       if ((tmpconfig & TILE_HIRESTEX) != (config & TILE_HIRESTEX))
427         WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_hirs_tile must be %s", (tmpconfig & TILE_HIRESTEX) ? "True" : "False");
428       if ((tmpconfig & FORCE16BPP_HIRESTEX) != (config & FORCE16BPP_HIRESTEX))
429         WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_hirs_f16bpp must be %s", (tmpconfig & FORCE16BPP_HIRESTEX) ? "True" : "False");
430       if ((tmpconfig & GZ_HIRESTEXCACHE) != (config & GZ_HIRESTEXCACHE))
431         WriteLog(M64MSG_WARNING, "ghq_hirs_gz must be %s", (tmpconfig & GZ_HIRESTEXCACHE) ? "True" : "False");
432       if ((tmpconfig & LET_TEXARTISTS_FLY) != (config & LET_TEXARTISTS_FLY))
433         WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_hirs_let_texartists_fly must be %s", (tmpconfig & LET_TEXARTISTS_FLY) ? "True" : "False");
434
435       if ((tmpconfig & FILTER_MASK) != (config & FILTER_MASK)) {
436         const char *conf_str;
437         if ((tmpconfig & FILTER_MASK) == NO_FILTER)
438           conf_str = "0";
439         else if ((tmpconfig & FILTER_MASK) == SMOOTH_FILTER_1)
440           conf_str = "1";
441         else if ((tmpconfig & FILTER_MASK) == SMOOTH_FILTER_2)
442           conf_str = "2";
443         else if ((tmpconfig & FILTER_MASK) == SMOOTH_FILTER_3)
444           conf_str = "3";
445         else if ((tmpconfig & FILTER_MASK) == SMOOTH_FILTER_4)
446           conf_str = "4";
447         else if ((tmpconfig & FILTER_MASK) == SHARP_FILTER_1)
448           conf_str = "5";
449         else if ((tmpconfig & FILTER_MASK) == SHARP_FILTER_2)
450           conf_str = "6";
451         else
452           conf_str = "set to an unsupported format";
453         WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_fltr must be %s", conf_str);
454       }
455
456       if ((tmpconfig & ENHANCEMENT_MASK) != (config & ENHANCEMENT_MASK)) {
457         const char *conf_str;
458         if ((tmpconfig & ENHANCEMENT_MASK) == NO_ENHANCEMENT)
459           conf_str = "0";
460         else if ((tmpconfig & ENHANCEMENT_MASK) == X2_ENHANCEMENT)
461           conf_str = "2";
462         else if ((tmpconfig & ENHANCEMENT_MASK) == X2SAI_ENHANCEMENT)
463           conf_str = "3";
464         else if ((tmpconfig & ENHANCEMENT_MASK) == HQ2X_ENHANCEMENT)
465           conf_str = "4";
466         else if ((tmpconfig & ENHANCEMENT_MASK) == HQ2XS_ENHANCEMENT)
467           conf_str = "5";
468         else if ((tmpconfig & ENHANCEMENT_MASK) == LQ2X_ENHANCEMENT)
469           conf_str = "6";
470         else if ((tmpconfig & ENHANCEMENT_MASK) == LQ2XS_ENHANCEMENT)
471           conf_str = "7";
472         else if ((tmpconfig & ENHANCEMENT_MASK) == HQ4X_ENHANCEMENT)
473           conf_str = "8";
474         else
475           conf_str = "set to an unsupported format";
476         WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_enht must be %s", conf_str);
477       }
478
479       if ((tmpconfig & COMPRESS_TEX) != (config & COMPRESS_TEX))
480         WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_enht_cmpr must be %s", (tmpconfig & COMPRESS_TEX) ? "True" : "False");
481       if ((tmpconfig & FORCE16BPP_TEX) != (config & FORCE16BPP_TEX))
482         WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_enht_f16bpp must be %s", (tmpconfig & FORCE16BPP_TEX) ? "True" : "False");
483       if ((tmpconfig & GZ_TEXCACHE) != (config & GZ_TEXCACHE))
484         WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_enht_gz must be %s", (tmpconfig & GZ_TEXCACHE) ? "True" : "False");
485     }
486   }
487
488   if (CHDIR(curpath) != 0)
489       ERRLOG("Error while changing current directory back to original path of '%s'!", curpath);
490
491   return !_cache.empty();
492 }
493
494 boolean
495 TxCache::del(uint64 checksum)
496 {
497   if (!checksum || _cache.empty()) return 0;
498
499   std::map<uint64, TXCACHE*>::iterator itMap = _cache.find(checksum);
500   if (itMap != _cache.end()) {
501
502     /* for texture cache (not hi-res cache) */
503     if (!_cachelist.empty()) _cachelist.erase(((*itMap).second)->it);
504
505     /* remove from cache */
506     free((*itMap).second->info.data);
507     _totalSize -= (*itMap).second->size;
508     delete (*itMap).second;
509     _cache.erase(itMap);
510
511     DBG_INFO(80, L"removed from cache: checksum = %08X %08X\n", (uint32)(checksum & 0xffffffff), (uint32)(checksum >> 32));
512
513     return 1;
514   }
515
516   return 0;
517 }
518
519 boolean
520 TxCache::is_cached(uint64 checksum)
521 {
522   std::map<uint64, TXCACHE*>::iterator itMap = _cache.find(checksum);
523   if (itMap != _cache.end()) return 1;
524
525   return 0;
526 }
527
528 void
529 TxCache::clear()
530 {
531   if (!_cache.empty()) {
532     std::map<uint64, TXCACHE*>::iterator itMap = _cache.begin();
533     while (itMap != _cache.end()) {
534       free((*itMap).second->info.data);
535       delete (*itMap).second;
536       itMap++;
537     }
538     _cache.clear();
539   }
540
541   if (!_cachelist.empty()) _cachelist.clear();
542
543   _totalSize = 0;
544 }