shithub: h264bsd

Download patch

ref: b175f65582213b620b9e9ceff31c870e92920848
parent: 31432bd518edadf97c038bd1b950ed3c04f2b665
author: Sam Leitch <sam@luceva.net>
date: Tue Apr 1 15:50:27 EDT 2014

Added GPU accelerated render class to ios.

--- a/ios/h264bsd.xcodeproj/project.pbxproj
+++ b/ios/h264bsd.xcodeproj/project.pbxproj
@@ -35,6 +35,8 @@
 		225DF43018E7C778002CE505 /* h264bsd_vlc.c in Sources */ = {isa = PBXBuildFile; fileRef = 225DF41418E7C778002CE505 /* h264bsd_vlc.c */; };
 		225DF43118E7C778002CE505 /* h264bsd_vui.c in Sources */ = {isa = PBXBuildFile; fileRef = 225DF41618E7C778002CE505 /* h264bsd_vui.c */; };
 		226ED51718E897CD00D4A03A /* H264bsdDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 226ED51618E897CD00D4A03A /* H264bsdDecoder.m */; };
+		226ED51F18E9237D00D4A03A /* H264bsdRendererGl.m in Sources */ = {isa = PBXBuildFile; fileRef = 226ED51E18E9237D00D4A03A /* H264bsdRendererGl.m */; };
+		226ED52318EB9D3C00D4A03A /* GLKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 226ED52218EB9D3C00D4A03A /* GLKit.framework */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXCopyFilesBuildPhase section */
@@ -109,6 +111,9 @@
 		225DF41718E7C778002CE505 /* h264bsd_vui.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = h264bsd_vui.h; sourceTree = "<group>"; };
 		226ED51518E897CD00D4A03A /* H264bsdDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = H264bsdDecoder.h; path = src/H264bsdDecoder.h; sourceTree = "<group>"; };
 		226ED51618E897CD00D4A03A /* H264bsdDecoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = H264bsdDecoder.m; path = src/H264bsdDecoder.m; sourceTree = "<group>"; };
+		226ED51D18E9237D00D4A03A /* H264bsdRendererGl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = H264bsdRendererGl.h; path = src/H264bsdRendererGl.h; sourceTree = "<group>"; };
+		226ED51E18E9237D00D4A03A /* H264bsdRendererGl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = H264bsdRendererGl.m; path = src/H264bsdRendererGl.m; sourceTree = "<group>"; };
+		226ED52218EB9D3C00D4A03A /* GLKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GLKit.framework; path = System/Library/Frameworks/GLKit.framework; sourceTree = SDKROOT; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -116,6 +121,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				226ED52318EB9D3C00D4A03A /* GLKit.framework in Frameworks */,
 				225DF3BA18E7C634002CE505 /* Foundation.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -144,6 +150,7 @@
 		225DF3B818E7C634002CE505 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				226ED52218EB9D3C00D4A03A /* GLKit.framework */,
 				225DF3B918E7C634002CE505 /* Foundation.framework */,
 			);
 			name = Frameworks;
@@ -217,6 +224,8 @@
 			children = (
 				226ED51518E897CD00D4A03A /* H264bsdDecoder.h */,
 				226ED51618E897CD00D4A03A /* H264bsdDecoder.m */,
+				226ED51D18E9237D00D4A03A /* H264bsdRendererGl.h */,
+				226ED51E18E9237D00D4A03A /* H264bsdRendererGl.m */,
 			);
 			name = src;
 			sourceTree = "<group>";
@@ -281,6 +290,7 @@
 				225DF41F18E7C778002CE505 /* h264bsd_inter_prediction.c in Sources */,
 				225DF41818E7C778002CE505 /* h264bsd_byte_stream.c in Sources */,
 				225DF42D18E7C778002CE505 /* h264bsd_stream.c in Sources */,
+				226ED51F18E9237D00D4A03A /* H264bsdRendererGl.m in Sources */,
 				225DF41A18E7C778002CE505 /* h264bsd_conceal.c in Sources */,
 				225DF42418E7C778002CE505 /* h264bsd_pic_order_cnt.c in Sources */,
 				225DF42C18E7C778002CE505 /* h264bsd_storage.c in Sources */,
