238 lines
5.1 KiB
Objective-C
Executable File
238 lines
5.1 KiB
Objective-C
Executable File
#import "PVRTexture.h"
|
|
|
|
#define PVR_TEXTURE_FLAG_TYPE_MASK 0xff
|
|
|
|
static char gPVRTexIdentifier[4] = "PVR!";
|
|
|
|
enum
|
|
{
|
|
kPVRTextureFlagTypePVRTC_2 = 24,
|
|
kPVRTextureFlagTypePVRTC_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;
|
|
} PVRTexHeader;
|
|
|
|
|
|
@implementation PVRTexture
|
|
|
|
@synthesize name = _name;
|
|
@synthesize width = _width;
|
|
@synthesize height = _height;
|
|
@synthesize internalFormat = _internalFormat;
|
|
@synthesize hasAlpha = _hasAlpha;
|
|
|
|
|
|
- (BOOL)unpackPVRData:(NSData *)data
|
|
{
|
|
BOOL success = FALSE;
|
|
PVRTexHeader *header = NULL;
|
|
uint32_t flags, pvrTag;
|
|
uint32_t dataLength = 0, dataOffset = 0, dataSize = 0;
|
|
uint32_t blockSize = 0, widthBlocks = 0, heightBlocks = 0;
|
|
uint32_t width = 0, height = 0, bpp = 4;
|
|
uint8_t *bytes = NULL;
|
|
uint32_t formatFlags;
|
|
|
|
header = (PVRTexHeader *)[data bytes];
|
|
|
|
pvrTag = CFSwapInt32LittleToHost(header->pvrTag);
|
|
|
|
if (gPVRTexIdentifier[0] != ((pvrTag >> 0) & 0xff) ||
|
|
gPVRTexIdentifier[1] != ((pvrTag >> 8) & 0xff) ||
|
|
gPVRTexIdentifier[2] != ((pvrTag >> 16) & 0xff) ||
|
|
gPVRTexIdentifier[3] != ((pvrTag >> 24) & 0xff))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
flags = CFSwapInt32LittleToHost(header->flags);
|
|
formatFlags = flags & PVR_TEXTURE_FLAG_TYPE_MASK;
|
|
|
|
if (formatFlags == kPVRTextureFlagTypePVRTC_4 || formatFlags == kPVRTextureFlagTypePVRTC_2)
|
|
{
|
|
[_imageData removeAllObjects];
|
|
|
|
if (formatFlags == kPVRTextureFlagTypePVRTC_4)
|
|
_internalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
|
|
else if (formatFlags == kPVRTextureFlagTypePVRTC_2)
|
|
_internalFormat = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
|
|
|
|
_width = width = CFSwapInt32LittleToHost(header->width);
|
|
_height = height = CFSwapInt32LittleToHost(header->height);
|
|
|
|
if (CFSwapInt32LittleToHost(header->bitmaskAlpha))
|
|
_hasAlpha = TRUE;
|
|
else
|
|
_hasAlpha = FALSE;
|
|
|
|
dataLength = CFSwapInt32LittleToHost(header->dataLength);
|
|
|
|
bytes = ((uint8_t *)[data bytes]) + sizeof(PVRTexHeader);
|
|
|
|
// Calculate the data size for each texture level and respect the minimum number of blocks
|
|
while (dataOffset < dataLength)
|
|
{
|
|
if (formatFlags == kPVRTextureFlagTypePVRTC_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;
|
|
|
|
dataSize = widthBlocks * heightBlocks * ((blockSize * bpp) / 8);
|
|
|
|
[_imageData addObject:[NSData dataWithBytes:bytes+dataOffset length:dataSize]];
|
|
|
|
dataOffset += dataSize;
|
|
|
|
width = MAX(width >> 1, 1);
|
|
height = MAX(height >> 1, 1);
|
|
}
|
|
|
|
success = TRUE;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
- (BOOL)createGLTexture
|
|
{
|
|
int width = _width;
|
|
int height = _height;
|
|
NSData *data;
|
|
GLenum err;
|
|
|
|
if ([_imageData count] > 0)
|
|
{
|
|
if (_name != 0)
|
|
glDeleteTextures(1, &_name);
|
|
|
|
glGenTextures(1, &_name);
|
|
glBindTexture(GL_TEXTURE_2D, _name);
|
|
}
|
|
|
|
if ([_imageData count] > 1) {
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
}
|
|
else {
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
}
|
|
|
|
NSLog(@"Image levels: %d\n", [_imageData count]);
|
|
for (int i=0; i < [_imageData count]; i++)
|
|
{
|
|
data = [_imageData objectAtIndex:i];
|
|
glCompressedTexImage2D(GL_TEXTURE_2D, i, _internalFormat, width, height, 0, [data length], [data bytes]);
|
|
|
|
err = glGetError();
|
|
if (err != GL_NO_ERROR)
|
|
{
|
|
NSLog(@"Error uploading compressed texture level: %d. glError: 0x%04X", i, err);
|
|
return FALSE;
|
|
}
|
|
|
|
width = MAX(width >> 1, 1);
|
|
height = MAX(height >> 1, 1);
|
|
}
|
|
|
|
[_imageData removeAllObjects];
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
- (id)initWithContentsOfFile:(NSString *)path
|
|
{
|
|
if (self = [super init])
|
|
{
|
|
NSData *data = [NSData dataWithContentsOfFile:path];
|
|
|
|
_imageData = [[NSMutableArray alloc] initWithCapacity:10];
|
|
|
|
_name = 0;
|
|
_width = _height = 0;
|
|
_internalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
|
|
_hasAlpha = FALSE;
|
|
|
|
if (!data || ![self unpackPVRData:data] || ![self createGLTexture])
|
|
{
|
|
[self release];
|
|
self = nil;
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
|
|
- (id)initWithContentsOfURL:(NSURL *)url
|
|
{
|
|
if (![url isFileURL])
|
|
{
|
|
[self release];
|
|
return nil;
|
|
}
|
|
|
|
return [self initWithContentsOfFile:[url path]];
|
|
}
|
|
|
|
|
|
+ (id)pvrTextureWithContentsOfFile:(NSString *)path
|
|
{
|
|
return [[[self alloc] initWithContentsOfFile:path] autorelease];
|
|
}
|
|
|
|
|
|
+ (id)pvrTextureWithContentsOfURL:(NSURL *)url
|
|
{
|
|
if (![url isFileURL])
|
|
return nil;
|
|
|
|
return [PVRTexture pvrTextureWithContentsOfFile:[url path]];
|
|
}
|
|
|
|
|
|
- (void)dealloc
|
|
{
|
|
[_imageData release];
|
|
|
|
if (_name != 0)
|
|
glDeleteTextures(1, &_name);
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
@end
|