3.3、深入探讨GLKTextureLoader是怎么工作的
OpenGLES的纹理缓存与第1章讨论过的其他的缓存具有相同的步骤。首先使用glGenTextures()函数生成-一个纹理缓存标识符;然后使用glBindTexture()函数将其绑定到当前上下文;接下来,通过使用glTexImage2D(函数复制图像数据来初始化纹理缓存的内容。例子OpenGLES_Ch3_2显示了所有的步骤。
大部分的OpenGL ES实现要么需要,要么受益于使用尺寸为2的幂的纹理。图3-1中的图像为256>256像素。这个尺寸符合OpenGLES的要求,因为256是2的幂。2的幂包括2^0 = 1、2^1 = 2、2^2 = 4、2^3 = 8、2^4 = 16、2^5 = 32、2^6 = 64、2^7 = 128、2^8 = 256和 2^9 = 512。一一个4X 64的纹理是有效的,一个128X 128的纹理可以工作良好,一个1X64的纹理也可以。--个200X200的纹理要么不工作,要么根据使用的OpenGLES版本在渲染时导致效率低下。限制纹理的尺寸通常不会引起任何问题。例子OpenGLES_Ch3_2显示了怎么生成纹理缓存,按需重新调整图像的尺寸为2的幂,使用图像初始化纹理缓存。
示例OpenGLES_Ch3_2 中的AGLKTextureLoader是苹果的GLKit的GLKTexture-Loader类的部分实现。AGLKTextureLoader 类不会用在产品代码中,它仅仅是为了消除关于GLKTextureLoader、Core Graphcis和OpenGL ES之间交互的神秘感。
使用AGLKTextureLoader代替GLKitGLKTextureLoader是例子OpenGLES_Ch3_2中的OpenGLES_Ch3_2ViewController 与例子OpenGLES_Ch3_1中的对应的视图控制器之间的唯一不同。在例子OpenGLES_Ch3_2中的AGLKTextureLoader类的接口和实现提供了GLKit提供的功能的一个子集。具体来说,GLKit的GLKTextureLoader类支持异步纹理加载,MIP贴图生成,以及比简单的2D平面更加吸引人的纹理缓存类型。AGLKTextureLoader只复制了在例子OpenGLES_Ch3_1 中用到的GLKTexture-Loader的功能。
例子OpenGLES_Ch3_2中的AGLKTextureLoader.h文件声明了两个类: AGLKTexture-Info和AGLKTextureLoader。
// AGLKTextureLoader. h
// OpenGLES_ Ch3_ 2
#import <GLKit/GLKit.h>
#pragma mark -AGLKTextureInfo
@interface AGLKTextureInfo : NSObject
{
@private
GLuint name;
GLenum target ;
GLuint width;
GLuint height ;
}
@property (readonly) GLuint name;
@property (readonly) GLenum target;
@property (readonly) GLuint width;
@property (readonly) GLuint height;
@end
#pragma mark AGLKTextureLoader
@interface AGLKTextureLoader : NSObject
+ (AGLKTextureInfo *)textureWithCGImage:(CGImageRef )cgImage
options:(NSDictionary * )options
error:(NSError ** )outError ;
@end
AGLKTextureInfo是一个封装了紋理緩存的有用信息的简单类,例如相立的OpenGL ES紋理緩存的示沢符以及紋理的圏像尺寸。AGLKTextureLoader 只声明了一 个方法---“ +textureWithCGImage:options:error:"。 AGLKTextureLoader的实现展現了Core Graphics和OpenGL ES的整合,提供了与GLKit的GLKTextureLoader相似的功能。在“+textureWithCGImage:options:error:" 方法中对于OpenGLEs函数的凋用完成了标准的緩存管理歩驟,包括生成、绑定和初始化一个新的紋理緩存,参見下面粗体显示的代码:
////////////////////////////////////////////////
// This method generates a new OpenGL ES texture buffer and
// initializes the buffer contents using pixel data from the
// specified Core Graphics image, cgImage. This method returns an
// immutable AGLKTextureInfo instance initialized with
// information about the newly generated texture buffer .
//The generated texture buffer has power of 2 dimensions. The
// provided image data is scaled (re-sampled) by Core Graphics as
// necessary to fit within the generated texture buffer.
+ (AGLKTextureInfo * )texturewithCGImage: (CGImageRef )cgImage
options:(NSDictionary * )options
error:(NSError * )outError ;
{
// Get the bytes to be used when copying data into new texture
// buffer
size_ t width;
size_ t height;
NSData *imageData = AGLKDataWithResizedCGImageBytes (
cgImage,
swidth,
sheight);
/// Generation, bind, and copy data into a new texture buffer
GLuint textureBufferID;
glGenTextures(1, ttextureBufferID); // step 1
glBindTexture(GL_ TEXTURB_ 2D,textureBufferID); // step 2
glzexImage2D( // step 3
GL_TBXTURE_2D,
GL_ RGBA,
width,
height,
GL_RGBA,
GL_UNSIGNED_BXTB,
[imageData bytes]);
// set parameters that control texture sampling for the bound
// texture
glTexParameteri(GL_TBXTURE_2D,
GL_TEXTURE_MIN_PILTER,
GL_LINEAR);
// Allocate and initialize the AGLKTextureInfo instance to be
// returned
AGLKTextureInfo *result = [ [AGLKTextureInfo alloc]
initWithName:textureBufferID
target:GL_TEXTURE_2D
width:width
height:height];
return result;
}
glGenTextures()和glBindTexture()函数与用于顶点缓存的命名方式相似的函数的工作方式相同。但是,glTexlmage2D(GLenum target, Glint level, Glint internalFormat,GLsizei width, GLsizei height, Glint border, GLenum format, GLenum type, const GLvoid *data)函数是OpenGL ES标准中最复杂的函数之一。它复制图片像素的颜色数据到绑定的纹理缓存中。glTextImage2D(函数的第一个参数是用于2D纹理的GL_TEXTURE_2D。第二个参数用于指定MIP贴图的初始细节级别。如果没有使用MIP贴图,第二个参数必须是0。如果开启了MIP贴图,使用第二个参数来明确地初始化每个细节级别,但是要小心,因为从全分辨率到只有一纹素的每个级别都必须被指定,否则GPU将不会接受这个纹理缓存。
glTexImage2D()的第三个参数是intermalFormat,用于指定在纹理缓存内每个纹素需要保存的信息的数量。对于iOS设备来说,纹素信息要么是GL_RGB, 要么是GL_RGBA。GL_RGB为每个纹素保存红、绿、蓝三种颜色元素。GL__RGBA保存一个额外的用于指定每个纹素透明度的透明度元素。
glTexlmage2D()函数的第四个和第五个参数用于指定图像的宽度和高度。高度和宽度需要是2的幂。border 参数一直是用来 确定围绕纹理的纹素的一个边界的大小,但是在OpenGL ES中它总是被设置为0。第七个参数format用于指定初始化缓存所使用的图像数据中的每个像素所要保存的信息,这个参数应该总是与intermalFormat参数相同。其他的OpenGL版本可能在format和internalFormat参数不一致时自动执行图像数据格式的转换。
倒数第二个参数用于指定缓存中的纹素数据所使用的位编码类型,可以是下面的符号值之一: GLUNSIGNED_BYTE、GL_UNSIGNED_SHORT_5_6_5、GL UNSIGNED_SHORT_4_4_4_4和GL_UNSIGNED_SHORT_5_5_5_1。使用GL_UNSiGNED_BYTE会提供最佳色彩质量,但是它每个纹素中每个颜色元素的保存需要一字节的存储空 间。结果是每次取样--个RGB类型的纹素,GPU都必须最少读取3字节(24位),每个RGBA类型的纹素需要读取4字节(32 位)。其他的纹素格式使用多种编码方式来把每个纹素的所有颜色元素的信息保存在2字节(16位)中。GL UNSIGNED SHORT 5 6 5格式把5位用于红色,6位用于绿色,5位用于蓝色,但是没有透明度部分。GL_UNSIGNED_SHORT_4_4_4_4 格式平均为每个纹素的颜色元素使用4位。GL_UNSIGNED_SHORT_5_5_5_1格式为红、绿、蓝各使用5位,但透明度只使用1位。使用GL_UNSIGNED_SHORT_5_5_5_1格式会让每个纹素要么完全透明,要么完全不透明。
不管为每个颜色元素保存的位数量是多少,颜色元素的强度最终都会被GPU缩放到0.0到1.0 的范围内。一个强度为满值的颜色元素(所有那个颜色元素的位都是1) 对应于一个1.0的强度。透明度颜色元素强度为1.0 表示完全不透明,透明度强度为0.5表示50%透明度,透明度强度为0.0则表示完全透明。
glTexImage2D()的最后一个参数是一个要被复制到绑定的纹理缓存中的图片的像素颜色数据的指针。
“+textureWithCGImage:options:error:”方法使用AGLKDataWithResizedCGlmage-Bytes()函数来获取用于初始化纹理缓存的内容的字节。AGLKDataWithResizedCGImageBytes()是在AGLKTextureLoader.m中实现的,并且包含转换一个Core Graphics图像为OpenGL ES可用的合适字节的代码。
// This function returns an NSData object that contains bytes
// loaded from the specified Core Graphics image, cgImage. This
// function also returns (by reference) the power of 2 width and
// height to be used when initializing an OpenGL ES texture buffer
// with the bytes in the returned NSData instance. The widthPtr
// and. heightPtr arguments must be valid pointers.
static NSData *AGLKDataWithRes izedCGImageBytes(
CGImageRef cgImage,
size_t *widthPtr ,
size_t *heightPtr)
{
NSCParameterAssert (NULL != cgImage);
NSCParameterAssert(NULL != widthPtr);
NSCParameterAssert (NULL != heightPtr);
size_t originalWidth = CGImageGetWidth(cgImage);
size_ t originalHeight = CGImageGetWidth(cgImage);
NSCAssert(0 < originalWidth, @"Invalid image width");
NSCAssert(0 < originalHeight, @"Invalid image width");
// Calculate the width and height of the new texture buffer
// The new texture buffer will have power of 2 dimensions.
size_ t width = AGLKCalculatePowerOf2ForDimension(
originalwidth);
size_ t height = AGLKCalculatePowerOf2ForDimension(
originalheight);
// Allocate sufficient storage for RGBA pixel color data with
// the power of 2 sizes specified
NSMutableData
* imageData = [NSMutableData dataWithLength:
height * width * 4]; // 4 bytes per RGBA pixel
NSCAssert(nil != imageData,
@*Unable to allocate image storage" );
// Create a Core Graphics context that draws into the
// allocated bytes
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef cgContext = CGBitmapContextCreate(
[ imageData mutableBytes], width, height, 8,
4 * width, colorSpace,
kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease( colorSpace);
// Flip the Core Graphics Y-axis for future drawing
CGContextTranslateCTM (cgContext, 0, height);
CGContextScaleCTM (cgContext, 1.0, -1.0);
// Draw the loaded image into the Core Graphics context
// resizing as necessary
CGContextDrawImage(cgContext, GRectMake(0, 0, width, height),
cgImage);
CGContextRelease( cgContext);
*widthPtr = width;
*heightPtr = height;
return imageData;
}
CoreGraphics函数会把指定的cglmage拖人imgaeData提供的字节中。Core Graphics把cgImage拖人一个适当大小的Core Graphics上下文中,这个过程的一个副作用是把图像的尺寸调整为了2的幂。图像在被绘制的时候还被翻转了。翻转Y轴是必须的,因为Core Graphics 是以原点在左上角同时Y轴向下增大的形式来实现iOS中的图片保存的。OpenGLES的纹理坐标系会放置原点在左下角,同时Y值向上增大。翻转Y轴确保了图像字节拥有适用于纹理缓存的正确的方向。
注意
Mac OS X版Core Graphics保存图像的方式跟OpenGL ES一样都是以原点在左下角,同时Y值向上增大的方式来保存的。在建立iOS时,苹果修改了Core Graphics的实现,导致了MacOSX上不存在的与OpenGL ES的轻微不兼容。
在cgImage被拖人imageData提供的字节之后,函数会返回imgaeData和数据对应的高度和宽度。
AGLKTextureLoader.m文件内剩下的代码是不言自明的。只有一个小细节的实现有点生疏,就是为用于初始化AGLKTextureInfo类的一个方法的实现和声明所使用的个 Objective-C类别(category)。 下面代码块中粗体标注的就是类别语句。
// Instances of AGLKTextureInfo are immutable once initialized
@interface AGLKTextureInfo (AGLKTextureLoader)
- (id)initWithName:(GLuint ) aName
target:(GLenum) aTarget
width:(size_ t)awidth
height:(size_ t)aHeight;
@end
Objective-C类别会向现存的类添加方法,并使跨多个文件实现一个类成为可能。在这个例子中,类别只是为了使“-initWithName:target:width:height:”方法的声明与类 的主接口相分离。在AGLKTextureLoader.m文件而不是.h文件中声明类别表示这个方法应该只用在AGLKTextureLoader.m文件中。这个类别的名字可以随意,但是命名它为AGLKTextureLoader进一步强调了添加的这个方法是要在AGLKTextureLoader类中使用的。