@@ -337,8 +347,8 @@
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
 				IPHONEOS_DEPLOYMENT_TARGET = 7.1;
-				ONLY_ACTIVE_ARCH = YES;
 				SDKROOT = iphoneos;
+				VALID_ARCHS = "armv7 armv7s";
 			};
 			name = Debug;
 		};
@@ -370,6 +380,7 @@
 				IPHONEOS_DEPLOYMENT_TARGET = 7.1;
 				SDKROOT = iphoneos;
 				VALIDATE_PRODUCT = YES;
+				VALID_ARCHS = "armv7 armv7s";
 			};
 			name = Release;
 		};
--- a/ios/src/H264bsdDecoder.m
+++ b/ios/src/H264bsdDecoder.m
@@ -56,6 +56,7 @@
 {
     u32 picId, isIdrPic, numErrMbs;
     u8* data = h264bsdNextOutputPicture(self.pStorage, &picId, &isIdrPic, &numErrMbs);
+    if(!data) return nil;
     size_t length = self.outputWidth * self.outputHeight * 3 / 2;
     return [NSData dataWithBytes:data length:length];
 }
@@ -64,6 +65,7 @@
 {
     u32 picId, isIdrPic, numErrMbs;
     u32* data = h264bsdNextOutputPictureRGBA(self.pStorage, &picId, &isIdrPic, &numErrMbs);
+    if(!data) return nil;
     size_t length = self.outputWidth * self.outputHeight * 4;
     return [NSData dataWithBytes:data length:length];
 }
