// by oggzee & usptactical #include #include #include #include #include #include #include #include #include #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= 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 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= 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 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; iid); } 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; iused) 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; iused) 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; irequest == 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; iused = 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 ****************************** //****************************************************************************