| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230 |
- #include "Base.h"
- #include "Image.h"
- #include "Texture.h"
- #include "FileSystem.h"
- // PVRTC (GL_IMG_texture_compression_pvrtc) : Imagination based gpus
- #ifndef GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG
- #define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01
- #endif
- #ifndef GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG
- #define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03
- #endif
- #ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG
- #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00
- #endif
- #ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG
- #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02
- #endif
- // S3TC/DXT (GL_EXT_texture_compression_s3tc) : Most desktop/console gpus
- #ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
- #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
- #endif
- #ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
- #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
- #endif
- #ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
- #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
- #endif
- // ATC (GL_AMD_compressed_ATC_texture) : Qualcomm/Adreno based gpus
- #ifndef ATC_RGB_AMD
- #define ATC_RGB_AMD 0x8C92
- #endif
- #ifndef ATC_RGBA_EXPLICIT_ALPHA_AMD
- #define ATC_RGBA_EXPLICIT_ALPHA_AMD 0x8C93
- #endif
- #ifndef ATC_RGBA_INTERPOLATED_ALPHA_AMD
- #define ATC_RGBA_INTERPOLATED_ALPHA_AMD 0x87EE
- #endif
- // ETC1 (OES_compressed_ETC1_RGB8_texture) : All OpenGL ES chipsets
- #ifndef ETC1_RGB8
- #define ETC1_RGB8 0x8D64
- #endif
- namespace gameplay
- {
- static std::vector<Texture*> __textureCache;
- static TextureHandle __currentTextureId = 0;
- static Texture::Type __currentTextureType = Texture::TEXTURE_2D;
- Texture::Texture() : _handle(0), _format(UNKNOWN), _type((Texture::Type)0), _width(0), _height(0), _mipmapped(false), _cached(false), _compressed(false),
- _wrapS(Texture::REPEAT), _wrapT(Texture::REPEAT), _wrapR(Texture::REPEAT), _minFilter(Texture::NEAREST_MIPMAP_LINEAR), _magFilter(Texture::LINEAR)
- {
- }
- Texture::~Texture()
- {
- if (_handle)
- {
- GL_ASSERT( glDeleteTextures(1, &_handle) );
- _handle = 0;
- }
- // Remove ourself from the texture cache.
- if (_cached)
- {
- std::vector<Texture*>::iterator itr = std::find(__textureCache.begin(), __textureCache.end(), this);
- if (itr != __textureCache.end())
- {
- __textureCache.erase(itr);
- }
- }
- }
- Texture* Texture::create(const char* path, bool generateMipmaps)
- {
- GP_ASSERT( path );
- // Search texture cache first.
- for (size_t i = 0, count = __textureCache.size(); i < count; ++i)
- {
- Texture* t = __textureCache[i];
- GP_ASSERT( t );
- if (t->_path == path)
- {
- // If 'generateMipmaps' is true, call Texture::generateMipamps() to force the
- // texture to generate its mipmap chain if it hasn't already done so.
- if (generateMipmaps)
- {
- t->generateMipmaps();
- }
- // Found a match.
- t->addRef();
- return t;
- }
- }
- Texture* texture = NULL;
- // Filter loading based on file extension.
- const char* ext = strrchr(FileSystem::resolvePath(path), '.');
- if (ext)
- {
- switch (strlen(ext))
- {
- case 4:
- if (tolower(ext[1]) == 'p' && tolower(ext[2]) == 'n' && tolower(ext[3]) == 'g')
- {
- Image* image = Image::create(path);
- if (image)
- texture = create(image, generateMipmaps);
- SAFE_RELEASE(image);
- }
- else if (tolower(ext[1]) == 'p' && tolower(ext[2]) == 'v' && tolower(ext[3]) == 'r')
- {
- // PowerVR Compressed Texture RGBA.
- texture = createCompressedPVRTC(path);
- }
- else if (tolower(ext[1]) == 'd' && tolower(ext[2]) == 'd' && tolower(ext[3]) == 's')
- {
- // DDS file format (DXT/S3TC) compressed textures
- texture = createCompressedDDS(path);
- }
- break;
- }
- }
- if (texture)
- {
- texture->_path = path;
- texture->_cached = true;
- // Add to texture cache.
- __textureCache.push_back(texture);
- return texture;
- }
- GP_ERROR("Failed to load texture from file '%s'.", path);
- return NULL;
- }
- Texture* Texture::create(Image* image, bool generateMipmaps)
- {
- GP_ASSERT( image );
- switch (image->getFormat())
- {
- case Image::RGB:
- return create(Texture::RGB, image->getWidth(), image->getHeight(), image->getData(), generateMipmaps);
- case Image::RGBA:
- return create(Texture::RGBA, image->getWidth(), image->getHeight(), image->getData(), generateMipmaps);
- default:
- GP_ERROR("Unsupported image format (%d).", image->getFormat());
- return NULL;
- }
- }
- Texture* Texture::create(Format format, unsigned int width, unsigned int height, const unsigned char* data, bool generateMipmaps, Texture::Type type)
- {
- GP_ASSERT( type == Texture::TEXTURE_2D || type == Texture::TEXTURE_CUBE );
- GLenum target = (GLenum)type;
- // Create the texture.
- GLuint textureId;
- GL_ASSERT( glGenTextures(1, &textureId) );
- GL_ASSERT( glBindTexture(target, textureId) );
- GL_ASSERT( glPixelStorei(GL_UNPACK_ALIGNMENT, 1) );
- #ifndef OPENGL_ES
- // glGenerateMipmap is new in OpenGL 3.0. For OpenGL 2.0 we must fallback to use glTexParameteri
- // with GL_GENERATE_MIPMAP prior to actual texture creation (glTexImage2D)
- if ( generateMipmaps && !std::addressof(glGenerateMipmap) )
- GL_ASSERT( glTexParameteri(target, GL_GENERATE_MIPMAP, GL_TRUE) );
- #endif
- // Load the texture
- if (type == Texture::TEXTURE_2D)
- {
- // Texture 2D
- GL_ASSERT( glTexImage2D(GL_TEXTURE_2D, 0, (GLenum)format, width, height, 0, (GLenum)format, GL_UNSIGNED_BYTE, data) );
- }
- else
- {
- // Get texture size
- unsigned int textureSize = width * height;
- switch (format)
- {
- case Texture::RGB:
- textureSize *= 3;
- break;
- case Texture::RGBA:
- textureSize *= 4;
- break;
- case Texture::ALPHA:
- break;
- case Texture::UNKNOWN:
- if (data)
- {
- glDeleteTextures(1, &textureId);
- GP_ERROR("Failed to determine texture size because format is UNKNOWN.");
- return NULL;
- }
- break;
- }
- // Texture Cube
- for (unsigned int i = 0; i < 6; i++)
- {
- const unsigned char* texturePtr = (data == NULL) ? NULL : &data[i * textureSize];
- GL_ASSERT( glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, (GLenum)format, width, height, 0, (GLenum)format, GL_UNSIGNED_BYTE, texturePtr) );
- }
- }
- // Set initial minification filter based on whether or not mipmaping was enabled.
- Filter minFilter = generateMipmaps ? NEAREST_MIPMAP_LINEAR : LINEAR;
- GL_ASSERT( glTexParameteri(target, GL_TEXTURE_MIN_FILTER, minFilter) );
- Texture* texture = new Texture();
- texture->_handle = textureId;
- texture->_format = format;
- texture->_type = type;
- texture->_width = width;
- texture->_height = height;
- texture->_minFilter = minFilter;
- if (generateMipmaps)
- {
- texture->generateMipmaps();
- }
- // Restore the texture id
- GL_ASSERT( glBindTexture((GLenum)__currentTextureType, __currentTextureId) );
- return texture;
- }
- Texture* Texture::create(TextureHandle handle, int width, int height, Format format)
- {
- GP_ASSERT( handle );
- Texture* texture = new Texture();
- if (glIsTexture(handle))
- {
- // There is no real way to query for texture type, but an error will be returned if a cube texture is bound to a 2D texture... so check for that
- glBindTexture(GL_TEXTURE_CUBE_MAP, handle);
- if (glGetError() == GL_NO_ERROR)
- {
- texture->_type = TEXTURE_CUBE;
- }
- else
- {
- // For now, it's either or. But if 3D textures and others are added, it might be useful to simply test a bunch of bindings and seeing which one doesn't error out
- texture->_type = TEXTURE_2D;
- }
- // Restore the texture id
- GL_ASSERT( glBindTexture((GLenum)__currentTextureType, __currentTextureId) );
- }
- texture->_handle = handle;
- texture->_format = format;
- texture->_width = width;
- texture->_height = height;
- return texture;
- }
- void Texture::setData(const unsigned char* data)
- {
- // Don't work with any compressed or cached textures
- GL_ASSERT( data );
- GL_ASSERT( (!_compressed) );
- GL_ASSERT( (!_cached) );
- GL_ASSERT( glBindTexture((GLenum)_type, _handle) );
- if (_type == Texture::TEXTURE_2D)
- {
- GL_ASSERT( glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _width, _height, (GLenum)_format, GL_UNSIGNED_BYTE, data) );
- }
- else
- {
- // Get texture size
- unsigned int textureSize = _width * _height;
- switch (_format)
- {
- case Texture::RGB:
- textureSize *= 3;
- break;
- case Texture::RGBA:
- textureSize *= 4;
- break;
- case Texture::ALPHA:
- break;
- }
- // Texture Cube
- for (unsigned int i = 0; i < 6; i++)
- {
- GL_ASSERT( glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, _width, _height, (GLenum)_format, GL_UNSIGNED_BYTE, &data[i * textureSize]) );
- }
- }
- if (_mipmapped)
- {
- generateMipmaps();
- }
- // Restore the texture id
- GL_ASSERT( glBindTexture((GLenum)__currentTextureType, __currentTextureId) );
- }
- // Computes the size of a PVRTC data chunk for a mipmap level of the given size.
- static unsigned int computePVRTCDataSize(int width, int height, int bpp)
- {
- int blockSize;
- int widthBlocks;
- int heightBlocks;
- if (bpp == 4)
- {
- blockSize = 4 * 4; // Pixel by pixel block size for 4bpp
- widthBlocks = std::max(width >> 2, 2);
- heightBlocks = std::max(height >> 2, 2);
- }
- else
- {
- blockSize = 8 * 4; // Pixel by pixel block size for 2bpp
- widthBlocks = std::max(width >> 3, 2);
- heightBlocks = std::max(height >> 2, 2);
- }
- return widthBlocks * heightBlocks * ((blockSize * bpp) >> 3);
- }
- Texture* Texture::createCompressedPVRTC(const char* path)
- {
- std::unique_ptr<Stream> stream(FileSystem::open(path));
- if (stream.get() == NULL || !stream->canRead())
- {
- GP_ERROR("Failed to load file '%s'.", path);
- return NULL;
- }
- // Read first 4 bytes to determine PVRTC format.
- size_t read;
- unsigned int version;
- read = stream->read(&version, sizeof(unsigned int), 1);
- if (read != 1)
- {
- GP_ERROR("Failed to read PVR version.");
- return NULL;
- }
- // Rewind to start of header.
- if (stream->seek(0, SEEK_SET) == false)
- {
- GP_ERROR("Failed to seek backwards to beginning of file after reading PVR version.");
- return NULL;
- }
- // Read texture data.
- GLsizei width, height;
- GLenum format;
- GLubyte* data = NULL;
- unsigned int mipMapCount;
- unsigned int faceCount;
- GLenum faces[6] = { GL_TEXTURE_2D };
- if (version == 0x03525650)
- {
- // Modern PVR file format.
- data = readCompressedPVRTC(path, stream.get(), &width, &height, &format, &mipMapCount, &faceCount, faces);
- }
- else
- {
- // Legacy PVR file format.
- data = readCompressedPVRTCLegacy(path, stream.get(), &width, &height, &format, &mipMapCount, &faceCount, faces);
- }
- if (data == NULL)
- {
- GP_ERROR("Failed to read texture data from PVR file '%s'.", path);
- return NULL;
- }
- stream->close();
- int bpp = (format == GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG || format == GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG) ? 2 : 4;
- // Generate our texture.
- GLenum target = faceCount > 1 ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
- GLuint textureId;
- GL_ASSERT( glGenTextures(1, &textureId) );
- GL_ASSERT( glBindTexture(target, textureId) );
- Filter minFilter = mipMapCount > 1 ? NEAREST_MIPMAP_LINEAR : LINEAR;
- GL_ASSERT( glTexParameteri(target, GL_TEXTURE_MIN_FILTER, minFilter) );
- Texture* texture = new Texture();
- texture->_handle = textureId;
- texture->_type = faceCount > 1 ? TEXTURE_CUBE : TEXTURE_2D;
- texture->_width = width;
- texture->_height = height;
- texture->_mipmapped = mipMapCount > 1;
- texture->_compressed = true;
- texture->_minFilter = minFilter;
- // Load the data for each level.
- GLubyte* ptr = data;
- for (unsigned int level = 0; level < mipMapCount; ++level)
- {
- unsigned int dataSize = computePVRTCDataSize(width, height, bpp);
- for (unsigned int face = 0; face < faceCount; ++face)
- {
- // Upload data to GL.
- GL_ASSERT(glCompressedTexImage2D(faces[face], level, format, width, height, 0, dataSize, &ptr[face * dataSize]));
- }
- width = std::max(width >> 1, 1);
- height = std::max(height >> 1, 1);
- ptr += dataSize * faceCount;
- }
- // Free data.
- SAFE_DELETE_ARRAY(data);
- // Restore the texture id
- GL_ASSERT( glBindTexture((GLenum)__currentTextureType, __currentTextureId) );
- return texture;
- }
- GLubyte* Texture::readCompressedPVRTC(const char* path, Stream* stream, GLsizei* width, GLsizei* height, GLenum* format, unsigned int* mipMapCount, unsigned int* faceCount, GLenum* faces)
- {
- GP_ASSERT( stream );
- GP_ASSERT( path );
- GP_ASSERT( width );
- GP_ASSERT( height );
- GP_ASSERT( format );
- GP_ASSERT( mipMapCount );
- GP_ASSERT( faceCount );
- GP_ASSERT( faces );
- struct pvrtc_file_header
- {
- unsigned int version;
- unsigned int flags;
- unsigned int pixelFormat[2];
- unsigned int colorSpace;
- unsigned int channelType;
- unsigned int height;
- unsigned int width;
- unsigned int depth;
- unsigned int surfaceCount;
- unsigned int faceCount;
- unsigned int mipMapCount;
- unsigned int metaDataSize;
- };
- struct pvrtc_metadata
- {
- char fourCC[4];
- unsigned int key;
- unsigned int dataSize;
- };
- size_t read;
- // Read header data.
- pvrtc_file_header header;
- read = stream->read(&header, sizeof(pvrtc_file_header), 1);
- if (read != 1)
- {
- GP_ERROR("Failed to read PVR header data for file '%s'.", path);
- return NULL;
- }
- if (header.pixelFormat[1] != 0)
- {
- // Unsupported pixel format.
- GP_ERROR("Unsupported pixel format in PVR file '%s'. (MSB == %d != 0)", path, header.pixelFormat[1]);
- return NULL;
- }
- int bpp;
- switch (header.pixelFormat[0])
- {
- case 0:
- *format = GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
- bpp = 2;
- break;
- case 1:
- *format = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
- bpp = 2;
- break;
- case 2:
- *format = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
- bpp = 4;
- break;
- case 3:
- *format = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
- bpp = 4;
- break;
- default:
- // Unsupported format.
- GP_ERROR("Unsupported pixel format value (%d) in PVR file '%s'.", header.pixelFormat[0], path);
- return NULL;
- }
- *width = (GLsizei)header.width;
- *height = (GLsizei)header.height;
- *mipMapCount = header.mipMapCount;
- *faceCount = std::min(header.faceCount, 6u);
- if ((*faceCount) > 1)
- {
- // Look for cubemap metadata and setup faces
- unsigned int remainingMetadata = header.metaDataSize;
- pvrtc_metadata mdHeader;
- bool foundTextureCubeMeta = false;
- while (remainingMetadata > 0)
- {
- read = stream->read(&mdHeader, sizeof(pvrtc_metadata), 1);
- if (read != 1)
- {
- GP_ERROR("Failed to read PVR metadata header data for file '%s'.", path);
- return NULL;
- }
- remainingMetadata -= sizeof(pvrtc_metadata) + mdHeader.dataSize;
- // Check that it's a known metadata type (specifically, cubemap order), otherwise skip to next metadata
- if ((mdHeader.fourCC[0] != 'P') ||
- (mdHeader.fourCC[1] != 'V') ||
- (mdHeader.fourCC[2] != 'R') ||
- (mdHeader.fourCC[3] != 3) ||
- (mdHeader.key != 2) || // Everything except cubemap order (cubemap order key is 2)
- (mdHeader.dataSize != 6)) // Cubemap order datasize should be 6
- {
- if (stream->seek(mdHeader.dataSize, SEEK_CUR) == false)
- {
- GP_ERROR("Failed to seek to next meta data header in PVR file '%s'.", path);
- return NULL;
- }
- continue;
- }
- // Get cubemap order
- foundTextureCubeMeta = true;
- char faceOrder[6];
- read = stream->read(faceOrder, 1, sizeof(faceOrder));
- if (read != sizeof(faceOrder))
- {
- GP_ERROR("Failed to read cubemap face order meta data for file '%s'.", path);
- return NULL;
- }
- for (unsigned int face = 0; face < (*faceCount); ++face)
- {
- faces[face] = GL_TEXTURE_CUBE_MAP_POSITIVE_X + (faceOrder[face] <= 'Z' ?
- ((faceOrder[face] - 'X') * 2) :
- (((faceOrder[face] - 'x') * 2) + 1));
- if (faces[face] < GL_TEXTURE_CUBE_MAP_POSITIVE_X)
- {
- // Just overwrite this face
- faces[face] = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
- }
- }
- }
- if (!foundTextureCubeMeta)
- {
- // Didn't find cubemap metadata. Just assume it's "in order"
- for (unsigned int face = 0; face < (*faceCount); ++face)
- {
- faces[face] = GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
- }
- }
- }
- else
- {
- // Skip meta-data.
- if (stream->seek(header.metaDataSize, SEEK_CUR) == false)
- {
- GP_ERROR("Failed to seek past header meta data in PVR file '%s'.", path);
- return NULL;
- }
- }
- // Compute total size of data to be read.
- int w = *width;
- int h = *height;
- size_t dataSize = 0;
- for (unsigned int level = 0; level < header.mipMapCount; ++level)
- {
- dataSize += computePVRTCDataSize(w, h, bpp) * (*faceCount);
- w = std::max(w>>1, 1);
- h = std::max(h>>1, 1);
- }
- // Read data.
- GLubyte* data = new GLubyte[dataSize];
- read = stream->read(data, 1, dataSize);
- if (read != dataSize)
- {
- SAFE_DELETE_ARRAY(data);
- GP_ERROR("Failed to read texture data from PVR file '%s'.", path);
- return NULL;
- }
- return data;
- }
- GLubyte* Texture::readCompressedPVRTCLegacy(const char* path, Stream* stream, GLsizei* width, GLsizei* height, GLenum* format, unsigned int* mipMapCount, unsigned int* faceCount, GLenum* faces)
- {
- char PVRTCIdentifier[] = "PVR!";
- struct pvrtc_file_header_legacy
- {
- unsigned int size; // size of the structure
- unsigned int height; // height of surface to be created
- unsigned int width; // width of input surface
- unsigned int mipmapCount; // number of mip-map levels requested
- unsigned int formatflags; // pixel format flags
- unsigned int dataSize; // total size in bytes
- unsigned int bpp; // number of bits per pixel
- unsigned int redBitMask; // mask for red bit
- unsigned int greenBitMask; // mask for green bits
- unsigned int blueBitMask; // mask for blue bits
- unsigned int alphaBitMask; // mask for alpha channel
- unsigned int pvrtcTag; // magic number identifying pvrtc file
- unsigned int surfaceCount; // number of surfaces present in the pvrtc
- };
- // Read the file header.
- unsigned int size = sizeof(pvrtc_file_header_legacy);
- pvrtc_file_header_legacy header;
- unsigned int read = (int)stream->read(&header, 1, size);
- if (read != size)
- {
- GP_ERROR("Failed to read file header for pvrtc file '%s'.", path);
- return NULL;
- }
- // Proper file header identifier.
- if (PVRTCIdentifier[0] != (char)((header.pvrtcTag >> 0) & 0xff) ||
- PVRTCIdentifier[1] != (char)((header.pvrtcTag >> 8) & 0xff) ||
- PVRTCIdentifier[2] != (char)((header.pvrtcTag >> 16) & 0xff) ||
- PVRTCIdentifier[3] != (char)((header.pvrtcTag >> 24) & 0xff))
- {
- GP_ERROR("Failed to load pvrtc file '%s': invalid header.", path);
- return NULL;
- }
- // Format flags for GLenum format.
- if (header.bpp == 4)
- {
- *format = header.alphaBitMask ? GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG : GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
- }
- else if (header.bpp == 2)
- {
- *format = header.alphaBitMask ? GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG : GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
- }
- else
- {
- GP_ERROR("Failed to load pvrtc file '%s': invalid pvrtc compressed texture format flags.", path);
- return NULL;
- }
- *width = (GLsizei)header.width;
- *height = (GLsizei)header.height;
- *mipMapCount = header.mipmapCount + 1; // +1 because mipmapCount does not include the base level
- *faceCount = 1;
- // Flags (needed legacy documentation on format, pre-PVR Format 3.0)
- if ((header.formatflags & 0x1000) != 0)
- {
- // Texture cube
- *faceCount = std::min(header.surfaceCount, 6u);
- for (unsigned int face = 0; face < (*faceCount); ++face)
- {
- faces[face] = GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
- }
- }
- else if ((header.formatflags & 0x4000) != 0)
- {
- // Volume texture
- GP_ERROR("Failed to load pvrtc file '%s': volume texture is not supported.", path);
- return NULL;
- }
- unsigned int totalSize = header.dataSize; // Docs say dataSize is the size of the whole surface, or one face of a texture cube. But this does not appear to be the case with the latest PVRTexTool
- GLubyte* data = new GLubyte[totalSize];
- read = (int)stream->read(data, 1, totalSize);
- if (read != totalSize)
- {
- SAFE_DELETE_ARRAY(data);
- GP_ERROR("Failed to load texture data for pvrtc file '%s'.", path);
- return NULL;
- }
- return data;
- }
- int Texture::getMaskByteIndex(unsigned int mask)
- {
- switch (mask)
- {
- case 0xff000000:
- return 3;
- case 0x00ff0000:
- return 2;
- case 0x0000ff00:
- return 1;
- case 0x000000ff:
- return 0;
- default:
- return -1; // no or invalid mask
- }
- }
- Texture* Texture::createCompressedDDS(const char* path)
- {
- GP_ASSERT( path );
- // DDS file structures.
- struct dds_pixel_format
- {
- unsigned int dwSize;
- unsigned int dwFlags;
- unsigned int dwFourCC;
- unsigned int dwRGBBitCount;
- unsigned int dwRBitMask;
- unsigned int dwGBitMask;
- unsigned int dwBBitMask;
- unsigned int dwABitMask;
- };
- struct dds_header
- {
- unsigned int dwSize;
- unsigned int dwFlags;
- unsigned int dwHeight;
- unsigned int dwWidth;
- unsigned int dwPitchOrLinearSize;
- unsigned int dwDepth;
- unsigned int dwMipMapCount;
- unsigned int dwReserved1[11];
- dds_pixel_format ddspf;
- unsigned int dwCaps;
- unsigned int dwCaps2;
- unsigned int dwCaps3;
- unsigned int dwCaps4;
- unsigned int dwReserved2;
- };
- struct dds_mip_level
- {
- GLubyte* data;
- GLsizei width;
- GLsizei height;
- GLsizei size;
- };
- Texture* texture = NULL;
- // Read DDS file.
- std::unique_ptr<Stream> stream(FileSystem::open(path));
- if (stream.get() == NULL || !stream->canRead())
- {
- GP_ERROR("Failed to open file '%s'.", path);
- return NULL;
- }
- // Validate DDS magic number.
- char code[4];
- if (stream->read(code, 1, 4) != 4 || strncmp(code, "DDS ", 4) != 0)
- {
- GP_ERROR("Failed to read DDS file '%s': invalid DDS magic number.", path);
- return NULL;
- }
- // Read DDS header.
- dds_header header;
- if (stream->read(&header, sizeof(dds_header), 1) != 1)
- {
- GP_ERROR("Failed to read header for DDS file '%s'.", path);
- return NULL;
- }
- if ((header.dwFlags & 0x20000/*DDSD_MIPMAPCOUNT*/) == 0)
- {
- // Mipmap count not specified (non-mipmapped texture).
- header.dwMipMapCount = 1;
- }
- // Check type of images. Default is a regular texture
- unsigned int facecount = 1;
- GLenum faces[6] = { GL_TEXTURE_2D };
- GLenum target = GL_TEXTURE_2D;
- if ((header.dwCaps2 & 0x200/*DDSCAPS2_CUBEMAP*/) != 0)
- {
- facecount = 0;
- for (unsigned int off = 0, flag = 0x400/*DDSCAPS2_CUBEMAP_POSITIVEX*/; off < 6; ++off, flag <<= 1)
- {
- if ((header.dwCaps2 & flag) != 0)
- {
- faces[facecount++] = GL_TEXTURE_CUBE_MAP_POSITIVE_X + off;
- }
- }
- target = GL_TEXTURE_CUBE_MAP;
- }
- else if ((header.dwCaps2 & 0x200000/*DDSCAPS2_VOLUME*/) != 0)
- {
- // Volume textures unsupported.
- GP_ERROR("Failed to create texture from DDS file '%s': volume textures are unsupported.", path);
- return NULL;
- }
- // Allocate mip level structures.
- dds_mip_level* mipLevels = new dds_mip_level[header.dwMipMapCount * facecount];
- memset(mipLevels, 0, sizeof(dds_mip_level) * header.dwMipMapCount * facecount);
- GLenum format = 0;
- GLenum internalFormat = 0;
- bool compressed = false;
- GLsizei width = header.dwWidth;
- GLsizei height = header.dwHeight;
- Texture::Format textureFormat = Texture::UNKNOWN;
- if (header.ddspf.dwFlags & 0x4/*DDPF_FOURCC*/)
- {
- compressed = true;
- int bytesPerBlock;
- // Compressed.
- switch (header.ddspf.dwFourCC)
- {
- case ('D'|('X'<<8)|('T'<<16)|('1'<<24)):
- format = internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
- bytesPerBlock = 8;
- break;
- case ('D'|('X'<<8)|('T'<<16)|('3'<<24)):
- format = internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
- bytesPerBlock = 16;
- break;
- case ('D'|('X'<<8)|('T'<<16)|('5'<<24)):
- format = internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
- bytesPerBlock = 16;
- break;
- case ('A'|('T'<<8)|('C'<<16)|(' '<<24)):
- format = internalFormat = ATC_RGB_AMD;
- bytesPerBlock = 8;
- break;
- case ('A'|('T'<<8)|('C'<<16)|('A'<<24)):
- format = internalFormat = ATC_RGBA_EXPLICIT_ALPHA_AMD;
- bytesPerBlock = 16;
- break;
- case ('A'|('T'<<8)|('C'<<16)|('I'<<24)):
- format = internalFormat = ATC_RGBA_INTERPOLATED_ALPHA_AMD;
- bytesPerBlock = 16;
- break;
- case ('E'|('T'<<8)|('C'<<16)|('1'<<24)):
- format = internalFormat = ETC1_RGB8;
- bytesPerBlock = 8;
- break;
- default:
- GP_ERROR("Unsupported compressed texture format (%d) for DDS file '%s'.", header.ddspf.dwFourCC, path);
- SAFE_DELETE_ARRAY(mipLevels);
- return NULL;
- }
- for (unsigned int face = 0; face < facecount; ++face)
- {
- for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
- {
- dds_mip_level& level = mipLevels[i + face * header.dwMipMapCount];
- level.width = width;
- level.height = height;
- level.size = std::max(1, (width + 3) >> 2) * std::max(1, (height + 3) >> 2) * bytesPerBlock;
- level.data = new GLubyte[level.size];
- if (stream->read(level.data, 1, level.size) != (unsigned int)level.size)
- {
- GP_ERROR("Failed to load dds compressed texture bytes for texture: %s", path);
- // Cleanup mip data.
- for (unsigned int face = 0; face < facecount; ++face)
- for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
- SAFE_DELETE_ARRAY(mipLevels[i + face * header.dwMipMapCount].data);
- SAFE_DELETE_ARRAY(mipLevels);
- return texture;
- }
- width = std::max(1, width >> 1);
- height = std::max(1, height >> 1);
- }
- width = header.dwWidth;
- height = header.dwHeight;
- }
- }
- else if (header.ddspf.dwFlags & 0x40/*DDPF_RGB*/)
- {
- // RGB/RGBA (uncompressed)
- bool colorConvert = false;
- unsigned int rmask = header.ddspf.dwRBitMask;
- unsigned int gmask = header.ddspf.dwGBitMask;
- unsigned int bmask = header.ddspf.dwBBitMask;
- unsigned int amask = header.ddspf.dwABitMask;
- int ridx = getMaskByteIndex(rmask);
- int gidx = getMaskByteIndex(gmask);
- int bidx = getMaskByteIndex(bmask);
- int aidx = getMaskByteIndex(amask);
- if (header.ddspf.dwRGBBitCount == 24)
- {
- format = internalFormat = GL_RGB;
- textureFormat = Texture::RGB;
- colorConvert = (ridx != 0) || (gidx != 1) || (bidx != 2);
- }
- else if (header.ddspf.dwRGBBitCount == 32)
- {
- format = internalFormat = GL_RGBA;
- textureFormat = Texture::RGBA;
- if (ridx == 0 && gidx == 1 && bidx == 2)
- {
- aidx = 3; // XBGR or ABGR
- colorConvert = false;
- }
- else if (ridx == 2 && gidx == 1 && bidx == 0)
- {
- aidx = 3; // XRGB or ARGB
- colorConvert = true;
- }
- else
- {
- format = 0; // invalid format
- }
- }
- if (format == 0)
- {
- GP_ERROR("Failed to create texture from uncompressed DDS file '%s': Unsupported color format (must be one of R8G8B8, A8R8G8B8, A8B8G8R8, X8R8G8B8, X8B8G8R8.", path);
- SAFE_DELETE_ARRAY(mipLevels);
- return NULL;
- }
- // Read data.
- for (unsigned int face = 0; face < facecount; ++face)
- {
- for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
- {
- dds_mip_level& level = mipLevels[i + face * header.dwMipMapCount];
- level.width = width;
- level.height = height;
- level.size = width * height * (header.ddspf.dwRGBBitCount >> 3);
- level.data = new GLubyte[level.size];
- if (stream->read(level.data, 1, level.size) != (unsigned int)level.size)
- {
- GP_ERROR("Failed to load bytes for RGB dds texture: %s", path);
- // Cleanup mip data.
- for (unsigned int face = 0; face < facecount; ++face)
- for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
- SAFE_DELETE_ARRAY(mipLevels[i + face * header.dwMipMapCount].data);
- SAFE_DELETE_ARRAY(mipLevels);
- return texture;
- }
- width = std::max(1, width >> 1);
- height = std::max(1, height >> 1);
- }
- width = header.dwWidth;
- height = header.dwHeight;
- }
- // Perform color conversion.
- if (colorConvert)
- {
- // Note: While it's possible to use BGRA_EXT texture formats here and avoid CPU color conversion below,
- // there seems to be different flavors of the BGRA extension, with some vendors requiring an internal
- // format of RGBA and others requiring an internal format of BGRA.
- // We could be smarter here later and skip color conversion in favor of GL_BGRA_EXT (for format
- // and/or internal format) based on which GL extensions are available.
- // Tip: Using A8B8G8R8 and X8B8G8R8 DDS format maps directly to GL RGBA and requires on no color conversion.
- GLubyte *pixel, r, g, b, a;
- if (format == GL_RGB)
- {
- for (unsigned int face = 0; face < facecount; ++face)
- {
- for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
- {
- dds_mip_level& level = mipLevels[i + face * header.dwMipMapCount];
- for (int j = 0; j < level.size; j += 3)
- {
- pixel = &level.data[j];
- r = pixel[ridx]; g = pixel[gidx]; b = pixel[bidx];
- pixel[0] = r; pixel[1] = g; pixel[2] = b;
- }
- }
- }
- }
- else if (format == GL_RGBA)
- {
- for (unsigned int face = 0; face < facecount; ++face)
- {
- for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
- {
- dds_mip_level& level = mipLevels[i + face * header.dwMipMapCount];
- for (int j = 0; j < level.size; j += 4)
- {
- pixel = &level.data[j];
- r = pixel[ridx]; g = pixel[gidx]; b = pixel[bidx]; a = pixel[aidx];
- pixel[0] = r; pixel[1] = g; pixel[2] = b; pixel[3] = a;
- }
- }
- }
- }
- }
- }
- else
- {
- // Unsupported.
- GP_ERROR("Failed to create texture from DDS file '%s': unsupported flags (%d).", path, header.ddspf.dwFlags);
- SAFE_DELETE_ARRAY(mipLevels);
- return NULL;
- }
- // Close file.
- stream->close();
- // Generate GL texture.
- GLuint textureId;
- GL_ASSERT( glGenTextures(1, &textureId) );
- GL_ASSERT( glBindTexture(target, textureId) );
- Filter minFilter = header.dwMipMapCount > 1 ? NEAREST_MIPMAP_LINEAR : LINEAR;
- GL_ASSERT( glTexParameteri(target, GL_TEXTURE_MIN_FILTER, minFilter ) );
- // Create gameplay texture.
- texture = new Texture();
- texture->_handle = textureId;
- texture->_type = (Type)target;
- texture->_width = header.dwWidth;
- texture->_height = header.dwHeight;
- texture->_compressed = compressed;
- texture->_mipmapped = header.dwMipMapCount > 1;
- texture->_minFilter = minFilter;
- // Load texture data.
- for (unsigned int face = 0; face < facecount; ++face)
- {
- GLenum texImageTarget = faces[face];
- for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
- {
- dds_mip_level& level = mipLevels[i + face * header.dwMipMapCount];
- if (compressed)
- {
- GL_ASSERT(glCompressedTexImage2D(texImageTarget, i, format, level.width, level.height, 0, level.size, level.data));
- }
- else
- {
- GL_ASSERT(glTexImage2D(texImageTarget, i, internalFormat, level.width, level.height, 0, format, GL_UNSIGNED_BYTE, level.data));
- }
- // Clean up the texture data.
- SAFE_DELETE_ARRAY(level.data);
- }
- }
- // Clean up mip levels structure.
- SAFE_DELETE_ARRAY(mipLevels);
- // Restore the texture id
- GL_ASSERT( glBindTexture((GLenum)__currentTextureType, __currentTextureId) );
- return texture;
- }
- Texture::Format Texture::getFormat() const
- {
- return _format;
- }
- Texture::Type Texture::getType() const
- {
- return _type;
- }
- const char* Texture::getPath() const
- {
- return _path.c_str();
- }
- unsigned int Texture::getWidth() const
- {
- return _width;
- }
- unsigned int Texture::getHeight() const
- {
- return _height;
- }
- TextureHandle Texture::getHandle() const
- {
- return _handle;
- }
- void Texture::generateMipmaps()
- {
- if (!_mipmapped)
- {
- GLenum target = (GLenum)_type;
- GL_ASSERT( glBindTexture(target, _handle) );
- GL_ASSERT( glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST) );
- if( std::addressof(glGenerateMipmap) )
- GL_ASSERT( glGenerateMipmap(target) );
- _mipmapped = true;
- // Restore the texture id
- GL_ASSERT( glBindTexture((GLenum)__currentTextureType, __currentTextureId) );
- }
- }
- bool Texture::isMipmapped() const
- {
- return _mipmapped;
- }
- bool Texture::isCompressed() const
- {
- return _compressed;
- }
- Texture::Sampler::Sampler(Texture* texture)
- : _texture(texture), _wrapS(Texture::REPEAT), _wrapT(Texture::REPEAT), _wrapR(Texture::REPEAT)
- {
- GP_ASSERT( texture );
- _minFilter = texture->_minFilter;
- _magFilter = texture->_magFilter;
- }
- Texture::Sampler::~Sampler()
- {
- SAFE_RELEASE(_texture);
- }
- Texture::Sampler* Texture::Sampler::create(Texture* texture)
- {
- GP_ASSERT( texture );
- GP_ASSERT( texture->_type == Texture::TEXTURE_2D || texture->_type == Texture::TEXTURE_CUBE );
- texture->addRef();
- return new Sampler(texture);
- }
- Texture::Sampler* Texture::Sampler::create(const char* path, bool generateMipmaps)
- {
- Texture* texture = Texture::create(path, generateMipmaps);
- return texture ? new Sampler(texture) : NULL;
- }
- void Texture::Sampler::setWrapMode(Wrap wrapS, Wrap wrapT, Wrap wrapR)
- {
- _wrapS = wrapS;
- _wrapT = wrapT;
- _wrapR = wrapR;
- }
- void Texture::Sampler::setFilterMode(Filter minificationFilter, Filter magnificationFilter)
- {
- _minFilter = minificationFilter;
- _magFilter = magnificationFilter;
- }
- Texture* Texture::Sampler::getTexture() const
- {
- return _texture;
- }
- void Texture::Sampler::bind()
- {
- GP_ASSERT( _texture );
- GLenum target = (GLenum)_texture->_type;
- if (__currentTextureId != _texture->_handle)
- {
- GL_ASSERT( glBindTexture(target, _texture->_handle) );
- __currentTextureId = _texture->_handle;
- __currentTextureType = _texture->_type;
- }
- if (_texture->_minFilter != _minFilter)
- {
- _texture->_minFilter = _minFilter;
- GL_ASSERT( glTexParameteri(target, GL_TEXTURE_MIN_FILTER, (GLenum)_minFilter) );
- }
- if (_texture->_magFilter != _magFilter)
- {
- _texture->_magFilter = _magFilter;
- GL_ASSERT( glTexParameteri(target, GL_TEXTURE_MAG_FILTER, (GLenum)_magFilter) );
- }
- if (_texture->_wrapS != _wrapS)
- {
- _texture->_wrapS = _wrapS;
- GL_ASSERT( glTexParameteri(target, GL_TEXTURE_WRAP_S, (GLenum)_wrapS) );
- }
- if (_texture->_wrapT != _wrapT)
- {
- _texture->_wrapT = _wrapT;
- GL_ASSERT( glTexParameteri(target, GL_TEXTURE_WRAP_T, (GLenum)_wrapT) );
- }
- #if defined(GL_TEXTURE_WRAP_R) // OpenGL ES 3.x and up, OpenGL 1.2 and up
- if (_texture->_wrapR != _wrapR)
- {
- _texture->_wrapR = _wrapR;
- if (target == GL_TEXTURE_CUBE_MAP) // We don't want to run this on something that we know will fail
- GL_ASSERT( glTexParameteri(target, GL_TEXTURE_WRAP_R, (GLenum)_wrapR) );
- }
- #endif
- }
- }
|