/* =========================================================================== maya2q3 - export .md3 files from maya Copyright (C) 2005 HermitWorks Entertainment Corporation 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. =========================================================================== */ #include "common.h" #include "DXDDS.h" namespace shader { /* static void DoMipMap( unsigned char *in, int width, int height ) { unsigned char *out; int row; //default Q3 seems to use the fast version... if( width == 1 && height == 1 ) return; row = width * 4; out = in; width >>= 1; height >>= 1; if( width == 0 || height == 0 ) { width += height; // get largest for( int i = 0 ; i < width ; i++, out += 4, in += 8 ) { out[0] = (in[0] + in[4]) >> 1; out[1] = (in[1] + in[5]) >> 1; out[2] = (in[2] + in[6]) >> 1; out[3] = (in[3] + in[7]) >> 1; } return; } for( int i = 0; i < height; i ++, in += row ) { for( int j = 0; j < width; j++, out += 4, in += 8 ) { out[0] = (in[0] + in[4] + in[row + 0] + in[row + 4]) >> 2; out[1] = (in[1] + in[5] + in[row + 1] + in[row + 5]) >> 2; out[2] = (in[2] + in[6] + in[row + 2] + in[row + 6]) >> 2; out[3] = (in[3] + in[7] + in[row + 3] + in[row + 7]) >> 2; } } } */ using namespace dx; #define LoadDDS_MAX_MIPS 16 static bool LoadDDS( const void *data, size_t /* size */, bool mipmap, GLenum wrapClampMode, GLuint &ret, unsigned int &retwidth, unsigned int &retheight ) { const char *buff = (const char*)data; const DDSURFACEDESC2_t *ddsd; //used to get at the dds header in the read buffer //based on texture type: // cube width != 0, height == 0, depth == 0 // 2D width != 0, height != 0, depth == 0 // volume width != 0, height != 0, depth != 0 int width, height, depth; //mip count and pointers to image data for each mip //level, idx 0 = top level last pointer does not start //a mip level, it's just there to mark off the end of //the final mip data segment (thus the odd + 1) // //for cube textures we only find the offsets into the //first face of the cube, subsequent faces will use the //same offsets, just shifted over int mipLevels; const char *mipOffsets[LoadDDS_MAX_MIPS + 1]; bool usingAlpha; bool compressed; GLuint format = 0; GLuint internal_format = 0; if( strncmp( (const char*)buff, "DDS ", 4 ) ) { warn( "invalid dds header" ); return false; } ddsd = (const DDSURFACEDESC2_t*)(buff + 4); if( ddsd->dwSize != sizeof( DDSURFACEDESC2_t ) || ddsd->u4.ddpfPixelFormat.dwSize != sizeof( DDPIXELFORMAT_t ) ) { warn( "invalid dds header" ); return false; } usingAlpha = ddsd->u4.ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS; mipLevels = ((ddsd->dwFlags & DDSD_MIPMAPCOUNT) && (ddsd->u2.dwMipMapCount > 1)) ? ddsd->u2.dwMipMapCount : 1; if( mipLevels > LoadDDS_MAX_MIPS ) { warn( "dds image has too many mip levels" ); return false; } //either a cube of a volume if( ddsd->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP ) { //shader limitation!! warn( "shaders do not support cube maps" ); return false; //cube texture /* if( ddsd->dwWidth != ddsd->dwHeight ) { warn( "invalid dds image" ); return false; } width = ddsd->dwWidth; height = 0; depth = 0; */ } else if( (ddsd->ddsCaps.dwCaps2 & DDSCAPS2_VOLUME) && (ddsd->dwFlags & DDSD_DEPTH) ) { //shader limitation!! warn( "shaders do not support volume maps" ); return false; //3D texture /* width = ddsd->dwWidth; height = ddsd->dwHeight; depth = ddsd->u1.dwDepth; */ } else { //2D texture width = ddsd->dwWidth; height = ddsd->dwHeight; depth = 0; } compressed = (ddsd->u4.ddpfPixelFormat.dwFlags & DDPF_FOURCC) != 0; if( compressed ) { int blockSize; if( !GLEW_ARB_texture_compression ) { warn( "missing compressed texture extension" ); return false; } if( depth != 0 ) { warn( "compressed volume textures are unsupported" ); return false; } //compressed FOURCC formats switch( ddsd->u4.ddpfPixelFormat.dwFourCC ) { case FOURCC_DXT1 : blockSize = 8; format = usingAlpha ? GL_COMPRESSED_RGBA_S3TC_DXT1_EXT : GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; case FOURCC_DXT3 : blockSize = 16; format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; case FOURCC_DXT5 : blockSize = 16; format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; default: warn( "unsupported FOURCC format" ); return false; } //get mip offsets if( format ) { int w = width; int h = height; int offset = 0; int i; if( h == 0 ) h = w; //cube map for( i = 0; (i < mipLevels) && (w || h); i++ ) { int qw, qh; mipOffsets[ i ] = buff + 4 + sizeof( DDSURFACEDESC2_t ) + offset; if( w == 0 ) w = 1; if( h == 0 ) h = 1; //size formula comes from DX docs (August 2005 SDK reference) qw = w >> 2; if( qw == 0 ) qw = 1; qh = h >> 2; if( qh == 0 ) qh = 1; offset += qw * qh * blockSize; w >>= 1; h >>= 1; } //put in the trailing offset mipOffsets[ i ] = buff + 4 + sizeof( DDSURFACEDESC2_t ) + offset; } else { warn( "error loading DDS image" ); return false; } } else { //uncompressed format if( ddsd->u4.ddpfPixelFormat.dwFlags & DDPF_RGB ) { switch( ddsd->u4.ddpfPixelFormat.u0.dwRGBBitCount ) { case 32: internal_format = 4; format = GL_RGBA; break; case 24: internal_format = 3; format = GL_RGB; break; default: warn( "DDS: only 24 and 32 bit RGBA formats are supported" ); return false; } } else if( ddsd->u4.ddpfPixelFormat.dwFlags & DDPF_LUMINANCE ) { internal_format = format = usingAlpha ? GL_LUMINANCE_ALPHA : GL_LUMINANCE; } else if( ddsd->u4.ddpfPixelFormat.dwFlags & DDPF_ALPHA ) { internal_format = format = GL_ALPHA; } else if( ddsd->u4.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED4 ) { internal_format = usingAlpha ? GL_RGB5_A1 : GL_RGB5; format = GL_COLOR_INDEX4_EXT; } else if( ddsd->u4.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8 ) { internal_format = usingAlpha ? GL_RGBA : GL_RGB; format = GL_COLOR_INDEX8_EXT; } else { warn( "unsupported DDS image type" ); return false; } if ( format ) { int offset = 0; int w = width; int h = height; int d = depth; int i; if( h == 0 ) h = w; //cube map for( i = 0; (i < mipLevels) && (w || h || d); i++ ) { mipOffsets[ i ] = buff + 4 + sizeof( DDSURFACEDESC2_t ) + offset; if( w == 0 ) w = 1; if( h == 0 ) h = 1; if( d == 0 ) d = 1; offset += (w * h * d * (ddsd->u4.ddpfPixelFormat.u0.dwRGBBitCount / 8)); w >>= 1; h >>= 1; d >>= 1; } //put in the trailing offset mipOffsets[ i ] = buff + 4 + sizeof( DDSURFACEDESC2_t ) + offset; } else { warn( "unexpected error reading DDS data" ); return false; } } //we now have a full description of our image set up //start loading it up! if( !mipmap ) mipLevels = 1; glGenTextures( 1, &ret ); retwidth = (unsigned int)width; retheight = (unsigned int)height; if( depth ) { //volume texture int w = width; int h = height; int d = depth; int i; GLboolean enabled; enabled = glIsEnabled( GL_TEXTURE_3D ); if( !enabled ) glEnable( GL_TEXTURE_3D ); glBindTexture( GL_TEXTURE_3D, ret ); for( i = 0; i < mipLevels; i++ ) { glTexImage3D( GL_TEXTURE_3D, i, internal_format, w, h, d, 0, format, GL_UNSIGNED_BYTE, mipOffsets[ i ] ); w >>= 1; if( w == 0 ) w = 1; h >>= 1; if( h == 0 ) h = 1; d >>= 1; if( d == 0 ) d = 1; } glBindTexture( GL_TEXTURE_3D, 0 ); if( !enabled ) glDisable( GL_TEXTURE_3D ); } else if( !height ) { //cube texture int w; int i; size_t shift = mipOffsets[ mipLevels ] - mipOffsets[ 0 ]; GLboolean enabled; enabled = glIsEnabled( GL_TEXTURE_CUBE_MAP ); if( !enabled ) glEnable( GL_TEXTURE_CUBE_MAP ); glBindTexture( GL_TEXTURE_CUBE_MAP, ret ); //macros so this doesn't get disgustingly huge #define loadCubeFace( glTarget ) \ w = width; \ \ for( i = 0; i < mipLevels; i++ ) \ { \ if( compressed ) \ { \ GLsizei size = mipOffsets[ i + 1 ] - mipOffsets[ i ]; \ glCompressedTexImage2DARB( glTarget, i, format, w, w, 0, \ size, mipOffsets[ i ] ); \ } \ else \ { \ glTexImage2D( glTarget, i, internal_format, w, w, \ 0, format, GL_UNSIGNED_BYTE, mipOffsets[ i ] ); \ } \ \ w >>= 1; if( w == 0 ) w = 1; \ } #define shiftMipOffsets \ for( i = 0; i <= mipLevels; i++ ) \ mipOffsets[ i ] += shift; //the faces are stored in the order +x, -x, +y, -y, +z, -z //but there may be missing faces in the sequence which we cannot upload if( ddsd->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEX ) { loadCubeFace( GL_TEXTURE_CUBE_MAP_POSITIVE_X ); shiftMipOffsets } if( ddsd->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEX ) { loadCubeFace( GL_TEXTURE_CUBE_MAP_NEGATIVE_X ); shiftMipOffsets } if( ddsd->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEY ) { loadCubeFace( GL_TEXTURE_CUBE_MAP_POSITIVE_Y ); shiftMipOffsets } if( ddsd->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEY ) { loadCubeFace( GL_TEXTURE_CUBE_MAP_NEGATIVE_Y ); shiftMipOffsets } if( ddsd->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEZ ) { loadCubeFace( GL_TEXTURE_CUBE_MAP_POSITIVE_Z ); shiftMipOffsets } if( ddsd->ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ ) { loadCubeFace( GL_TEXTURE_CUBE_MAP_NEGATIVE_Z ); shiftMipOffsets } #undef shiftMipOffsets #undef loadCubeFace glBindTexture( GL_TEXTURE_CUBE_MAP, 0 ); if( !enabled ) glDisable( GL_TEXTURE_CUBE_MAP ); } else { //planar texture int w = width; int h = height; int i; GLboolean enabled; enabled = glIsEnabled( GL_TEXTURE_2D ); if( !enabled ) glEnable( GL_TEXTURE_2D ); glBindTexture( GL_TEXTURE_2D, ret ); for( i = 0; i < mipLevels; i++ ) { if( compressed ) { GLsizei size = mipOffsets[ i + 1 ] - mipOffsets[ i ]; glCompressedTexImage2DARB( GL_TEXTURE_2D, i, format, w, h, 0, size, mipOffsets[ i ] ); } else { glTexImage2D( GL_TEXTURE_2D, i, internal_format, w, h, 0, format, GL_UNSIGNED_BYTE, mipOffsets[ i ] ); } w >>= 1; if( w == 0 ) w = 1; h >>= 1; if( h == 0 ) h = 1; } glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapClampMode ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapClampMode ); if( mipLevels > 1 ) { glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); } else { glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); } glBindTexture( GL_TEXTURE_2D, 0 ); if( !enabled ) glDisable( GL_TEXTURE_2D ); } return true; } struct TargaHeader { unsigned char id_length, colormap_type, image_type; unsigned short colormap_index, colormap_length; unsigned char colormap_size; unsigned short x_origin, y_origin, width, height; unsigned char pixel_size, attributes; }; static bool LoadTGA( const void *data, size_t /* size */, bool mipmap, GLenum wrapClampMode, GLuint &ret, unsigned int &retwidth, unsigned int &retheight ) { int columns, rows, numPixels; char *pixbuf; const char *buf_p; TargaHeader targa_header; char *targa_rgba; buf_p = (const char*)data; targa_header.id_length = *buf_p++; targa_header.colormap_type = *buf_p++; targa_header.image_type = *buf_p++; targa_header.colormap_index = *(short*)buf_p; buf_p += 2; targa_header.colormap_length = *(short*)buf_p; buf_p += 2; targa_header.colormap_size = *buf_p; buf_p += 1; targa_header.x_origin = *(short*)buf_p; buf_p += 2; targa_header.y_origin = *(short*)buf_p; buf_p += 2; targa_header.width = *(short*)buf_p; buf_p += 2; targa_header.height = *(short*)buf_p; buf_p += 2; targa_header.pixel_size = *buf_p; buf_p += 1; targa_header.attributes = *buf_p; buf_p += 1; if( targa_header.image_type != 2 && targa_header.image_type != 10 && targa_header.image_type != 3 ) { warn( "LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported" ); return false; } if( targa_header.colormap_type != 0 ) { warn( "LoadTGA: colormaps not supported" ); return false; } if( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 && targa_header.image_type != 3 ) { warn( "LoadTGA: Only 32 or 24 bit images supported (no colormaps)" ); return false; } if( targa_header.image_type == 3 && targa_header.pixel_size != 8 ) { warn( "invalid gray image size" ); return false; } columns = targa_header.width; rows = targa_header.height; numPixels = columns * rows; targa_rgba = (char*)malloc( numPixels * 4 ); if( targa_header.id_length != 0 ) buf_p += targa_header.id_length; // skip TARGA image comment if( targa_header.image_type == 2 || targa_header.image_type == 3 ) { // Uncompressed RGB or gray scale image for( int row = rows - 1; row >= 0; row-- ) { pixbuf = targa_rgba + row * columns * 4; for( int column = 0; column < columns; column++ ) { unsigned char red = 0, green = 0, blue = 0, alphabyte = 0; switch( targa_header.pixel_size ) { case 8: blue = *buf_p++; green = blue; red = blue; alphabyte = 0xFF; break; case 24: blue = *buf_p++; green = *buf_p++; red = *buf_p++; alphabyte = 0xFF; break; case 32: blue = *buf_p++; green = *buf_p++; red = *buf_p++; alphabyte = *buf_p++; break; } *pixbuf++ = red; *pixbuf++ = green; *pixbuf++ = blue; *pixbuf++ = alphabyte; } } } else if( targa_header.image_type == 10 ) { // Runlength encoded RGB images unsigned char red, green, blue, alphabyte; red = 0; green = 0; blue = 0; alphabyte = 0xFF; for( int row = rows - 1; row >= 0; row-- ) { pixbuf = targa_rgba + row * columns * 4; for( int column = 0; column < columns; ) { unsigned char packetHeader = *buf_p++; unsigned char packetSize = 1 + (packetHeader & 0x7F); if( packetHeader & 0x80 ) { // run-length packet switch( targa_header.pixel_size ) { case 24: blue = *buf_p++; green = *buf_p++; red = *buf_p++; alphabyte = 0xFF; break; case 32: blue = *buf_p++; green = *buf_p++; red = *buf_p++; alphabyte = *buf_p++; break; } for( int j = 0;j < packetSize; j++ ) { *pixbuf++ = red; *pixbuf++ = green; *pixbuf++ = blue; *pixbuf++ = alphabyte; column++; if( column == columns ) { //run spans across rows column = 0; if( row > 0 ) row--; else goto breakOut; pixbuf = targa_rgba + row * columns * 4; } } } else { //non run-length packet for( int j = 0; j < packetSize; j++ ) { switch( targa_header.pixel_size ) { case 24: blue = *buf_p++; green = *buf_p++; red = *buf_p++; alphabyte = 0xFF; break; case 32: blue = *buf_p++; green = *buf_p++; red = *buf_p++; alphabyte = *buf_p++; break; } *pixbuf++ = red; *pixbuf++ = green; *pixbuf++ = blue; *pixbuf++ = alphabyte; column++; if( column == columns ) { //pixel packet run spans across rows column = 0; if( row > 0 ) row--; else goto breakOut; pixbuf = targa_rgba + row * columns * 4; } } } } breakOut: ; } } if( targa_header.attributes & 0x20 ) { unsigned char *flip = (unsigned char*)malloc( columns * 4 ); unsigned char *src, *dst; for( int row = 0; row < rows / 2; row++ ) { src = (unsigned char*)(targa_rgba + row * 4 * columns); dst = (unsigned char*)(targa_rgba + (rows - row - 1) * 4 * columns); memcpy( flip, src, columns * 4 ); memcpy( src, dst, columns * 4 ); memcpy( dst, flip, columns * 4 ); } free( flip ); } glGenTextures( 1, &ret ); retwidth = targa_header.width; retheight = targa_header.height; GLenum enabled = glIsEnabled( GL_TEXTURE_2D ); if( !enabled ) glEnable( GL_TEXTURE_2D ); glBindTexture( GL_TEXTURE_2D, ret ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); //ToDo: fall back to DoMipMap in the case that this isn't supported if( mipmap ) { glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); } else glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, targa_header.width, targa_header.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, targa_rgba ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapClampMode ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapClampMode ); if( !enabled ) glDisable( GL_TEXTURE_2D ); free( targa_rgba ); return true; } /* static bool LoadBMP( const void *data, size_t size, bool mipmap, GLenum wrapClampMode, GLuint &ret, unsigned int &width, unsigned int &height ) { //ToDo: implement! return false; } */ /* static bool LoadPCX( const void *data, size_t size, bool mipmap, GLenum wrapClampMode, GLuint &ret, unsigned int &width, unsigned int &height ) { //ToDo: implement! return false; } */ #define DEFAULT_SIZE 16 static GLuint DefaultTexture( unsigned int &width, unsigned int &height ) { byte data[DEFAULT_SIZE][DEFAULT_SIZE][4]; //the default image will be a box, to allow you to see the mapping coordinates memset( data, 32, sizeof( data ) ); for ( int x = 0 ; x < DEFAULT_SIZE ; x++ ) { data[0][x][0] = data[0][x][1] = data[0][x][2] = data[0][x][3] = 0xFF; data[x][0][0] = data[x][0][1] = data[x][0][2] = data[x][0][3] = 0xFF; data[DEFAULT_SIZE - 1][x][0] = data[DEFAULT_SIZE - 1][x][1] = data[DEFAULT_SIZE - 1][x][2] = data[DEFAULT_SIZE - 1][x][3] = 0xFF; data[x][DEFAULT_SIZE - 1][0] = data[x][DEFAULT_SIZE - 1][1] = data[x][DEFAULT_SIZE - 1][2] = data[x][DEFAULT_SIZE - 1][3] = 0xFF; } GLuint ret; glGenTextures( 1, &ret ); GLenum enabled = glIsEnabled( GL_TEXTURE_2D ); if( !enabled ) glEnable( GL_TEXTURE_2D ); glBindTexture( GL_TEXTURE_2D, ret ); //ToDo: fall back to DoMipMap in the case that this isn't supported glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE ); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, DEFAULT_SIZE, DEFAULT_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); if( !enabled ) glDisable( GL_TEXTURE_2D ); width = DEFAULT_SIZE; height = DEFAULT_SIZE; return ret; } #define WHITE_SIZE 1 static GLuint WhiteTexture( unsigned int &width, unsigned int &height ) { byte data[WHITE_SIZE][WHITE_SIZE][4]; memset( data, 0xFF, sizeof( data ) ); GLuint ret; glGenTextures( 1, &ret ); GLenum enabled = glIsEnabled( GL_TEXTURE_2D ); if( !enabled ) glEnable( GL_TEXTURE_2D ); glBindTexture( GL_TEXTURE_2D, ret ); glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE ); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, WHITE_SIZE, WHITE_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); if( !enabled ) glDisable( GL_TEXTURE_2D ); width = WHITE_SIZE; height = WHITE_SIZE; return ret; } static const struct { const char *ext; bool (*loader)( const void *data, size_t size, bool mipmap, GLenum wrapClampMode, GLuint &ret, unsigned int &width, unsigned int &height ); } g_loaders[] = { { ".dds", &LoadDDS }, { ".tga", &LoadTGA }, // { ".bmp", &LoadBMP }, // { ".pcx", &LoadPCX }, }; #define NUM_LOADERS (sizeof( g_loaders ) / sizeof( g_loaders[0] )) /* Loads a texture into the GL. So far dds and tga images are supported. BMP is on its way, PCX probably is too, no plans for JPEG. */ GLuint Shader::LoadTexture( const char *name, bool mipmap, GLenum wrapClampMode, unsigned int &width, unsigned int &height ) { ospath path = name; if( path == "$whiteimage" ) return WhiteTexture( width, height ); path.Replace( '\\', '/' ); int iDot = path.LastIndexOf( '.' ); int iSlash = path.LastIndexOf( '/' ); bool loaded = false; GLuint ret = 0; size_t size; void *pData = 0; if( iDot != -1 && iDot > iSlash ) { //we have an extension //try dds first (hworks extension) ospath tmp = path.Substring( 0, iDot ); tmp.Append( ".dds" ); if( q3::ResourceLoader::GetData( tmp, pData, size ) ) { loaded = LoadDDS( pData, size, mipmap, wrapClampMode, ret, width, height ); } else if( q3::ResourceLoader::GetData( path, pData, size ) ) for( int i = 0; i < NUM_LOADERS; i++ ) if( !stricmp( g_loaders[i].ext, path + iDot ) ) { loaded = g_loaders[i].loader( pData, size, mipmap, wrapClampMode, ret, width, height ); break; } } else { //we have no extension for( int i = 0; i < NUM_LOADERS; i++ ) { ospath tmp = path; tmp.Append( g_loaders[i].ext ); if( q3::ResourceLoader::GetData( tmp, pData, size ) ) { loaded = g_loaders[i].loader( pData, size, mipmap, wrapClampMode, ret, width, height ); break; } } } if( loaded ) q3::ResourceLoader::FreeData( pData ); else { ret = DefaultTexture( width, height ); warn( "could not find shader texture, using default" ); } return ret; } /* static */ void Shader::GetDefaultableTextures( const char *dirName, std::vector< q3::qname > &retList ) { const char **shaderFiles = q3::ResourceLoader::FindFiles( dirName ); for( const char **cch = shaderFiles; *cch; cch++ ) { size_t len = strlen( *cch ); for( int i = 0; i < NUM_LOADERS; i++ ) if( !stricmp( *cch + len - strlen( g_loaders[i].ext ), g_loaders[i].ext ) ) { ospath resName = dirName; if( resName[resName.Length() - 1] != '/' ) resName.Append( "/" ); resName.Append( *cch ); if( resName[0] == '/' ) retList.push_back( resName + 1 ); else retList.push_back( resName ); } } q3::ResourceLoader::FindClose( shaderFiles ); } }