--- /dev/null
+++ b/ios/src/H264bsdRendererGl.h
@@ -1,0 +1,20 @@
+//
+//  YuvDataRenderer.h
+//  h264bsd
+//
+//  Created by Sam Leitch on 2014-03-30.
+//  Copyright (c) 2014 h264bsd. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <GLKit/GLKit.h>
+#import "H264bsdDecoder.h"
+
+@interface H264bsdRendererGl : NSObject
+@property (strong, readonly) EAGLContext *context;
+@property (strong, readonly) H264bsdDecoder *decoder;
+
+- (id)initWithContext:(EAGLContext *)context decoder:(H264bsdDecoder *)decoder;
+- (void)renderNextOutputPicture;
+
+@end
--- /dev/null
+++ b/ios/src/H264bsdRendererGl.m
@@ -1,0 +1,270 @@
+//
+//  YuvDataRenderer.m
+//  h264bsd
+//
+//  Created by Sam Leitch on 2014-03-30.
+//  Copyright (c) 2014 h264bsd. All rights reserved.
+//
+
+#import "H264bsdRendererGl.h"
+
+@interface H264bsdRendererGl ()
+{
+    GLuint _programRef;
+    
+    GLuint _positionRef;
+    GLuint _positionBufferRef;
+    
+    GLuint _texcoordRef;
+    GLuint _texcoordBufferRef;
+    
+    GLuint _samplerRef[3];
+    GLuint _textureRef[3];
+}
+
+@end
+
+#define STRINGIZE(x) #x
+#define STRINGIZE2(x) STRINGIZE(x)
+#define SHADER_STRING(text) @ STRINGIZE2(text)
+
+NSString *const vertexShaderString;
+NSString *const fragmentShaderString;
+static GLuint compileShader(GLenum type, NSString *shaderString);
+
+static const GLfloat positionVertices[] = {
+    -1.0f, -1.0f,
+    1.0f, -1.0f,
+    -1.0f,  1.0f,
+    1.0f,  1.0f,
+};
+
+static const GLfloat texcoordVertices[] = {
+    0.0f, 1.0f,
+    1.0f, 1.0f,
+    0.0f, 0.0f,
+    1.0f, 0.0f,
+};
+
+
+@implementation H264bsdRendererGl
+
+- (id)initWithContext:(EAGLContext *)context decoder:(H264bsdDecoder *)decoder
+{
+    self = [super init];
+    
+    if(self)
+    {
+        _context = context;
+        _decoder = decoder;
+        
+        if(![EAGLContext setCurrentContext:context]) {
+            NSLog(@"failed to setup context");
+            return nil;
+        }
+        
+        [self initProgram];
+        [self initBuffers];
+        [self initTextures];
+    }
+    
+    return self;
+}
+
+- (void)initProgram
+{
+    GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderString);
+	if (!vertexShader) return;
+    
+	GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderString);
+    if (!fragmentShader) return;
+    
+    _programRef = glCreateProgram();
+	glAttachShader(_programRef, vertexShader);
+	glAttachShader(_programRef, fragmentShader);
+	glLinkProgram(_programRef);
+    
+    GLint status;
+    glGetProgramiv(_programRef, GL_LINK_STATUS, &status);
+    if (status == GL_FALSE) {
+		NSLog(@"Failed to link program %d", _programRef);
+        return;
+    }
+    
+    _positionRef = glGetAttribLocation(_programRef, "position");
+    _texcoordRef = glGetAttribLocation(_programRef, "texcoord_a");
+    
+    _samplerRef[0] = glGetUniformLocation(_programRef, "ySampler");
+    _samplerRef[1] = glGetUniformLocation(_programRef, "uSampler");
+    _samplerRef[2] = glGetUniformLocation(_programRef, "vSampler");
+}
+
+- (void)initBuffers
+{
+    glGenBuffers(1, &_positionBufferRef);
+    glBindBuffer(GL_ARRAY_BUFFER, _positionBufferRef);
+    glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), positionVertices, GL_STATIC_DRAW);
+    glEnableVertexAttribArray(_positionRef);
+    glVertexAttribPointer(_positionRef, 2, GL_FLOAT, GL_FALSE, 0, 0);
+    
+    glGenBuffers(1, &_texcoordBufferRef);
+    glBindBuffer(GL_ARRAY_BUFFER, _texcoordBufferRef);
+    glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), texcoordVertices, GL_STATIC_DRAW);
+    glEnableVertexAttribArray(_texcoordRef);
+    glVertexAttribPointer(_texcoordRef, 2, GL_FLOAT, GL_FALSE, 0, 0);
+}
+
+- (void)initTextures
+{
+    glGenTextures(3, _textureRef);
+    
+    for(int i = 0; i < 3; ++i)
+    {
+        glActiveTexture(GL_TEXTURE0 + i);
+        glBindTexture(GL_TEXTURE_2D, _textureRef[i]);
+        
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    }
+}
+
+- (void)dealloc
+{
+    if(![EAGLContext setCurrentContext:_context]) {
+        NSLog(@"failed to setup context");
+        return;
+    }
+    glDeleteProgram(_programRef);
+    glDeleteTextures(3, _textureRef);
+    glDeleteBuffers(1, &_positionBufferRef);
+    glDeleteBuffers(1, &_texcoordBufferRef);
+}
+
+- (void)renderNextOutputPicture
+{
+    if(![EAGLContext setCurrentContext:self.context]) {
+        NSLog(@"failed to setup context");
+        return;
+    }
+    
+    NSData *yuvData = [self.decoder nextOutputPicture];
+    if(!yuvData) return;
+
+    NSInteger width = self.decoder.outputWidth;
+    NSInteger height = self.decoder.outputHeight;
+    NSInteger uvWidth = width/2;
+    NSInteger uvHeight = height/2;
+    
+    UInt8* yBuf = (UInt8*)yuvData.bytes;
+    UInt8* uBuf = yBuf + (width * height);
+    UInt8* vBuf = uBuf + (uvWidth * uvHeight);
+    
+    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+    glClear(GL_COLOR_BUFFER_BIT);
+	glUseProgram(_programRef);
+    
+    GLsizei textureWidth[] = { width, uvWidth, uvWidth };
+    GLsizei textureHeight[] = { height, uvHeight, uvHeight };
+    GLvoid* textureBuf[] = { yBuf, uBuf, vBuf };
+    
+    for(int i = 0; i < 3; ++i)
+    {
+        glUniform1i(_samplerRef[i], i);
+        
+        glActiveTexture(GL_TEXTURE0 + i);
+        glBindTexture(GL_TEXTURE_2D, _textureRef[i]);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, textureWidth[i], textureHeight[i], 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, textureBuf[i]);
+    }
+    
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+    
+    bool glError = false;
+    GLenum glStatus = glGetError();
+    while (glStatus != GL_NO_ERROR) {
+        NSLog(@"GL error: %x", glStatus);
+        glError = true;
+        glStatus = glGetError();
+    }
+}
+
+@end
+
+NSString *const vertexShaderString = SHADER_STRING
+(
+ attribute vec4 position;
+ attribute vec4 texcoord_a;
+ varying vec2 texcoord;
+ 
+ void main()
+ {
+     gl_Position = position;
+     texcoord = texcoord_a.xy;
+ }
+ );
+
+NSString *const fragmentShaderString = SHADER_STRING
+(
+ varying highp vec2 texcoord;
+ 
+ uniform sampler2D ySampler;
+ uniform sampler2D uSampler;
+ uniform sampler2D vSampler;
+ 
+ const mediump mat4 yuv2rgb = mat4(1.1643828125, 0, 1.59602734375, -.87078515625,
+                                   1.1643828125, -.39176171875, -.81296875, .52959375,
+                                   1.1643828125, 2.017234375, 0, -1.081390625,
+                                   0, 0, 0, 1);
+ 
+ void main()
+ {
+     mediump vec4 yuv = vec4(0.0, 0.0, 0.0, 1.0);
+     
+     yuv.r = texture2D(ySampler, texcoord).r;
+     yuv.g = texture2D(uSampler, texcoord).r;
+     yuv.b = texture2D(vSampler, texcoord).r;
+     
+     mediump vec4 rgb = yuv * yuv2rgb;
+     
+     gl_FragColor = rgb;
+ }
+ );
+
+static GLuint compileShader(GLenum type, NSString *shaderString)
+{
+	GLint status;
+	const GLchar *sources = (GLchar *)shaderString.UTF8String;
+    
+    GLuint shader = glCreateShader(type);
+    if (shader == 0 || shader == GL_INVALID_ENUM) {
+        NSLog(@"Failed to create shader %d", type);
+        return 0;
+    }
+    
+    glShaderSource(shader, 1, &sources, NULL);
+    glCompileShader(shader);
+    
+#ifdef DEBUG
+	GLint logLength;
+    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
+    if (logLength > 0)
+    {
+        GLchar *log = (GLchar *)malloc(logLength);
+        glGetShaderInfoLog(shader, logLength, &logLength, log);
+        NSLog(@"Shader compile log:\n%s", log);
+        free(log);
+    }
+#endif
+    
+    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
+    if (status == GL_FALSE) {
+        glDeleteShader(shader);
+		NSLog(@"Failed to compile shader:\n");
+        return 0;
+    }
+    
+	return shader;
+}
+
+
--- a/ios/test/h264bsd Test.xcodeproj/project.pbxproj
+++ b/ios/test/h264bsd Test.xcodeproj/project.pbxproj
@@ -20,6 +20,7 @@
 		225DF48618E7C9CC002CE505 /* test_1920x1080.h264 in Resources */ = {isa = PBXBuildFile; fileRef = 225DF48318E7C9CC002CE505 /* test_1920x1080.h264 */; };
 		225DF48718E7C9CC002CE505 /* test_640x360.h264 in Resources */ = {isa = PBXBuildFile; fileRef = 225DF48418E7C9CC002CE505 /* test_640x360.h264 */; };
 		226ED51918E8981300D4A03A /* libh264bsd.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 225DF47F18E7C8B8002CE505 /* libh264bsd.a */; };
