nkeynes@103: /** nkeynes@352: * $Id: texcache.c,v 1.26 2007-02-11 10:09:32 nkeynes Exp $ nkeynes@103: * nkeynes@103: * Texture cache. Responsible for maintaining a working set of OpenGL nkeynes@103: * textures. nkeynes@103: * nkeynes@103: * nkeynes@103: * Copyright (c) 2005 Nathan Keynes. nkeynes@103: * nkeynes@103: * This program is free software; you can redistribute it and/or modify nkeynes@103: * it under the terms of the GNU General Public License as published by nkeynes@103: * the Free Software Foundation; either version 2 of the License, or nkeynes@103: * (at your option) any later version. nkeynes@103: * nkeynes@103: * This program is distributed in the hope that it will be useful, nkeynes@103: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@103: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@103: * GNU General Public License for more details. nkeynes@103: */ nkeynes@103: nkeynes@103: #include nkeynes@103: #include "pvr2/pvr2.h" nkeynes@103: nkeynes@103: /** Specifies the maximum number of OpenGL nkeynes@103: * textures we're willing to have open at a time. If more are nkeynes@103: * needed, textures will be evicted in LRU order. nkeynes@103: */ nkeynes@349: #define MAX_TEXTURES 256 nkeynes@103: nkeynes@103: /** nkeynes@103: * Data structure: nkeynes@103: * nkeynes@103: * Main operations: nkeynes@103: * find entry by texture_addr nkeynes@103: * add new entry nkeynes@103: * move entry to tail of lru list nkeynes@103: * remove entry nkeynes@103: */ nkeynes@103: nkeynes@103: typedef signed short texcache_entry_index; nkeynes@107: #define EMPTY_ENTRY 0xFF nkeynes@103: nkeynes@107: static texcache_entry_index texcache_free_ptr = 0; nkeynes@103: static GLuint texcache_free_list[MAX_TEXTURES]; nkeynes@103: nkeynes@103: typedef struct texcache_entry { nkeynes@103: uint32_t texture_addr; nkeynes@103: int width, height, mode; nkeynes@103: GLuint texture_id; nkeynes@103: texcache_entry_index next; nkeynes@103: uint32_t lru_count; nkeynes@103: } *texcache_entry_t; nkeynes@103: nkeynes@103: static uint8_t texcache_page_lookup[PVR2_RAM_PAGES]; nkeynes@103: static uint32_t texcache_ref_counter; nkeynes@103: static struct texcache_entry texcache_active_list[MAX_TEXTURES]; nkeynes@103: nkeynes@103: /** nkeynes@108: * Initialize the texture cache. nkeynes@103: */ nkeynes@103: void texcache_init( ) nkeynes@103: { nkeynes@103: int i; nkeynes@103: for( i=0; i> 12; nkeynes@103: texcache_entry_index replace_next = texcache_active_list[slot].next; nkeynes@337: texcache_active_list[slot].texture_addr = -1; nkeynes@103: texcache_active_list[slot].next = EMPTY_ENTRY; /* Just for safety */ nkeynes@103: if( texcache_page_lookup[evict_page] == slot ) { nkeynes@103: texcache_page_lookup[evict_page] = replace_next; nkeynes@103: } else { nkeynes@103: texcache_entry_index idx = texcache_page_lookup[evict_page]; nkeynes@103: texcache_entry_index next; nkeynes@103: do { nkeynes@103: next = texcache_active_list[idx].next; nkeynes@103: if( next == slot ) { nkeynes@103: texcache_active_list[idx].next = replace_next; nkeynes@103: break; nkeynes@103: } nkeynes@103: idx = next; nkeynes@103: } while( next != EMPTY_ENTRY ); nkeynes@103: } nkeynes@337: } nkeynes@337: nkeynes@337: /** nkeynes@337: * Evict a single texture from the cache. nkeynes@337: * @return the slot of the evicted texture. nkeynes@337: */ nkeynes@337: static texcache_entry_index texcache_evict_lru( void ) nkeynes@337: { nkeynes@337: /* Full table scan - take over the entry with the lowest lru value */ nkeynes@337: texcache_entry_index slot = 0; nkeynes@337: int lru_value = texcache_active_list[0].lru_count; nkeynes@337: int i; nkeynes@337: for( i=1; i> 12; nkeynes@337: texcache_entry_index idx = texcache_page_lookup[texture_page]; nkeynes@337: if( idx == EMPTY_ENTRY ) nkeynes@337: return; nkeynes@337: assert( texcache_free_ptr >= 0 ); nkeynes@337: do { nkeynes@337: texcache_entry_t entry = &texcache_active_list[idx]; nkeynes@337: entry->texture_addr = -1; nkeynes@337: /* release entry */ nkeynes@337: texcache_free_ptr--; nkeynes@337: texcache_free_list[texcache_free_ptr] = idx; nkeynes@337: idx = entry->next; nkeynes@337: entry->next = EMPTY_ENTRY; nkeynes@337: } while( idx != EMPTY_ENTRY ); nkeynes@337: texcache_page_lookup[texture_page] = EMPTY_ENTRY; nkeynes@337: } nkeynes@337: nkeynes@337: /** nkeynes@337: * Mark all textures that use the palette table as needing a re-read (ie nkeynes@337: * for when the palette is changed. We could track exactly which ones are nkeynes@337: * affected, but it's not clear that the extra maintanence overhead is nkeynes@337: * worthwhile. nkeynes@337: */ nkeynes@337: void texcache_invalidate_palette( ) nkeynes@337: { nkeynes@337: int i; nkeynes@337: for( i=0; i> 4)]; nkeynes@315: in++; nkeynes@315: } nkeynes@315: } nkeynes@315: nkeynes@315: nkeynes@321: static void decode_pal4_to_16( uint16_t *out, uint8_t *in, int inbytes, uint32_t *pal ) nkeynes@315: { nkeynes@315: int i; nkeynes@315: for( i=0; i> 4)]; nkeynes@315: in++; nkeynes@315: } nkeynes@315: } nkeynes@315: nkeynes@224: #define VQ_CODEBOOK_SIZE 2048 /* 256 entries * 4 pixels per quad * 2 byte pixels */ nkeynes@224: nkeynes@224: struct vq_codebook { nkeynes@224: uint16_t quad[256][4]; nkeynes@224: }; nkeynes@224: nkeynes@270: static void vq_get_codebook( struct vq_codebook *codebook, nkeynes@270: uint16_t *input ) nkeynes@270: { nkeynes@270: /* Detwiddle the codebook, for the sake of my own sanity if nothing else */ nkeynes@270: uint16_t *p = (uint16_t *)input; nkeynes@270: int i; nkeynes@270: for( i=0; i<256; i++ ) { nkeynes@270: codebook->quad[i][0] = *p++; nkeynes@270: codebook->quad[i][2] = *p++; nkeynes@270: codebook->quad[i][1] = *p++; nkeynes@270: codebook->quad[i][3] = *p++; nkeynes@270: } nkeynes@270: } nkeynes@270: nkeynes@311: static void vq_decode( uint16_t *output, char *input, int width, int height, nkeynes@311: struct vq_codebook *codebook ) { nkeynes@224: int i,j; nkeynes@224: nkeynes@270: uint8_t *c = (uint8_t *)input; nkeynes@311: for( j=0; jquad[code][0]; nkeynes@311: output[i + 1 + j*width] = codebook->quad[code][1]; nkeynes@311: output[i + (j+1)*width] = codebook->quad[code][2]; nkeynes@311: output[i + 1 + (j+1)*width] = codebook->quad[code][3]; nkeynes@224: } nkeynes@224: } nkeynes@224: } nkeynes@113: nkeynes@282: static inline uint32_t yuv_to_rgb32( float y, float u, float v ) nkeynes@282: { nkeynes@282: u -= 128; nkeynes@282: v -= 128; nkeynes@282: int r = (int)(y + v*1.375); nkeynes@282: int g = (int)(y - u*0.34375 - v*0.6875); nkeynes@282: int b = (int)(y + u*1.71875); nkeynes@282: if( r > 255 ) { r = 255; } else if( r < 0 ) { r = 0; } nkeynes@282: if( g > 255 ) { g = 255; } else if( g < 0 ) { g = 0; } nkeynes@282: if( b > 255 ) { b = 255; } else if( b < 0 ) { b = 0; } nkeynes@289: return 0xFF000000 | (r<<16) | (g<<8) | (b); nkeynes@282: } nkeynes@282: nkeynes@282: nkeynes@282: /** nkeynes@311: * Convert raster YUV texture data into RGB32 data - most GL implementations don't nkeynes@282: * directly support this format unfortunately. The input data is formatted as nkeynes@282: * 32 bits = 2 horizontal pixels, UYVY. This is currently done rather inefficiently nkeynes@282: * in floating point. nkeynes@282: */ nkeynes@311: static void yuv_decode( uint32_t *output, uint32_t *input, int width, int height ) nkeynes@282: { nkeynes@282: int x, y; nkeynes@282: uint32_t *p = input; nkeynes@282: for( y=0; y>8)&0xFF ); nkeynes@282: float v = (float)( (*p>>16)&0xFF ); nkeynes@282: float y1 = (float)( (*p>>24)&0xFF ); nkeynes@282: *output++ = yuv_to_rgb32( y0, u, v ); nkeynes@282: *output++ = yuv_to_rgb32( y1, u, v ); nkeynes@287: p++; nkeynes@282: } nkeynes@282: } nkeynes@282: } nkeynes@282: nkeynes@103: /** nkeynes@103: * Load texture data from the given address and parameters into the currently nkeynes@103: * bound OpenGL texture. nkeynes@103: */ nkeynes@103: static texcache_load_texture( uint32_t texture_addr, int width, int height, nkeynes@103: int mode ) { nkeynes@284: int bpp_shift = 1; /* bytes per (output) pixel as a power of 2 */ nkeynes@349: GLint intFormat = GL_RGBA, format, type; nkeynes@108: int tex_format = mode & PVR2_TEX_FORMAT_MASK; nkeynes@270: struct vq_codebook codebook; nkeynes@270: GLint filter = GL_LINEAR; nkeynes@108: nkeynes@352: glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 ); nkeynes@270: /* Decode the format parameters */ nkeynes@270: switch( tex_format ) { nkeynes@270: case PVR2_TEX_FORMAT_IDX4: nkeynes@270: case PVR2_TEX_FORMAT_IDX8: nkeynes@284: /* For indexed-colour modes, we need to lookup the palette control nkeynes@284: * word to determine the de-indexed texture format. nkeynes@284: */ nkeynes@191: switch( MMIO_READ( PVR2, RENDER_PALETTE ) & 0x03 ) { nkeynes@108: case 0: /* ARGB1555 */ nkeynes@329: format = GL_BGRA; nkeynes@129: type = GL_UNSIGNED_SHORT_1_5_5_5_REV; nkeynes@108: break; nkeynes@284: case 1: /* RGB565 */ nkeynes@349: intFormat = GL_RGB; nkeynes@108: format = GL_RGB; nkeynes@300: type = GL_UNSIGNED_SHORT_5_6_5; nkeynes@108: break; nkeynes@284: case 2: /* ARGB4444 */ nkeynes@129: format = GL_BGRA; nkeynes@129: type = GL_UNSIGNED_SHORT_4_4_4_4_REV; nkeynes@108: break; nkeynes@284: case 3: /* ARGB8888 */ nkeynes@113: format = GL_BGRA; nkeynes@113: type = GL_UNSIGNED_INT_8_8_8_8_REV; nkeynes@284: bpp_shift = 2; nkeynes@108: break; nkeynes@108: } nkeynes@270: break; nkeynes@270: nkeynes@270: case PVR2_TEX_FORMAT_ARGB1555: nkeynes@329: format = GL_BGRA; nkeynes@270: type = GL_UNSIGNED_SHORT_1_5_5_5_REV; nkeynes@270: break; nkeynes@270: case PVR2_TEX_FORMAT_RGB565: nkeynes@349: intFormat = GL_RGB; nkeynes@270: format = GL_RGB; nkeynes@300: type = GL_UNSIGNED_SHORT_5_6_5; nkeynes@270: break; nkeynes@270: case PVR2_TEX_FORMAT_ARGB4444: nkeynes@270: format = GL_BGRA; nkeynes@270: type = GL_UNSIGNED_SHORT_4_4_4_4_REV; nkeynes@270: break; nkeynes@270: case PVR2_TEX_FORMAT_YUV422: nkeynes@284: /* YUV422 isn't directly supported by most implementations, so decode nkeynes@284: * it to a (reasonably) standard ARGB32. nkeynes@284: */ nkeynes@284: bpp_shift = 2; nkeynes@282: format = GL_BGRA; nkeynes@282: type = GL_UNSIGNED_INT_8_8_8_8_REV; nkeynes@270: break; nkeynes@270: case PVR2_TEX_FORMAT_BUMPMAP: nkeynes@270: ERROR( "Bumpmap not supported" ); nkeynes@270: break; nkeynes@270: } nkeynes@270: nkeynes@321: if( PVR2_TEX_IS_STRIDE(mode) && tex_format != PVR2_TEX_FORMAT_IDX4 && nkeynes@321: tex_format != PVR2_TEX_FORMAT_IDX8 ) { nkeynes@284: /* Stride textures cannot be mip-mapped, compressed, indexed or twiddled */ nkeynes@284: uint32_t stride = (MMIO_READ( PVR2, RENDER_TEXSIZE ) & 0x003F) << 5; nkeynes@284: char data[(width*height) << bpp_shift]; nkeynes@284: if( tex_format == PVR2_TEX_FORMAT_YUV422 ) { nkeynes@284: char tmp[(width*height)<<1]; nkeynes@291: pvr2_vram64_read_stride( tmp, width<<1, texture_addr, stride<<1, height ); nkeynes@311: yuv_decode( (uint32_t *)data, (uint32_t *)tmp, width, height ); nkeynes@284: } else { nkeynes@291: pvr2_vram64_read_stride( data, width<= 0; level-- ) { nkeynes@349: char data[dest_bytes]; nkeynes@270: /* load data from image, detwiddling/uncompressing as required */ nkeynes@108: if( tex_format == PVR2_TEX_FORMAT_IDX8 ) { nkeynes@349: src_bytes = (mip_width * mip_height); nkeynes@108: int bank = (mode >> 25) &0x03; nkeynes@324: uint32_t *palette = ((uint32_t *)mmio_region_PVR2PAL.mem) + (bank<<8); nkeynes@349: char tmp[src_bytes]; nkeynes@311: pvr2_vram64_read_twiddled_8( tmp, texture_addr, mip_width, mip_height ); nkeynes@284: if( bpp_shift == 2 ) { nkeynes@349: decode_pal8_to_32( (uint32_t *)data, tmp, src_bytes, palette ); nkeynes@113: } else { nkeynes@349: decode_pal8_to_16( (uint16_t *)data, tmp, src_bytes, palette ); nkeynes@108: } nkeynes@315: } else if( tex_format == PVR2_TEX_FORMAT_IDX4 ) { nkeynes@349: src_bytes = (mip_width * mip_height) >> 1; nkeynes@315: int bank = (mode >>21 ) & 0x3F; nkeynes@324: uint32_t *palette = ((uint32_t *)mmio_region_PVR2PAL.mem) + (bank<<4); nkeynes@349: char tmp[src_bytes]; nkeynes@315: pvr2_vram64_read_twiddled_4( tmp, texture_addr, mip_width, mip_height ); nkeynes@315: if( bpp_shift == 2 ) { nkeynes@349: decode_pal4_to_32( (uint32_t *)data, tmp, src_bytes, palette ); nkeynes@315: } else { nkeynes@349: decode_pal4_to_16( (uint16_t *)data, tmp, src_bytes, palette ); nkeynes@315: } nkeynes@282: } else if( tex_format == PVR2_TEX_FORMAT_YUV422 ) { nkeynes@349: src_bytes = ((mip_width*mip_height)<<1); nkeynes@349: char tmp[src_bytes]; nkeynes@314: if( PVR2_TEX_IS_TWIDDLED(mode) ) { nkeynes@314: pvr2_vram64_read_twiddled_16( tmp, texture_addr, mip_width, mip_height ); nkeynes@314: } else { nkeynes@349: pvr2_vram64_read( tmp, texture_addr, src_bytes ); nkeynes@314: } nkeynes@311: yuv_decode( (uint32_t *)data, (uint32_t *)tmp, mip_width, mip_height ); nkeynes@270: } else if( PVR2_TEX_IS_COMPRESSED(mode) ) { nkeynes@349: src_bytes = ((mip_width*mip_height) >> 2); nkeynes@349: char tmp[src_bytes]; nkeynes@311: if( PVR2_TEX_IS_TWIDDLED(mode) ) { nkeynes@313: pvr2_vram64_read_twiddled_8( tmp, texture_addr, mip_width>>1, mip_height>>1 ); nkeynes@311: } else { nkeynes@349: pvr2_vram64_read( tmp, texture_addr, src_bytes ); nkeynes@311: } nkeynes@311: vq_decode( (uint16_t *)data, tmp, mip_width, mip_height, &codebook ); nkeynes@270: } else if( PVR2_TEX_IS_TWIDDLED(mode) ) { nkeynes@311: pvr2_vram64_read_twiddled_16( data, texture_addr, mip_width, mip_height ); nkeynes@270: } else { nkeynes@349: pvr2_vram64_read( data, texture_addr, src_bytes ); nkeynes@108: } nkeynes@313: nkeynes@108: /* Pass to GL */ nkeynes@313: if( level == last_level && level != 0 ) { /* 1x1 stored within a 2x2 */ nkeynes@313: glTexImage2D( GL_TEXTURE_2D, level, intFormat, 1, 1, 0, format, type, nkeynes@313: data + (3 << bpp_shift) ); nkeynes@349: texture_addr += src_bytes; nkeynes@313: } else { nkeynes@313: glTexImage2D( GL_TEXTURE_2D, level, intFormat, mip_width, mip_height, 0, format, type, nkeynes@313: data ); nkeynes@349: texture_addr += src_bytes; nkeynes@313: mip_width <<= 1; nkeynes@313: mip_height <<= 1; nkeynes@349: dest_bytes <<= 2; nkeynes@349: src_bytes <<= 2; nkeynes@313: } nkeynes@103: } nkeynes@270: nkeynes@270: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); nkeynes@108: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); nkeynes@103: } nkeynes@103: nkeynes@103: /** nkeynes@103: * Return a texture ID for the texture specified at the supplied address nkeynes@103: * and given parameters (the same sequence of bytes could in theory have nkeynes@103: * multiple interpretations). We use the texture address as the primary nkeynes@103: * index, but allow for multiple instances at each address. The texture nkeynes@103: * will be bound to the GL_TEXTURE_2D target before being returned. nkeynes@103: * nkeynes@103: * If the texture has already been bound, return the ID to which it was nkeynes@103: * bound. Otherwise obtain an unused texture ID and set it up appropriately. nkeynes@103: */ nkeynes@103: GLuint texcache_get_texture( uint32_t texture_addr, int width, int height, nkeynes@103: int mode ) nkeynes@103: { nkeynes@103: uint32_t texture_page = texture_addr >> 12; nkeynes@103: texcache_entry_index idx = texcache_page_lookup[texture_page]; nkeynes@103: while( idx != EMPTY_ENTRY ) { nkeynes@103: texcache_entry_t entry = &texcache_active_list[idx]; nkeynes@103: if( entry->texture_addr == texture_addr && nkeynes@103: entry->mode == mode && nkeynes@103: entry->width == width && nkeynes@103: entry->height == height ) { nkeynes@103: entry->lru_count = texcache_ref_counter++; nkeynes@103: glBindTexture( GL_TEXTURE_2D, entry->texture_id ); nkeynes@103: return entry->texture_id; nkeynes@103: } nkeynes@103: idx = entry->next; nkeynes@103: } nkeynes@103: nkeynes@103: /* Not found - check the free list */ nkeynes@103: int slot = 0; nkeynes@103: nkeynes@103: if( texcache_free_ptr < MAX_TEXTURES ) { nkeynes@103: slot = texcache_free_list[texcache_free_ptr++]; nkeynes@103: } else { nkeynes@337: slot = texcache_evict_lru(); nkeynes@103: } nkeynes@103: nkeynes@103: /* Construct new entry */ nkeynes@103: texcache_active_list[slot].texture_addr = texture_addr; nkeynes@103: texcache_active_list[slot].width = width; nkeynes@103: texcache_active_list[slot].height = height; nkeynes@103: texcache_active_list[slot].mode = mode; nkeynes@103: texcache_active_list[slot].lru_count = texcache_ref_counter++; nkeynes@103: nkeynes@103: /* Add entry to the lookup table */ nkeynes@103: texcache_active_list[slot].next = texcache_page_lookup[texture_page]; nkeynes@103: texcache_page_lookup[texture_page] = slot; nkeynes@103: nkeynes@103: /* Construct the GL texture */ nkeynes@108: glBindTexture( GL_TEXTURE_2D, texcache_active_list[slot].texture_id ); nkeynes@103: texcache_load_texture( texture_addr, width, height, mode ); nkeynes@103: nkeynes@103: return texcache_active_list[slot].texture_id; nkeynes@103: }