2.5、对于GLKit的推断
GLKit类封装并简化了CocoaTouch应用和OpenGLES之间的常见交互。就像推测已存的GLKit类可能是怎么实现的是一种指导一样,遵循苹果的用来构建GLKit的范例来创建新类也是可能的。
本章的OpenGLES_Ch2_1例子使用了GLKit,并且为了实现两个目的添加了对于OpenGL ES函数的直接调用,这两个目的是:清除帧缓存,以及使用一个顶点数组缓存来绘图。就像GLKit的GLKView封装了帧缓存和层管理,例子OpenGLES_Ch2_3重构OpenGLES_Ch2_1的可重用OpenGL ES代码为两个新类: AGLKContext和AGLKVertexAttribArrayBuffer。本书使用AGLK作为类和函数的前缀来代表是对于GLKit类的追加。GLKit 将来的版本可能包含与AGLK类相似的类。苹果通常不会预告对于其框架的增强,并且即使苹果最终实现了与本书中的AGLK类具有相似功能的类,也无法保证苹果会按相同的方式实现它。
AGLKContext类是一个在例子OpenGLES_Ch2_1中使用的内建的EAGLContext类的简单子类。对于本例来说,AGLKContext 仅仅添加了一个clearColor属性和一个用来告诉OpenGLES去设置在上下文的帧缓存中的每个像素颜色为clearColor的元素值的“-clear:” 方法。
//
// GLKContext.h
/ OpenGLES :Ch2_ 3
//
#import <GLKit/GLKit.h>
@interface AGLRContext: EAGLContext
GLKVector4 clearColor;
@property (nonatomic, assign) GLKVector4
clearColor ;
- (void)clear: (GLbitfield)mask;
@end
AGLKContext的奕現実現了“ -clear:"方法和用于clearColor属性的彷向器。通过凋用一个OpenGL ES的glClearColor0函数来没畳clearColor属性。“-clear:"方法通过凋用glClear(0方法来実現。
//
// GLKContext.m
// OpenGLES_ Ch2_ 3
//
#import "AGLKContext.h"
@implementation AGLKContext
//////////////////////////////////1/1/1///1
// This method sets the clear (background) RGBA color.
// The clear color is undefined until this method is called.
- (void)setclearColor:
{
(GLKVector4 )clearColorRGBA clearColor = clearColorRGBA;
NSAssert(self == Tlself class] currentContext],
@"Receiving context required to be current context" );
glClearColor(
clearColorRGBA.r,
clearColorRGBA.g,
clearColorRGBA.b,
clearColorRGBA.a);
}
//////////////////////////////////1/11111
// Returns the clear (background) color set via -setClearColor:.
// If no clear color has been set via -setClearcolor:, the
// return clear color is undefined.
- (GLKVector4 )clearColor {
return clearColor;
}
////////////////////////////////////////////1
// This method instructs OpenGL ES to set all data in the
// current Context's Render Buffex(s) identified by mask to
// colors (values) specified via -setClearColor: and/or
// OpenGL Es functions for each Render Buffer type.
- (void)clear:(GLbitfield)mask {
NSAssert(self == [lself class] currentContext],
@"Receiving context required to be current context" );
glClear(mask);
}
@end
GLKVertexAttribArrayBuffer类封装了使用OpenGL ES 2.0的顶点属性数组缓存(或者简称“ 顶点缓存”)的所有7个步骤。这个函数减少了应用需要调用的OpenGLES函数的数量。在后续章节的例子中重用AGLKVertexAttribArrayBuffer会在尽可能减少与7个缓存管理步骤相关的错误的同时减少编写的代码量。
//
// GLKVertexAttribArrayBuffer.h
// openGLES_ Ch2_ _3
//
#import <GLKit/GLKit.h>
@class AGLKElementIndexArrayBuffer;
@interface AGLKVertexAttribArrayBuffer : NSObject
{
GLsizeiptr stride;
GLsizeiptr bufferSizeBytes;
GLuint glName;
}
@property (nonatomic, readonly) GLuint
glName;
@property (nonatomic, readon1y) GLsizeiptr
bufferSizeBytes;
@property (nonatomic, readon1y) GLsizeiptr
stride;
- (id)initwithAttribStride:(GLsizeiptr)stride numberofVertices: (GLsizei )count
data: (const GLvoid *)dataPtr
usage: (GLenum)usage;
- (void)prepareToDrawwithAttrib: (GLuint )index
numberofcoordinates : (GLint )count
attriboffset: (GLsizeiptr )offset
shouldEnable:(BOOL) shouldEnable;
- (void)drawArrayWithMode: (GLenum)mode
startvertexIndex : (GLint)first
numberOfVertices : (GLsizei )count;
@end
下面的実現在三个方法中封装了7个緩存管理歩驟。AGLKVertextAttribArrayBuffer类包含一些错误检查代码,但除此之外还包含一个対于例子OpenGLES_Ch2_1中的緩 存管理代碣的筒単重用和重杓。除了在炎接口中声明的3个方法之外,丕実現了一个“-dealloc'’方法来刪除-一个相美咲的OpenGL ES緩存示沢符。
//
// GLKVertexAttribArrayBuffer .m
// OpenGLES_ Ch2_ 3
//
#import "AGLKVertexAttribArrayBuffer .h"
@interface AGLKVertexAttribArrayBuffer ()
@property (nonatomic, assign) GLsizeiptr
buffersizeBytes;
@property (nonatomic, assign) GLsizeiptr
stride;
@end
@implementation AGLKVertexAttribArrayBuffer
@synthesize glName; ;
@synthesize bufferSizeBytes;
@synthesize stride;
///////////////////////////////////////////1
// This method creates a vertex attribute array buffer in
// the current OpenGL ES context for the thread upon which this
// method is called.
- (id)initwithAttribstride: (GLsizeiptr )aStride
number0fvertices:(GLsizei )count
data:(const GIvoid * )dataPtr
usage:(GLenum)usage;
NSParameterAssert(0 < aStride);
NSParameterAssert(0 < count);
NSParameterAssert(NULL != dataPtr);
if(nil != (self = [super initJ))
stride = aStride;
bufferSizeBytes = stride * count;
glGenBuffers(1, // STEP 1
&glName);
glBindBuffer(GL_ ARRAY_ BUFFER, // STEP 2
self.glName);
glBufferData( // STEP 3
GL_ARRAY_BUFFER, // Initialize buffer contents
bufferSizeBytes, // Number of bytes to copy
dataPtr,
// address of bytes to copy
usage);
// Hint: cache in GPU memory
NSAssert(O != glName, @"Failed to generate glName");
return self;
}
//
// A vertex attribute array buffer must be prepared when your
// application wants to use the buffer to render any geometry.
// When your application prepares an buffer, some OpenGL ES state
// is altered to allow bind the buffer and configure pointers.
- (void)prepareToDrawWithAttrib:(GLuint ) index
numberOfCoordinates:(GLint )count
attribOffset:(GLsizeiptr )offset
shouldEnable:(BOOL) shouldEnable
(
NSParameterAssert((O < count) && (count < 4));
NSParameterAssert(offset < self .stride);
NSAssert(O != glName, ê"Invalid glName");
glBindBuffer (GL_ ARRAY_ BUFFER,
self .glName);
if (shouldEnable)
glEnableVertexAttribarray(
index);
}
glVertexAttribPointer(
index,
count,
GL_ FLOAT,
GL_ PALSE,
self.stride,
NULL + offset);
)
//
// Submits the drawing command identified by mode and instructs
// 0penGL ES to use count vertices from the buffer starting from
// the vertex at index first. Vertex indices start at o.
- (void)drawArrayWithMode:(GLenum)mode
startVertexIndex:(GLint)first
numberOfVertices:(GLsizei )count {
NSAssert( self.bufferSizeBytes > = ((first + count) * self.stride), @"Attempt to draw more vertex data than available.");
glDrawArrays (mode, first, count);
)
//
// This method deletes the receiver's buffer from the current
// Context when the receiver is deallocated.
-(void)dealloc {
// Delete buffer from current context
if (0 != glName) {
glDeleteBuffers (1,
&glName);
glName = 0;
}
}
@end
仔细检查例子OpenGLES_Ch2_3中的OpenGLES_Ch2_3ViewController 类的实现,并把它与例子OpenGLES_Ch2_1中OpenGLES_Ch2_1ViewController 类的实现做下对比。在OpenGLES_Ch2_3ViewController中没有对于OpenGLES函数的直接调用。另外,你可能会注意到一些小的代码风格的不--致。例如OpenGLES_Ch2_1使用一个GLKVector4结构体来设置恒定的颜色,但是用来设置清除颜色的对于OpenGL ES函数的调用可以直接接收RGBA颜色元素值:
self.baseEffect.constantColor = GLKVector4Make(
1.0f, 1/ Red
1.0f, // Green
1.0f, // Blue
1.0f);// Alpha
// Set the background color stored in the current context
g1ClearColor(0.0f, 0.0f, 0.0f, 1.0f); // background color
AGLKVertexAttribArrayBuffer
和AGLKContext
类扩展了GLKit引人的代码样式,因此OpenGLES_Ch2_3例子会使用一个一贯的样式:
self.baseEffect.constantColor = GLKVector4Make (
1.0f,// Red
1.0f, // Green
1.0f, // Blue
1.0f);// Alpha
// set the background color stored in the current context
( (AGLKContext *}view. context).clearColor = GLKVector 4Make(
0.0f, // Red
0.0f, // Green
0.0f, // Blue
1.0f);// Alpha
细小的样式一致性上的改进会让应用代码更整洁,但更重要的是,在Objective-C类中封装OpenGL ES代码会让代码重用更容易。减少缓存被创建并使用时出现程序错误的机会。同时,可让使用多个缓存的应用需要编写、测试和调试的代码更少。
2.6 小结
所有的图形iOS应用都包含Core Animation层。所有的绘图都发生在层之上。Core Animation合成器会混合当前应用与操作系统的层,从而在OpenGL ES的后帧缓存中产生最终的像素颜色。之后CoreAnimation合成器切换前后缓存以便把合成的内容显示到屏幕上。
原生iOS应用使用的是苹果的Cocoa Touch框架,并且由Xcode IDE组建而成。由Xcode生成的main.m文件包含建立CocoaTouch应用结构和从storyboard文件中加载应用的用户界面的代码。一个标准的CocoaTouch对象集合会提供标准iOs应用的所有功能。特定于某一个应用的功能是通过修改在标准应用结构中的应用委托或者根视图控制器对象来实现的。复杂的应用会向Xcode工程添加很多的额外文件。
使用OpenGL ES的Cocoa Touch应用要么使用Core Animation层配置一个与一个OpenGLES的帧缓存分享内存的自定义UIView,要么使用内建的GLKView类。苹果的GLKit框架提供了GLKView以处理细节,因此开发者很少会重新创建UIView的子类。一个OpenGLES的上下文会存储当前OpenGLES的状态,并控制GPU硬件。每个GLKView实例都需要一个上下文。
本章中的OpenGLES_Ch2_1例子为接下来的例子奠定了基础。所有本例专有的代码都是在OpenGLES_Ch2_1ViewController类中实现的。应用的storyboard文件指定了GLKView实例,这个实例使用根视图控制器作为委托。委托会接收来自其他对象的消息并在响应中执行特定应用的操作或者控制其他对象。
例子中OpenGLES_Ch2_1ViewController 类的实现包含3个方法:一个会在视图从storyboard文件中加载时被自动调用,一个会在每次视图需要被重绘时被自动调用,还 有一个会在视图卸载时被自动调用。这三个方法分别通过创建必要的上下文和顶点缓存来初始化OpenGL ES,使用上下文和顶点缓存在GLKView的与Core Animation层分享内存的帧缓存中绘图,并删除上下文和顶点缓存。
GLKBaseEffect类隐藏了iOS 所支持的OpenGL ES版本之间的很多不同。当使用OpenGL ES 2.0时GLKBaseEffect会生成直接在GPU上运行的Shading Language程序。GLKBaseEffect使程序员专注于应用的功能和图形概念,而无须学习Shading Language。
本章中的OpenGLES_Ch2_3例子把OpenGLES_Ch2_1例子中的OpenGLES函数调用重构为AGLKVertexAttribArrayBuffer类和AGLKContext类。 第3章会扩展OpenGLES__Ch2_1示例以使三角形渲染更加有趣和强大。