+		226ED52518EB9D5100D4A03A /* GLKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 226ED52418EB9D5100D4A03A /* GLKit.framework */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -48,10 +49,10 @@
 		225DF45A18E7C884002CE505 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
 		225DF45B18E7C884002CE505 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
 		225DF45D18E7C884002CE505 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
-		225DF46418E7C884002CE505 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
 		225DF47A18E7C8B8002CE505 /* h264bsd.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = h264bsd.xcodeproj; path = ../h264bsd.xcodeproj; sourceTree = "<group>"; };
 		225DF48318E7C9CC002CE505 /* test_1920x1080.h264 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_1920x1080.h264; sourceTree = "<group>"; };
 		225DF48418E7C9CC002CE505 /* test_640x360.h264 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_640x360.h264; sourceTree = "<group>"; };
+		226ED52418EB9D5100D4A03A /* GLKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GLKit.framework; path = System/Library/Frameworks/GLKit.framework; sourceTree = SDKROOT; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -59,6 +60,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				226ED52518EB9D5100D4A03A /* GLKit.framework in Frameworks */,
 				226ED51918E8981300D4A03A /* libh264bsd.a in Frameworks */,
 				225DF44518E7C883002CE505 /* CoreGraphics.framework in Frameworks */,
 				225DF44718E7C883002CE505 /* UIKit.framework in Frameworks */,
