2.4、深入探讨GLKView是怎么工作的
在介绍iOs5的GLKit之前,对于每个开发者来说创建--个类似于GLKView的UIView的子类是必要的。苹果没有提供GLKit类的源代码,但是根据OpenGLES可以推断出类似GLKView这样的类的主要功能可能的实现方式。本节剩下的部分和OpenGLES_Ch2_2这个例子会介绍AGLKView类以及它对于GLKView的部分重新实现。AGLKView类不应该用在产品代码中,它仅仅是为了消除对于GLKView、CoreAnimation和OpenGL ES之间的交互的神秘感。不管从哪个方面来说,苹果优化的、测试的、面向未来的对于GLKit的实现都是最好的。如果你对于深人了解GLKView没有兴趣,可以跳到下一节。
注意 如果你不想在你的应用中使用苹果的GLKit类,你就需要使用AGLKView示例或者自己再创建一个相似的类。本书剩下的例子都假设使用了GLKit。因此AGLKView不会出现在本书其他的任何例子中。
在OpenGLES_Ch2_2例子中的AGLKView类継承CocoaTouch的UIView类,下面是这个类的接ロ声明,与苹果的GLKView类接ロ相似。
//
// AGLKView.h
// OpenGLES_Ch2_1
//
#import <UIKit/UIKit.h>
#import <0penGLES/ES2/gl.h>
#import <0penGLES/BS2/glext.h>
@class EAGLContext;
@protocol AGLKViewDelegate;
//
// This subclass of the Cocoa Touch UIView class uses OpenGL ES
// to render pixel data into a Frame Buffer that shares pixel
// color storage with a Core Animation Layer.
@interface AGLKView : UIView {
BAGLContext *context;
GLuint defaultFrameBuffer;
GLuint colorRenderBuffer;
GLint drawableWidth;
GLint drawableHeight;
}
@property (nonatomic, weak) IBOutlet id <AGLKViewDelegate> delegate;
@property (nonatomic, retain) BAGLContext *context;
@property (nonatomic, readonly) NSInteger
drawableWidth;
@property (nonatomic, readonly) NSInteger drawableHeight;
- (void)display;
@end
#pragma mark - AGLKViewDelegate
@protocol AGLKViewDelegate <NSObject>
@required
- (void)glkView:(AGLKView * )view drawInRect:(CGRect)rect;
-
@end
AGLKViewDelegate协议指定了一个任何AGLKView的委托都必须实现的方法。如果AGLKView实例的委托属性不等于nil,每个AGLKView实例都会向它的委托发送“-glkView:drawInRect:"消息。AGLKView的实现比较简单易懂,但重写了来自UIView的多个方法并添加了一些用于支持OpenGL ES绘图的方法。
//
// AGLKView.m
// 0penGLBS_Ch2_1
//
#import "AGLKView.h"
#import <QuartzCore/QuartzCore.h>
@implementation AGLKView
@synthesize delegate;
@synthesize context;
// This method returns the CALayer subclass to be used by
//1 CoreAnimation with this view
+ (Class )layerClass
(
return [CAEAGLLayer class];
)
每一个UIView实例都有一个相关联的被CocoaTouch按需自动创建的CoreAnimation层。Cocoa Touch会调用“ +layerClass”方法来确定要创建什么类型的层。在这个例子中,AGLKView类重写了继承自UIView的实现。当Cocoa Touch调用AGLKView实现的“+layerClass” 方法时,它被告知要使用-一个CAEAGLLayer类的实例,而不是原先的CALayer。CAEAGLLayer 是Core Animation提供的标准层类之一。CAEAGLLayer会与一-个OpenGL ES的帧缓存共享它的像素颜色仓库。
接下来的代码块实现了“-initWithFrame:context:"方法并重写了继承来的“-initWithCoder:”方法。“-initWithFrame:context:" 方法初始化了通过代码手动分配的实例。“-initWithCoder:" 方法是Cocoa Touch用于初始化对象的标准方法之-一。 Cocoa Touch会自动调用“ -initWithCoder:"”方法,这是反归档先前归档人一个文件的对象的过程的一部分。归档和反归档在其他流行的面向对象的框架中(比如Java和微软的.NET)叫做串行化和反串行化。当OpenGLES_Ch2_2应用启动时,在这个例子中使用的 AGLKView实例会自动地从应用的storyboard文件中加载(又叫做反归档)。
之后的两个方法的代码几乎是相同的。每个实例初始化的时候只有一个方法会被调用。两个方法首先都会给超类UIView 一个执行其初始化的机会,然后再执行这个例子需要的CoreAnimation和OpenGLES.上下文的一次性初始化。第一步是初始化视图的Core Animation层的本地指针,具体代码如下:
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
需要C珸盲的炎型装換(CAEAGLLayer *),这是因カUIView的“-layer" 方法返回的是一个CALayer実例的指針。在AGLKView炎的実現中,真正使用的是CAEAGLLayer炎型,因此彊制編承器接受CAEAGLLayer炎型的込个装換是安全的。
///////////////////////////////////////////1
// This method is designated initializer for the class
- (id)initWithFrame;(CGRect)frame context:(EAGLContext * )aContext {
if ((self = [super initWithFrame:frame]) {
CABAGLLayer *eaglLayer = CAEAGLLayer *)self.layer;
eaglLayer .drawableProperties =
[NSDictionary dictionaryWith0bjectsAndKeys:
[NSNumber numberWithBool :NO],
kBAGLDrawablePropertyRetainedBacking,
kBAGLColorFormatRGBA8,
kBAGLDrawablePropertyColorFormat,
nil];
self.context = aContext;
return self;
}
///////////////////////////////////////1
// This method is called automatically to initialize each Cocoa
// Touch object as the object is unarchived from an
// Interface Builder .xib or . storyboard file.
-(id)initwithcoder:(NsCoder* )coder {
if ((self = [super initWithcoder :coder])) {
CABAGLLayer *eagllayer = (CAEAGLLayer *)self.layer;
eagllayer.drawableProperties =
[NSDictionary dictionaryWithobjectsAndKeys:
[NSNumber numberWithBoo1 ;NO],
kBAGLDrawablepropertyRetainedBacking,
kBAGLColorFormatRGBA8,
kBAGLDrawablePropertyColorPormat,
nil];
}
return self;
这个示例设置kEAGLDrawablePropertyRetainedBacking键的值为NO并设置kEAGL-DrawablePropertyColorFormat键的值为kEAGLColorFormatRGBA8。不使用“保留背景”的意思是告诉CoreAnimation在层的任何部分需要在屏幕上显示的时候都要绘制整个层的内容。换句话说,这段代码是告诉CoreAnimation不要试图保留任何以前绘制的图像留作以后重用。RGBA8颜色格式是告诉CoreAnimation用8位来保存层内的每个像素的每个颜色元素的值。
两个手动实现的访问器方法用于设置和返回视图的特定于平台的OpenGLES上下文。因为AGLKView实例需要创建和配置一个帧缓存和一个像素颜色渲染缓存来与视图的CoreAnimation层- -起使用,所以设置上下文会引起一些副作用。由于,上下文保存缓存,因此修改视图的上下文会导致先前创建的所有缓存全部失效,并需要创建和配置 新的缓存。
会受缓存操作影响的上下文是在调用OpenGLES函数之前设定为当前上下文的。请注意在下面的代码中,创建帧缓存和渲染缓存会遵循一些适用于其他类型的缓存的相同的步骤,包括在OpenGLES_Ch2_1例子中的顶点数组缓存。一个新的步骤会调用glFramebufferRenderbuffer()函数来配置当前绑定的帧缓存以便在colorRenderBuffer中 保存渲染的像素颜色。
//
// This method sets the receiver's OpenGL ES Context. If the
// receiver already has a different Context, this method deletes
// OpenGL ES Frame Buffer resources in the old Context and the
// recreates them in the new Context.
- (void)setContext:(EAGLContext * )aContext {
if(context != aContext) {
//Delete any buffers previously created in old Context
[EAGLContext setCurrentContext:context];
if (0 != defaultFrameBuffer) {
glDeleteFramebuffers(1, &defaultFrameBuffer); // Step 7
defaultFrameBuffer .0;
}
if (0 != colorRenderBuffer) {
glDeleteRenderbuffers(1,&colorRenderBuffer); // Step 7
colorRenderBuffer = O;
}
context = aContext;
if(nil != context) {
//Configure the new Context with required buffers
context = aContext;
[BAGLContext setCurrentContext:context];
glGenFramebuffers(1,sdefaultPrameBuffer);// Step 1
glBindFramebuffer( //Step 2
GL_FRAMEBUFFER,
defaultPrameBuffer);
glGenRenderbuffers(1, &colorRenderBuffer);// Step 1
glBindRenderbuffer(// Step 2
GL_ RENDERBUFPER,
colorRenderBuffer);
//Attach color render buffer to bound Frame Buffer
glPramebufferRenderbuffer(
GL_ FRAMEBUPPER,
GI_ COLOR_ _ATTACEMBNTO,
GL_ RENDERBUFPER,
colorRenderBuffer);
// This method returns the receiver's OpenGL ES Context
- (EAGLContext *)context
(
return context;
}
下面的“-display” 方法设置视图的上下文为当前上下文,告诉OpenGL ES让渲染填满整个帧缓存,调用视图的“-drawRect:"方法来实现用OpenGL ES函数进行真正的绘图,然后让上下文调整外观并使用CoreAnimation合成器把帧缓存的像素颜色渲染缓存与其他相关层混合起来。
// Calling this method tells the receiver to redraw the contents
// of its associated openGL ES Frame Buffer. This method
// configures OpenGL ES and then calls -drawRect:
- (void)display {
[EAGLContext setCurrentContext:self .context];
glViewport(0, 0, self.drawablewidth, self.drawableHeight);
[self drawRect:[self bounds]];
{self.context presentRenderbuffer:GL_RENDERBUFFER];
gIViewport()函数可以用来控制渲染至帧缓存的子集,但是在这个例子中使用的是整个帧缓存。如果视图的委托属性不是nil,“-drawRect:"”方法会调用委托的“-glkView: drawInRect:”方法。没有委托,AGLKView什么都不会绘制。AGLKView 的子类可以通过重写继承的“-drawRect:"实现来绘图,即使是没有指定委托。“-glkView:drawInRect:”的参数是- - 个要被绘制的视图和-一个覆盖整个视图范围的矩形。
// This method is called automatically whenever the receiver
// needs to redraw the contents of its associated openGL es
// Frame Buffer. This method should not be called directly. Call
// -display instead which configures openGl ES before calling
// -drawRect:
- (void)drawRect:(CGRect)rect {
if(self.delegate) {
[self.delegate glkView:self drawInRect:[self bounds]];
}
}
任何在接收到视图重新调整大小的消息时,CocoaTouch都会调用下面的-layout-Subviews方法。视图附属的帧缓存和像素颜色渲染缓存取决于视图的尺寸。视图会自动地凋整相美晨的尺寸。上下文的“-renderbufferStorage:fromDrawable:" 方法会凋整 視圏的緩存的尺寸以匹配居的新尺寸。
//////////////////////////////////////111111
// This method is called automatically whenever a UIview is
// resized including just after the view is added to a UIWindow.
- (void)layoutSubviews {
CABAGLLayer *eagllayer = (CAEAGLLayer *)self.layer;
// Initialize the current Frame Buffer's pixel color buffer
// so that it shares the corresponding Core Animation Layer's
// pixel color storage .
[context renderbufferStorage:GL_RENDERBUFER
fromDrawable:eagllayer];
// Make the Color Render Buffer the current buffer for display
glBindRenderbuffer (GL_RENDERBUFFER, colorRenderBuffer);
// Check for any errors configuring the render buffer
GLenum status = glCheckFramebufferStatus(
GL_ FRAMEBUFFER) ;
if(status != GL_FRAMEBUFFER_ OMPLETE)
NSLog(@"failed to make complete frame buffer object %x" ,status);
‘-drawableWidth " 和“-drawableHeight" 方法是它們各自的属性的坊向器。他们被实现用来通过OpenGL ES的glGetRenderbufferParameteriv0方法狹取和返回当前上下文的幀緩存的像素顔色諠染緩存的尺寸。
///////////////////////////////////////////
// This method returns the width in pixels of current context's
// Pixel Color Render Buffer
-(NSInteger )drawableWidth {
GLint backingWidth;
glGetRenderbufferParameteriv(
GL_RENDBRBUFFER,
GL_RENDERBUFFER_WIDTH,
&backingWidth);
return (NSInteger )backingWidth;
}
// This method returns the height in pixels of current context's
// Pixel color Render Buffer
- (NSInteger )drawableHeight {
GLint backingHeight;
g1GetRenderbufferParameteriv(
GL_RENDERBUFFER,
GL_RENDERBUFFER_HEIGHT,
&backingHeight);
return (NSInteger )backingHeight;
最后,在一个对象可以被回收时CocoaTouch会自动地调用“-dealloc”方法,然后它的资源就会返回给操作系统。AGLKView实现“-dealloc”方法是为了确保视图的上下文不再是当前上下文,其次是为了设置上下文属性为nil。如果在属性变成nil之后,视图的上下文不再被使用了,那这个上下文也会被自动回收。
// This method is called automatically when the reference count
// for a Cocoa Touch object reaches zero.
- (void)dealloc
{
// Make sure the receiver's openGL eS Context is not current
if ([EAGLContext currentContext] == context)
{
[EAGLContext setCurrentContext:nil];
// Deletes the receiver's 0penGL ES Context
context = nil;
@end
关于AGLKView类就介绍这些内容。例子OpenGLES_Ch2_2还包含一个与GLKit的GLKViewController类相似的AGLKViewController类。除了在显示GLKit类的运行 机制时使用了AGLKView和AGLKViewController类而不是GLKit类之外,OpenGLES_Ch2_2和OpenGLES_Ch2_1 是相同的。
与GLKit的GLKViewController类相似,AGLKViewContoller使用-个Core Animation CADisplayLink对象来调度和执行与控制器相关联的视图的周期性的重绘。CADisplayLink本质上是一个用于显示更新的同步计时器,它能够被设置用来在每个显示更新或者其他 更新时发送-一个消息。CADisplayLink 计时器的周期是以显示更新来计量的。
显示更新率通常是由嵌人设备的硬件决定的,它代表--个帧缓存的内容每秒最多能够被在屏幕上通过的像素显示出来的次数。因此来自CADisplayLink的消息为重新渲染一个场景提供了理想的触发器。渲染速度如果快于显示刷新就是一种浪费,因为用户永远看不到两次显示刷新之间的额外的帧缓存的更新。