diff --git a/include/osg/Texture b/include/osg/Texture index c3cc7aad5..0bc6b8746 100644 --- a/include/osg/Texture +++ b/include/osg/Texture @@ -64,6 +64,13 @@ #define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE #endif +#ifndef GL_IMG_texture_compression_pvrtc + #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 + #define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01 + #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 + #define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03 +#endif + #ifndef GL_ARB_INTERNAL_TEXTURE_FORMAT #define GL_RGBA32F_ARB 0x8814 #define GL_RGB32F_ARB 0x8815 @@ -642,6 +649,9 @@ class OSG_EXPORT Texture : public osg::StateAttribute void setTextureCompressionRGTCSupported(bool flag) { _isTextureCompressionRGTCSupported=flag; } bool isTextureCompressionRGTCSupported() const { return _isTextureCompressionRGTCSupported; } + void setTextureCompressionPVRTCSupported(bool flag) { _isTextureCompressionPVRTCSupported=flag; } + bool isTextureCompressionPVRTCSupported() const { return _isTextureCompressionPVRTCSupported; } + void setTextureMirroredRepeatSupported(bool flag) { _isTextureMirroredRepeatSupported=flag; } bool isTextureMirroredRepeatSupported() const { return _isTextureMirroredRepeatSupported; } @@ -741,6 +751,7 @@ class OSG_EXPORT Texture : public osg::StateAttribute bool _isTextureCompressionARBSupported; bool _isTextureCompressionS3TCSupported; bool _isTextureCompressionRGTCSupported; + bool _isTextureCompressionPVRTCSupported; bool _isTextureMirroredRepeatSupported; bool _isTextureEdgeClampSupported; bool _isTextureBorderClampSupported; diff --git a/src/osg/Image.cpp b/src/osg/Image.cpp index fcbe3d15b..c3adfe0ac 100644 --- a/src/osg/Image.cpp +++ b/src/osg/Image.cpp @@ -307,6 +307,10 @@ unsigned int Image::computeNumComponents(GLenum pixelFormat) case(GL_COMPRESSED_RED_RGTC1_EXT): return 1; case(GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT): return 2; case(GL_COMPRESSED_RED_GREEN_RGTC2_EXT): return 2; + case(GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG): return 3; + case(GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG): return 3; + case(GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG): return 4; + case(GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG): return 4; case(GL_COLOR_INDEX): return 1; case(GL_STENCIL_INDEX): return 1; case(GL_DEPTH_COMPONENT): return 1; @@ -417,6 +421,11 @@ unsigned int Image::computePixelSizeInBits(GLenum format,GLenum type) case(GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT): return 8; case(GL_COMPRESSED_RED_GREEN_RGTC2_EXT): return 8; + case(GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG): return 4; + case(GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG): return 2; + case(GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG): return 4; + case(GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG): return 2; + default: break; } @@ -551,6 +560,10 @@ bool Image::isCompressed() const case(GL_COMPRESSED_RED_RGTC1_EXT): case(GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT): case(GL_COMPRESSED_RED_GREEN_RGTC2_EXT): + case(GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG): + case(GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG): + case(GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG): + case(GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG): return true; default: return false; diff --git a/src/osg/Texture.cpp b/src/osg/Texture.cpp index d5f3d40da..3b53dced4 100644 --- a/src/osg/Texture.cpp +++ b/src/osg/Texture.cpp @@ -135,6 +135,11 @@ void Texture::TextureProfile::computeSize() case(GL_COMPRESSED_RED_RGTC1_EXT): numBitsPerTexel = 4; break; case(GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT): numBitsPerTexel = 8; break; case(GL_COMPRESSED_RED_GREEN_RGTC2_EXT): numBitsPerTexel = 8; break; + + case(GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG): numBitsPerTexel = 2; break; + case(GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG): numBitsPerTexel = 2; break; + case(GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG): numBitsPerTexel = 4; break; + case(GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG): numBitsPerTexel = 4; break; } _size = (unsigned int)(ceil(double(_width * _height * _depth * numBitsPerTexel)/8.0)); @@ -1351,6 +1356,10 @@ bool Texture::isCompressedInternalFormat(GLint internalFormat) case(GL_COMPRESSED_RED_RGTC1_EXT): case(GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT): case(GL_COMPRESSED_RED_GREEN_RGTC2_EXT): + case(GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG): + case(GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG): + case(GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG): + case(GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG): return true; default: return false; @@ -1367,6 +1376,38 @@ void Texture::getCompressedSize(GLenum internalFormat, GLint width, GLint height blockSize = 8; else if (internalFormat == GL_COMPRESSED_RED_GREEN_RGTC2_EXT || internalFormat == GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT) blockSize = 16; + else if (internalFormat == GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG || internalFormat == GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG) + { + blockSize = 8 * 4; // Pixel by pixel block size for 2bpp + GLint widthBlocks = width / 8; + GLint heightBlocks = height / 4; + GLint bpp = 2; + + // Clamp to minimum number of blocks + if(widthBlocks < 2) + widthBlocks = 2; + if(heightBlocks < 2) + heightBlocks = 2; + + size = widthBlocks * heightBlocks * ((blockSize * bpp) / 8); + return; + } + else if (internalFormat == GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG || internalFormat == GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG) + { + blockSize = 4 * 4; // Pixel by pixel block size for 4bpp + GLint widthBlocks = width / 4; + GLint heightBlocks = height / 4; + GLint bpp = 4; + + // Clamp to minimum number of blocks + if(widthBlocks < 2) + widthBlocks = 2; + if(heightBlocks < 2) + heightBlocks = 2; + + size = widthBlocks * heightBlocks * ((blockSize * bpp) / 8); + return; + } else { OSG_WARN<<"Texture::getCompressedSize(...) : cannot compute correct size of compressed format ("< +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +using namespace osg; + +#define PVR_TEXTURE_FLAG_TYPE_MASK 0xff + +static char gPVRTexIdentifier[5] = "PVR!"; + +enum +{ + kPVRTextureFlagTypePVRTC_2 = 12, + kPVRTextureFlagTypePVRTC_4, + kPVRTextureFlagTypeOGLPVRTC_2 = 24, + kPVRTextureFlagTypeOGLPVRTC_4 +}; + +typedef struct _PVRTexHeader +{ + uint32_t headerLength; + uint32_t height; + uint32_t width; + uint32_t numMipmaps; + uint32_t flags; + uint32_t dataLength; + uint32_t bpp; + uint32_t bitmaskRed; + uint32_t bitmaskGreen; + uint32_t bitmaskBlue; + uint32_t bitmaskAlpha; + uint32_t pvrTag; + uint32_t numSurfs; + + typedef unsigned char * BytePtr; + + bool needsBytesSwapped() + { + union { + int testWord; + char testByte[sizeof(int)]; + }endianTest; + endianTest.testWord = 1; + if( endianTest.testByte[0] == 1 ) + return false; + else + return true; + } + + template + inline void swapBytes( T &s ) + { + if( sizeof( T ) == 1 ) + return; + + T d = s; + BytePtr sptr = (BytePtr)&s; + BytePtr dptr = &(((BytePtr)&d)[sizeof(T)-1]); + + for( unsigned int i = 0; i < sizeof(T); i++ ) + *(sptr++) = *(dptr--); + } + + void swapBytes() + { + swapBytes(headerLength); + swapBytes(height); + swapBytes(width); + swapBytes(numMipmaps); + swapBytes(flags); + swapBytes(dataLength); + swapBytes(bpp); + swapBytes(bitmaskRed); + swapBytes(bitmaskGreen); + swapBytes(bitmaskBlue); + swapBytes(bitmaskAlpha); + swapBytes(pvrTag); + swapBytes(numSurfs); + } + +} PVRTexHeader; + + +class ReaderWriterPVR : public osgDB::ReaderWriter +{ +public: + + ReaderWriterPVR() + { + supportsExtension("pvr","PVR image format"); + } + + virtual const char* className() const { return "PVR Image Reader/Writer"; } + + + ReadResult readPVRStream(std::istream& fin) const + { + PVRTexHeader header; + + fin.read((char*)&header, sizeof(PVRTexHeader)); + if(!fin.good()){ + osg::notify(osg::WARN) << "Failed to read pvr header." << std::endl; + return ReadResult::ERROR_IN_READING_FILE; + } + + if(header.needsBytesSwapped()) + header.swapBytes(); + + if(gPVRTexIdentifier[0] != static_cast((header.pvrTag >> 0) & 0xff) || + gPVRTexIdentifier[1] != static_cast((header.pvrTag >> 8) & 0xff) || + gPVRTexIdentifier[2] != static_cast((header.pvrTag >> 16) & 0xff) || + gPVRTexIdentifier[3] != static_cast((header.pvrTag >> 24) & 0xff)) + { + osg::notify(osg::WARN) << "Failed to verify pvr header: " << ((header.pvrTag >> 0) & 0xff) << ", " << ((header.pvrTag >> 8) & 0xff) << ", " << ((header.pvrTag >> 16) & 0xff) << ", " << ((header.pvrTag >> 24) & 0xff) << std::endl; + return ReadResult::FILE_NOT_HANDLED; + } + + + uint32_t formatFlags = header.flags & PVR_TEXTURE_FLAG_TYPE_MASK; + GLenum internalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; + uint32_t width, height; + bool hasAlpha; + + if(formatFlags == kPVRTextureFlagTypePVRTC_4 || formatFlags == kPVRTextureFlagTypePVRTC_2 || + formatFlags == kPVRTextureFlagTypeOGLPVRTC_4 || formatFlags == kPVRTextureFlagTypeOGLPVRTC_2){ + if(formatFlags == kPVRTextureFlagTypePVRTC_4 || formatFlags == kPVRTextureFlagTypeOGLPVRTC_4) + internalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; + else if(formatFlags == kPVRTextureFlagTypePVRTC_2 || formatFlags == kPVRTextureFlagTypeOGLPVRTC_2) + internalFormat = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; + + width = header.width; + height = header.height; + + if(header.bitmaskAlpha) + hasAlpha = true; + else + hasAlpha = false; + + osg::Image *image = new osg::Image; + unsigned char *imageData = new unsigned char[header.dataLength]; + fin.read((char*)imageData, header.dataLength); + if(!fin.good()) + return ReadResult::ERROR_IN_READING_FILE; + + image->setImage(header.width, header.height, 1, + internalFormat, internalFormat, + GL_UNSIGNED_BYTE, + imageData, + osg::Image::USE_NEW_DELETE); + + uint32_t dataOffset = 0; + uint32_t blockSize = 0, widthBlocks = 0, heightBlocks = 0; + uint32_t bpp = 4; + + osg::Image::MipmapDataType mipmapdata; + + // Calculate the data size for each texture level and respect the minimum number of blocks + while(dataOffset < header.dataLength){ + if(formatFlags == kPVRTextureFlagTypePVRTC_4 || formatFlags == kPVRTextureFlagTypeOGLPVRTC_4){ + blockSize = 4 * 4; // Pixel by pixel block size for 4bpp + widthBlocks = width / 4; + heightBlocks = height / 4; + bpp = 4; + }else{ + blockSize = 8 * 4; // Pixel by pixel block size for 2bpp + widthBlocks = width / 8; + heightBlocks = height / 4; + bpp = 2; + } + + // Clamp to minimum number of blocks + if(widthBlocks < 2) + widthBlocks = 2; + if(heightBlocks < 2) + heightBlocks = 2; + + if(dataOffset > 0) + mipmapdata.push_back(dataOffset); + + dataOffset += widthBlocks * heightBlocks * ((blockSize * bpp) / 8); + + width = std::max(width >> 1, (uint32_t)1); + height = std::max(height >> 1, (uint32_t)1); + } + + if(!mipmapdata.empty()) + image->setMipmapLevels(mipmapdata); + + return image; + } + + osg::notify(osg::WARN) << "Failed to read pvr data." << std::endl; + return ReadResult::FILE_NOT_HANDLED; + } + + virtual ReadResult readObject(std::istream& fin,const osgDB::ReaderWriter::Options* options =NULL) const + { + return readImage(fin, options); + } + + virtual ReadResult readObject(const std::string& file, const osgDB::ReaderWriter::Options* options =NULL) const + { + return readImage(file, options); + } + + virtual ReadResult readImage(std::istream& fin,const osgDB::ReaderWriter::Options* =NULL) const + { + return readPVRStream(fin); + } + + virtual ReadResult readImage(const std::string& file, const osgDB::ReaderWriter::Options* options) const + { + std::string ext = osgDB::getLowerCaseFileExtension(file); + if(!acceptsExtension(ext)) + return ReadResult::FILE_NOT_HANDLED; + + std::string fileName = osgDB::findDataFile(file, options); + if(fileName.empty()) + return ReadResult::FILE_NOT_FOUND; + + std::ifstream istream(fileName.c_str(), std::ios::in | std::ios::binary); + if(!istream) return ReadResult::FILE_NOT_HANDLED; + ReadResult rr = readPVRStream(istream); + if(rr.validImage()) rr.getImage()->setFileName(file); + return rr; + } + +}; + +// now register with Registry to instantiate the above +// reader/writer. +REGISTER_OSGPLUGIN(pvr, ReaderWriterPVR)