GLAirPlay/GLView.m
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
The OpenGL ES view which renders a rotating cube. Responsible for creating a CADisplayLink for the new target display when a connection/disconnection occurs. |
*/ |
#import <OpenGLES/ES2/gl.h> |
#import <OpenGLES/ES2/glext.h> |
#import <QuartzCore/QuartzCore.h> |
#import "GLView.h" |
#import "CubePlayback.h" |
#define BUFFER_OFFSET(i) ((char *)NULL + (i)) |
GLfloat gCubeVertexData[216] = |
{ |
// Data layout for each line below is: |
// positionX, positionY, positionZ, normalX, normalY, normalZ, |
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, |
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, |
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, |
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, |
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, |
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, |
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, |
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, |
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, |
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, |
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, |
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, |
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, |
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, |
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, |
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, |
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, |
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, |
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, |
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, |
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, |
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, |
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, |
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, |
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, |
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, |
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, |
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, |
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, |
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, |
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, |
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, |
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, |
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, |
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, |
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f |
}; |
static double GetTimeMS() |
{ |
return (CACurrentMediaTime()*1000.0); |
} |
@interface GLView () |
{ |
NSInteger _animationFrameInterval; |
CADisplayLink *_displayLink; |
UIScreen *_targetScreen; |
EAGLContext *_context; |
// The pixel dimensions of the CAEAGLLayer |
GLint _backingWidth; |
GLint _backingHeight; |
// The OpenGL names for the framebuffer and renderbuffer used to render to this view |
GLuint _defaultFramebuffer, _colorRenderbuffer; |
// The OpenGL frame for the depth buffer |
GLuint _depthRenderbuffer; |
GLuint _vertexArray; |
GLuint _vertexBuffer; |
float _rotation; |
float _radius; |
double _renderTime; |
BOOL _zeroDeltaTime; |
} |
@property (nonatomic, strong) GLKBaseEffect *effect; |
// OpenAL playback is wired up in the storyboard scene |
@property (nonatomic, strong) IBOutlet CubePlayback *playback; |
@end |
@implementation GLView |
+ (Class)layerClass |
{ |
return [CAEAGLLayer class]; |
} |
// The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder: |
- (id)initWithCoder:(NSCoder*)coder |
{ |
if ((self = [super initWithCoder:coder])) |
{ |
// Get the layer |
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer; |
eaglLayer.opaque = TRUE; |
eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys: |
[NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil]; |
_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; |
if (!_context || ![EAGLContext setCurrentContext:_context]) |
{ |
return nil; |
} |
[self setupGL]; |
_animating = FALSE; |
_animationFrameInterval = 1; |
_displayLink = nil; |
_zeroDeltaTime = TRUE; |
} |
return self; |
} |
- (void)setupGL |
{ |
// Create default framebuffer object. The backing will be allocated for the current layer in -resizeFromLayer |
glGenFramebuffers(1, &_defaultFramebuffer); |
glGenRenderbuffers(1, &_colorRenderbuffer); |
glBindFramebuffer(GL_FRAMEBUFFER, _defaultFramebuffer); |
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer); |
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderbuffer); |
// Create a depth buffer as we want to enalbe GL_DEPTH_TEST in this sample |
glGenRenderbuffers(1, &_depthRenderbuffer); |
// Create a GLKBaseEffect to render the object |
self.effect = [[GLKBaseEffect alloc] init]; |
self.effect.light0.enabled = GL_TRUE; |
self.effect.light0.diffuseColor = GLKVector4Make(1.0f, 0.4f, 0.4f, 1.0f); |
glEnable(GL_DEPTH_TEST); |
// Create a VAO that stores the cube vertex and normal data |
glGenVertexArraysOES(1, &_vertexArray); |
glBindVertexArrayOES(_vertexArray); |
glGenBuffers(1, &_vertexBuffer); |
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); |
glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData), gCubeVertexData, GL_STATIC_DRAW); |
glEnableVertexAttribArray(GLKVertexAttribPosition); |
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0)); |
glEnableVertexAttribArray(GLKVertexAttribNormal); |
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12)); |
glBindVertexArrayOES(0); |
} |
- (void)drawView:(id)sender |
{ |
double currentTime = GetTimeMS(); |
double deltaTime = _zeroDeltaTime ? 0.0 : currentTime - _renderTime; |
_renderTime = currentTime; |
if (_zeroDeltaTime) |
_zeroDeltaTime = FALSE; |
// Update animation states |
if (self.userControlDelegate) |
_radius = [self.userControlDelegate rotatingRadius]; |
_rotation += deltaTime * 0.05 * M_PI / 180.0; |
// Update OpenGL |
[EAGLContext setCurrentContext:_context]; |
glBindFramebuffer(GL_FRAMEBUFFER, _defaultFramebuffer); |
glViewport(0, 0, _backingWidth, _backingHeight); |
glClearColor(0.65f, 0.65f, 0.65f, 1.0f); |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
glBindVertexArrayOES(_vertexArray); |
// Compute the projection matrix |
float aspect = (GLfloat)_backingWidth / (GLfloat)_backingHeight; |
GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0f), aspect, 0.1f, 100.0f); |
self.effect.transform.projectionMatrix = projectionMatrix; |
// Compute the model view matrix |
GLKMatrix4 baseModelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, -4.0f); |
baseModelViewMatrix = GLKMatrix4Rotate(baseModelViewMatrix, _rotation, 0.0f, 1.0f, 0.0f); |
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, -fabs(_radius)); |
modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, _rotation, 1.0f, 1.0f, 1.0f); |
modelViewMatrix = GLKMatrix4Multiply(baseModelViewMatrix, modelViewMatrix); |
self.effect.transform.modelviewMatrix = modelViewMatrix; |
// Render the object with GLKit |
[self.effect prepareToDraw]; |
glDrawArrays(GL_TRIANGLES, 0, 36); |
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer); |
[_context presentRenderbuffer:GL_RENDERBUFFER]; |
// Update OpenAL playback |
// cube's current position |
GLKVector4 pos = GLKVector4Make(0.0f, 0.0f, 0.0f, 1.0f); |
pos = GLKMatrix4MultiplyVector4(modelViewMatrix, pos); |
// source |
float *s = pos.v; |
[self.playback setSourcePos:s]; |
// listener |
float l[] = { 1.0f, 0.0f, -4.0f }; |
[self.playback setListenerPos:l]; |
[self.playback setListenerRotation:(float)-M_PI_2]; //points inwards to the screen |
} |
- (BOOL)resizeFromLayer |
{ |
CAEAGLLayer *layer = (CAEAGLLayer*)self.layer; |
// Allocate color buffer backing based on the current layer size |
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer); |
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer]; |
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth); |
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight); |
// Allocate storage for the depth buffer, and attach it to the framebuffer’s depth attachment point |
glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderbuffer); |
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, _backingWidth, _backingHeight); |
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderbuffer); |
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) |
{ |
NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER)); |
return NO; |
} |
return YES; |
} |
- (void)layoutSubviews |
{ |
if ([self resizeFromLayer]) |
{ |
// An external display might just have been connected/disconnected. We do not want to |
// consider time spent in the connection/disconnection in the animation. |
_zeroDeltaTime = TRUE; |
[self drawView:nil]; |
} |
} |
#pragma Display Link |
- (NSInteger)animationFrameInterval |
{ |
return _animationFrameInterval; |
} |
- (void)setAnimationFrameInterval:(NSInteger)frameInterval |
{ |
// Frame interval defines how many display frames must pass between each time the |
// display link fires. The display link will only fire 30 times a second when the |
// frame internal is two on a display that refreshes 60 times a second. The default |
// frame interval setting of one will fire 60 times a second when the display refreshes |
// at 60 times a second. A frame interval setting of less than one results in undefined |
// behavior. |
if (frameInterval >= 1) |
{ |
_animationFrameInterval = frameInterval; |
if (_animating) |
{ |
[self stopAnimation]; |
[self startAnimation]; |
} |
} |
} |
- (UIScreen *)targetScreen |
{ |
return _targetScreen; |
} |
- (void)setTargetScreen:(UIScreen *)screen |
{ |
if (_targetScreen != screen) |
{ |
_targetScreen = screen; |
if (_animating) |
{ |
[self stopAnimation]; |
[self startAnimation]; |
} |
} |
} |
- (void)startAnimation |
{ |
if (!_animating) |
{ |
if (self.targetScreen) { |
// Create a CADisplayLink for the target display. |
// This will result in the native fps for whatever display you create it from. |
_displayLink = [self.targetScreen displayLinkWithTarget:self selector:@selector(drawView:)]; |
} |
else { |
// Fall back to use CADislayLink's class method. |
// A CADisplayLink created using the class method is always bound to the internal display. |
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView:)]; |
} |
[_displayLink setFrameInterval:self.animationFrameInterval]; |
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; |
// Start playing sound if not already |
if (!self.playback.isPlaying) |
[self.playback startSound]; |
// An external display might just have been connected/disconnected. We do not want to |
// consider time spent in the connection/disconnection in the animation. |
_zeroDeltaTime = TRUE; |
_animating = TRUE; |
} |
} |
- (void)stopAnimation |
{ |
if (_animating) |
{ |
[_displayLink invalidate]; |
_displayLink = nil; |
// Stop playing sound if currently playing |
if (self.playback.isPlaying) |
[self.playback stopSound]; |
_animating = FALSE; |
} |
} |
- (void)dealloc |
{ |
// tear down OpenGL |
if (_defaultFramebuffer) |
{ |
glDeleteFramebuffers(1, &_defaultFramebuffer); |
_defaultFramebuffer = 0; |
} |
if (_colorRenderbuffer) |
{ |
glDeleteRenderbuffers(1, &_colorRenderbuffer); |
_colorRenderbuffer = 0; |
} |
glDeleteBuffers(1, &_vertexBuffer); |
glDeleteVertexArraysOES(1, &_vertexArray); |
// tear down context |
if ([EAGLContext currentContext] == _context) |
[EAGLContext setCurrentContext:nil]; |
} |
@end |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-08-12