Glide Plugin GLES2 port from mupen64plus-ae, but with special FrameSkip code
[mupen64plus-pandora.git] / source / gles2glide64 / src / GlideHQ / TxCache.cpp
diff --git a/source/gles2glide64/src/GlideHQ/TxCache.cpp b/source/gles2glide64/src/GlideHQ/TxCache.cpp
new file mode 100644 (file)
index 0000000..46960f4
--- /dev/null
@@ -0,0 +1,544 @@
+/*
+ * Texture Filtering
+ * Version:  1.0
+ *
+ * Copyright (C) 2007  Hiroshi Morii   All Rights Reserved.
+ * Email koolsmoky(at)users.sourceforge.net
+ * Web   http://www.3dfxzone.it/koolsmoky
+ *
+ * this is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * this is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Make; see the file COPYING.  If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifdef __MSC__
+#pragma warning(disable: 4786)
+#endif
+
+#include <boost/filesystem.hpp>
+#include <zlib.h>
+#include "TxCache.h"
+#include "TxDbg.h"
+#include "../Glide64/m64p.h"
+#include "../Glide64/Gfx_1.3.h"
+
+TxCache::~TxCache()
+{
+  /* free memory, clean up, etc */
+  clear();
+
+  delete _txUtil;
+}
+
+TxCache::TxCache(int options, int cachesize, const wchar_t *datapath,
+                 const wchar_t *cachepath, const wchar_t *ident,
+                 dispInfoFuncExt callback)
+{
+  _txUtil = new TxUtil();
+
+  _options = options;
+  _cacheSize = cachesize;
+  _callback = callback;
+  _totalSize = 0;
+
+  /* save path name */
+  if (datapath)
+    _datapath.assign(datapath);
+  if (cachepath)
+    _cachepath.assign(cachepath);
+
+  /* save ROM name */
+  if (ident)
+    _ident.assign(ident);
+
+  /* zlib memory buffers to (de)compress hires textures */
+  if (_options & (GZ_TEXCACHE|GZ_HIRESTEXCACHE)) {
+    _gzdest0   = TxMemBuf::getInstance()->get(0);
+    _gzdest1   = TxMemBuf::getInstance()->get(1);
+    _gzdestLen = (TxMemBuf::getInstance()->size_of(0) < TxMemBuf::getInstance()->size_of(1)) ?
+                  TxMemBuf::getInstance()->size_of(0) : TxMemBuf::getInstance()->size_of(1);
+
+    if (!_gzdest0 || !_gzdest1 || !_gzdestLen) {
+      _options &= ~(GZ_TEXCACHE|GZ_HIRESTEXCACHE);
+      _gzdest0 = NULL;
+      _gzdest1 = NULL;
+      _gzdestLen = 0;
+    }
+  }
+}
+
+boolean
+TxCache::add(uint64 checksum, GHQTexInfo *info, int dataSize)
+{
+  /* NOTE: dataSize must be provided if info->data is zlib compressed. */
+
+  if (!checksum || !info->data) return 0;
+
+  uint8 *dest = info->data;
+  uint16 format = info->format;
+
+  if (!dataSize) {
+    dataSize = _txUtil->sizeofTx(info->width, info->height, info->format);
+
+    if (!dataSize) return 0;
+
+    if (_options & (GZ_TEXCACHE|GZ_HIRESTEXCACHE)) {
+      /* zlib compress it. compression level:1 (best speed) */
+      uLongf destLen = _gzdestLen;
+      dest = (dest == _gzdest0) ? _gzdest1 : _gzdest0;
+      if (compress2(dest, &destLen, info->data, dataSize, 1) != Z_OK) {
+        dest = info->data;
+        DBG_INFO(80, L"Error: zlib compression failed!\n");
+      } else {
+        DBG_INFO(80, L"zlib compressed: %.02fkb->%.02fkb\n", (float)dataSize/1000, (float)destLen/1000);
+        dataSize = destLen;
+        format |= GR_TEXFMT_GZ;
+      }
+    }
+  }
+
+  /* if cache size exceeds limit, remove old cache */
+  if (_cacheSize > 0) {
+    _totalSize += dataSize;
+    if ((_totalSize > _cacheSize) && !_cachelist.empty()) {
+      /* _cachelist is arranged so that frequently used textures are in the back */
+      std::list<uint64>::iterator itList = _cachelist.begin();
+      while (itList != _cachelist.end()) {
+        /* find it in _cache */
+        std::map<uint64, TXCACHE*>::iterator itMap = _cache.find(*itList);
+        if (itMap != _cache.end()) {
+          /* yep we have it. remove it. */
+          _totalSize -= (*itMap).second->size;
+          free((*itMap).second->info.data);
+          delete (*itMap).second;
+          _cache.erase(itMap);
+        }
+        itList++;
+
+        /* check if memory cache has enough space */
+        if (_totalSize <= _cacheSize)
+          break;
+      }
+      /* remove from _cachelist */
+      _cachelist.erase(_cachelist.begin(), itList);
+
+      DBG_INFO(80, L"+++++++++\n");
+    }
+    _totalSize -= dataSize;
+  }
+
+  /* cache it */
+  uint8 *tmpdata = (uint8*)malloc(dataSize);
+  if (tmpdata) {
+    TXCACHE *txCache = new TXCACHE;
+    if (txCache) {
+      /* we can directly write as we filter, but for now we get away
+       * with doing memcpy after all the filtering is done.
+       */
+      memcpy(tmpdata, dest, dataSize);
+
+      /* copy it */
+      memcpy(&txCache->info, info, sizeof(GHQTexInfo));
+      txCache->info.data = tmpdata;
+      txCache->info.format = format;
+      txCache->size = dataSize;
+
+      /* add to cache */
+      if (_cacheSize > 0) {
+        _cachelist.push_back(checksum);
+        txCache->it = --(_cachelist.end());
+      }
+      /* _cache[checksum] = txCache; */
+      _cache.insert(std::map<uint64, TXCACHE*>::value_type(checksum, txCache));
+
+#ifdef DEBUG
+      DBG_INFO(80, L"[%5d] added!! crc:%08X %08X %d x %d gfmt:%x total:%.02fmb\n",
+               _cache.size(), (uint32)(checksum >> 32), (uint32)(checksum & 0xffffffff),
+               info->width, info->height, info->format, (float)_totalSize/1000000);
+
+      DBG_INFO(80, L"smalllodlog2:%d largelodlog2:%d aspectratiolog2:%d\n",
+               txCache->info.smallLodLog2, txCache->info.largeLodLog2, txCache->info.aspectRatioLog2);
+
+      if (info->tiles) {
+        DBG_INFO(80, L"tiles:%d un-tiled size:%d x %d\n", info->tiles, info->untiled_width, info->untiled_height);
+      }
+
+      if (_cacheSize > 0) {
+        DBG_INFO(80, L"cache max config:%.02fmb\n", (float)_cacheSize/1000000);
+
+        if (_cache.size() != _cachelist.size()) {
+          DBG_INFO(80, L"Error: cache/cachelist mismatch! (%d/%d)\n", _cache.size(), _cachelist.size());
+        }
+      }
+#endif
+
+      /* total cache size */
+      _totalSize += dataSize;
+
+      return 1;
+    }
+    free(tmpdata);
+  }
+
+  return 0;
+}
+
+boolean
+TxCache::get(uint64 checksum, GHQTexInfo *info)
+{
+  if (!checksum || _cache.empty()) return 0;
+
+  /* find a match in cache */
+  std::map<uint64, TXCACHE*>::iterator itMap = _cache.find(checksum);
+  if (itMap != _cache.end()) {
+    /* yep, we've got it. */
+    memcpy(info, &(((*itMap).second)->info), sizeof(GHQTexInfo));
+
+    /* push it to the back of the list */
+    if (_cacheSize > 0) {
+      _cachelist.erase(((*itMap).second)->it);
+      _cachelist.push_back(checksum);
+      ((*itMap).second)->it = --(_cachelist.end());
+    }
+
+    /* zlib decompress it */
+    if (info->format & GR_TEXFMT_GZ) {
+      uLongf destLen = _gzdestLen;
+      uint8 *dest = (_gzdest0 == info->data) ? _gzdest1 : _gzdest0;
+      if (uncompress(dest, &destLen, info->data, ((*itMap).second)->size) != Z_OK) {
+        DBG_INFO(80, L"Error: zlib decompression failed!\n");
+        return 0;
+      }
+      info->data = dest;
+      info->format &= ~GR_TEXFMT_GZ;
+      DBG_INFO(80, L"zlib decompressed: %.02fkb->%.02fkb\n", (float)(((*itMap).second)->size)/1000, (float)destLen/1000);
+    }
+
+    return 1;
+  }
+
+  return 0;
+}
+
+boolean
+TxCache::save(const wchar_t *path, const wchar_t *filename, int config)
+{
+  if (!_cache.empty()) {
+    /* dump cache to disk */
+    char cbuf[MAX_PATH];
+
+    boost::filesystem::wpath cachepath(path);
+    boost::filesystem::create_directory(cachepath);
+
+    /* Ugly hack to enable fopen/gzopen in Win9x */
+#ifdef BOOST_WINDOWS_API
+    wchar_t curpath[MAX_PATH];
+    GETCWD(MAX_PATH, curpath);
+    CHDIR(cachepath.wstring().c_str());
+#else
+    char curpath[MAX_PATH];
+    wcstombs(cbuf, cachepath.wstring().c_str(), MAX_PATH);
+    if (GETCWD(MAX_PATH, curpath) == NULL)
+        ERRLOG("Error while retrieving working directory!");
+    if (CHDIR(cbuf) != 0)
+        ERRLOG("Error while changing current directory to '%s'!", cbuf);
+#endif
+
+    wcstombs(cbuf, filename, MAX_PATH);
+
+    gzFile gzfp = gzopen(cbuf, "wb1");
+    DBG_INFO(80, L"gzfp:%x file:%ls\n", gzfp, filename);
+    if (gzfp) {
+      /* write header to determine config match */
+      gzwrite(gzfp, &config, 4);
+
+      std::map<uint64, TXCACHE*>::iterator itMap = _cache.begin();
+      while (itMap != _cache.end()) {
+        uint8 *dest    = (*itMap).second->info.data;
+        uint32 destLen = (*itMap).second->size;
+        uint16 format  = (*itMap).second->info.format;
+
+        /* to keep things simple, we save the texture data in a zlib uncompressed state. */
+        /* sigh... for those who cannot wait the extra few seconds. changed to keep
+         * texture data in a zlib compressed state. if the GZ_TEXCACHE or GZ_HIRESTEXCACHE
+         * option is toggled, the cache will need to be rebuilt.
+         */
+        /*if (format & GR_TEXFMT_GZ) {
+          dest = _gzdest0;
+          destLen = _gzdestLen;
+          if (dest && destLen) {
+            if (uncompress(dest, &destLen, (*itMap).second->info.data, (*itMap).second->size) != Z_OK) {
+              dest = NULL;
+              destLen = 0;
+            }
+            format &= ~GR_TEXFMT_GZ;
+          }
+        }*/
+
+        if (dest && destLen) {
+          /* texture checksum */
+          gzwrite(gzfp, &((*itMap).first), 8);
+
+          /* other texture info */
+          gzwrite(gzfp, &((*itMap).second->info.width), 4);
+          gzwrite(gzfp, &((*itMap).second->info.height), 4);
+          gzwrite(gzfp, &format, 2);
+
+          gzwrite(gzfp, &((*itMap).second->info.smallLodLog2), 4);
+          gzwrite(gzfp, &((*itMap).second->info.largeLodLog2), 4);
+          gzwrite(gzfp, &((*itMap).second->info.aspectRatioLog2), 4);
+
+          gzwrite(gzfp, &((*itMap).second->info.tiles), 4);
+          gzwrite(gzfp, &((*itMap).second->info.untiled_width), 4);
+          gzwrite(gzfp, &((*itMap).second->info.untiled_height), 4);
+
+          gzwrite(gzfp, &((*itMap).second->info.is_hires_tex), 1);
+
+          gzwrite(gzfp, &destLen, 4);
+          gzwrite(gzfp, dest, destLen);
+        }
+
+        itMap++;
+
+        /* not ready yet */
+        /*if (_callback)
+          (*_callback)(L"Total textures saved to HDD: %d\n", std::distance(itMap, _cache.begin()));*/
+      }
+      gzclose(gzfp);
+    }
+
+    if (CHDIR(curpath) != 0)
+        ERRLOG("Error while changing current directory back to original path of '%s'!", curpath);
+  }
+
+  return _cache.empty();
+}
+
+boolean
+TxCache::load(const wchar_t *path, const wchar_t *filename, int config)
+{
+  /* find it on disk */
+  char cbuf[MAX_PATH];
+
+  boost::filesystem::wpath cachepath(path);
+
+#ifdef BOOST_WINDOWS_API
+  wchar_t curpath[MAX_PATH];
+  GETCWD(MAX_PATH, curpath);
+  CHDIR(cachepath.wstring().c_str());
+#else
+  char curpath[MAX_PATH];
+  wcstombs(cbuf, cachepath.wstring().c_str(), MAX_PATH);
+  if (GETCWD(MAX_PATH, curpath) == NULL)
+      ERRLOG("Error while retrieving working directory!");
+  if (CHDIR(cbuf) != 0)
+      ERRLOG("Error while changing current directory to '%s'!", cbuf);
+#endif
+
+  wcstombs(cbuf, filename, MAX_PATH);
+
+  gzFile gzfp = gzopen(cbuf, "rb");
+  DBG_INFO(80, L"gzfp:%x file:%ls\n", gzfp, filename);
+  if (gzfp) {
+    /* yep, we have it. load it into memory cache. */
+    int dataSize;
+    uint64 checksum;
+    GHQTexInfo tmpInfo;
+    int tmpconfig;
+    /* read header to determine config match */
+    gzread(gzfp, &tmpconfig, 4);
+
+    if (tmpconfig == config) {
+      do {
+        memset(&tmpInfo, 0, sizeof(GHQTexInfo));
+
+        gzread(gzfp, &checksum, 8);
+
+        gzread(gzfp, &tmpInfo.width, 4);
+        gzread(gzfp, &tmpInfo.height, 4);
+        gzread(gzfp, &tmpInfo.format, 2);
+
+        gzread(gzfp, &tmpInfo.smallLodLog2, 4);
+        gzread(gzfp, &tmpInfo.largeLodLog2, 4);
+        gzread(gzfp, &tmpInfo.aspectRatioLog2, 4);
+
+        gzread(gzfp, &tmpInfo.tiles, 4);
+        gzread(gzfp, &tmpInfo.untiled_width, 4);
+        gzread(gzfp, &tmpInfo.untiled_height, 4);
+
+        gzread(gzfp, &tmpInfo.is_hires_tex, 1);
+
+        gzread(gzfp, &dataSize, 4);
+
+        tmpInfo.data = (uint8*)malloc(dataSize);
+        if (tmpInfo.data) {
+          gzread(gzfp, tmpInfo.data, dataSize);
+
+          /* add to memory cache */
+          add(checksum, &tmpInfo, (tmpInfo.format & GR_TEXFMT_GZ) ? dataSize : 0);
+
+          free(tmpInfo.data);
+        } else {
+          gzseek(gzfp, dataSize, SEEK_CUR);
+        }
+
+        /* skip in between to prevent the loop from being tied down to vsync */
+        if (_callback && (!(_cache.size() % 100) || gzeof(gzfp)))
+          (*_callback)(L"[%d] total mem:%.02fmb - %ls\n", _cache.size(), (float)_totalSize/1000000, filename);
+
+      } while (!gzeof(gzfp));
+      gzclose(gzfp);
+    } else {
+      if ((tmpconfig & HIRESTEXTURES_MASK) != (config & HIRESTEXTURES_MASK)) {
+        const char *conf_str;
+        if ((tmpconfig & HIRESTEXTURES_MASK) == NO_HIRESTEXTURES)
+          conf_str = "0";
+        else if ((tmpconfig & HIRESTEXTURES_MASK) == RICE_HIRESTEXTURES)
+          conf_str = "1";
+        else
+          conf_str = "set to an unsupported format";
+
+        WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_hirs must be %s", conf_str);
+      }
+      if ((tmpconfig & COMPRESS_HIRESTEX) != (config & COMPRESS_HIRESTEX))
+        WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_hirs_cmpr must be %s", (tmpconfig & COMPRESS_HIRESTEX) ? "True" : "False");
+      if ((tmpconfig & COMPRESSION_MASK) != (config & COMPRESSION_MASK) && (tmpconfig & COMPRESS_HIRESTEX)) {
+        const char *conf_str;
+        if ((tmpconfig & COMPRESSION_MASK) == FXT1_COMPRESSION)
+          conf_str = "1";
+        else if ((tmpconfig & COMPRESSION_MASK) == S3TC_COMPRESSION)
+          conf_str = "0";
+        else
+          conf_str = "set to an unsupported format";
+
+        WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_cmpr must be %s", conf_str);
+      }
+      if ((tmpconfig & TILE_HIRESTEX) != (config & TILE_HIRESTEX))
+        WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_hirs_tile must be %s", (tmpconfig & TILE_HIRESTEX) ? "True" : "False");
+      if ((tmpconfig & FORCE16BPP_HIRESTEX) != (config & FORCE16BPP_HIRESTEX))
+        WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_hirs_f16bpp must be %s", (tmpconfig & FORCE16BPP_HIRESTEX) ? "True" : "False");
+      if ((tmpconfig & GZ_HIRESTEXCACHE) != (config & GZ_HIRESTEXCACHE))
+        WriteLog(M64MSG_WARNING, "ghq_hirs_gz must be %s", (tmpconfig & GZ_HIRESTEXCACHE) ? "True" : "False");
+      if ((tmpconfig & LET_TEXARTISTS_FLY) != (config & LET_TEXARTISTS_FLY))
+        WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_hirs_let_texartists_fly must be %s", (tmpconfig & LET_TEXARTISTS_FLY) ? "True" : "False");
+
+      if ((tmpconfig & FILTER_MASK) != (config & FILTER_MASK)) {
+        const char *conf_str;
+        if ((tmpconfig & FILTER_MASK) == NO_FILTER)
+          conf_str = "0";
+        else if ((tmpconfig & FILTER_MASK) == SMOOTH_FILTER_1)
+          conf_str = "1";
+        else if ((tmpconfig & FILTER_MASK) == SMOOTH_FILTER_2)
+          conf_str = "2";
+        else if ((tmpconfig & FILTER_MASK) == SMOOTH_FILTER_3)
+          conf_str = "3";
+        else if ((tmpconfig & FILTER_MASK) == SMOOTH_FILTER_4)
+          conf_str = "4";
+        else if ((tmpconfig & FILTER_MASK) == SHARP_FILTER_1)
+          conf_str = "5";
+        else if ((tmpconfig & FILTER_MASK) == SHARP_FILTER_2)
+          conf_str = "6";
+        else
+          conf_str = "set to an unsupported format";
+        WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_fltr must be %s", conf_str);
+      }
+
+      if ((tmpconfig & ENHANCEMENT_MASK) != (config & ENHANCEMENT_MASK)) {
+        const char *conf_str;
+        if ((tmpconfig & ENHANCEMENT_MASK) == NO_ENHANCEMENT)
+          conf_str = "0";
+        else if ((tmpconfig & ENHANCEMENT_MASK) == X2_ENHANCEMENT)
+          conf_str = "2";
+        else if ((tmpconfig & ENHANCEMENT_MASK) == X2SAI_ENHANCEMENT)
+          conf_str = "3";
+        else if ((tmpconfig & ENHANCEMENT_MASK) == HQ2X_ENHANCEMENT)
+          conf_str = "4";
+        else if ((tmpconfig & ENHANCEMENT_MASK) == HQ2XS_ENHANCEMENT)
+          conf_str = "5";
+        else if ((tmpconfig & ENHANCEMENT_MASK) == LQ2X_ENHANCEMENT)
+          conf_str = "6";
+        else if ((tmpconfig & ENHANCEMENT_MASK) == LQ2XS_ENHANCEMENT)
+          conf_str = "7";
+        else if ((tmpconfig & ENHANCEMENT_MASK) == HQ4X_ENHANCEMENT)
+          conf_str = "8";
+        else
+          conf_str = "set to an unsupported format";
+        WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_enht must be %s", conf_str);
+      }
+
+      if ((tmpconfig & COMPRESS_TEX) != (config & COMPRESS_TEX))
+        WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_enht_cmpr must be %s", (tmpconfig & COMPRESS_TEX) ? "True" : "False");
+      if ((tmpconfig & FORCE16BPP_TEX) != (config & FORCE16BPP_TEX))
+        WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_enht_f16bpp must be %s", (tmpconfig & FORCE16BPP_TEX) ? "True" : "False");
+      if ((tmpconfig & GZ_TEXCACHE) != (config & GZ_TEXCACHE))
+        WriteLog(M64MSG_WARNING, "Ignored texture cache due to incompatible setting: ghq_enht_gz must be %s", (tmpconfig & GZ_TEXCACHE) ? "True" : "False");
+    }
+  }
+
+  if (CHDIR(curpath) != 0)
+      ERRLOG("Error while changing current directory back to original path of '%s'!", curpath);
+
+  return !_cache.empty();
+}
+
+boolean
+TxCache::del(uint64 checksum)
+{
+  if (!checksum || _cache.empty()) return 0;
+
+  std::map<uint64, TXCACHE*>::iterator itMap = _cache.find(checksum);
+  if (itMap != _cache.end()) {
+
+    /* for texture cache (not hi-res cache) */
+    if (!_cachelist.empty()) _cachelist.erase(((*itMap).second)->it);
+
+    /* remove from cache */
+    free((*itMap).second->info.data);
+    _totalSize -= (*itMap).second->size;
+    delete (*itMap).second;
+    _cache.erase(itMap);
+
+    DBG_INFO(80, L"removed from cache: checksum = %08X %08X\n", (uint32)(checksum & 0xffffffff), (uint32)(checksum >> 32));
+
+    return 1;
+  }
+
+  return 0;
+}
+
+boolean
+TxCache::is_cached(uint64 checksum)
+{
+  std::map<uint64, TXCACHE*>::iterator itMap = _cache.find(checksum);
+  if (itMap != _cache.end()) return 1;
+
+  return 0;
+}
+
+void
+TxCache::clear()
+{
+  if (!_cache.empty()) {
+    std::map<uint64, TXCACHE*>::iterator itMap = _cache.begin();
+    while (itMap != _cache.end()) {
+      free((*itMap).second->info.data);
+      delete (*itMap).second;
+      itMap++;
+    }
+    _cache.clear();
+  }
+
+  if (!_cachelist.empty()) _cachelist.clear();
+
+  _totalSize = 0;
+}