mirror of
https://github.com/nitraiolo/CfgUSBLoader.git
synced 2024-11-27 13:44:17 +01:00
2002 lines
46 KiB
C
2002 lines
46 KiB
C
|
|
// by oggzee & usptactical
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <ogcsys.h>
|
|
#include <malloc.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "cache.h"
|
|
#include "mem.h"
|
|
#include "cfg.h"
|
|
#include "gui.h"
|
|
#include "coverflow.h"
|
|
//#include "disc.h"
|
|
//#include "gettext.h"
|
|
|
|
//#define cc_dbg dbg_printf
|
|
#define cc_dbg(...)
|
|
|
|
//0x2000000 = 32 MB
|
|
//0x1700000 = 23 MB
|
|
#define COVER_CACHE_DATA_SIZE 0x1900000 // 25 MB
|
|
|
|
extern struct discHdr *all_gameList;
|
|
extern struct discHdr *gameList;
|
|
extern s32 gameCnt, all_gameCnt;
|
|
|
|
#if 0
|
|
|
|
struct Cover_State
|
|
{
|
|
char used;
|
|
char id[8];
|
|
int gid; // ccache.game[gid]
|
|
GRRLIB_texImg tx;
|
|
//int lru;
|
|
};
|
|
|
|
struct Game_State
|
|
{
|
|
int request;
|
|
int state; // idle, loading, present, missing
|
|
int cid; // cache index
|
|
int agi; // actual all_gameList[agi] (mapping gameList -> all_gameList)
|
|
char id[8];
|
|
};
|
|
|
|
struct Cover_Cache
|
|
{
|
|
void *data;
|
|
int width4, height4;
|
|
int csize;
|
|
int num, cover_alloc, lru;
|
|
struct Cover_State *cover;
|
|
int num_game, game_alloc;
|
|
struct Game_State *game;
|
|
volatile int run;
|
|
volatile int idle;
|
|
volatile int restart;
|
|
volatile int quit;
|
|
lwp_t lwp;
|
|
lwpq_t queue;
|
|
mutex_t mutex;
|
|
};
|
|
|
|
struct Cover_Cache ccache;
|
|
int ccache_init = 0;
|
|
int ccache_inv = 0;
|
|
|
|
//****************************************************************************
|
|
//************************** START CACHE CODE ******************************
|
|
//****************************************************************************
|
|
|
|
void game_populate_ids()
|
|
{
|
|
int i;
|
|
char *id;
|
|
|
|
for (i=0; i<all_gameCnt; i++) {
|
|
memcheck_ptr(all_gameList, &all_gameList[i]);
|
|
id = (char*)all_gameList[i].id;
|
|
memcheck_ptr(ccache.game,&ccache.game[i]);
|
|
STRCOPY(ccache.game[i].id, id);
|
|
}
|
|
}
|
|
|
|
int cache_game_find_id(int gi)
|
|
{
|
|
int i;
|
|
bool getNewIds = true;
|
|
char *id = (char*)gameList[gi].id;
|
|
|
|
retry:;
|
|
for (i=0; i<all_gameCnt; i++) {
|
|
memcheck_ptr(ccache.game,&ccache.game[i]);
|
|
if (strcmp(ccache.game[i].id, id) == 0) {
|
|
ccache.game[gi].agi = i;
|
|
return i;
|
|
}
|
|
}
|
|
//didn't find the game so lets load up the
|
|
//ids in case they're not loaded yet
|
|
if (getNewIds) {
|
|
game_populate_ids();
|
|
getNewIds = false;
|
|
goto retry;
|
|
}
|
|
//should never get here...
|
|
return -1;
|
|
}
|
|
|
|
// find gameList[gi] in all_gameList[]
|
|
int cache_game_find(int gi)
|
|
{
|
|
// try if direct mapping exists
|
|
int agi = ccache.game[gi].agi;
|
|
if ( (strcmp(ccache.game[agi].id, (char*)gameList[gi].id) == 0) &&
|
|
(strcmp(ccache.game[agi].id, (char*)all_gameList[agi].id) == 0) )
|
|
{
|
|
return agi;
|
|
}
|
|
// direct mapping not found, search for id
|
|
return cache_game_find_id(gi);
|
|
}
|
|
|
|
int cache_find(char *id)
|
|
{
|
|
int i;
|
|
for (i=0; i<ccache.num; i++) {
|
|
if (strcmp(ccache.cover[i].id, id) == 0) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int cache_find_free()
|
|
{
|
|
int i;
|
|
for (i=0; i<ccache.num; i++) {
|
|
if (*ccache.cover[i].id == 0) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int cache_find_lru()
|
|
{
|
|
int i, idx;
|
|
//int lru = -1;
|
|
//int found = -1;
|
|
// not a true lru, but good enough
|
|
for (i=0; i<ccache.num; i++) {
|
|
idx = (ccache.lru + i) % ccache.num;
|
|
if (ccache.cover[idx].used == 0) {
|
|
ccache.lru = (idx + 1) % ccache.num;
|
|
return idx;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void cache_free(int i)
|
|
{
|
|
memcheck_ptr(ccache.cover, &ccache.cover[i]);
|
|
int gid = ccache.cover[i].gid;
|
|
if (*ccache.cover[i].id && gid >= 0) {
|
|
memcheck_ptr(ccache.game, &ccache.game[gid]);
|
|
ccache.game[gid].state = CS_IDLE;
|
|
}
|
|
*ccache.cover[i].id = 0;
|
|
ccache.cover[i].used = 0;
|
|
//ccache.cover[i].lru = 0;
|
|
ccache.cover[i].gid = -1;
|
|
}
|
|
|
|
int cache_alloc(char *id)
|
|
{
|
|
int i;
|
|
i = cache_find(id);
|
|
if (i >= 0) goto found;
|
|
i = cache_find_free();
|
|
if (i >= 0) goto found;
|
|
i = cache_find_lru();
|
|
if (i >= 0) goto found;
|
|
return -1;
|
|
found:
|
|
cache_free(i);
|
|
return i;
|
|
}
|
|
|
|
#ifdef DEBUG_CACHE
|
|
|
|
struct Cache_Debug
|
|
{
|
|
int cc_rq_prio;
|
|
int cc_i;
|
|
int cc_line;
|
|
int cc_line_lock;
|
|
int cc_line_unlock;
|
|
int cc_locked;
|
|
int cc_cid;
|
|
void *cc_img;
|
|
void *cc_buf;
|
|
};
|
|
|
|
struct Cache_Debug ccdbg;
|
|
|
|
#define ___ ccdbg.cc_line = __LINE__
|
|
#define CACHE_LOCK() do{ \
|
|
ccdbg.cc_line_lock = __LINE__; \
|
|
LWP_MutexLock(ccache.mutex); \
|
|
ccdbg.cc_locked = 1; }while(0)
|
|
|
|
#define CACHE_UNLOCK() do{ \
|
|
ccdbg.cc_line_unlock = __LINE__; \
|
|
LWP_MutexUnlock(ccache.mutex); \
|
|
ccdbg.cc_locked = 0; }while(0)
|
|
|
|
#define CCDBG(X) X
|
|
|
|
#else
|
|
|
|
#define ___
|
|
#define CACHE_LOCK() LWP_MutexLock(ccache.mutex)
|
|
#define CACHE_UNLOCK() LWP_MutexUnlock(ccache.mutex)
|
|
#define CCDBG(X)
|
|
|
|
#endif
|
|
|
|
|
|
/**
|
|
* This is the main cache thread. This method loops through all the game indexes
|
|
* (0 through ccache.num_game) and loads each requested cover. The covers are loaded
|
|
* based on the priority level (rq_prio) -> 1 is highest priority, 3 is lowest.
|
|
*
|
|
*/
|
|
|
|
void* cache_thread(void *arg)
|
|
{
|
|
int i, ret, actual_i;
|
|
char *id;
|
|
void *img;
|
|
void *buf;
|
|
int cid;
|
|
int rq_prio;
|
|
bool resizeToFullCover = false;
|
|
int cover_height, cover_width;
|
|
char path[200];
|
|
//cc_dbg("thread started\n");
|
|
|
|
___;
|
|
while (!ccache.quit) {
|
|
___;
|
|
CACHE_LOCK();
|
|
ccache.idle = 0;
|
|
//cc_dbg("thread running\n");
|
|
|
|
restart:
|
|
|
|
ccache.restart = 0;
|
|
___;
|
|
//if (0) // disable
|
|
for (rq_prio=1; rq_prio<=3; rq_prio++) {
|
|
___;
|
|
for (i=0; i<ccache.num_game; i++) {
|
|
___;
|
|
CCDBG(ccdbg.cc_rq_prio = rq_prio);
|
|
CCDBG(ccdbg.cc_i = i);
|
|
if (ccache.restart) goto restart;
|
|
//get the actual game index, in case we're using favorites
|
|
actual_i = cache_game_find(i);
|
|
memcheck_ptr(ccache.game, &ccache.game[actual_i]);
|
|
if (ccache.game[actual_i].request != rq_prio) continue; //was "<"
|
|
// load
|
|
ccache.game[actual_i].state = CS_LOADING;
|
|
memcheck_ptr(gameList, &gameList[i]);
|
|
id = (char*)gameList[i].id;
|
|
|
|
//cc_dbg("thread processing %d %s\n", i, id);
|
|
img = NULL;
|
|
___;
|
|
CACHE_UNLOCK();
|
|
___;
|
|
|
|
//capture the current cover width and height
|
|
cover_height = COVER_HEIGHT;
|
|
cover_width = COVER_WIDTH;
|
|
|
|
//load the cover image
|
|
if (CFG.cover_style == CFG_COVER_STYLE_FULL) {
|
|
//try to load the full cover
|
|
ret = Gui_LoadCover_style((u8*)id, &img, false, CFG_COVER_STYLE_FULL, path);
|
|
if (ret < 0) {
|
|
//try to load the 2D cover
|
|
ret = Gui_LoadCover_style((u8*)id, &img, true, CFG_COVER_STYLE_2D, path);
|
|
if (ret && img) resizeToFullCover = true;
|
|
}
|
|
} else {
|
|
ret = Gui_LoadCover_style((u8*)id, &img, false, CFG.cover_style, path);
|
|
}
|
|
//sleep(1);//dbg
|
|
___;
|
|
if (ccache.quit) goto quit;
|
|
___;
|
|
CACHE_LOCK();
|
|
___;
|
|
if (ret > 0 && img) {
|
|
___;
|
|
cid = cache_alloc(id);
|
|
if (cid < 0) {
|
|
// should not happen
|
|
free(img);
|
|
ccache.game[actual_i].state = CS_IDLE;
|
|
goto end_request;
|
|
}
|
|
buf = ccache.data + ccache.csize * cid;
|
|
___;
|
|
CACHE_UNLOCK();
|
|
___;
|
|
memcheck_ptr(ccache.cover, &ccache.cover[cid]);
|
|
CCDBG(ccdbg.cc_img = img);
|
|
CCDBG(ccdbg.cc_buf = buf);
|
|
CCDBG(ccdbg.cc_cid = cid);
|
|
//sleep(3);//dbg
|
|
|
|
LWP_MutexUnlock(ccache.mutex);
|
|
|
|
//make sure the cover height and width didn't change on us before we resize it
|
|
if ((cover_height == COVER_HEIGHT) &&
|
|
(cover_width == COVER_WIDTH)) {
|
|
|
|
if (resizeToFullCover) {
|
|
ccache.cover[cid].tx = Gui_LoadTexture_fullcover(img, COVER_WIDTH, COVER_HEIGHT, COVER_WIDTH_FRONT, buf, path);
|
|
resizeToFullCover = false;
|
|
} else {
|
|
if (CFG.cover_style == CFG_COVER_STYLE_FULL && CFG.gui_compress_covers) {
|
|
ccache.cover[cid].tx = Gui_LoadTexture_CMPR(img, COVER_WIDTH, COVER_HEIGHT, buf, path);
|
|
} else {
|
|
ccache.cover[cid].tx = Gui_LoadTexture_RGBA8(img, COVER_WIDTH, COVER_HEIGHT, buf, path);
|
|
}
|
|
if (!ccache.cover[cid].tx.data && CFG.cover_style == CFG_COVER_STYLE_FULL) {
|
|
//corrupted image - try to load the 2D cover
|
|
ret = Gui_LoadCover_style((u8*)id, &img, true, CFG_COVER_STYLE_2D, path);
|
|
if (ret && img)
|
|
ccache.cover[cid].tx = Gui_LoadTexture_fullcover(img, COVER_WIDTH, COVER_HEIGHT, COVER_WIDTH_FRONT, buf, path);
|
|
}
|
|
}
|
|
} else {
|
|
CCDBG(ccdbg.cc_img = NULL);
|
|
CCDBG(ccdbg.cc_buf = NULL);
|
|
free(img);
|
|
if (ccache.quit) goto quit;
|
|
CACHE_LOCK();
|
|
goto end_request;
|
|
}
|
|
|
|
CCDBG(ccdbg.cc_img = NULL);
|
|
CCDBG(ccdbg.cc_buf = NULL);
|
|
free(img);
|
|
___;
|
|
if (ccache.quit) goto quit;
|
|
___;
|
|
CACHE_LOCK();
|
|
___;
|
|
if (ccache.cover[cid].tx.data == NULL) {
|
|
cache_free(cid);
|
|
goto noimg;
|
|
}
|
|
// mark
|
|
STRCOPY(ccache.cover[cid].id, id);
|
|
// check if req change
|
|
if (ccache.game[actual_i].request) {
|
|
ccache.cover[cid].used = 1;
|
|
ccache.cover[cid].gid = actual_i;
|
|
ccache.game[actual_i].cid = cid;
|
|
ccache.game[actual_i].state = CS_PRESENT;
|
|
//cc_dbg("Load OK %d %d %s\n", i, cid, id);
|
|
|
|
} else {
|
|
ccache.game[actual_i].state = CS_IDLE;
|
|
}
|
|
} else {
|
|
noimg:
|
|
ccache.game[actual_i].state = CS_MISSING;
|
|
//cc_dbg("Load FAIL %d %s\n", i, id);
|
|
}
|
|
end_request:
|
|
// request processed.
|
|
ccache.game[actual_i].request = 0;
|
|
___;
|
|
}
|
|
}
|
|
___;
|
|
CCDBG(ccdbg.cc_rq_prio = 0);
|
|
CCDBG(ccdbg.cc_i = -1);
|
|
ccache.idle = 1;
|
|
//cc_dbg("thread idle\n");
|
|
// all processed. wait.
|
|
while (!ccache.restart && !ccache.quit) {
|
|
___;
|
|
CACHE_UNLOCK();
|
|
//cc_dbg("thread sleep\n");
|
|
___;
|
|
LWP_ThreadSleep(ccache.queue);
|
|
//cc_dbg("thread wakeup\n");
|
|
___;
|
|
CACHE_LOCK();
|
|
___;
|
|
}
|
|
___;
|
|
ccache.restart = 0;
|
|
CACHE_UNLOCK();
|
|
}
|
|
|
|
quit:
|
|
___;
|
|
ccache.quit = 0;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#undef ___
|
|
|
|
void cache_wait_idle()
|
|
{
|
|
int i;
|
|
if (!ccache_init) return;
|
|
// wait till idle
|
|
i = 1000; // 1 second
|
|
while (!ccache.idle && i) {
|
|
usleep(1000);
|
|
i--;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method is used to set the caching priority of the passed in game index.
|
|
* If the image is already cached or missing then the CoverCache is updated for
|
|
* the cover.
|
|
*
|
|
* @param game_i the game index
|
|
* @param rq_prio the cache priority: 1 = high priority
|
|
*/
|
|
void cache_request_1(int game_i, int rq_prio)
|
|
{
|
|
int cid, actual_i;
|
|
if (game_i < 0 || game_i >= ccache.num_game) return;
|
|
|
|
actual_i = cache_game_find(game_i);
|
|
if (ccache.game[actual_i].request>0) return;
|
|
// check if already present
|
|
if (ccache.game[actual_i].state == CS_PRESENT) {
|
|
cid = ccache.game[actual_i].cid;
|
|
ccache.cover[cid].used = 1;
|
|
return;
|
|
}
|
|
// check if already missing
|
|
if (ccache.game[actual_i].state == CS_MISSING) {
|
|
return;
|
|
}
|
|
// check if already cached
|
|
cid = cache_find((char*)gameList[game_i].id);
|
|
if (cid >= 0) {
|
|
ccache.cover[cid].used = 1;
|
|
ccache.cover[cid].gid = actual_i;
|
|
ccache.game[actual_i].cid = cid;
|
|
ccache.game[actual_i].state = CS_PRESENT;
|
|
return;
|
|
}
|
|
// add request
|
|
ccache.game[actual_i].request = rq_prio;
|
|
}
|
|
|
|
|
|
/**
|
|
* This method is used to set the caching priority of the passed in game index
|
|
* as well as the next x number of games.
|
|
*
|
|
* @param game_i the starting game index
|
|
* @param num the number of images to set the priority on
|
|
* @param rq_prio the cache priority: 1 = high priority
|
|
*/
|
|
void cache_request(int game_i, int num, int rq_prio)
|
|
{
|
|
int i, idle;
|
|
// setup requests
|
|
CACHE_LOCK();
|
|
for (i=0; i<num; i++) {
|
|
cache_request_1(game_i + i, rq_prio);
|
|
}
|
|
// restart thread
|
|
ccache.restart = 1;
|
|
idle = ccache.idle;
|
|
CACHE_UNLOCK();
|
|
//cc_dbg("cache restart\n");
|
|
LWP_ThreadSignal(ccache.queue);
|
|
if (idle) {
|
|
//cc_dbg("thread idle wait restart\n");
|
|
i = 10;
|
|
while (ccache.restart && i) {
|
|
usleep(10);
|
|
LWP_ThreadSignal(ccache.queue);
|
|
i--;
|
|
}
|
|
if (ccache.restart) {
|
|
//cc_dbg("thread fail restart\n");
|
|
}
|
|
}
|
|
//cc_dbg("cache restart done\n");
|
|
}
|
|
|
|
|
|
/**
|
|
* This method is used to set the caching priority of the passed in game index
|
|
* as well as the next x number of games before and after the index. All the
|
|
* rest of the covers are given a lower priority.
|
|
*
|
|
* @param game_i the starting game index
|
|
* @param num the number of images before and after the game_i to set the priority on
|
|
* @param rq_prio the cache priority: 1 = high priority
|
|
*/
|
|
void cache_request_before_and_after(int game_i, int num, int rq_prio)
|
|
{
|
|
int i, idle;
|
|
int nextRight, nextLeft;
|
|
|
|
// setup requests
|
|
CACHE_LOCK();
|
|
|
|
// set the first one high
|
|
cache_request_1(game_i, rq_prio);
|
|
|
|
// alternate the rest
|
|
nextRight = game_i + 1;
|
|
nextLeft = game_i - 1;
|
|
for (i=0; i<num; i++) {
|
|
if (nextRight >= ccache.num_game)
|
|
nextRight = 0;
|
|
cache_request_1(nextRight, 2);
|
|
|
|
if (nextLeft < 0)
|
|
nextLeft = ccache.num_game - 1;
|
|
cache_request_1(nextLeft, 2);
|
|
|
|
nextRight++;
|
|
nextLeft--;
|
|
}
|
|
|
|
// now set the priority to low for +-10 covers
|
|
for (i=0; i<10; i++) {
|
|
if (nextRight >= ccache.num_game)
|
|
nextRight = 0;
|
|
cache_request_1(nextRight, 3);
|
|
|
|
if (nextLeft < 0)
|
|
nextLeft = ccache.num_game - 1;
|
|
cache_request_1(nextLeft, 3);
|
|
|
|
nextRight++;
|
|
nextLeft--;
|
|
}
|
|
|
|
// restart thread
|
|
ccache.restart = 1;
|
|
idle = ccache.idle;
|
|
CACHE_UNLOCK();
|
|
LWP_ThreadSignal(ccache.queue);
|
|
if (idle) {
|
|
//cc_dbg("thread idle wait restart\n");
|
|
i = 10;
|
|
while (ccache.restart && i) {
|
|
usleep(10);
|
|
LWP_ThreadSignal(ccache.queue);
|
|
i--;
|
|
}
|
|
if (ccache.restart) {
|
|
//cc_dbg("thread fail restart\n");
|
|
}
|
|
}
|
|
//cc_dbg("cache restart done\n");
|
|
}
|
|
|
|
|
|
void cache_release_1(int game_i)
|
|
{
|
|
int cid;
|
|
if (game_i < 0 || game_i >= ccache.num_game) return;
|
|
memcheck_ptr(ccache.game, &ccache.game[game_i]);
|
|
ccache.game[game_i].request = 0;
|
|
if (ccache.game[game_i].state == CS_PRESENT) {
|
|
// release if already cached
|
|
cid = ccache.game[game_i].cid;
|
|
memcheck_ptr(ccache.cover, &ccache.cover[cid]);
|
|
ccache.cover[cid].used = 0; //--
|
|
ccache.game[game_i].state = CS_IDLE;
|
|
}
|
|
}
|
|
|
|
void cache_release(int game_i, int num)
|
|
{
|
|
int i;
|
|
// cancel requests
|
|
CACHE_LOCK();
|
|
for (i=0; i<num; i++) {
|
|
cache_release_1(game_i + i);
|
|
}
|
|
CACHE_UNLOCK();
|
|
}
|
|
|
|
void cache_release_all()
|
|
{
|
|
int i;
|
|
// cancel all requests
|
|
CACHE_LOCK();
|
|
for (i=0; i<ccache.num_game; i++) {
|
|
cache_release_1(i);
|
|
}
|
|
CACHE_UNLOCK();
|
|
}
|
|
|
|
void cache_num_game()
|
|
{
|
|
ccache.num_game = gameCnt;
|
|
}
|
|
|
|
/**
|
|
* Reallocates cache tables, if gamelist resized
|
|
*/
|
|
int cache_resize_tables()
|
|
{
|
|
bool resized = false;
|
|
int size, new_num;
|
|
|
|
//cc_dbg("\ncache_resize %p %d %d %p %d %d\n",
|
|
// ccache.game, ccache.game_alloc, ccache.num_game,
|
|
// ccache.cover, ccache.cover_alloc, ccache.num);
|
|
|
|
|
|
memcheck();
|
|
// game table
|
|
// resize if games added
|
|
if (all_gameCnt > ccache.game_alloc || ccache.game == NULL)
|
|
{
|
|
ccache.game_alloc = all_gameCnt;
|
|
size = sizeof(struct Game_State) * ccache.game_alloc;
|
|
ccache.game = realloc(ccache.game, size);
|
|
if (ccache.game == NULL) goto err;
|
|
resized = true;
|
|
}
|
|
cache_num_game();
|
|
|
|
memcheck();
|
|
// cover table
|
|
// check cover size
|
|
|
|
if (CFG.cover_style == CFG_COVER_STYLE_FULL && CFG.gui_compress_covers) {
|
|
ccache.width4 = COVER_WIDTH;
|
|
ccache.height4 = COVER_HEIGHT;
|
|
ccache.csize = (ccache.width4 * ccache.height4)/2;
|
|
} else {
|
|
ccache.width4 = (COVER_WIDTH + 3) >> 2 << 2;
|
|
ccache.height4 = (COVER_HEIGHT + 3) >> 2 << 2;
|
|
ccache.csize = ccache.width4 * ccache.height4 * 4;
|
|
}
|
|
|
|
new_num = COVER_CACHE_DATA_SIZE / ccache.csize;
|
|
if (new_num > ccache.cover_alloc || ccache.cover == NULL) {
|
|
ccache.cover_alloc = new_num;
|
|
size = sizeof(struct Cover_State) * ccache.cover_alloc;
|
|
ccache.cover = realloc(ccache.cover, size);
|
|
if (ccache.cover == NULL) goto err;
|
|
resized = true;
|
|
}
|
|
ccache.num = new_num;
|
|
|
|
memcheck();
|
|
if (resized) {
|
|
// clear both tables
|
|
size = sizeof(struct Game_State) * ccache.game_alloc;
|
|
memset(ccache.game, 0, size);
|
|
size = sizeof(struct Cover_State) * ccache.cover_alloc;
|
|
memset(ccache.cover, 0, size);
|
|
ccache.lru = 0;
|
|
}
|
|
memcheck();
|
|
|
|
//cc_dbg("cache_resize %p %d %d %p %d %d\n",
|
|
// ccache.game, ccache.game_alloc, ccache.num_game,
|
|
// ccache.cover, ccache.cover_alloc, ccache.num);
|
|
|
|
return 0;
|
|
|
|
err:
|
|
// fatal
|
|
printf(gt("ERROR: cache: out of memory"));
|
|
printf("\n");
|
|
sleep(1);
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Initializes the cover cache
|
|
*/
|
|
int Cache_Init()
|
|
{
|
|
int ret;
|
|
|
|
//update the cache tables size
|
|
if (ccache_inv || !ccache_init) {
|
|
// call invalidate again, for safety
|
|
// this will cancel all requests and wait till idle
|
|
Cache_Invalidate();
|
|
ret = cache_resize_tables();
|
|
if (ret) return -1;
|
|
ccache_inv = 0;
|
|
}
|
|
ccache.num_game = gameCnt;
|
|
|
|
if (ccache_init) return 0;
|
|
ccache_init = 1;
|
|
|
|
ccache.data = LARGE_memalign(32, COVER_CACHE_DATA_SIZE);
|
|
|
|
ccache.lwp = LWP_THREAD_NULL;
|
|
ccache.queue = LWP_TQUEUE_NULL;
|
|
ccache.mutex = LWP_MUTEX_NULL;
|
|
|
|
// start thread
|
|
LWP_InitQueue(&ccache.queue);
|
|
LWP_MutexInit(&ccache.mutex, FALSE);
|
|
ccache.run = 1;
|
|
//cc_dbg("running thread\n");
|
|
ret = LWP_CreateThread(&ccache.lwp, cache_thread, NULL, NULL, 32*1024, 40);
|
|
if (ret < 0) {
|
|
//cache_thread_stop();
|
|
return -1;
|
|
}
|
|
//cc_dbg("lwp ret: %d id: %x\n", ret, ccache.lwp);
|
|
return 0;
|
|
}
|
|
|
|
void Cache_Invalidate()
|
|
{
|
|
if (!ccache_init) return;
|
|
CACHE_LOCK();
|
|
// clear requests
|
|
memset(ccache.game, 0, sizeof(struct Game_State) * ccache.game_alloc);
|
|
CACHE_UNLOCK();
|
|
// wait till idle
|
|
cache_wait_idle();
|
|
if (!ccache.idle) {
|
|
//printf("\nERROR: cache not idle\n"); sleep(3);
|
|
}
|
|
// clear all
|
|
memset(ccache.game, 0, sizeof(struct Game_State) * ccache.game_alloc);
|
|
memset(ccache.cover, 0, sizeof(struct Cover_State) * ccache.cover_alloc);
|
|
|
|
ccache_inv = 1;
|
|
}
|
|
|
|
void Cache_Close()
|
|
{
|
|
int i;
|
|
if (!ccache_init) return;
|
|
ccache_init = 0;
|
|
CACHE_LOCK();
|
|
ccache.quit = 1;
|
|
CACHE_UNLOCK();
|
|
LWP_ThreadSignal(ccache.queue);
|
|
i = 1000; // 1 second
|
|
while (ccache.quit && i) {
|
|
usleep(1000);
|
|
LWP_ThreadSignal(ccache.queue);
|
|
i--;
|
|
}
|
|
if (ccache.quit) {
|
|
printf("\n");
|
|
printf(gt("ERROR: Cache Close"));
|
|
printf("\n");
|
|
sleep(5);
|
|
} else {
|
|
LWP_JoinThread(ccache.lwp, NULL);
|
|
}
|
|
LWP_CloseQueue(ccache.queue);
|
|
LWP_MutexDestroy(ccache.mutex);
|
|
ccache.lwp = LWP_THREAD_NULL;
|
|
ccache.queue = LWP_TQUEUE_NULL;
|
|
ccache.mutex = LWP_MUTEX_NULL;
|
|
SAFE_FREE(ccache.cover);
|
|
SAFE_FREE(ccache.game);
|
|
}
|
|
|
|
void draw_Cache()
|
|
{
|
|
#ifdef DEBUG_CACHE
|
|
unsigned int i, x, y, c, cid;
|
|
|
|
// thread state
|
|
x = 15;
|
|
y = 30;
|
|
c = ccache.idle ? 0x00FF00FF : 0xFF0000FF;
|
|
GRRLIB_Rectangle(x, y, 8, 8, c, 1);
|
|
x += 10;
|
|
c = !ccache.restart ? 0x00FF00FF : 0xFF0000FF;
|
|
GRRLIB_Rectangle(x, y, 8, 8, c, 1);
|
|
x += 10;
|
|
c = !ccache.quit ? 0x00FF00FF : 0xFF0000FF;
|
|
GRRLIB_Rectangle(x, y, 8, 8, c, 1);
|
|
x += 20;
|
|
GRRLIB_Printf(x, y, tx_cfont, 0xFFFFFFFF, 1, "%d %d %d",
|
|
ccdbg.cc_rq_prio, ccdbg.cc_i, ccdbg.cc_line);
|
|
// lock state
|
|
x = 15;
|
|
y = 40;
|
|
c = !ccdbg.cc_locked ? 0x00FF00FF : 0xFF0000FF;
|
|
GRRLIB_Rectangle(x, y, 8, 8, c, 1);
|
|
x += 40;
|
|
GRRLIB_Printf(x, y, tx_cfont, 0xFFFFFFFF, 1, "%d %d %p %p %d",
|
|
ccdbg.cc_line_lock, ccdbg.cc_line_unlock,
|
|
ccdbg.cc_img, ccdbg.cc_buf, ccdbg.cc_cid);
|
|
|
|
// cover state
|
|
for (i=0; i<ccache.num; i++) {
|
|
x = 15 + (i % 10) * 2;
|
|
y = 50 + (i / 10) * 2;
|
|
// free: black
|
|
// used: blue
|
|
// present: green
|
|
// add lru to red?
|
|
if (ccache.cover[i].id[0] == 0) {
|
|
c = 0x000000FF;
|
|
} else if (ccache.cover[i].used) {
|
|
c = 0x8080FFFF;
|
|
} else {
|
|
c = 0x80FF80FF;
|
|
}
|
|
GRRLIB_Rectangle(x, y, 2, 2, c, 1);
|
|
}
|
|
|
|
// game state
|
|
for (i=0; i<ccache.num_game; i++) {
|
|
x = 15 + (i % 10)*2;
|
|
y = 110 + (i / 10)*2;
|
|
// idle: black
|
|
// loading: yellow
|
|
// used: blue
|
|
// present: green
|
|
// missing: red
|
|
// request: violet
|
|
if (ccache.game[i].request) {
|
|
c = 0xFF00FFFF;
|
|
} else
|
|
switch (ccache.game[i].state) {
|
|
case CS_LOADING: c = 0xFFFF80FF; break;
|
|
case CS_PRESENT:
|
|
cid = ccache.game[i].cid;
|
|
if (ccache.cover[cid].used) {
|
|
c = 0x8080FFFF;
|
|
} else {
|
|
c = 0x80FF80FF;
|
|
}
|
|
break;
|
|
case CS_MISSING: c = 0xFF8080FF; break;
|
|
default:
|
|
case CS_IDLE: c = 0x000000FF; break;
|
|
}
|
|
//GRRLIB_Plot(x, y, c);
|
|
GRRLIB_Rectangle(x, y, 2, 2, c, 1);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
GRRLIB_texImg* cache_request_cover(int game_index, int style, int format, int priority, int *state)
|
|
{
|
|
GRRLIB_texImg *tx = NULL;
|
|
int actual_i = cache_game_find(game_index);
|
|
int cstate = ccache.game[actual_i].state;
|
|
if (cstate == CS_PRESENT) {
|
|
tx = &ccache.cover[ccache.game[actual_i].cid].tx;
|
|
// mark used
|
|
}
|
|
if (state) *state = cstate;
|
|
return tx;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#else // newcache
|
|
|
|
|
|
|
|
/*
|
|
sizes
|
|
2d: 64 kb 160x224 = 140 kb rgba
|
|
3d: 67 kb 176x248 = 170 kb rgba
|
|
full: 315 kb 512x340 = 680 kb rgba cmpr: 512x336/2 = 84 kb
|
|
hq: 1081 kb 1024x680 = 2720 kb rgba cmpr: 340 kb
|
|
mipmap full : 512x512 + LOD0 = 128 kb cmpr
|
|
mipmap full : 512x512 + LOD4 = 170 kb cmpr
|
|
mipmap hq : 1024x1024 + LOD5 = 682 kb cmpr
|
|
|
|
mipmap LOD 4 : 512, 256, 128, 64, 32
|
|
|
|
num of covers that fit in 25MB:
|
|
25MB / 140 = 128 2d rgba
|
|
25MB / 170 = 150 3d rgba
|
|
25MB / 84 = 304 full cmpr
|
|
25MB / 128 = 200 full mipmap lod0
|
|
25MB / 170 = 150 full mipmap lod4
|
|
25MB / 340 = 75 hq cmpr
|
|
25MB / 682 = 37 hq mipmap lod5
|
|
|
|
times: (seconds) (from SD)
|
|
load,decode,sum,covers, ms/c
|
|
3d: 1.114 + 1.228 = 2.342 / 71 = 32.9
|
|
full: 4.344 + 7.064 = 11.408 / 68 = 167.7
|
|
.ccc: 2.363 + 0 = 2.363 / 68 = 34.7
|
|
*/
|
|
|
|
// sizeof(GRRLIB_texImg) = 60
|
|
|
|
|
|
#define CACHE_STATE_NUM 500
|
|
#define LRU_MAX 0xFF
|
|
|
|
typedef struct Cover_Key
|
|
{
|
|
u8 id[7];
|
|
char style; // 2d, 3d, disc, full, hq
|
|
char format; // RGBA, C4x4, CMPR, MIPMAP
|
|
} Cover_Key;
|
|
|
|
typedef struct Cover_State
|
|
{
|
|
Cover_Key key;
|
|
char used; // 0=unused, 1=used
|
|
char request; // request priority: 3=hi 2=med 1=lo
|
|
char state; // free, loading, present, missing
|
|
char hq_available;
|
|
u8 lru; // least recently used
|
|
int hnext; // hash next
|
|
GRRLIB_texImg tx;
|
|
} Cover_State;
|
|
|
|
struct Cover_Cache
|
|
{
|
|
void *data;
|
|
heap heap;
|
|
Cover_State cstate[CACHE_STATE_NUM];
|
|
HashTable hash;
|
|
lwp_t lwp;
|
|
mutex_t mutex;
|
|
cond_t cond;
|
|
bool stop;
|
|
bool waiting;
|
|
int new_request; // highest priority of a new request
|
|
long long stat_load;
|
|
long long stat_decode;
|
|
int stat_n2d;
|
|
};
|
|
|
|
struct Cover_Cache ccache;
|
|
int ccache_init = 0;
|
|
int ccache_inv = 0;
|
|
|
|
|
|
#define CACHE_LOCK() LWP_MutexLock(ccache.mutex)
|
|
#define CACHE_UNLOCK() LWP_MutexUnlock(ccache.mutex)
|
|
|
|
u32 cover_key_hash(void *cb, void *key)
|
|
{
|
|
Cover_Key *ck = key;
|
|
return hash_string((char*)ck->id);
|
|
}
|
|
|
|
bool cover_key_cmp(void *cb, void *key, int handle)
|
|
{
|
|
Cover_Key *k1 = key;
|
|
Cover_Key *k2 = &ccache.cstate[handle].key;
|
|
return memcmp(k1, k2, sizeof(*k1)) == 0;
|
|
}
|
|
|
|
int* cover_hnext(void *cb, int handle)
|
|
{
|
|
return &ccache.cstate[handle].hnext;
|
|
}
|
|
|
|
void make_cover_key(Cover_Key *ck, u8 *id, int style, int format)
|
|
{
|
|
memset(ck, 0, sizeof(*ck));
|
|
strcopy((char*)ck->id, (char*)id, sizeof(ck->id));
|
|
ck->style = style;
|
|
ck->format = format;
|
|
}
|
|
|
|
Cover_State* cache_find_cover(u8 *id, int style, int format)
|
|
{
|
|
Cover_Key ck;
|
|
int i;
|
|
make_cover_key(&ck, id, style, format);
|
|
CACHE_LOCK();
|
|
i = hash_get(&ccache.hash, &ck);
|
|
CACHE_UNLOCK();
|
|
if (i < 0) return NULL;
|
|
return &ccache.cstate[i];
|
|
}
|
|
|
|
Cover_State* cache_find_free()
|
|
{
|
|
int i;
|
|
Cover_State *cs = NULL;
|
|
for (i=0; i<CACHE_STATE_NUM; i++) {
|
|
cs = &ccache.cstate[i];
|
|
if (cs->used) continue;
|
|
if (cs->request) continue;
|
|
if (cs->state != CS_IDLE) continue;
|
|
if (cs->key.id[0]) continue;
|
|
return cs;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
Cover_State* cache_find_lru(bool present)
|
|
{
|
|
Cover_State *cs = NULL;
|
|
Cover_State *lru_cs = NULL;
|
|
Cover_State *hq_cs = NULL;
|
|
int lru = -1;
|
|
int hq_lru = -1;
|
|
int hq_cnt = 0;
|
|
int rgb_cnt = 0;
|
|
int i;
|
|
CACHE_LOCK();
|
|
for (i=0; i<CACHE_STATE_NUM; i++) {
|
|
cs = &ccache.cstate[i];
|
|
// don't return a cover that is:
|
|
// used or requested or being loaded
|
|
// or the present flag doesn't match the present state
|
|
if (cs->used) continue;
|
|
if (cs->request) continue;
|
|
if (cs->state == CS_LOADING) continue;
|
|
if (present) {
|
|
if (cs->state != CS_PRESENT) continue;
|
|
} else {
|
|
if (cs->state == CS_PRESENT) continue;
|
|
}
|
|
if (cs->lru > lru) {
|
|
lru = cs->lru;
|
|
lru_cs = cs;
|
|
//if (lru >= LRU_MAX / 2) break;
|
|
}
|
|
if (present) {
|
|
// limit FULL/HQ RGBA to 3
|
|
if ( (cs->key.style == CFG_COVER_STYLE_FULL
|
|
|| cs->key.style == CFG_COVER_STYLE_HQ)
|
|
&& cs->key.format == CC_FMT_RGBA)
|
|
{
|
|
rgb_cnt++;
|
|
if (rgb_cnt > 3) {
|
|
lru_cs = cs;
|
|
break;
|
|
}
|
|
}
|
|
// limit HQ to 10
|
|
if (cs->key.style == CFG_COVER_STYLE_HQ) {
|
|
hq_cnt++;
|
|
if (cs->lru > hq_lru) {
|
|
hq_lru = cs->lru;
|
|
hq_cs = cs;
|
|
}
|
|
}
|
|
if (hq_cnt > 10) {
|
|
lru_cs = hq_cs;
|
|
}
|
|
}
|
|
}
|
|
CACHE_UNLOCK();
|
|
return lru_cs;
|
|
}
|
|
|
|
void cache_free_buf(void *ptr)
|
|
{
|
|
CACHE_LOCK();
|
|
if (ptr) heap_free(&ccache.heap, ptr);
|
|
CACHE_UNLOCK();
|
|
}
|
|
|
|
#define CACHE_SAFE_FREE(ptr) if(ptr){cache_free_buf(ptr);ptr=NULL;}
|
|
|
|
void* cache_alloc_buf(int size)
|
|
{
|
|
CACHE_LOCK();
|
|
void *ptr = heap_alloc(&ccache.heap, size);
|
|
CACHE_UNLOCK();
|
|
return ptr;
|
|
}
|
|
|
|
void cache_free_cover(Cover_State *cs)
|
|
{
|
|
int i = cs - ccache.cstate;
|
|
CACHE_LOCK();
|
|
hash_remove(&ccache.hash, &cs->key, i);
|
|
CACHE_SAFE_FREE(cs->tx.data);
|
|
memset(cs, 0, sizeof(*cs));
|
|
CACHE_UNLOCK();
|
|
}
|
|
|
|
Cover_State* cache_free_lru(bool present)
|
|
{
|
|
Cover_State *cs;
|
|
cs = cache_find_lru(present);
|
|
if (cs) {
|
|
cache_free_cover(cs);
|
|
}
|
|
return cs;
|
|
}
|
|
|
|
void* cache_alloc_data(int size)
|
|
{
|
|
void *ptr = NULL;
|
|
CACHE_LOCK();
|
|
do {
|
|
ptr = cache_alloc_buf(size);
|
|
if (!ptr) {
|
|
// try to free
|
|
// find lru with data
|
|
Cover_State *cs = cache_free_lru(true);
|
|
if (!cs) break;
|
|
}
|
|
} while (!ptr);
|
|
CACHE_UNLOCK();
|
|
if (!ptr) dbg_printf("cc: alloc err %d\n", size);
|
|
return ptr;
|
|
}
|
|
|
|
Cover_State* cache_get_free_cover()
|
|
{
|
|
Cover_State *cs = NULL;
|
|
cs = cache_find_free();
|
|
if (!cs) {
|
|
cs = cache_free_lru(false);
|
|
}
|
|
if (!cs) {
|
|
cs = cache_free_lru(true);
|
|
}
|
|
return cs;
|
|
}
|
|
|
|
Cover_State* cache_alloc_cover(u8 *id, int style, int format)
|
|
{
|
|
Cover_State *cs = NULL;
|
|
CACHE_LOCK();
|
|
cs = cache_get_free_cover();
|
|
if (cs) {
|
|
make_cover_key(&cs->key, id, style, format);
|
|
int i = cs - ccache.cstate;
|
|
hash_add(&ccache.hash, &cs->key, i);
|
|
}
|
|
CACHE_UNLOCK();
|
|
return cs;
|
|
}
|
|
|
|
// find existing mark as used and return
|
|
// or create new, set key, mark as used and return
|
|
// return NULL on error
|
|
Cover_State* cache_get_cover(u8 *id, int style, int format)
|
|
{
|
|
Cover_State *cs = NULL;
|
|
CACHE_LOCK();
|
|
cs = cache_find_cover(id, style, format);
|
|
if (!cs) {
|
|
cs = cache_alloc_cover(id, style, format);
|
|
}
|
|
if (cs) {
|
|
cs->used = 1;
|
|
}
|
|
CACHE_UNLOCK();
|
|
return cs;
|
|
}
|
|
|
|
//
|
|
|
|
struct CIMG_HDR
|
|
{
|
|
char tag[4];
|
|
char id[8];
|
|
short version;
|
|
short gxformat;
|
|
short width;
|
|
short height;
|
|
short lod;
|
|
short hq;
|
|
short pad[4];
|
|
};
|
|
|
|
char* cache_image_path(u8 *id, char *path, int size)
|
|
{
|
|
char *coverpath = cfg_get_covers_path(CFG_COVER_STYLE_CACHE);
|
|
snprintf(path, size, "%s/%.6s.ccc", coverpath, (char*)id);
|
|
return coverpath;
|
|
}
|
|
|
|
// the normal quality 512x512 mipmap is saved first followed by HQ
|
|
// so that it loads faster since HQ is used less frequently
|
|
int cache_save_image(u8 *id, GRRLIB_texImg *tx)
|
|
{
|
|
char filepath[200];
|
|
char *dir;
|
|
struct CIMG_HDR hdr;
|
|
int fd, ret;
|
|
int size, hq_size = 0;
|
|
u8 *data;
|
|
|
|
if (!id || !*id || !tx || !tx->data) return -1;
|
|
dir = cache_image_path(id, filepath, sizeof(filepath));
|
|
dbg_printf("cc save: %s\n", filepath);
|
|
mkdir(dir, 0777);
|
|
fd = open(filepath, O_CREAT | O_WRONLY, 0333);
|
|
if (fd < 0) return -1;
|
|
|
|
memset(&hdr, 0, sizeof(hdr));
|
|
memcpy(hdr.tag, "CIMG", 4);
|
|
memcpy(hdr.id, id, 6);
|
|
hdr.version = 1;
|
|
hdr.gxformat = tx->tex_format;
|
|
hdr.width = tx->w;
|
|
hdr.height = tx->h;
|
|
hdr.lod = tx->tex_lod;
|
|
hdr.hq = 0;
|
|
data = tx->data;
|
|
if (tx->w > 512 && tx->tex_lod) {
|
|
// hq
|
|
hq_size = fixGX_GetTexBufferSize(hdr.width, hdr.height,
|
|
hdr.gxformat, GX_FALSE, 1);
|
|
data += hq_size;
|
|
hdr.width /= 2;
|
|
hdr.height /= 2;
|
|
hdr.lod--;
|
|
hdr.hq = 1;
|
|
}
|
|
|
|
ret = write(fd, &hdr, sizeof(hdr));
|
|
if (ret != sizeof(hdr)) goto error;
|
|
|
|
size = fixGX_GetTexBufferSize(hdr.width, hdr.height,
|
|
hdr.gxformat, hdr.lod ? GX_TRUE : GX_FALSE, hdr.lod);
|
|
ret = write(fd, data, size);
|
|
if (ret != size) goto error;
|
|
|
|
if (hdr.hq) {
|
|
ret = write(fd, tx->data, hq_size);
|
|
if (ret != hq_size) goto error;
|
|
}
|
|
close(fd);
|
|
return 0;
|
|
|
|
error:
|
|
if (fd >= 0) close(fd);
|
|
remove(filepath);
|
|
return -1;
|
|
}
|
|
|
|
// style: FULL OR HQ
|
|
// FULL is always loaded, HQ only if requested and available in file
|
|
// hqavail is set to what is available in the file
|
|
int cache_load_image(u8 *id, GRRLIB_texImg *tx, int style, bool *hqavail)
|
|
{
|
|
char filepath[200];
|
|
struct CIMG_HDR hdr;
|
|
int fd, ret;
|
|
int size, hq_size = 0, data_size;
|
|
u8 *data = NULL;
|
|
|
|
if (!id || !*id || !tx || tx->data) return -1;
|
|
memset(tx, 0, sizeof(*tx));
|
|
memset(&hdr, 0, sizeof(hdr));
|
|
*hqavail = false;
|
|
cache_image_path(id, filepath, sizeof(filepath));
|
|
fd = open(filepath, O_RDONLY);
|
|
if (fd < 0) return -1;
|
|
dbg_printf("cc load: %s\n", filepath);
|
|
ret = read(fd, &hdr, sizeof(hdr));
|
|
if (ret != sizeof(hdr)) goto error;
|
|
if (memcmp(hdr.tag, "CIMG", 4)) goto error;
|
|
if (memcmp(hdr.id, id, 6)) goto error;
|
|
if (hdr.version != 1) goto error;
|
|
if (hdr.gxformat != GX_TF_CMPR) goto error;
|
|
if (hdr.width > 512) goto error;
|
|
if (hdr.height > 512) goto error;
|
|
|
|
size = fixGX_GetTexBufferSize(hdr.width, hdr.height,
|
|
hdr.gxformat, hdr.lod ? GX_TRUE : GX_FALSE, hdr.lod);
|
|
|
|
if (style == CFG_COVER_STYLE_HQ && hdr.hq) {
|
|
// load HQ too
|
|
hdr.width *= 2;
|
|
hdr.height *= 2;
|
|
hdr.lod++;
|
|
hq_size = fixGX_GetTexBufferSize(hdr.width, hdr.height,
|
|
hdr.gxformat, GX_FALSE, 1);
|
|
}
|
|
|
|
data_size = size + hq_size;
|
|
data = cache_alloc_data(data_size);
|
|
if (!data) {
|
|
close(fd);
|
|
return -2; // alloc error
|
|
}
|
|
|
|
ret = read(fd, data + hq_size, size);
|
|
if (ret != size) goto error;
|
|
|
|
if (hq_size) {
|
|
ret = read(fd, data, hq_size);
|
|
if (ret != hq_size) goto error;
|
|
//memset(data, 0xAA, 16000); // dbg red stripe
|
|
}
|
|
|
|
tx->data = data;
|
|
tx->w = hdr.width;
|
|
tx->h = hdr.height;
|
|
tx->tex_lod = hdr.lod;
|
|
tx->tex_format = hdr.gxformat;
|
|
GRRLIB_FlushTex(tx);
|
|
*hqavail = hdr.hq ? true : false;
|
|
close(fd);
|
|
return 0;
|
|
|
|
error:
|
|
CACHE_SAFE_FREE(data);
|
|
memset(tx, 0, sizeof(*tx));
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
|
|
int cache_convert_image(u8 *id)
|
|
{
|
|
char path[200];
|
|
void *img = NULL;
|
|
GRRLIB_texImg tx;
|
|
u32 width=0, height=0;
|
|
int gx_lod = CC_MIPMAP_LOD;
|
|
int ret;
|
|
memset(&tx, 0, sizeof(tx));
|
|
ret = Gui_LoadCover_style(id, &img, false, CFG_COVER_STYLE_HQ, path);
|
|
if (ret <= 0) return -1;
|
|
ret = __Gui_GetPngDimensions(img, &width, &height);
|
|
if (ret < 0) goto out;
|
|
width = upperPower(width);
|
|
height = upperPower(height);
|
|
if (width > 512) gx_lod++; // +1 if HQ
|
|
tx = Gui_LoadTexture_MIPMAP(img, width, height, gx_lod, NULL, path);
|
|
if (!tx.data) { ret = -1; goto out; }
|
|
ret = cache_save_image(id, &tx);
|
|
out:
|
|
SAFE_FREE(img);
|
|
SAFE_FREE(tx.data);
|
|
return ret;
|
|
}
|
|
|
|
void cache_check_convert_image(u8 *id)
|
|
{
|
|
struct stat st1, st2;
|
|
char path1[200], path2[200];
|
|
int ret1, ret2;
|
|
ret1 = find_cover_path(id, CFG_COVER_STYLE_FULL, path1, sizeof(path1), &st1);
|
|
cache_image_path(id, path2, sizeof(path2));
|
|
ret1 = stat(path1, &st1);
|
|
if (ret1 == 0) {
|
|
ret2 = stat(path2, &st2);
|
|
if (ret2 == 0 && st1.st_mtime <= st2.st_mtime) return;
|
|
// ignore if modify time in future
|
|
if (ret2 == 0 && st1.st_mtime > time(NULL)) return;
|
|
cache_convert_image(id);
|
|
}
|
|
}
|
|
|
|
|
|
void cache_cover_load(Cover_State *cs)
|
|
{
|
|
char path[200];
|
|
void *img = NULL;
|
|
void *buf = NULL;
|
|
int size;
|
|
bool resizeToFullCover = false;
|
|
GRRLIB_texImg tx;
|
|
int ret;
|
|
int state = CS_IDLE;
|
|
long long t1, tload = 0, tdecode = 0;
|
|
bool hqavail = false;
|
|
Cover_State *csfull = NULL;
|
|
memset(&tx, 0, sizeof(tx));
|
|
|
|
if (cs->key.style == CFG_COVER_STYLE_HQ) {
|
|
//sleep(2);//dbg
|
|
// if FULL is present it will have the info if HQ is available
|
|
CACHE_LOCK();
|
|
csfull = cache_find_cover(cs->key.id, CFG_COVER_STYLE_FULL, cs->key.format);
|
|
if (csfull) hqavail = csfull->hq_available;
|
|
CACHE_UNLOCK();
|
|
if (csfull && !hqavail) {
|
|
state = CS_MISSING;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
// use cached converted image
|
|
if (cs->key.style == CFG_COVER_STYLE_FULL || cs->key.style == CFG_COVER_STYLE_HQ) {
|
|
if (cs->key.format == CC_FMT_MIPMAP) {
|
|
t1 = gettime();
|
|
cache_check_convert_image(cs->key.id);
|
|
tdecode = diff_usec(t1, gettime());
|
|
t1 = gettime();
|
|
ret = cache_load_image(cs->key.id, &tx, cs->key.style, &hqavail);
|
|
tload = diff_usec(t1, gettime());
|
|
if (ret == 0 && tx.data) goto out;
|
|
if (ret == -2) {
|
|
// alloc error
|
|
state = CS_IDLE;
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
t1 = gettime();
|
|
//load the cover image
|
|
ret = Gui_LoadCover_style((u8*)cs->key.id, &img, false, cs->key.style, path);
|
|
tload = diff_usec(t1, gettime());
|
|
//cc_dbg("cc ld: %.6s %d %d = %d %p\n",
|
|
// cs->key.id, cs->key.style, cs->key.format, ret, img);
|
|
if (ret <= 0 && cs->key.style == CFG_COVER_STYLE_FULL) {
|
|
//try to load the 2D cover
|
|
;overlay_2d:;
|
|
tload = 0;
|
|
SAFE_FREE(img);
|
|
ret = Gui_LoadCover_style((u8*)cs->key.id, &img, true, CFG_COVER_STYLE_2D, path);
|
|
//cc_dbg("cc ld 2d: %.6s %d %d = %d %p\n",
|
|
// cs->key.id, cs->key.style, cs->key.format, ret, img);
|
|
resizeToFullCover = true;
|
|
}
|
|
if (ret <= 0 || !img) {
|
|
state = CS_MISSING;
|
|
goto out;
|
|
}
|
|
if (resizeToFullCover) {
|
|
int cover_width_front = (int)(tx_nocover_full.h / 1.4) >> 2 << 2;
|
|
size = tx_nocover_full.w * tx_nocover_full.h * 4;
|
|
if (CFG.gui_compress_covers) {
|
|
size /= 8;
|
|
}
|
|
buf = cache_alloc_data(size);
|
|
if (!buf) {
|
|
state = CS_IDLE;
|
|
goto out;
|
|
}
|
|
// XXX format depends on CFG.gui_compress_covers
|
|
// needs to be an arg.
|
|
tx = Gui_LoadTexture_fullcover(img,
|
|
tx_nocover_full.w, tx_nocover_full.h,
|
|
cover_width_front, buf, path);
|
|
if (!tx.data) {
|
|
CACHE_SAFE_FREE(buf);
|
|
} else {
|
|
ccache.stat_n2d++;
|
|
}
|
|
} else {
|
|
u32 width=0, height=0;
|
|
// Get image dimensions
|
|
ret = __Gui_GetPngDimensions(img, &width, &height);
|
|
if (ret < 0) goto invalid_img;
|
|
//cc_dbg("cc sz %d x %d / %d x %d\n", width, height, COVER_WIDTH, COVER_HEIGHT);
|
|
if (width > 512) hqavail = true;
|
|
if (cs->key.style == CFG_COVER_STYLE_FULL) {
|
|
// scale down HQ if FULL requested
|
|
if (width > 512) {
|
|
width = 512;
|
|
height = 340;
|
|
//if (cs->key.format == CC_FMT_CMPR) height = 336;
|
|
}
|
|
//if (width > 64) { width = 64; height = 64; } // dbg
|
|
}
|
|
int gx_fmt = GX_TF_RGBA8;
|
|
int gx_mipmap = GX_FALSE;
|
|
int gx_lod = 0;
|
|
switch (cs->key.format) {
|
|
case CC_FMT_CMPR:
|
|
gx_fmt = GX_TF_CMPR;
|
|
// 512x512 CMPR looks better than 512x340
|
|
// but is slower - old AA with 512x512 is too slow
|
|
//height = width;
|
|
break;
|
|
case CC_FMT_MIPMAP:
|
|
gx_fmt = GX_TF_CMPR;
|
|
gx_mipmap = GX_TRUE;
|
|
gx_lod = CC_MIPMAP_LOD;
|
|
width = upperPower(width);
|
|
height = upperPower(height);
|
|
if (width > 512) gx_lod++; // +1 if HQ
|
|
break;
|
|
}
|
|
size = fixGX_GetTexBufferSize(width, height, gx_fmt, gx_mipmap, gx_lod);
|
|
buf = cache_alloc_data(size);
|
|
if (!buf) {
|
|
state = CS_IDLE;
|
|
goto out;
|
|
}
|
|
t1 = gettime();
|
|
switch (cs->key.format) {
|
|
default:
|
|
case CC_FMT_C4x4:
|
|
tx = Gui_LoadTexture_RGBA8(img, width, height, buf, path);
|
|
break;
|
|
case CC_FMT_CMPR:
|
|
tx = Gui_LoadTexture_CMPR(img, width, height, buf, path);
|
|
break;
|
|
case CC_FMT_MIPMAP:
|
|
tx = Gui_LoadTexture_MIPMAP(img, width, height, gx_lod, buf, path);
|
|
break;
|
|
}
|
|
tdecode = diff_usec(t1, gettime());
|
|
//cc_dbg("cc tx %d (%p %d %d %p) = %p\n", cs->key.format,
|
|
// img, COVER_WIDTH, COVER_HEIGHT, buf, tx.data);
|
|
if (!tx.data) {
|
|
;invalid_img:;
|
|
tdecode = 0;
|
|
state = CS_MISSING;
|
|
CACHE_SAFE_FREE(buf);
|
|
// corrupted image or out of mem?
|
|
dbg_printf("cc error %.6s\n", cs->key.id);
|
|
if (cs->key.style == CFG_COVER_STYLE_FULL) {
|
|
// try to load the 2D cover
|
|
goto overlay_2d;
|
|
}
|
|
}
|
|
}
|
|
out:
|
|
SAFE_FREE(img);
|
|
CACHE_LOCK();
|
|
if (tx.data) {
|
|
state = CS_PRESENT;
|
|
if (cs->key.style == CFG_COVER_STYLE_HQ && !hqavail) {
|
|
// HQ requested but only full found
|
|
// if full exists then discard currently loaded
|
|
// else store as full and mark HQ as not available
|
|
csfull = cache_find_cover(cs->key.id, CFG_COVER_STYLE_FULL, cs->key.format);
|
|
if (!csfull) {
|
|
csfull = cache_get_cover(cs->key.id, CFG_COVER_STYLE_FULL, cs->key.format);
|
|
csfull->used = 0;
|
|
}
|
|
if (csfull) {
|
|
if (csfull->state != CS_PRESENT) {
|
|
csfull->state = CS_PRESENT;
|
|
csfull->tx = tx;
|
|
csfull->request = 0;
|
|
buf = NULL;
|
|
}
|
|
csfull->hq_available = false;
|
|
}
|
|
CACHE_SAFE_FREE(buf);
|
|
memset(&tx, 0, sizeof(tx));
|
|
state = CS_MISSING;
|
|
}
|
|
}
|
|
cs->state = state;
|
|
cs->request = 0;
|
|
cs->tx = tx;
|
|
cs->hq_available = hqavail;
|
|
if (tload && tx.data) {
|
|
ccache.stat_load += tload;
|
|
ccache.stat_decode += tdecode;
|
|
}
|
|
CACHE_UNLOCK();
|
|
if (tx.data) cc_dbg("cc load: %.6s %d %d = %p %d\n",
|
|
cs->key.id, cs->key.style, cs->key.format, tx.data, state);
|
|
}
|
|
|
|
void cache_process_requests()
|
|
{
|
|
Cover_State *cs;
|
|
int prio;
|
|
int i;
|
|
do {
|
|
ccache.new_request = 0;
|
|
for (prio = 3; prio > 0; prio--) {
|
|
;restart:;
|
|
for (i=0; i<CACHE_STATE_NUM; i++) {
|
|
if (ccache.stop) break;
|
|
cs = &ccache.cstate[i];
|
|
CACHE_LOCK();
|
|
if (cs->request == prio) {
|
|
cs->state = CS_LOADING;
|
|
}
|
|
CACHE_UNLOCK();
|
|
if (cs->state == CS_LOADING) {
|
|
// load
|
|
//sleep(1);//dbg
|
|
cache_cover_load(cs);
|
|
// need to restart loop at higher priority?
|
|
if (ccache.new_request > prio) {
|
|
//cc_dbg("new rq %d %d\n", ccache.new_request, prio);
|
|
prio = ccache.new_request;
|
|
ccache.new_request = 0;
|
|
goto restart;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//if (ccache.new_request) cc_dbg("new rq %d\n", ccache.new_request);
|
|
} while (ccache.new_request);
|
|
}
|
|
|
|
void* cache_thread(void *arg)
|
|
{
|
|
cc_dbg("cc started\n");
|
|
while (1) {
|
|
// lock
|
|
CACHE_LOCK();
|
|
if (!ccache.stop && ccache.new_request == 0) {
|
|
cc_dbg("cc waiting\n");
|
|
ccache.waiting = true;
|
|
// LWP_CondWait unlocks mutex on enter
|
|
LWP_CondWait(ccache.cond, ccache.mutex);
|
|
// LWP_CondWait locks mutex on exit
|
|
ccache.waiting = false;
|
|
cc_dbg("cc -> running\n");
|
|
}
|
|
// unlock
|
|
CACHE_UNLOCK();
|
|
if (ccache.stop) {
|
|
break;
|
|
}
|
|
cache_process_requests();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int Cache_Init()
|
|
{
|
|
if (ccache_init) return 0;
|
|
int ret;
|
|
ccache.data = mem2_alloc(COVER_CACHE_DATA_SIZE);
|
|
if (!ccache.data) return -1;
|
|
heap_init(&ccache.heap, ccache.data, COVER_CACHE_DATA_SIZE);
|
|
hash_init(&ccache.hash, 0, NULL, cover_key_hash, cover_key_cmp, cover_hnext);
|
|
ccache.lwp = LWP_THREAD_NULL;
|
|
ccache.mutex = LWP_MUTEX_NULL;
|
|
ccache.cond = LWP_COND_NULL;
|
|
LWP_MutexInit(&ccache.mutex, true);
|
|
LWP_CondInit(&ccache.cond);
|
|
// start thread
|
|
dbg_printf("cc. start\n");
|
|
ret = LWP_CreateThread(&ccache.lwp, cache_thread, NULL, NULL, 32*1024, 40);
|
|
if (ret < 0) {
|
|
return -1;
|
|
}
|
|
//cc_dbg("lwp ret: %d id: %x\n", ret, ccache.lwp);
|
|
ccache_init = 1;
|
|
return 0;
|
|
}
|
|
|
|
void Cache_Wakeup()
|
|
{
|
|
//cc_dbg("cc wakeup\n");
|
|
CACHE_LOCK();
|
|
bool waiting = ccache.waiting;
|
|
CACHE_UNLOCK();
|
|
// wake
|
|
if (waiting) {
|
|
cc_dbg("cc signal\n");
|
|
LWP_CondSignal(ccache.cond);
|
|
}
|
|
}
|
|
|
|
void Cache_Close()
|
|
{
|
|
dbg_printf("cc close\n");
|
|
if (!ccache_init) return;
|
|
if (ccache.lwp == LWP_THREAD_NULL) return;
|
|
CACHE_LOCK();
|
|
ccache.stop = true;
|
|
CACHE_UNLOCK();
|
|
Cache_Wakeup();
|
|
// wait for exit and cleanup
|
|
LWP_JoinThread(ccache.lwp, NULL);
|
|
LWP_MutexDestroy(ccache.mutex);
|
|
LWP_CondDestroy(ccache.cond);
|
|
hash_close(&ccache.hash);
|
|
//heap_close
|
|
SAFE_FREE(ccache.data);
|
|
memset(&ccache, 0, sizeof(ccache));
|
|
ccache.lwp = LWP_THREAD_NULL;
|
|
ccache_init = 0;
|
|
dbg_printf("cc stop\n");
|
|
}
|
|
|
|
GRRLIB_texImg* cache_request_cover1(int game_index, int style, int format, int priority, int *state)
|
|
{
|
|
Cover_State *cs = NULL;
|
|
GRRLIB_texImg *tx = NULL;
|
|
bool need_wake = false;
|
|
u8 *id = NULL;
|
|
if (game_index < 0 || game_index >= gameCnt) goto error;
|
|
if (style > CFG_COVER_STYLE_HQ) goto error;
|
|
id = gameList[game_index].id;
|
|
//cc_dbg("cc req: %d %.6s %d %d %d\n", game_index, id, style, format, priority);
|
|
if (priority) {
|
|
cs = cache_get_cover(id, style, format);
|
|
if (!cs) goto error;
|
|
// cs will already be marked as used by cache_get_cover
|
|
} else {
|
|
// if priority == CC_PRIO_NONE then
|
|
// return the cover only if already loaded
|
|
// but don't updte LRU
|
|
CACHE_LOCK();
|
|
cs = cache_find_cover(id, style, format);
|
|
if (cs) cs->used = 1;
|
|
CACHE_UNLOCK();
|
|
if (!cs) {
|
|
if (state) *state = CS_IDLE;
|
|
return NULL;
|
|
}
|
|
|
|
}
|
|
if (priority > 0) {
|
|
CACHE_LOCK();
|
|
if (cs->state == CS_IDLE) {
|
|
if (cs->request == 0) {
|
|
need_wake = true;
|
|
//cc_dbg("cc rq: %d %.6s %d %d %d\n", game_index, id, style, format, priority);
|
|
}
|
|
if (priority > cs->request) {
|
|
cs->request = priority;
|
|
// mark new request
|
|
if (priority > ccache.new_request) {
|
|
ccache.new_request = priority;
|
|
}
|
|
}
|
|
}
|
|
cs->lru = 0;
|
|
CACHE_UNLOCK();
|
|
}
|
|
if (state) *state = cs->state;
|
|
tx = &cs->tx;
|
|
if (need_wake) Cache_Wakeup();
|
|
//cc_dbg("cc req = %d %p\n", cs->state, tx);
|
|
return tx;
|
|
error:
|
|
// out of cover slots or invalid index
|
|
if (state) *state = CS_ERROR;
|
|
return NULL;
|
|
}
|
|
|
|
void cache_remap_style_fmt(int *cstyle, int *fmt)
|
|
{
|
|
switch (*cstyle) {
|
|
case CFG_COVER_STYLE_HQ_OR_FULL_RGB:
|
|
*cstyle = CFG_COVER_STYLE_HQ_OR_FULL;
|
|
*fmt = CC_FMT_C4x4;
|
|
break;
|
|
case CFG_COVER_STYLE_FULL_RGB:
|
|
*cstyle = CFG_COVER_STYLE_FULL;
|
|
*fmt = CC_FMT_C4x4;
|
|
break;
|
|
case CFG_COVER_STYLE_HQ_RGB:
|
|
*cstyle = CFG_COVER_STYLE_HQ;
|
|
*fmt = CC_FMT_C4x4;
|
|
break;
|
|
case CFG_COVER_STYLE_FULL_CMPR:
|
|
*cstyle = CFG_COVER_STYLE_FULL;
|
|
*fmt = CC_FMT_CMPR;
|
|
break;
|
|
case CFG_COVER_STYLE_HQ_CMPR:
|
|
*cstyle = CFG_COVER_STYLE_HQ;
|
|
*fmt = CC_FMT_CMPR;
|
|
break;
|
|
case CFG_COVER_STYLE_FULL_MIPMAP:
|
|
*cstyle = CFG_COVER_STYLE_FULL;
|
|
*fmt = CC_FMT_MIPMAP;
|
|
break;
|
|
case CFG_COVER_STYLE_HQ_MIPMAP:
|
|
*cstyle = CFG_COVER_STYLE_HQ;
|
|
*fmt = CC_FMT_MIPMAP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
GRRLIB_texImg* cache_request_cover(int game_index, int style, int format, int priority, int *state)
|
|
{
|
|
GRRLIB_texImg *tx;
|
|
int my_state;
|
|
cache_remap_style_fmt(&style, &format);
|
|
if (style == CFG_COVER_STYLE_HQ_OR_FULL) {
|
|
// HQ or FULL requested, try HQ first:
|
|
tx = cache_request_cover1(game_index, CFG_COVER_STYLE_HQ,
|
|
format, priority, &my_state);
|
|
if (my_state == CS_PRESENT) {
|
|
// HQ present: return it
|
|
if (state) *state = my_state;
|
|
return tx;
|
|
}
|
|
if ((my_state == CS_IDLE && priority > 0) || my_state == CS_LOADING) {
|
|
// HQ requested or loading: return FULL if present
|
|
// but don't add a request for FULL (CC_PRIO_NONE)
|
|
return cache_request_cover1(game_index, CFG_COVER_STYLE_FULL,
|
|
format, CC_PRIO_NONE, state);
|
|
}
|
|
// HQ not found: request FULL
|
|
style = CFG_COVER_STYLE_FULL;
|
|
}
|
|
return cache_request_cover1(game_index, style, format, priority, state);
|
|
}
|
|
|
|
/**
|
|
* This method is used to set the caching priority of the passed in game index
|
|
* as well as the next x number of games.
|
|
*
|
|
* @param game_i the starting game index
|
|
* @param num the number of images to set the priority on
|
|
* @param rq_prio the cache priority: 1 = high priority
|
|
*/
|
|
void cache_request(int game_i, int num, int rq_prio)
|
|
{
|
|
int i;
|
|
//cc_dbg("cc_req(%d %d %d)\n", game_i, num, rq_prio);
|
|
for (i = game_i; i < game_i + num; i++) {
|
|
cache_request_cover(i, CFG.cover_style, CC_FMT_C4x4, CC_PRIO_HIGH, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* This method is used to set the caching priority of the passed in game index
|
|
* as well as the next x number of games before and after the index. All the
|
|
* rest of the covers are given a lower priority.
|
|
*
|
|
* @param game_i the starting game index
|
|
* @param num the number of images before and after the game_i to set the priority on
|
|
* @param rq_prio the cache priority: 1 = high priority
|
|
*/
|
|
void cache_request_before_and_after(int game_i, int num, int rq_prio)
|
|
{
|
|
int i;
|
|
int format = CC_FMT_C4x4;
|
|
if (CFG.gui_compress_covers) format = CC_COVERFLOW_FMT;
|
|
//cc_dbg("cc_rqba(%d %d %d)\n", game_i, num, format);
|
|
if (showingFrontCover) {
|
|
cache_request_cover(game_i, CFG_COVER_STYLE_FULL, format, CC_PRIO_HIGH, NULL);
|
|
} else {
|
|
cache_request_cover(game_i, CFG_COVER_STYLE_HQ_OR_FULL, format, CC_PRIO_HIGH, NULL);
|
|
}
|
|
for (i = game_i - num; i < game_i + num; i++) {
|
|
if (i == game_i) continue;
|
|
cache_request_cover(i, CFG_COVER_STYLE_FULL, format, CC_PRIO_MED, NULL);
|
|
}
|
|
num += 5;
|
|
for (i = game_i - num; i < game_i + num; i++) {
|
|
if (i == game_i) continue;
|
|
cache_request_cover(i, CFG_COVER_STYLE_FULL, format, CC_PRIO_LOW, NULL);
|
|
}
|
|
}
|
|
|
|
void cache_release_cover(int game_index, int style, int format)
|
|
{
|
|
Cover_State *cs = NULL;
|
|
u8 *id = gameList[game_index].id;
|
|
cs = cache_get_cover(id, style, format);
|
|
if (cs) {
|
|
CACHE_LOCK();
|
|
cs->used = 0;
|
|
if (cs->request > 0) cs->request--;
|
|
if (cs->lru < LRU_MAX) cs->lru++;
|
|
CACHE_UNLOCK();
|
|
}
|
|
}
|
|
|
|
void cache_release_full(bool full)
|
|
{
|
|
Cover_State *cs;
|
|
int i;
|
|
CACHE_LOCK();
|
|
for (i=0; i<CACHE_STATE_NUM; i++) {
|
|
cs = &ccache.cstate[i];
|
|
cs->used = 0;
|
|
if (full) {
|
|
// completely release
|
|
cs->request = 0;
|
|
} else {
|
|
// decrease priority
|
|
if (cs->request > 0) cs->request--;
|
|
}
|
|
if (cs->lru < LRU_MAX) cs->lru++;
|
|
}
|
|
CACHE_UNLOCK();
|
|
}
|
|
|
|
void cache_release_all()
|
|
{
|
|
cache_release_full(false);
|
|
}
|
|
|
|
void Cache_Invalidate()
|
|
{
|
|
}
|
|
|
|
void cache_wait_idle()
|
|
{
|
|
cache_release_full(true);
|
|
while (!ccache.waiting) {
|
|
LWP_YieldThread();
|
|
usleep(1000);
|
|
}
|
|
}
|
|
|
|
void Cache_Invalidate_Cover(u8 *id, int style)
|
|
{
|
|
int i;
|
|
bool do_free;
|
|
if (!ccache_init) return;
|
|
Cover_State *cs;
|
|
cache_wait_idle();
|
|
CACHE_LOCK();
|
|
for (i = 0; i < CACHE_STATE_NUM; i++) {
|
|
cs = &ccache.cstate[i];
|
|
if (strcmp((char*)cs->key.id, (char*)id) == 0) {
|
|
do_free = false;
|
|
if (cs->key.style == style) do_free = true;
|
|
if (cs->key.style == CFG_COVER_STYLE_FULL
|
|
&& (style == CFG_COVER_STYLE_2D || style == CFG_COVER_STYLE_HQ))
|
|
{
|
|
do_free = true;
|
|
}
|
|
if (do_free) cache_free_cover(cs);
|
|
}
|
|
}
|
|
if (style == CFG_COVER_STYLE_FULL || style == CFG_COVER_STYLE_HQ) {
|
|
cache_check_convert_image(id);
|
|
}
|
|
CACHE_UNLOCK();
|
|
}
|
|
|
|
void cache_num_game()
|
|
{
|
|
}
|
|
|
|
void cache_stats(char *str, int size)
|
|
{
|
|
int i;
|
|
int present = 0;
|
|
int missing = 0;
|
|
int nfree = 0;
|
|
int nstyle[5];
|
|
int style;
|
|
memset(nstyle, 0, sizeof(nstyle));
|
|
Cover_State *cs;
|
|
for (i = 0; i < CACHE_STATE_NUM; i++) {
|
|
cs = &ccache.cstate[i];
|
|
if (cs->state == CS_MISSING) missing++;
|
|
if (cs->state == CS_PRESENT) {
|
|
present++;
|
|
style = cs->key.style;
|
|
if (style >= 0 && style < 5) {
|
|
nstyle[style]++;
|
|
}
|
|
|
|
}
|
|
}
|
|
heap_stats hs;
|
|
heap_stat(&ccache.heap, &hs);
|
|
#define MBf (1024.0 * 1024.0)
|
|
nfree = CACHE_STATE_NUM - (present + missing);
|
|
snprintf(str, size,
|
|
"ccache p: %d m: %d f: %d / %d\n"
|
|
"cc mem: s:%.1f u:%.1f f:%.1f [%d,%d]\n"
|
|
"cc tm: load: %.3f decode: %.3f\n"
|
|
"cc 2d:%d 3d:%d d:%d f:%d hq:%d f2d:%d\n",
|
|
present, missing, nfree, CACHE_STATE_NUM,
|
|
hs.size / MBf, hs.used / MBf, hs.free / MBf,
|
|
ccache.heap.used_list.num,
|
|
ccache.heap.free_list.num,
|
|
(float)ccache.stat_load / 1000000.0,
|
|
(float)ccache.stat_decode / 1000000.0,
|
|
nstyle[0], nstyle[1], nstyle[2], nstyle[3], nstyle[4],
|
|
ccache.stat_n2d
|
|
);
|
|
}
|
|
|
|
#endif
|
|
|
|
//****************************************************************************
|
|
//**************************** END CACHE CODE ******************************
|
|
//****************************************************************************
|