project64/Source/Project64-video/TextureEnhancer/TxCache.cpp

430 lines
13 KiB
C++
Raw Normal View History

2021-03-02 02:13:17 +00:00
// Project64 - A Nintendo 64 emulator
// https://www.pj64-emu.com/
2021-03-02 02:13:17 +00:00
// Copyright(C) 2001-2021 Project64
// Copyright(C) 2007 Hiroshi Morii
// Copyright(C) 2003 Rice1964
// GNU/GPLv2 licensed: https://gnu.org/licenses/gpl-2.0.html
2013-04-17 10:30:38 +00:00
#ifdef WIN32
#pragma warning(disable: 4786)
#endif
#include <string.h> // memcpy, memset
#include <stdlib.h> // malloc, free
2013-04-17 10:30:38 +00:00
#include "TxCache.h"
#include "TxDbg.h"
#include <zlib/zlib.h>
#include <Common/path.h>
#include <Common/StdString.h>
#include <Project64-video/Renderer/types.h>
2013-04-17 10:30:38 +00:00
TxCache::~TxCache()
{
// Free memory, clean up, etc.
2016-02-04 17:28:21 +00:00
clear();
2013-04-17 10:30:38 +00:00
2016-02-04 17:28:21 +00:00
delete _txUtil;
2013-04-17 10:30:38 +00:00
}
2016-02-10 07:02:20 +00:00
TxCache::TxCache(int options, int cachesize, const char *path, const char *ident,
2016-02-04 17:28:21 +00:00
dispInfoFuncExt callback)
2013-04-17 10:30:38 +00:00
{
2016-02-04 17:28:21 +00:00
_txUtil = new TxUtil();
_options = options;
_cacheSize = cachesize;
_callback = callback;
_totalSize = 0;
// Save path name
2016-02-04 17:28:21 +00:00
if (path)
2016-02-10 07:02:20 +00:00
{
2016-02-04 17:28:21 +00:00
_path.assign(path);
2016-02-10 07:02:20 +00:00
}
2016-02-04 17:28:21 +00:00
// Save ROM name
2016-02-04 17:28:21 +00:00
if (ident)
2016-02-10 07:02:20 +00:00
{
2016-02-04 17:28:21 +00:00
_ident.assign(ident);
2016-02-10 07:02:20 +00:00
}
2016-02-04 17:28:21 +00:00
// zlib memory buffers to (de)compress high resolution textures
2016-02-10 07:02:20 +00:00
if (_options & (GZ_TEXCACHE | GZ_HIRESTEXCACHE))
{
2016-02-04 17:28:21 +00:00
_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);
2016-02-10 07:02:20 +00:00
if (!_gzdest0 || !_gzdest1 || !_gzdestLen)
{
2016-02-04 17:28:21 +00:00
_options &= ~(GZ_TEXCACHE | GZ_HIRESTEXCACHE);
2021-04-12 11:35:39 +00:00
_gzdest0 = nullptr;
_gzdest1 = nullptr;
2016-02-04 17:28:21 +00:00
_gzdestLen = 0;
}
2013-04-17 10:30:38 +00:00
}
}
2017-04-26 10:23:36 +00:00
bool
TxCache::add(uint64_t checksum, GHQTexInfo *info, int dataSize)
2013-04-17 10:30:38 +00:00
{
// NOTE: dataSize must be provided if info->data is zlib compressed
2016-02-04 17:28:21 +00:00
if (!checksum || !info->data) return 0;
uint8 *dest = info->data;
uint16 format = info->format;
2016-02-10 07:02:20 +00:00
if (!dataSize)
{
2016-02-04 17:28:21 +00:00
dataSize = _txUtil->sizeofTx(info->width, info->height, info->format);
if (!dataSize) return 0;
2016-02-10 07:02:20 +00:00
if (_options & (GZ_TEXCACHE | GZ_HIRESTEXCACHE))
{
// zlib compress it. Compression level:1 (best speed)
2016-02-04 17:28:21 +00:00
uLongf destLen = _gzdestLen;
dest = (dest == _gzdest0) ? _gzdest1 : _gzdest0;
2016-02-10 07:02:20 +00:00
if (compress2(dest, &destLen, info->data, dataSize, 1) != Z_OK)
{
2016-02-04 17:28:21 +00:00
dest = info->data;
DBG_INFO(80, "Error: zlib compression failed!\n");
2016-02-04 17:28:21 +00:00
}
2016-02-10 07:02:20 +00:00
else
{
DBG_INFO(80, "zlib compressed: %.02fkb->%.02fkb\n", (float)dataSize / 1000, (float)destLen / 1000);
2016-02-04 17:28:21 +00:00
dataSize = destLen;
format |= GFX_TEXFMT_GZ;
2016-02-04 17:28:21 +00:00
}
2013-04-17 10:30:38 +00:00
}
2016-02-04 17:28:21 +00:00
}
2013-04-17 10:30:38 +00:00
// If cache size exceeds limit, remove old cache
2016-02-10 07:02:20 +00:00
if (_cacheSize > 0)
{
2016-02-04 17:28:21 +00:00
_totalSize += dataSize;
2016-02-10 07:02:20 +00:00
if ((_totalSize > _cacheSize) && !_cachelist.empty())
{
// _cachelist is arranged so that frequently used textures are in the back
2017-04-26 10:23:36 +00:00
std::list<uint64_t>::iterator itList = _cachelist.begin();
2016-02-10 07:02:20 +00:00
while (itList != _cachelist.end())
{
// Find it in _cache
2017-04-26 10:23:36 +00:00
std::map<uint64_t, TXCACHE*>::iterator itMap = _cache.find(*itList);
2016-02-10 07:02:20 +00:00
if (itMap != _cache.end())
{
// Yep we have it, let's remove it
2016-02-04 17:28:21 +00:00
_totalSize -= (*itMap).second->size;
free((*itMap).second->info.data);
delete (*itMap).second;
_cache.erase(itMap);
}
itList++;
// Check if memory cache has enough space
2016-02-04 17:28:21 +00:00
if (_totalSize <= _cacheSize)
break;
}
// Remove from _cachelist
2016-02-04 17:28:21 +00:00
_cachelist.erase(_cachelist.begin(), itList);
2013-04-17 10:30:38 +00:00
DBG_INFO(80, "+++++++++\n");
2016-02-04 17:28:21 +00:00
}
_totalSize -= dataSize;
2013-04-17 10:30:38 +00:00
}
2016-02-04 17:28:21 +00:00
// Cache it
2016-02-04 17:28:21 +00:00
uint8 *tmpdata = (uint8*)malloc(dataSize);
2016-02-10 07:02:20 +00:00
if (tmpdata)
{
2016-02-04 17:28:21 +00:00
TXCACHE *txCache = new TXCACHE;
2016-02-10 07:02:20 +00:00
if (txCache)
{
// We can directly write as we filter, but for now we get away
// with doing memcpy after all the filtering is done.
2016-02-04 17:28:21 +00:00
memcpy(tmpdata, dest, dataSize);
// Copy it
2016-02-04 17:28:21 +00:00
memcpy(&txCache->info, info, sizeof(GHQTexInfo));
txCache->info.data = tmpdata;
txCache->info.format = format;
txCache->size = dataSize;
// Add to cache
2016-02-10 07:02:20 +00:00
if (_cacheSize > 0)
{
2016-02-04 17:28:21 +00:00
_cachelist.push_back(checksum);
txCache->it = --(_cachelist.end());
}
// _cache[checksum] = txCache;
2017-04-26 10:23:36 +00:00
_cache.insert(std::map<uint64_t, TXCACHE*>::value_type(checksum, txCache));
2013-04-17 10:30:38 +00:00
#ifdef DEBUG
DBG_INFO(80, "[%5d] added! CRC:%08X %08X %d x %d gfmt:%x total:%.02fmb\n",
2016-02-04 17:28:21 +00:00
_cache.size(), (uint32)(checksum >> 32), (uint32)(checksum & 0xffffffff),
2016-02-10 07:02:20 +00:00
info->width, info->height, info->format, (float)_totalSize / 1000000);
2013-04-17 10:30:38 +00:00
DBG_INFO(80, "smalllodlog2:%d largelodlog2:%d aspectratiolog2:%d\n",
2016-02-04 17:28:21 +00:00
txCache->info.smallLodLog2, txCache->info.largeLodLog2, txCache->info.aspectRatioLog2);
2013-04-17 10:30:38 +00:00
2016-02-10 07:02:20 +00:00
if (info->tiles)
{
DBG_INFO(80, "tiles:%d un-tiled size:%d x %d\n", info->tiles, info->untiled_width, info->untiled_height);
2016-02-04 17:28:21 +00:00
}
2013-04-17 10:30:38 +00:00
2016-02-10 07:02:20 +00:00
if (_cacheSize > 0)
{
DBG_INFO(80, "Cache max config:%.02fmb\n", (float)_cacheSize / 1000000);
2013-04-17 10:30:38 +00:00
2016-02-10 07:02:20 +00:00
if (_cache.size() != _cachelist.size())
{
DBG_INFO(80, "Error: Cache/cache list mismatch! (%d/%d)\n", _cache.size(), _cachelist.size());
2016-02-04 17:28:21 +00:00
}
2016-02-10 07:02:20 +00:00
}
2013-04-17 10:30:38 +00:00
#endif
// Total cache size
2016-02-04 17:28:21 +00:00
_totalSize += dataSize;
2013-04-17 10:30:38 +00:00
2016-02-04 17:28:21 +00:00
return 1;
2016-02-10 07:02:20 +00:00
}
2016-02-04 17:28:21 +00:00
free(tmpdata);
2016-02-10 07:02:20 +00:00
}
2013-04-17 10:30:38 +00:00
2016-02-04 17:28:21 +00:00
return 0;
2013-04-17 10:30:38 +00:00
}
2017-04-26 10:23:36 +00:00
bool
TxCache::get(uint64_t checksum, GHQTexInfo *info)
2013-04-17 10:30:38 +00:00
{
2016-02-04 17:28:21 +00:00
if (!checksum || _cache.empty()) return 0;
// Find a match in cache
2017-04-26 10:23:36 +00:00
std::map<uint64_t, TXCACHE*>::iterator itMap = _cache.find(checksum);
2016-02-10 07:02:20 +00:00
if (itMap != _cache.end())
{
// Yep, we've got it
2016-02-04 17:28:21 +00:00
memcpy(info, &(((*itMap).second)->info), sizeof(GHQTexInfo));
// Push it to the back of the list
2016-02-10 07:02:20 +00:00
if (_cacheSize > 0)
{
2016-02-04 17:28:21 +00:00
_cachelist.erase(((*itMap).second)->it);
_cachelist.push_back(checksum);
((*itMap).second)->it = --(_cachelist.end());
}
2013-04-17 10:30:38 +00:00
// zlib decompress it
if (info->format & GFX_TEXFMT_GZ)
2016-02-10 07:02:20 +00:00
{
2016-02-04 17:28:21 +00:00
uLongf destLen = _gzdestLen;
uint8 *dest = (_gzdest0 == info->data) ? _gzdest1 : _gzdest0;
2016-02-10 07:02:20 +00:00
if (uncompress(dest, &destLen, info->data, ((*itMap).second)->size) != Z_OK)
{
DBG_INFO(80, "Error: zlib decompression failed!\n");
2016-02-04 17:28:21 +00:00
return 0;
}
info->data = dest;
info->format &= ~GFX_TEXFMT_GZ;
DBG_INFO(80, "zlib decompressed: %.02fkb->%.02fkb\n", (float)(((*itMap).second)->size) / 1000, (float)destLen / 1000);
2016-02-04 17:28:21 +00:00
}
return 1;
2013-04-17 10:30:38 +00:00
}
2016-02-04 17:28:21 +00:00
return 0;
2013-04-17 10:30:38 +00:00
}
2017-04-26 10:23:36 +00:00
bool TxCache::save(const char *path, const char *filename, int config)
2013-04-17 10:30:38 +00:00
{
2016-02-10 07:02:20 +00:00
if (!_cache.empty())
{
CPath(path, "").DirectoryCreate();
2013-04-17 10:30:38 +00:00
2016-02-10 07:02:20 +00:00
gzFile gzfp = gzopen(CPath(path, filename), "wb1");
DBG_INFO(80, "gzfp:%x file:%ls\n", gzfp, filename);
2016-02-10 07:02:20 +00:00
if (gzfp)
{
// Write header to determine config match
2016-02-04 17:28:21 +00:00
gzwrite(gzfp, &config, 4);
2017-04-26 10:23:36 +00:00
std::map<uint64_t, TXCACHE*>::iterator itMap = _cache.begin();
2016-02-10 07:02:20 +00:00
while (itMap != _cache.end())
{
2016-02-04 17:28:21 +00:00
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
For those who cannot wait the extra few seconds, we 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 & GFX_TEXFMT_GZ) {
2017-04-26 10:23:36 +00:00
dest = _gzdest0;
destLen = _gzdestLen;
if (dest && destLen) {
if (uncompress(dest, &destLen, (*itMap).second->info.data, (*itMap).second->size) != Z_OK) {
2021-04-12 11:35:39 +00:00
dest = nullptr;
2017-04-26 10:23:36 +00:00
destLen = 0;
}
format &= ~GFX_TEXFMT_GZ;
2017-04-26 10:23:36 +00:00
}
}*/
2016-02-04 17:28:21 +00:00
2016-02-10 07:02:20 +00:00
if (dest && destLen)
{
// Texture checksum
2016-02-04 17:28:21 +00:00
gzwrite(gzfp, &((*itMap).first), 8);
// Other texture info
2016-02-04 17:28:21 +00:00
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
2016-02-04 17:28:21 +00:00
/*if (_callback)
(*_callback)("Total textures saved to storage: %d\n", std::distance(itMap, _cache.begin()));*/
2013-04-17 10:30:38 +00:00
}
2016-02-04 17:28:21 +00:00
gzclose(gzfp);
2013-04-17 10:30:38 +00:00
}
}
2016-02-04 17:28:21 +00:00
return _cache.empty();
2013-04-17 10:30:38 +00:00
}
2017-04-26 10:23:36 +00:00
bool TxCache::load(const char *path, const char *filename, int config)
2013-04-17 10:30:38 +00:00
{
// Find it on disk
2016-02-10 07:02:20 +00:00
CPath cbuf(path, filename);
2013-04-17 10:30:38 +00:00
2016-02-04 17:28:21 +00:00
gzFile gzfp = gzopen(cbuf, "rb");
DBG_INFO(80, "gzfp:%x file:%ls\n", gzfp, filename);
2016-02-10 07:02:20 +00:00
if (gzfp)
{
// Yep, we have it, let's load it into memory cache
2016-02-04 17:28:21 +00:00
int dataSize;
2017-04-26 10:23:36 +00:00
uint64_t checksum;
2016-02-04 17:28:21 +00:00
GHQTexInfo tmpInfo;
int tmpconfig;
// Read header to determine config match
2016-02-04 17:28:21 +00:00
gzread(gzfp, &tmpconfig, 4);
2016-02-10 07:02:20 +00:00
if (tmpconfig == config)
{
do
{
2016-02-04 17:28:21 +00:00
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);
2016-02-10 07:02:20 +00:00
if (tmpInfo.data)
{
2016-02-04 17:28:21 +00:00
gzread(gzfp, tmpInfo.data, dataSize);
// Add to memory cache
add(checksum, &tmpInfo, (tmpInfo.format & GFX_TEXFMT_GZ) ? dataSize : 0);
2016-02-04 17:28:21 +00:00
free(tmpInfo.data);
}
2016-02-10 07:02:20 +00:00
else
{
2016-02-04 17:28:21 +00:00
gzseek(gzfp, dataSize, SEEK_CUR);
}
// Skip in between to prevent the loop from being tied down to v-sync
2016-02-04 17:28:21 +00:00
if (_callback && (!(_cache.size() % 100) || gzeof(gzfp)))
(*_callback)("[%d] total mem:%.02fmb - %ls\n", _cache.size(), (float)_totalSize / 1000000, filename);
2016-02-04 17:28:21 +00:00
} while (!gzeof(gzfp));
gzclose(gzfp);
2013-04-17 10:30:38 +00:00
}
}
2016-02-04 17:28:21 +00:00
return !_cache.empty();
2013-04-17 10:30:38 +00:00
}
2017-04-26 10:23:36 +00:00
bool TxCache::del(uint64_t checksum)
2013-04-17 10:30:38 +00:00
{
2016-02-04 17:28:21 +00:00
if (!checksum || _cache.empty()) return 0;
2013-04-17 10:30:38 +00:00
2017-04-26 10:23:36 +00:00
std::map<uint64_t, TXCACHE*>::iterator itMap = _cache.find(checksum);
2016-02-10 07:02:20 +00:00
if (itMap != _cache.end())
{
// For texture cache (not high resolution cache)
2016-02-04 17:28:21 +00:00
if (!_cachelist.empty()) _cachelist.erase(((*itMap).second)->it);
2013-04-17 10:30:38 +00:00
// Remove from cache
2016-02-04 17:28:21 +00:00
free((*itMap).second->info.data);
_totalSize -= (*itMap).second->size;
delete (*itMap).second;
_cache.erase(itMap);
2013-04-17 10:30:38 +00:00
DBG_INFO(80, "Removed from cache: checksum = %08X %08X\n", (uint32)(checksum & 0xffffffff), (uint32)(checksum >> 32));
2013-04-17 10:30:38 +00:00
2016-02-04 17:28:21 +00:00
return 1;
}
2013-04-17 10:30:38 +00:00
2016-02-04 17:28:21 +00:00
return 0;
2013-04-17 10:30:38 +00:00
}
2017-04-26 10:23:36 +00:00
bool TxCache::is_cached(uint64_t checksum)
2013-04-17 10:30:38 +00:00
{
2017-04-26 10:23:36 +00:00
std::map<uint64_t, TXCACHE*>::iterator itMap = _cache.find(checksum);
2016-02-04 17:28:21 +00:00
if (itMap != _cache.end()) return 1;
2013-04-17 10:30:38 +00:00
2016-02-04 17:28:21 +00:00
return 0;
2013-04-17 10:30:38 +00:00
}
2016-02-10 07:02:20 +00:00
void TxCache::clear()
2013-04-17 10:30:38 +00:00
{
2016-02-10 07:02:20 +00:00
if (!_cache.empty())
{
2017-04-26 10:23:36 +00:00
std::map<uint64_t, TXCACHE*>::iterator itMap = _cache.begin();
2016-02-10 07:02:20 +00:00
while (itMap != _cache.end())
{
2016-02-04 17:28:21 +00:00
free((*itMap).second->info.data);
delete (*itMap).second;
itMap++;
}
_cache.clear();
2013-04-17 10:30:38 +00:00
}
2016-02-04 17:28:21 +00:00
if (!_cachelist.empty()) _cachelist.clear();
2013-04-17 10:30:38 +00:00
2016-02-04 17:28:21 +00:00
_totalSize = 0;
}