/* Copyright (C) 2009-2011 id Software LLC, a ZeniMax Media company. Copyright (C) 2009 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "../doomiphone.h" pkHeader_t *pkHeader; int pkSize; // images and wavs have writable state, so they need separate // structs that also point to the source in the pak file pkTexture_t *pkTextures; pkWav_t *pkWavs; void PK_LoadTexture( pkTexture_t *image ); /* ================== PK_Init ================== */ void PK_Init( const char *pakFileName ) { printf( "PK_Init( %s )\n", pakFileName ); int fd = open( pakFileName, O_RDONLY ); if ( fd == -1 ) { printf( "Couldn't open file\n" ); assert( 0 ); } struct stat s; fstat( fd, &s ); pkSize = s.st_size; pkHeader = mmap( NULL, pkSize, PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0 ); // mmap keeps the file internally, we can close our descriptor close( fd ); if ( (int)pkHeader == -1 ) { printf( "mmap failed: %s\n", strerror( errno ) ); assert( 0 ); } if ( pkHeader->version != PKFILE_VERSION ) { printf( "bad pak file version: 0x%x != 0x%x\n", pkHeader->version, PKFILE_VERSION ); assert( 0 ); } // build the local image table pkTextures = malloc( sizeof( pkTextures[0] ) * pkHeader->textures.count ); memset( pkTextures, 0, sizeof( pkTextures[0] ) * pkHeader->textures.count ); for ( int i = 0 ; i < pkHeader->textures.count ; i++ ) { pkTextures[i].textureData = (pkTextureData_t *)( (byte *)pkHeader + pkHeader->textures.tableOfs + i * pkHeader->textures.structSize ); } // build the local wav table int startLoadingWavs = SysIphoneMicroseconds(); pkWavs = malloc( sizeof( pkWavs[0] ) * pkHeader->wavs.count ); memset( pkWavs, 0, sizeof( pkWavs[0] ) * pkHeader->wavs.count ); for ( int i = 0 ; i < pkHeader->wavs.count ; i++ ) { pkWav_t *sfx = &pkWavs[i]; sfx->wavData = (pkWavData_t *)( (byte *)pkHeader + pkHeader->wavs.tableOfs + i * pkHeader->wavs.structSize ); // there is no harm in setting the OpenAl static buffer up for everything now alGenBuffers( 1, &sfx->alBufferNum ); int alFormat; if ( sfx->wavData->wavChannels == 1 ) { if ( sfx->wavData->wavChannelBytes == 1 ) { alFormat = AL_FORMAT_MONO8; } else { alFormat = AL_FORMAT_MONO16; } } else { if ( sfx->wavData->wavChannelBytes == 1 ) { alFormat = AL_FORMAT_STEREO8; } else { alFormat = AL_FORMAT_STEREO16; } } alBufferData( sfx->alBufferNum, alFormat, (byte *)pkHeader + sfx->wavData->wavDataOfs , sfx->wavData->wavChannels*sfx->wavData->wavChannelBytes*sfx->wavData->wavNumSamples , sfx->wavData->wavRate ); } int endLoadingWavs = SysIphoneMicroseconds(); printf( "%i usec to load wavs\n", endLoadingWavs - startLoadingWavs ); printf( "Mapped %i bytes of %s at 0x%p\n", pkSize, pakFileName, pkHeader ); printf( "%4i textures\n", pkHeader->textures.count ); printf( "%4i wavs\n", pkHeader->wavs.count ); printf( "%4i raws\n", pkHeader->raws.count ); #if 0 // testing for ( int j = 0 ; j < 4 ; j++ ) { int startTime = Sys_Microseconds(); int sum = 0; for ( int i = 0 ; i < pkSize ; i+=16 ) { sum += ((byte *)pkHeader)[i]; } int endTime = Sys_Microseconds(); printf( "%5.1f mb/s page-in speed (%i)\n", (float)pkSize / (endTime - startTime ), endTime - startTime ); } for ( int i = 0 ; i < pkHeader->numTextures ; i++ ) { printf( "-------------------------\n" ); for ( int j = 0 ; j < 8 ; j++ ) { pkTexture_t *tex = &pkTextures[i]; int start = Sys_Microseconds(); PK_LoadTexture( tex ); int middle = Sys_Microseconds(); PK_StretchTexture( tex, 0, 0, 0, 0 ); int middle2 = Sys_Microseconds(); PK_StretchTexture( tex, 0, 0, 0, 0 ); int end = Sys_Microseconds(); printf( "%i usec load, %i usec first draw, %i usec second draw\n", middle - start, middle2 - middle, end - middle2 ); glDeleteTextures( 1, &tex->glTexNum ); tex->glTexNum = 0; } } #endif } /* ================== PK_LoadTexture ================== */ void PK_LoadTexture( pkTexture_t *tex ) { int startTime = SysIphoneMicroseconds(); const pkTextureData_t *imd = tex->textureData; glGenTextures( 1, &tex->glTexNum ); glBindTexture( GL_TEXTURE_2D, tex->glTexNum ); // load the image directly from the mapped file typedef struct { int internalFormat; int externalFormat; int type; int bpp; } formatInfo_t; static formatInfo_t formatInfo[9] = { { GL_RGB , GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 16 }, { GL_RGBA, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, 16 }, { GL_RGBA, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, 16 }, { GL_RGBA, GL_BGRA, GL_UNSIGNED_BYTE, 32 }, { GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, 16 }, { GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, 0, 0, 4 }, { GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, 0, 0, 4 }, { GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG, 0, 0, 2 }, { GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, 0, 0, 2 }, }; assert( imd->format < 9 ); formatInfo_t *fi = &formatInfo[imd->format]; unsigned char *s = (byte *)pkHeader + imd->picDataOfs; int w = imd->uploadWidth; int h = imd->uploadHeight; // upload each mip level int l = 0; int totalSize = 0; while( 1 ) { int size = (w*h*fi->bpp)/8; if ( fi->type == 0 ) { if ( size < 32 ) { // minimum PVRTC size size = 32; } glCompressedTexImage2D( GL_TEXTURE_2D, l, fi->internalFormat, w, h, 0, size, s ); } else { glTexImage2D( GL_TEXTURE_2D, l, fi->internalFormat, w, h, 0, fi->externalFormat, fi->type, s ); } GLCheckError( "texture upload" ); totalSize += size; if ( ++l == imd->numLevels ) { break; } if ( w == 1 && h == 1 ) { break; } s += size; w >>= 1; if ( w == 0 ) { w = 1; } h >>= 1; if ( h == 0 ) { h = 1; } } glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, imd->minFilter ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, imd->magFilter ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, imd->wrapS ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, imd->wrapT ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 2.0f ); int endTime = SysIphoneMicroseconds(); printf( "%5.1f mb/s TexImage for %s\n", (float)totalSize / ( endTime - startTime ), imd->name.name ); } /* ================== PK_FindTexture Fully creates the gl texture before returning. ================== */ pkTexture_t *PK_FindTexture( const char *imageName ) { int index; pkTexture_t *texData = (pkTexture_t *)PK_FindType( imageName, &pkHeader->textures, &index ); if ( !texData ) { return NULL; } pkTexture_t *tex = pkTextures + index; if ( tex->glTexNum == 0 ) { PK_LoadTexture( tex ); } return tex; } /* ================== PK_FindWav ================== */ pkWav_t *PK_FindWav( const char *soundName ) { int index; pkWavData_t *wavData = (pkWavData_t *)PK_FindType( soundName, &pkHeader->wavs, &index ); if ( !wavData ) { return NULL; } pkWav_t *wav = pkWavs + index; // create the OpenAL buffer return wav; } /* ================== PK_FindRaw ================== */ const byte *PK_FindRaw( const char *rawName, int *len ) { pkRawData_t *raw = (pkRawData_t *)PK_FindType( rawName, &pkHeader->raws, NULL ); if ( !raw ) { if ( len ) { *len = -1; } return NULL; } if ( len ) { *len = raw->rawDataLen; } return (byte *)pkHeader + raw->rawDataOfs; } /* ================== PK_HashName ================== */ int PK_HashName( const char *name, char canonical[MAX_PK_NAME] ) { int o = 0; int hash = 0; do { int c = name[o]; if ( c == 0 ) { break; } // backslashes to forward slashes if ( c == '\\' ) { c = '/'; } // to lowercase c = tolower( c ); canonical[o++] = c; hash = (hash << 5) - hash + c; } while ( o < MAX_PK_NAME-1 ); canonical[o] = 0; return hash; } /* ================== PK_FindType ================== */ const pkName_t *PK_FindType( const char *rawName, const pkType_t *type, int *indexOutput ) { char canonicalName[MAX_PK_NAME]; int hash = PK_HashName( rawName, canonicalName ); int hashChain = hash & (PK_HASH_CHAINS-1); int index = type->hashChains[hashChain]; while ( index != -1 ) { assert( index >= 0 && index < type->count ); const pkName_t *name = (pkName_t *)((byte *)pkHeader + type->tableOfs + index * type->structSize ); if ( name->nameHash == hash && !strcmp( canonicalName, name->name ) ) { // this is it if ( indexOutput ) { *indexOutput = index; } return name; } index = name->nextOnHashChain; } // not found if ( indexOutput ) { *indexOutput = -1; } return NULL; } /* ================== PK_BindTexture ================== */ void PK_BindTexture( pkTexture_t *tex ) { assert( tex->glTexNum ); glBindTexture( GL_TEXTURE_2D, tex->glTexNum ); } /* ================== PK_DrawTexture ================== */ void PK_DrawTexture( pkTexture_t *tex, int x, int y ) { PK_BindTexture( tex ); int w = tex->textureData->srcWidth; int h = tex->textureData->srcHeight; glBegin( GL_QUADS ); glTexCoord2f( 0.0f, 0.0f ); glVertex2i( x, y ); glTexCoord2f( tex->textureData->maxS, 0.0f ); glVertex2i( x+w, y ); glTexCoord2f( tex->textureData->maxS, tex->textureData->maxT ); glVertex2i( x+w, y+h ); glTexCoord2f( 0.0f, tex->textureData->maxT ); glVertex2i( x, y+h ); glEnd(); } void PK_StretchTexture( pkTexture_t *tex, float x, float y, float w, float h ) { PK_BindTexture( tex ); glBegin( GL_QUADS ); glTexCoord2f( 0.0f, 0.0f ); glVertex2i( x, y ); glTexCoord2f( tex->textureData->maxS, 0.0f ); glVertex2i( x+w, y ); glTexCoord2f( tex->textureData->maxS, tex->textureData->maxT ); glVertex2i( x+w, y+h ); glTexCoord2f( 0.0f, tex->textureData->maxT ); glVertex2i( x, y+h ); glEnd(); }