その結果、CATiledLayer は非常にうまく機能し、パフォーマンスの問題は解決され、コード量は GCD で達成されるものと同等になりました。唯一の問題は、画像が画面に読み込まれた後に顕著なフェードインがあることです (図 14.4)。
図 14.4 画像ロード後のフェードイン
CATiledLayer の fadeDuration プロパティを調整してフェードイン速度を調整したり、グラデーション全体を直接削除したりできますが、これでは問題が根本的に解決されるわけではありません。ロードされて描画の準備ができていますが、常に遅延が発生し、スライド時に新しい画像が飛び込む原因になります。これは CATiledLayer の問題ではなく、GCD を使用するバージョンにもこの問題があります。
上で説明した画像の読み込みとキャッシュのテクニックをすべて使用したとしても、大きな画像をリアルタイムで読み込むときに問題が発生することがあります。第 13 章で述べたように、iPad の Retina 画面全体の画像解像度は 2048x1536 に達し、12MB の RAM (非圧縮) を消費します。第 3 世代 iPad のハードウェアは、このような画像のロード、解凍、および 1/60 秒のフレーム レートでの表示をサポートできません。アニメーションのフリーズを避けるためにバックグラウンド スレッドの読み込みを使用したとしても、問題は依然として解決できません。
読み込み中にプレースホルダー画像を表示することはできますが、これは問題を根本的に解決するものではなく、もっと改善することができます。
Retina 解像度 (Apple の市場で定義) は、通常の視距離で人間の目が解像できる最小のピクセル サイズを表します。ただし、これは静的ピクセルにのみ適用できます。動画を見るとき、目は細部に鈍感になるため、低解像度の画像は網膜品質の画像と区別できなくなります。
大きな動画を素早くロードして表示する必要がある場合、簡単な方法は、人間の目をだましてテレポーターを動かすときに小さな画像 (または低解像度) を表示し、停止したときに大きな画像に変更することです。これは、各画像の 2 つのコピーを異なる解像度で保存する必要があることを意味しますが、幸いなことに、これは Retina デバイスと非 Retina デバイスの両方をサポートする必要があるため、一般的に行われることです。
リモート ソースまたはユーザーのフォト アルバムからロードするときに利用可能な画像の低解像度バージョンがない場合は、大きな画像をより小さな CGContext に動的に描画し、それを再利用するためにどこかに保存できます。
画像を交換するには、UIScrollViewDelegate プロトコルを実装する UIScrollView のいくつかのデリゲート メソッドを使用する必要があります (UITableView や UICollectionView などの他のスクロール ビュー ベースのコントロールと同じ):
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
これらのメソッドを使用して検出できますテレポーターがスクロールを停止してから高解像度の画像を読み込むかどうか。高解像度画像と低解像度画像のサイズと色が同じである限り、置換プロセスにほとんど気づきません (これらの画像を同じマシン上で生成するには、必ず同じグラフィックス プログラムまたはスクリプトを使用してください)。
表示する画像がたくさんある場合は、事前にすべてをロードせず、画面外に移動したらすぐに破棄することをお勧めします。選択的にキャッシュすることで、前後にスクロールするときに画像を繰り返し読み込むことを避けることができます。
キャッシュは実際には非常に単純です。キャッシュは、高価な計算の結果 (またはフラッシュ メモリやネットワークからロードされたファイル) を後で使用できるようにメモリに保存し、すぐにアクセスできるようにします。問題は、キャッシュが本質的にトレードオフであることです。パフォーマンスを向上させるためにメモリが消費されますが、メモリは非常に貴重なリソースであるため、すべてをキャッシュすることはできません。
いつ何を (そしてどのくらいの期間) キャッシュするかは必ずしも明らかではありません。幸いなことに、ほとんどの場合、iOS は画像をキャッシュします。
[UIImage imageNamed:] を使用して画像をロードすると、描画を待たずにすぐに画像を解凍できるという利点があることを前に説明しました。しかし、[UIImage imageNamed:] メソッドにはもう 1 つの非常に重要な利点があります。それは、自分で参照を保持していなくても、解凍された画像を自動的にメモリにキャッシュします。
iOS アプリケーションのメイン画像 (アイコン、ボタン、背景画像など) の場合、[UIImage imageNamed:] を使用して画像を読み込むのが最も簡単で効果的な方法です。 nib ファイルで参照される画像もこのメカニズムを使用するため、暗黙的に使用することがよくあります。
しかし、[UIImage imageNamed:] はどのような状況にも適用されません。ユーザー インターフェイス用に最適化されていますが、アプリケーションが表示する必要があるすべての種類の画像には適しているわけではありません。以下の理由により、独自のキャッシュ メカニズムを実装する必要がある場合もあります:
[UIImage imageNamed:] メソッドは、アプリケーション リソース バンドル ディレクトリ内のイメージにのみ適用されますが、ほとんどのアプリケーションでは、多くのイメージをネットワークまたはユーザーのディレクトリから取得する必要があります。カメラから取得するため、[UIImage imageNamed:]は使用できません。
[UIImage imageNamed:] キャッシュは、アプリケーション インターフェイス (ボタン、背景など) の画像を保存するために使用されます。このキャッシュが写真などの大きな画像にも使用されている場合、iOS システムはメモリを節約するためにこれらの画像を削除する可能性があります。これらの画像を再ロードする必要があるため、ページを切り替えるときにパフォーマンスが低下します。トランスミッターのイメージに別個のキャッシュ メカニズムを使用すると、トランスミッターのイメージがアプリケーションのイメージのライフサイクルから切り離されます。
[UIImage imageNamed:]缓存机制并不是公开的,所以你不能很好地控制它。例如,你没法做到检测图片是否在加载之前就做了缓存,不能够设置缓存大小,当图片没用的时候也不能把它从缓存中移除。
构建一个所谓的缓存系统非常困难。菲尔 卡尔顿曾经说过:“在计算机科学中只有两件难事:缓存和命名”。
如果要写自己的图片缓存的话,那该如何实现呢?让我们来看看要涉及哪些方面:
选择一个合适的缓存键 - 缓存键用来做图片的唯一标识。如果实时创建图片,通常不太好生成一个字符串来区分别的图片。在我们的图片传送带例子中就很简单,我们可以用图片的文件名或者表格索引。
提前缓存 - 如果生成和加载数据的代价很大,你可能想当第一次需要用到的时候再去加载和缓存。提前加载的逻辑是应用内在就有的,但是在我们的例子中,这也非常好实现,因为对于一个给定的位置和滚动方向,我们就可以精确地判断出哪一张图片将会出现。
缓存失效 - 如果图片文件发生了变化,怎样才能通知到缓存更新呢?这是个非常困难的问题(就像菲尔 卡尔顿提到的),但是幸运的是当从程序资源加载静态图片的时候并不需要考虑这些。对用户提供的图片来说(可能会被修改或者覆盖),一个比较好的方式就是当图片缓存的时候打上一个时间戳以便当文件更新的时候作比较。
缓存回收 - 当内存不够的时候,如何判断哪些缓存需要清空呢?这就需要到你写一个合适的算法了。幸运的是,对缓存回收的问题,苹果提供了一个叫做NSCache通用的解决方案
NSCache和NSDictionary类似。你可以通过-setObject:forKey:和-object:forKey:方法分别来插入,检索。和字典不同的是,NSCache在系统低内存的时候自动丢弃存储的对象。
NSCache用来判断何时丢弃对象的算法并没有在文档中给出,但是你可以使用-setCountLimit:方法设置缓存大小,以及-setObject:forKey:cost:来对每个存储的对象指定消耗的值来提供一些暗示。
指定消耗数值可以用来指定相对的重建成本。如果对大图指定一个大的消耗值,那么缓存就知道这些物体的存储更加昂贵,于是当有大的性能问题的时候才会丢弃这些物体。你也可以用-setTotalCostLimit:方法来指定全体缓存的尺寸。
NSCache是一个普遍的缓存解决方案,我们创建一个比传送器案例更好的自定义的缓存类。(例如,我们可以基于不同的缓存图片索引和当前中间索引来判断哪些图片需要首先被释放)。但是NSCache对我们当前的缓存需求来说已经足够了;没必要过早做优化。
使用图片缓存和提前加载的实现来扩展之前的传送器案例,然后来看看是否效果更好(见清单14.5)。
清单14.5 添加缓存
#import "ViewController.h"@interface ViewController() <UICollectionViewDataSource>@property (nonatomic, copy) NSArray *imagePaths;@property (nonatomic, weak) IBOutlet UICollectionView *collectionView;@end@implementation ViewController- (void)viewDidLoad{ //set up data self.imagePaths = [[NSBundle mainBundle] pathsForResourcesOfType:@"png" ?inDirectory:@"Vacation Photos"]; //register cell class [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"Cell"];}- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ return [self.imagePaths count];}- (UIImage *)loadImageAtIndex:(NSUInteger)index{ //set up cache static NSCache *cache = nil; if (!cache) { cache = [[NSCache alloc] init]; } //if already cached, return immediately UIImage *image = [cache objectForKey:@(index)]; if (image) { return [image isKindOfClass:[NSNull class]]? nil: image; } //set placeholder to avoid reloading image multiple times [cache setObject:[NSNull null] forKey:@(index)]; //switch to background thread dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ //load image NSString *imagePath = self.imagePaths[index]; UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; //redraw image using device context UIGraphicsBeginImageContextWithOptions(image.size, YES, 0); [image drawAtPoint:CGPointZero]; image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); //set image for correct image view dispatch_async(dispatch_get_main_queue(), ^{ //cache the image [cache setObject:image forKey:@(index)]; //display the image NSIndexPath *indexPath = [NSIndexPath indexPathForItem: index inSection:0]; UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath]; UIImageView *imageView = [cell.contentView.subviews lastObject]; imageView.image = image; }); }); //not loaded yet return nil;}- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ //dequeue cell UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath]; //add image view UIImageView *imageView = [cell.contentView.subviews lastObject]; if (!imageView) { imageView = [[UIImageView alloc] initWithFrame:cell.contentView.bounds]; imageView.contentMode = UIViewContentModeScaleAspectFit; [cell.contentView addSubview:imageView]; } //set or load image for this index imageView.image = [self loadImageAtIndex:indexPath.item]; //preload image for previous and next index if (indexPath.item < [self.imagePaths count] - 1) { [self loadImageAtIndex:indexPath.item + 1]; } if (indexPath.item > 0) { [self loadImageAtIndex:indexPath.item - 1]; } return cell;}@end
果然效果更好了!当滚动的时候虽然还有一些图片进入的延迟,但是已经非常罕见了。缓存意味着我们做了更少的加载。这里提前加载逻辑非常粗暴,其实可以把滑动速度和方向也考虑进来,但这已经比之前没做缓存的版本好很多了。
图片加载性能取决于加载大图的时间和解压小图时间的权衡。很多苹果的文档都说PNG是iOS所有图片加载的最好格式。但这是极度误导的过时信息了。
PNG图片使用的无损压缩算法可以比使用JPEG的图片做到更快地解压,但是由于闪存访问的原因,这些加载的时间并没有什么区别。
清单14.6展示了标准的应用程序加载不同尺寸图片所需要时间的一些代码。为了保证实验的准确性,我们会测量每张图片的加载和绘制时间来确保考虑到解压性能的因素。另外每隔一秒重复加载和绘制图片,这样就可以取到平均时间,使得结果更加准确。
清单14.6
#import "ViewController.h"static NSString *const ImageFolder = @"Coast Photos";@interface ViewController () <UITableViewDataSource>@property (nonatomic, copy) NSArray *items;@property (nonatomic, weak) IBOutlet UITableView *tableView;@end@implementation ViewController- (void)viewDidLoad{ [super viewDidLoad]; //set up image names self.items = @[@"2048x1536", @"1024x768", @"512x384", @"256x192", @"128x96", @"64x48", @"32x24"];}- (CFTimeInterval)loadImageForOneSec:(NSString *)path{ //create drawing context to use for decompression UIGraphicsBeginImageContext(CGSizeMake(1, 1)); //start timing NSInteger imagesLoaded = 0; CFTimeInterval endTime = 0; CFTimeInterval startTime = CFAbsoluteTimeGetCurrent(); while (endTime - startTime < 1) { //load image UIImage *image = [UIImage imageWithContentsOfFile:path]; //decompress image by drawing it [image drawAtPoint:CGPointZero]; //update totals imagesLoaded ++; endTime = CFAbsoluteTimeGetCurrent(); } //close context UIGraphicsEndImageContext(); //calculate time per image return (endTime - startTime) / imagesLoaded;}- (void)loadImageAtIndex:(NSUInteger)index{ //load on background thread so as not to //prevent the UI from updating between runs dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ //setup NSString *fileName = self.items[index]; NSString *pngPath = [[NSBundle mainBundle] pathForResource:filename ofType:@"png" inDirectory:ImageFolder]; NSString *jpgPath = [[NSBundle mainBundle] pathForResource:filename ofType:@"jpg" inDirectory:ImageFolder]; //load NSInteger pngTime = [self loadImageForOneSec:pngPath] * 1000; NSInteger jpgTime = [self loadImageForOneSec:jpgPath] * 1000; //updated UI on main thread dispatch_async(dispatch_get_main_queue(), ^{ //find table cell and update NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0]; UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; cell.detailTextLabel.text = [NSString stringWithFormat:@"PNG: %03ims JPG: %03ims", pngTime, jpgTime]; }); });}- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return [self.items count];}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ //dequeue cell UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Cell"]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleValue1 reuseIdentifier:@"Cell"]; } //set up cell NSString *imageName = self.items[indexPath.row]; cell.textLabel.text = imageName; cell.detailTextLabel.text = @"Loading..."; //load image [self loadImageAtIndex:indexPath.row]; return cell;}@end
PNG和JPEG压缩算法作用于两种不同的图片类型:JPEG对于噪点大的图片效果很好;但是PNG更适合于扁平颜色,锋利的线条或者一些渐变色的图片。为了让测评的基准更加公平,我们用一些不同的图片来做实验:一张照片和一张彩虹色的渐变。JPEG版本的图片都用默认的Photoshop60%“高质量”设置编码。结果见图片14.5。
图14.5 不同类型图片的相对加载性能
如结果所示,相对于不友好的PNG图片,相同像素的JPEG图片总是比PNG加载更快,除非一些非常小的图片、但对于友好的PNG图片,一些中大尺寸的图效果还是很好的。
所以对于之前的图片传送器程序来说,JPEG会是个不错的选择。如果用JPEG的话,一些多线程和缓存策略都没必要了。
但JPEG图片并不是所有情况都适用。如果图片需要一些透明效果,或者压缩之后细节损耗很多,那就该考虑用别的格式了。苹果在iOS系统中对PNG和JPEG都做了一些优化,所以普通情况下都应该用这种格式。也就是说在一些特殊的情况下才应该使用别的格式。
对于包含透明的图片来说,最好是使用压缩透明通道的PNG图片和压缩RGB部分的JPEG图片混合起来加载。这就对任何格式都适用了,而且无论从质量还是文件尺寸还是加载性能来说都和PNG和JPEG的图片相近。相关分别加载颜色和遮罩图片并在运行时合成的代码见14.7。
清单14.7 从PNG遮罩和JPEG创建的混合图片
#import "ViewController.h"@interface ViewController ()@property (nonatomic, weak) IBOutlet UIImageView *imageView;@end@implementation ViewController- (void)viewDidLoad{ [super viewDidLoad]; //load color image UIImage *image = [UIImage imageNamed:@"Snowman.jpg"]; //load mask image UIImage *mask = [UIImage imageNamed:@"SnowmanMask.png"]; //convert mask to correct format CGColorSpaceRef graySpace = CGColorSpaceCreateDeviceGray(); CGImageRef maskRef = CGImageCreateCopyWithColorSpace(mask.CGImage, graySpace); CGColorSpaceRelease(graySpace); //combine images CGImageRef resultRef = CGImageCreateWithMask(image.CGImage, maskRef); UIImage *result = [UIImage imageWithCGImage:resultRef]; CGImageRelease(resultRef); CGImageRelease(maskRef); //display result self.imageView.image = result;}@end
对每张图片都使用两个独立的文件确实有些累赘。JPNG的库(https://github.com/nicklockwood/JPNG)对这个技术提供了一个开源的可以复用的实现,并且添加了直接使用+imageNamed:和+imageWithContentsOfFile:方法的支持。
除了JPEG和PNG之外iOS还支持别的一些格式,例如TIFF和GIF,但是由于他们质量压缩得更厉害,性能比JPEG和PNG糟糕的多,所以大多数情况并不用考虑。
但是iOS之后,苹果低调添加了对JPEG 2000图片格式的支持,所以大多数人并不知道。它甚至并不被Xcode很好的支持 - JPEG 2000图片都没在Interface Builder中显示。
但是JPEG 2000图片在(设备和模拟器)运行时会有效,而且比JPEG质量更好,同样也对透明通道有很好的支持。但是JPEG 2000图片在加载和显示图片方面明显要比PNG和JPEG慢得多,所以对图片大小比运行效率更敏感的时候,使用它是一个不错的选择。
但仍然要对JPEG 2000保持关注,因为在后续iOS版本说不定就对它的性能做提升,但是在现阶段,混合图片对更小尺寸和质量的文件性能会更好。
当前市场的每个iOS设备都使用了Imagination Technologies PowerVR图像芯片作为GPU。PowerVR芯片支持一种叫做PVRTC(PowerVR Texture Compression)的标准图片压缩。
和iOS上可用的大多数图片格式不同,PVRTC不用提前解压就可以被直接绘制到屏幕上。这意味着在加载图片之后不需要有解压操作,所以内存中的图片比其他图片格式大大减少了(这取决于压缩设置,大概只有1/60那么大)。
但是PVRTC仍然有一些弊端:
尽管加载的时候消耗了更少的RAM,PVRTC文件比JPEG要大,有时候甚至比PNG还要大(这取决于具体内容),因为压缩算法是针对于性能,而不是文件尺寸。
PVRTC必须要是二维正方形,如果源图片不满足这些要求,那必须要在转换成PVRTC的时候强制拉伸或者填充空白空间。
质量并不是很好,尤其是透明图片。通常看起来更像严重压缩的JPEG文件。
PVRTC不能用Core Graphics绘制,也不能在普通的UIImageView显示,也不能直接用作图层的内容。你必须要用作OpenGL纹理加载PVRTC图片,然后映射到一对三角板来在CAEAGLLayer或者GLKView中显示。
创建一个OpenGL纹理来绘制PVRTC图片的开销相当昂贵。除非你想把所有图片绘制到一个相同的上下文,不然这完全不能发挥PVRTC的优势。
PVRTC使用了一个不对称的压缩算法。尽管它几乎立即解压,但是压缩过程相当漫长。在一个现代快速的桌面Mac电脑上,它甚至要消耗一分钟甚至更多来生成一个PVRTC大图。因此在iOS设备上最好不要实时生成。
如果你愿意使用OpehGL,而且即使提前生成图片也能忍受得了,那么PVRTC将会提供相对于别的可用格式来说非常高效的加载性能。比如,可以在主线程1/60秒之内加载并显示一张2048×2048的PVRTC图片(这已经足够大来填充一个视网膜屏幕的iPad了),这就避免了很多使用线程或者缓存等等复杂的技术难度。
Xcode包含了一些命令行工具例如texturetool来生成PVRTC图片,但是用起来很不方便(它存在于Xcode应用程序束中),而且很受限制。一个更好的方案就是使用Imagination Technologies PVRTexTool,可以从http://www.imgtec.com/powervr/insider/sdkdownloads免费获得。
安装了PVRTexTool之后,就可以使用如下命令在终端中把一个合适大小的PNG图片转换成PVRTC文件:
/Applications/Imagination/PowerVR/GraphicsSDK/PVRTexTool/CL/OSX_x86/PVRTexToolCL -i {input_file_name}.png -o {output_file_name}.pvr -legacypvr -p -f PVRTC1_4 -q pvrtcbest
清单14.8的代码展示了加载和显示PVRTC图片的步骤(第6章CAEAGLLayer例子代码改动而来)。
清单14.8 加载和显示PVRTC图片
#import "ViewController.h" #import <QuartzCore/QuartzCore.h> #import <GLKit/GLKit.h>@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *glView;@property (nonatomic, strong) EAGLContext *glContext;@property (nonatomic, strong) CAEAGLLayer *glLayer;@property (nonatomic, assign) GLuint framebuffer;@property (nonatomic, assign) GLuint colorRenderbuffer;@property (nonatomic, assign) GLint framebufferWidth;@property (nonatomic, assign) GLint framebufferHeight;@property (nonatomic, strong) GLKBaseEffect *effect;@property (nonatomic, strong) GLKTextureInfo *textureInfo;@end@implementation ViewController- (void)setUpBuffers{ //set up frame buffer glGenFramebuffers(1, &_framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); //set up color render buffer glGenRenderbuffers(1, &_colorRenderbuffer); glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderbuffer); [self.glContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.glLayer]; glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_framebufferWidth); glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_framebufferHeight); //check success if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { NSLog(@"Failed to make complete framebuffer object: %i", glCheckFramebufferStatus(GL_FRAMEBUFFER)); }}- (void)tearDownBuffers{ if (_framebuffer) { //delete framebuffer glDeleteFramebuffers(1, &_framebuffer); _framebuffer = 0; } if (_colorRenderbuffer) { //delete color render buffer glDeleteRenderbuffers(1, &_colorRenderbuffer); _colorRenderbuffer = 0; }}- (void)drawFrame{ //bind framebuffer & set viewport glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); glViewport(0, 0, _framebufferWidth, _framebufferHeight); //bind shader program [self.effect prepareToDraw]; //clear the screen glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.0, 0.0, 0.0, 0.0); //set up vertices GLfloat vertices[] = { -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f }; //set up colors GLfloat texCoords[] = { 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; //draw triangle glEnableVertexAttribArray(GLKVertexAttribPosition); glEnableVertexAttribArray(GLKVertexAttribTexCoord0); glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, vertices); glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, 0, texCoords); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); //present render buffer glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer); [self.glContext presentRenderbuffer:GL_RENDERBUFFER];}- (void)viewDidLoad{ [super viewDidLoad]; //set up context self.glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; [EAGLContext setCurrentContext:self.glContext]; //set up layer self.glLayer = [CAEAGLLayer layer]; self.glLayer.frame = self.glView.bounds; self.glLayer.opaque = NO; [self.glView.layer addSublayer:self.glLayer]; self.glLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking: @NO, kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8}; //load texture glActiveTexture(GL_TEXTURE0); NSString *imageFile = [[NSBundle mainBundle] pathForResource:@"Snowman" ofType:@"pvr"]; self.textureInfo = [GLKTextureLoader textureWithContentsOfFile:imageFile options:nil error:NULL]; //create texture GLKEffectPropertyTexture *texture = [[GLKEffectPropertyTexture alloc] init]; texture.enabled = YES; texture.envMode = GLKTextureEnvModeDecal; texture.name = self.textureInfo.name; //set up base effect self.effect = [[GLKBaseEffect alloc] init]; self.effect.texture2d0.name = texture.name; //set up buffers [self setUpBuffers]; //draw frame [self drawFrame];}- (void)viewDidUnload{ [self tearDownBuffers]; [super viewDidUnload];}- (void)dealloc{ [self tearDownBuffers]; [EAGLContext setCurrentContext:nil];}@end
如你所见,非常不容易,如果你对在常规应用中使用PVRTC图片很感兴趣的话(例如基于OpenGL的游戏),可以参考一下GLView的库(https://github.com/nicklockwood/GLView),它提供了一个简单的GLImageView类,重新实现了UIImageView的各种功能,但同时提供了PVRTC图片,而不需要你写任何OpenGL代码。