From 788b374269a67557e127667b7131ce2d6e7ad786 Mon Sep 17 00:00:00 2001 From: Samo Pajk Date: Wed, 18 Sep 2013 12:36:18 +0200 Subject: [PATCH] Added support for .pvr textures. (Make sure you use texture tools as described in apple documentation) --- .../Xni/Framework/Content/ContentManager.m | 2 +- .../Pipeline/Graphics/PixelBitmapContent.h | 2 +- .../Pipeline/Graphics/PixelBitmapContent.m | 4 +- .../Pipeline/Graphics/VectorConverter.h | 2 +- .../Pipeline/Graphics/VectorConverter.m | 12 +- .../Content/Pipeline/TextureImporter.m | 231 +++++++++++++++--- .../Xni/Framework/Content/Texture2DReader.m | 2 +- .../Xni/Framework/Graphics/GraphicsDevice.m | 43 +++- .../Xni/Framework/Graphics/GraphicsEnums.h | 6 +- 9 files changed, 250 insertions(+), 54 deletions(-) diff --git a/Classes/Retronator/Xni/Framework/Content/ContentManager.m b/Classes/Retronator/Xni/Framework/Content/ContentManager.m index db33817..ec38f4e 100644 --- a/Classes/Retronator/Xni/Framework/Content/ContentManager.m +++ b/Classes/Retronator/Xni/Framework/Content/ContentManager.m @@ -106,7 +106,7 @@ if ([extension isEqualToString:@"png"] || [extension isEqualToString:@"jpg"] || [extension isEqualToString:@"jpeg"] || [extension isEqualToString:@"gif"] || [extension isEqualToString:@"tif"] || [extension isEqualToString:@"tiff"] || - [extension isEqualToString:@"ico"] || [extension isEqualToString:@"bmp"]) { + [extension isEqualToString:@"ico"] || [extension isEqualToString:@"bmp"] || [extension isEqualToString:@"pvr"]) { // Texture content if (!importer) { importer = [[[TextureImporter alloc] init] autorelease]; diff --git a/Classes/Retronator/Xni/Framework/Content/Pipeline/Graphics/PixelBitmapContent.h b/Classes/Retronator/Xni/Framework/Content/Pipeline/Graphics/PixelBitmapContent.h index 5d16eb1..5ba5972 100644 --- a/Classes/Retronator/Xni/Framework/Content/Pipeline/Graphics/PixelBitmapContent.h +++ b/Classes/Retronator/Xni/Framework/Content/Pipeline/Graphics/PixelBitmapContent.h @@ -12,7 +12,7 @@ @interface PixelBitmapContent : BitmapContent { SurfaceFormat format; - int bytesPerPixel; + float bytesPerPixel; } - (id) initWithWidth:(int)theWidth height:(int)theHeight format:(SurfaceFormat)theFormat; diff --git a/Classes/Retronator/Xni/Framework/Content/Pipeline/Graphics/PixelBitmapContent.m b/Classes/Retronator/Xni/Framework/Content/Pipeline/Graphics/PixelBitmapContent.m index 22c3130..a06d51b 100644 --- a/Classes/Retronator/Xni/Framework/Content/Pipeline/Graphics/PixelBitmapContent.m +++ b/Classes/Retronator/Xni/Framework/Content/Pipeline/Graphics/PixelBitmapContent.m @@ -31,13 +31,13 @@ - (Byte *) getPixelAtX:(int)x Y:(int)y { // Index into the data array at bytesPerPixel intervals. Byte *bytes = (Byte*)[pixelData bytes]; - return &bytes[(x + y * width) * bytesPerPixel]; + return &bytes[(int)((x + y * width) * bytesPerPixel)]; } - (void) setPixelAtX:(int)x Y:(int)y Value:(Byte *)value { // The value contains bytesPerPixel bytes. Byte *bytes = (Byte*)[pixelData bytes]; - memcpy(&bytes[(x + y * width) * bytesPerPixel], value, bytesPerPixel); + memcpy(&bytes[(int)((x + y * width) * bytesPerPixel)], value, bytesPerPixel); } @end diff --git a/Classes/Retronator/Xni/Framework/Content/Pipeline/Graphics/VectorConverter.h b/Classes/Retronator/Xni/Framework/Content/Pipeline/Graphics/VectorConverter.h index 83cca08..51ce216 100644 --- a/Classes/Retronator/Xni/Framework/Content/Pipeline/Graphics/VectorConverter.h +++ b/Classes/Retronator/Xni/Framework/Content/Pipeline/Graphics/VectorConverter.h @@ -15,7 +15,7 @@ } -+ (BOOL) tryGetSizeInBytesOfSurfaceFormat:(SurfaceFormat)surfaceFormat sizeInBytes:(int*)sizeInBytes; ++ (BOOL) tryGetSizeInBytesOfSurfaceFormat:(SurfaceFormat)surfaceFormat sizeInBytes:(float*)sizeInBytes; + (BOOL) tryGetVectorTypeOfSurfaceFormat:(SurfaceFormat)surfaceFormat vectorType:(Class*)type; diff --git a/Classes/Retronator/Xni/Framework/Content/Pipeline/Graphics/VectorConverter.m b/Classes/Retronator/Xni/Framework/Content/Pipeline/Graphics/VectorConverter.m index 5360403..b0b03c0 100644 --- a/Classes/Retronator/Xni/Framework/Content/Pipeline/Graphics/VectorConverter.m +++ b/Classes/Retronator/Xni/Framework/Content/Pipeline/Graphics/VectorConverter.m @@ -13,7 +13,7 @@ @implementation VectorConverter -+ (BOOL) tryGetSizeInBytesOfSurfaceFormat:(SurfaceFormat)surfaceFormat sizeInBytes:(int*)sizeInBytes { ++ (BOOL) tryGetSizeInBytesOfSurfaceFormat:(SurfaceFormat)surfaceFormat sizeInBytes:(float*)sizeInBytes { switch (surfaceFormat) { case SurfaceFormatColor: *sizeInBytes = sizeof(Byte4); @@ -29,7 +29,15 @@ return YES; case SurfaceFormatAlpha8: *sizeInBytes = sizeof(Alpha8); - return YES; + return YES; + case SurfaceFormatPvrtc4bAlpha: + case SurfaceFormatPvrtc4b: + *sizeInBytes = 0.5f; + return YES; + case SurfaceFormatPvrtc2bAlpha: + case SurfaceFormatPvrtc2b: + *sizeInBytes = 0.25f; + return YES; default: break; } diff --git a/Classes/Retronator/Xni/Framework/Content/Pipeline/TextureImporter.m b/Classes/Retronator/Xni/Framework/Content/Pipeline/TextureImporter.m index 002be6e..725a81a 100644 --- a/Classes/Retronator/Xni/Framework/Content/Pipeline/TextureImporter.m +++ b/Classes/Retronator/Xni/Framework/Content/Pipeline/TextureImporter.m @@ -13,49 +13,202 @@ #import "Retronator.Xni.Framework.Content.Pipeline.h" #import "Retronator.Xni.Framework.Content.Pipeline.Graphics.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 TextureImporter -- (TextureContent*) importFile:(NSString*)filename { - NSData *textureData = [NSData dataWithContentsOfFile:filename]; - UIImage *image = [UIImage imageWithData:textureData]; - if (image == nil) { - [NSException raise:@"InvalidArgumentException" format:@"The provided file is not a supported texture resource."]; - } - - // Allocate space for raw data. - GLuint width = CGImageGetWidth(image.CGImage); - GLuint height = CGImageGetHeight(image.CGImage); - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - void *imageData = malloc(width * height * 4); +- (TextureContent*) importFile:(NSString*)filename { + NSData *textureData = [NSData dataWithContentsOfFile:filename]; - // Create a context for drawing to the raw data space. - CGContextRef textureContext = CGBitmapContextCreate(imageData, width, height, 8, 4 * width, colorSpace, - kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big ); + if ([[filename pathExtension] isEqualToString:@"pvr"]) { + //Import pvrtc texture + NSMutableArray *images = [NSMutableArray array]; + SurfaceFormat format; + int width; + int height; + + [self unpackPVR:textureData to:images format:&format width:&width height:&height]; + if (images.count == 0) { + [NSException raise:@"InvalidArgumentException" format:@"Make sure that .pvr texture is properly compressed and that the file has correct header. Use this command in terminal:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/texturetool -e PVRTC -f PVR --channel-weighting-perceptual --bits-per-pixel-4 -o output.pvr input.png"]; + } + + // This bitmap is the only one in the mipmap chain. + MipmapChain *mipmaps = [[[MipmapChain alloc] init] autorelease]; + + for(NSData *imgData in images){ + PixelBitmapContent *bitmap = [[PixelBitmapContent alloc] initWithWidth:(int)width height:(int)height format:format]; + NSLog(@"Storing compressed texture with data length:%d", [imgData length]); + [bitmap setPixelData:[NSData dataWithBytes:[imgData bytes] length:[imgData length]]]; + [mipmaps addObject:bitmap]; + [bitmap release]; + } + + // Create the texture content. + Texture2DContent *content = [[[Texture2DContent alloc] init] autorelease]; + content.identity.sourceFilename = filename; + content.mipmaps = mipmaps; + return content; + }else{ + + UIImage *image = [UIImage imageWithData:textureData]; + if (image == nil) { + [NSException raise:@"InvalidArgumentException" format:@"The provided file is not a supported texture resource."]; + } + + // Allocate space for raw data. + GLuint width = CGImageGetWidth(image.CGImage); + GLuint height = CGImageGetHeight(image.CGImage); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + void *imageData = malloc(width * height * 4); + + // Create a context for drawing to the raw data space. + CGContextRef textureContext = CGBitmapContextCreate(imageData, width, height, 8, 4 * width, colorSpace, + kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big ); - // Draw the image to our context. - CGContextClearRect(textureContext, CGRectMake(0, 0, width, height)); - CGContextTranslateCTM(textureContext, 0, 0); - CGContextDrawImage(textureContext, CGRectMake(0, 0, width, height), image.CGImage); - - // Create pixel bitmap content. - PixelBitmapContent *bitmap = [[[PixelBitmapContent alloc] initWithWidth:(int)width height:(int)height format:SurfaceFormatColor] autorelease]; - [bitmap setPixelData:[NSData dataWithBytes:imageData length:width*height*4]]; - - // Clean up. - CGColorSpaceRelease(colorSpace); - CGContextRelease(textureContext); - free(imageData); - - // This bitmap is the only one in the mipmap chain. - MipmapChain *mipmaps = [[[MipmapChain alloc] init] autorelease]; - [mipmaps addObject:bitmap]; - - // Create the texture content. - Texture2DContent *content = [[[Texture2DContent alloc] init] autorelease]; - content.identity.sourceFilename = filename; - content.mipmaps = mipmaps; - - return content; + // Draw the image to our context. + CGContextClearRect(textureContext, CGRectMake(0, 0, width, height)); + CGContextTranslateCTM(textureContext, 0, 0); + CGContextDrawImage(textureContext, CGRectMake(0, 0, width, height), image.CGImage); + + // Create pixel bitmap content. + PixelBitmapContent *bitmap = [[[PixelBitmapContent alloc] initWithWidth:(int)width height:(int)height format:SurfaceFormatColor] autorelease]; + [bitmap setPixelData:[NSData dataWithBytes:imageData length:width*height*4]]; + + // Clean up. + CGColorSpaceRelease(colorSpace); + CGContextRelease(textureContext); + free(imageData); + + // This bitmap is the only one in the mipmap chain. + MipmapChain *mipmaps = [[[MipmapChain alloc] init] autorelease]; + [mipmaps addObject:bitmap]; + + // Create the texture content. + Texture2DContent *content = [[[Texture2DContent alloc] init] autorelease]; + content.identity.sourceFilename = filename; + content.mipmaps = mipmaps; + + return content; + } +} + +- (BOOL) unpackPVR:(NSData*)data to:(NSMutableArray*)imageData format:(GLenum*)formatOut width:(int*)widthOut height:(int*)heightOut{ + + 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]; + + BOOL alpha = FALSE; + + if (CFSwapInt32LittleToHost(header->bitmaskAlpha)) + alpha = TRUE; + else + alpha = FALSE; + + if (formatFlags == kPVRTextureFlagTypePVRTC_4 && alpha) + *formatOut = SurfaceFormatPvrtc4bAlpha; + else if (formatFlags == kPVRTextureFlagTypePVRTC_4 && !alpha) + *formatOut = SurfaceFormatPvrtc4b; + else if (formatFlags == kPVRTextureFlagTypePVRTC_2 && alpha) + *formatOut = SurfaceFormatPvrtc2bAlpha; + else if (formatFlags == kPVRTextureFlagTypePVRTC_2 && !alpha) + *formatOut = SurfaceFormatPvrtc2b; + + *widthOut = width = CFSwapInt32LittleToHost(header->width); + *heightOut = height = CFSwapInt32LittleToHost(header->height); + + 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; } @end diff --git a/Classes/Retronator/Xni/Framework/Content/Texture2DReader.m b/Classes/Retronator/Xni/Framework/Content/Texture2DReader.m index 9111b97..a795a11 100644 --- a/Classes/Retronator/Xni/Framework/Content/Texture2DReader.m +++ b/Classes/Retronator/Xni/Framework/Content/Texture2DReader.m @@ -32,7 +32,7 @@ for (int i=0;i<[mipmaps count];i++) { bitmap = [mipmaps objectAtIndex:i]; - [texture setDataToLevel:i sourceRectangle:nil from:(void*)[[bitmap getPixelData] bytes]]; + [texture setDataToLevel:i sourceRectangle:nil from:(void*)[[bitmap getPixelData] bytes] ]; } return texture; diff --git a/Classes/Retronator/Xni/Framework/Graphics/GraphicsDevice.m b/Classes/Retronator/Xni/Framework/Graphics/GraphicsDevice.m index 4e1292a..388a3d3 100644 --- a/Classes/Retronator/Xni/Framework/Graphics/GraphicsDevice.m +++ b/Classes/Retronator/Xni/Framework/Graphics/GraphicsDevice.m @@ -19,6 +19,7 @@ #import "IndexBuffer+Internal.h" #import "VertexBuffer+Internal.h" #import "RenderTarget2D+Internal.h" +#import "VectorConverter.h" @interface GraphicsDevice(){ BOOL rrt; @@ -363,7 +364,7 @@ glDeleteTextures(1, &textureId); } -- (void) setData:(void*)data toTexture2D:(Texture2D*)texture SourceRectangle:(Rectangle*)rect level:(int)level { +- (void) setData:(void*)data toTexture2D:(Texture2D*)texture SourceRectangle:(Rectangle*)rect level:(int)level{ GLenum format, type; [GraphicsDevice getFormat:&format AndType:&type ForSurfaceFormat:texture.format]; @@ -374,12 +375,30 @@ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - if (rect) { - glTexSubImage2D(GL_TEXTURE_2D, level, rect.x, rect.y, rect.width, rect.height, format, type, data); - } else { - glTexImage2D(GL_TEXTURE_2D, level, format, texture.width, texture.height, 0, format, type, data); + if (texture.format == SurfaceFormatPvrtc4bAlpha || texture.format == SurfaceFormatPvrtc4b || texture.format == SurfaceFormatPvrtc2bAlpha || texture.format == SurfaceFormatPvrtc2b) { + float bytesPerPixel = 0; + BOOL hasValue = [VectorConverter tryGetSizeInBytesOfSurfaceFormat:texture.format sizeInBytes:&bytesPerPixel]; + if (hasValue) { + int size = (int)(bytesPerPixel*texture.width*texture.height); + glCompressedTexImage2D(GL_TEXTURE_2D, level, format, texture.width, texture.height, 0, size, data); + + GLenum err = glGetError(); + if (err != GL_NO_ERROR) + { + NSLog(@"Error uploading compressed texture level: %d. glError: 0x%04X", level, err); + } + + }else{ + [NSException raise:@"ArgumentException" format:@"The provided format is not supported"]; + } + }else{ + if (rect) { + glTexSubImage2D(GL_TEXTURE_2D, level, rect.x, rect.y, rect.width, rect.height, format, type, data); + } else { + glTexImage2D(GL_TEXTURE_2D, level, format, texture.width, texture.height, 0, format, type, data); + } } - + glBindTexture(GL_TEXTURE_2D, 0); } @@ -480,6 +499,18 @@ *format = GL_RGBA; *type = GL_UNSIGNED_SHORT_5_5_5_1; break; + case SurfaceFormatPvrtc4bAlpha: + *format = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; + break; + case SurfaceFormatPvrtc4b: + *format = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; + break; + case SurfaceFormatPvrtc2bAlpha: + *format = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; + break; + case SurfaceFormatPvrtc2b: + *format = GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG; + break; default: break; } diff --git a/Classes/Retronator/Xni/Framework/Graphics/GraphicsEnums.h b/Classes/Retronator/Xni/Framework/Graphics/GraphicsEnums.h index 9803698..4d64def 100644 --- a/Classes/Retronator/Xni/Framework/Graphics/GraphicsEnums.h +++ b/Classes/Retronator/Xni/Framework/Graphics/GraphicsEnums.h @@ -187,7 +187,11 @@ typedef enum { //SurfaceFormatHalfSingle, //SurfaceFormatHalfVector2, //SurfaceFormatHalfVector4, - //SurfaceFormatHdrBlendable + //SurfaceFormatHdrBlendable + SurfaceFormatPvrtc4b, + SurfaceFormatPvrtc2b, + SurfaceFormatPvrtc4bAlpha, + SurfaceFormatPvrtc2bAlpha } SurfaceFormat; typedef enum {