@@ -91,10 +93,10 @@
 		225DF44118E7C883002CE505 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				226ED52418EB9D5100D4A03A /* GLKit.framework */,
 				225DF44218E7C883002CE505 /* Foundation.framework */,
 				225DF44418E7C883002CE505 /* CoreGraphics.framework */,
 				225DF44618E7C883002CE505 /* UIKit.framework */,
-				225DF46418E7C884002CE505 /* XCTest.framework */,
 			);
 			name = Frameworks;
 			sourceTree = "<group>";
@@ -296,9 +298,9 @@
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
 				IPHONEOS_DEPLOYMENT_TARGET = 7.1;
-				ONLY_ACTIVE_ARCH = YES;
 				SDKROOT = iphoneos;
 				TARGETED_DEVICE_FAMILY = "1,2";
+				VALID_ARCHS = "armv7 armv7s";
 			};
 			name = Debug;
 		};
@@ -332,6 +334,7 @@
 				SDKROOT = iphoneos;
 				TARGETED_DEVICE_FAMILY = "1,2";
 				VALIDATE_PRODUCT = YES;
+				VALID_ARCHS = "armv7 armv7s";
 			};
 			name = Release;
 		};
--- a/ios/test/h264bsd Test/AppDelegate.m
+++ b/ios/test/h264bsd Test/AppDelegate.m
@@ -30,9 +30,6 @@
 {
     // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
     // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
-    
-    ViewController *mainViewController = (ViewController *)self.window.rootViewController;
-    [mainViewController stopPlayback];
 }
 
 - (void)applicationDidEnterBackground:(UIApplication *)application
@@ -49,9 +46,6 @@
 - (void)applicationDidBecomeActive:(UIApplication *)application
 {
     // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
-    
-    ViewController *mainViewController = (ViewController *)self.window.rootViewController;
-    [mainViewController startPlayback];
 }
 
 - (void)applicationWillTerminate:(UIApplication *)application
--- a/ios/test/h264bsd Test/Base.lproj/Main_iPad.storyboard
+++ b/ios/test/h264bsd Test/Base.lproj/Main_iPad.storyboard
@@ -12,11 +12,10 @@
                         <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
                         <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
                     </layoutGuides>
-                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+                    <glkView key="view" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" enableSetNeedsDisplay="NO" id="59T-la-xYc">
                         <rect key="frame" x="0.0" y="0.0" width="768" height="1024"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
-                    </view>
+                    </glkView>
                 </viewController>
                 <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
             </objects>
--- a/ios/test/h264bsd Test/Base.lproj/Main_iPhone.storyboard
+++ b/ios/test/h264bsd Test/Base.lproj/Main_iPhone.storyboard
@@ -12,11 +12,10 @@
                         <viewControllerLayoutGuide type="top" id="jyV-Pf-zRb"/>
                         <viewControllerLayoutGuide type="bottom" id="2fi-mo-0CV"/>
                     </layoutGuides>
-                    <view key="view" contentMode="scaleToFill" id="kh9-bI-dsS">
+                    <glkView key="view" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" enableSetNeedsDisplay="NO" id="t17-Jj-Bg2">
                         <rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
-                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
-                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
-                    </view>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                    </glkView>
                 </viewController>
                 <placeholder placeholderIdentifier="IBFirstResponder" id="x5A-6p-PRh" sceneMemberID="firstResponder"/>
             </objects>
--- a/ios/test/h264bsd Test/ViewController.h
+++ b/ios/test/h264bsd Test/ViewController.h
@@ -16,8 +16,9 @@
  */
 
 #import <UIKit/UIKit.h>
+#import <GLKit/GLKit.h>
 
-@interface ViewController : UIViewController <UIApplicationDelegate>
+@interface ViewController : UIViewController <UIApplicationDelegate, GLKViewDelegate>
 
 - (void)startPlayback;
 - (void)stopPlayback;
--- a/ios/test/h264bsd Test/ViewController.m
+++ b/ios/test/h264bsd Test/ViewController.m
@@ -16,7 +16,8 @@
  */
 
 #import "ViewController.h"
-#include "H264bsdDecoder.h"
+#import "H264bsdDecoder.h"
+#import "H264bsdRendererGl.h"
 
 @interface ViewController ()
 
@@ -24,6 +25,7 @@
 @property (strong, nonatomic) H264bsdDecoder *decoder;
 @property (assign, nonatomic) NSUInteger offset;
 @property (assign, atomic) BOOL playbackStarted;
+@property (strong, nonatomic) H264bsdRendererGl *renderer;
 
 @end
 
@@ -35,9 +37,19 @@
     [self stopPlayback];
 	[self loadVideoData];
     [self initializeDecoder];
+    [self initializeView];
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
     [self startPlayback];
 }
 
+- (void)viewWillDisappear:(BOOL)animated
+{
+    [self stopPlayback];
+}
+
 - (void)didReceiveMemoryWarning
 {
     [super didReceiveMemoryWarning];
@@ -57,6 +69,27 @@
     self.decoder = decoder;
 }
 
+- (void)initializeView
+{
+    EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+    
+    if (!context) {
+        NSLog(@"failed to create context");
+        return;
+    }
+    
+    self.renderer = [[H264bsdRendererGl alloc] initWithContext:context decoder:self.decoder];
+    
+    GLKView *view = (GLKView *)self.view;
+    view.delegate = self;
+    view.enableSetNeedsDisplay = YES;
+    view.context = context;
+    
+    view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
+    view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
+    view.drawableStencilFormat = GLKViewDrawableStencilFormat8;
+}
+
 - (void)startPlayback
 {
     if(self.playbackStarted) return;
@@ -73,8 +106,7 @@
 {
     if(!self.playbackStarted) return;
     
-    NSData* nextImage = [self decodeNextImage];
-    [self displayImage:nextImage];
+    [self decodeNextImage];
 
     dispatch_async(dispatch_get_main_queue(), ^{
         [self playbackLoop];
@@ -81,7 +113,7 @@
     });
 }
 
-- (NSData*)decodeNextImage
+- (void)decodeNextImage
 {
     H264bsdStatus status = H264bsdStatusReady;
     
@@ -96,25 +128,12 @@
         if(self.offset >= self.videoData.length) self.offset = 0;
     }
     
-    NSData *nextOutputPicture = [self.decoder nextOutputPictureRGBA];
-    return nextOutputPicture;
+    [self.view setNeedsDisplay];
 }
 
-- (void)displayImage:(NSData *)nextImage
+- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
 {
-    size_t width = self.decoder.outputWidth;
-    size_t height = self.decoder.outputHeight;
-    size_t bytesPerRow = width * 4;
-    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
-    CGDataProviderRef imageDataProvider = CGDataProviderCreateWithCFData((CFDataRef)nextImage);
-    
-    CGImageRef image = CGImageCreate(width, height, 8, 32, bytesPerRow, colorSpace, kCGBitmapByteOrderDefault|kCGImageAlphaLast, imageDataProvider, NULL, NO, kCGRenderingIntentDefault);
-    
-    self.view.layer.contents = (__bridge id)image;
-    
-    CGImageRelease(image);
-    CGDataProviderRelease(imageDataProvider);
-    CGColorSpaceRelease(colorSpace);
+    [self.renderer renderNextOutputPicture];
 }
 
 @end