Nathan de Vries

Wielder of bits, bytes & all things nice

Enabling & Using WebGL on iOS

Background

I’ve become a lot more interested in WebGL lately, and remembered reading on Hacker News a while back that WebGL would be available in iOS 5 as part of the iAd framework.

WebGL will not be publicly available in iOS 5. It will only be available to iAd developers.

When iOS 5.0 went GM, I decided to knock up a quick WebGL demo to see what was involved & how well it performed.

Unfortunately it didn’t run at all — creating a webgl-experimental context via the canvas element’s getContext() API would fail.

Breakthrough

I shot through a tweet to Antoine Quint (iAd JS/iOS software engineer at Apple), asking for confirmation of Chris Marrin’s email:

Chris Marrin suggested that WebGL would be available in iAds as of iOS 5.0. Is that actually the case?

To which he replied:

WebGL has been supported for iAds since iOS 4.2.

That’s even better news than I had anticipated! However, it still doesn’t explain why I’m unable to create a WebGL context.

With a little bit more prompting, I got to the bottom of why HTMLCanvasElement was returning null when I called getContext("experimental-webgl"):

You also need to have “uses-webgl” in your ad’s plist. Sorry, forgot about that.

The uses-webgl setting is completely undocumented, but it certainly works!

Demo iAd WebGL Project

I’ve put together a demo WebGL iAd project that can simply be dragged into the Simulator or synced to your device via iTunes.

Clone WebGL.ad on Github

Drag the WebGL.ad directory into the iOS Simulator & the built-in iAd Tester app will launch. You’ll see a banner at the bottom, which when tapped will bring up the WebGL demo:

If you’re having difficulties getting the iAd running, take a look at the Testing on the iOS Simulator and Testing on the Device sections of the iAd JS Programming Guide.

Do standard UIWebViews (secretly) support WebGL?

So if the iAd framework is using a UIWebView, perhaps we can get the same behaviour in our own web views?

Looking at the output of ps while running iAd Tester in the iOS Simulator, you’ll notice an app called AdSheet running. Using otool, we can see what private frameworks it’s linked against:

otool -L /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.0.sdk//Applications/AdSheet.app/AdSheet | grep PrivateFrameworks
  /System/Library/PrivateFrameworks/iAdCore.framework/iAdCore (compatibility version 1.0.0, current version 1.0.0)
  /System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices (compatibility version 1.0.0, current version 1.0.0)

iAdCore.framework looks interesting. Let’s take a look for WebGL-related functionality using class-dump:

class-dump /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.0.sdk/System/Library/PrivateFrameworks/iAdCore.framework/iAdCore

Searching through the output, it’s pretty clear that we’ve found what we’re after:

Relevant subset of class-dump output, revealing the private ADWebView class
1
2
3
4
5
@interface ADWebView : UIWebView {
    BOOL _webGLEnabled;
}
@property(nonatomic) BOOL webGLEnabled; // @synthesize webGLEnabled=_webGLEnabled;
@end

Turns out we can simply link against the private iAdCore.framework, and use ADWebViews instead of UIWebViews. Here’s an implementation which does just that:

Create an ADWebView, enable WebGL & load HTLM containing WebGL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@implementation WGLAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  Class ADWebView = NSClassFromString(@"ADWebView");
  UIWebView* webView = (UIWebView *)[[[ADWebView alloc] initWithFrame:self.window.bounds] autorelease];
  [webView setWebGLEnabled:YES];

  NSString* htmlPath = [[NSBundle mainBundle] pathForResource:@"WebGL" ofType:@"html"];
  NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:htmlPath]];
  [webView loadRequest:request];

  UIViewController* viewController = [[[UIViewController alloc] init] autorelease];
  viewController.view = webView;

  self.window.rootViewController = viewController;
  [self.window makeKeyAndVisible];

  return YES;
}
@end

Is ADWebView really doing that much?

Perhaps we can get rid of ADWebView altogether & just implement whatever -[ADWebView setWebGLEnabled:] is doing in our own custom UIWebView subclass. We’re still going to be calling private APIs, but at least we won’t need to link against private frameworks.

To reproduce -[ADWebView setWebGLEnabled:], we’ll need to disassemble it using otool -tVv, or install objdump via Homebrew & use gobjdump -d. Here’s the bit we’re interested in:

Disassembly of -[ADWebView setWebGLEnabled:]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
0005aba3 <-[ADWebView setWebGLEnabled:]>:
   5aba3:       55                      push   %ebp
   5aba4:       89 e5                   mov    %esp,%ebp
   5aba6:       56                      push   %esi
   5aba7:       83 ec 24                sub    $0x24,%esp
   5abaa:       e8 00 00 00 00          call   5abaf <-[ADWebView setWebGLEnabled:]+0xc>
   5abaf:       59                      pop    %ecx
   5abb0:       8b b1 89 e4 04 00       mov    0x4e489(%ecx),%esi
   5abb6:       8a 45 10                mov    0x10(%ebp),%al
   5abb9:       8b 55 08                mov    0x8(%ebp),%edx
   5abbc:       88 04 32                mov    %al,(%edx,%esi,1)
   5abbf:       8b b1 d5 65 03 00       mov    0x365d5(%ecx),%esi
   5abc5:       89 75 e0                mov    %esi,-0x20(%ebp)
   5abc8:       c7 45 e4 00 00 00 42    movl   $0x42000000,-0x1c(%ebp)
   5abcf:       c7 45 e8 00 00 00 00    movl   $0x0,-0x18(%ebp)
   5abd6:       8d b1 50 00 00 00       lea    0x50(%ecx),%esi
   5abdc:       89 75 ec                mov    %esi,-0x14(%ebp)
   5abdf:       8d 89 c1 d9 04 00       lea    0x4d9c1(%ecx),%ecx
   5abe5:       89 4d f0                mov    %ecx,-0x10(%ebp)
   5abe8:       89 55 f4                mov    %edx,-0xc(%ebp)
   5abeb:       88 45 f8                mov    %al,-0x8(%ebp)
   5abee:       8d 45 e0                lea    -0x20(%ebp),%eax
   5abf1:       89 04 24                mov    %eax,(%esp)
   5abf4:       e8 d7 45 00 00          call   5f1d0 <_WebThreadRun$stub>
   5abf9:       83 c4 24                add    $0x24,%esp
   5abfc:       5e                      pop    %esi
   5abfd:       5d                      pop    %ebp
   5abfe:       c3                      ret    

0005abff <___29-[ADWebView setWebGLEnabled:]_block_invoke_0>:
   5abff:       55                      push   %ebp
   5ac00:       89 e5                   mov    %esp,%ebp
   5ac02:       57                      push   %edi
   5ac03:       56                      push   %esi
   5ac04:       83 ec 10                sub    $0x10,%esp
   5ac07:       e8 00 00 00 00          call   5ac0c <___29-[ADWebView setWebGLEnabled:]_block_invoke_0+0xd>
   5ac0c:       5e                      pop    %esi
   5ac0d:       8b 7d 08                mov    0x8(%ebp),%edi
   5ac10:       8b 47 14                mov    0x14(%edi),%eax
   5ac13:       8b 8e 54 66 04 00       mov    0x46654(%esi),%ecx
   5ac19:       89 4c 24 04             mov    %ecx,0x4(%esp)
   5ac1d:       89 04 24                mov    %eax,(%esp)
   5ac20:       e8 a7 46 00 00          call   5f2cc <_objc_msgSend$stub>
   5ac25:       8b 8e 50 66 04 00       mov    0x46650(%esi),%ecx
   5ac2b:       89 4c 24 04             mov    %ecx,0x4(%esp)
   5ac2f:       89 04 24                mov    %eax,(%esp)
   5ac32:       e8 95 46 00 00          call   5f2cc <_objc_msgSend$stub>
   5ac37:       0f be 57 18             movsbl 0x18(%edi),%edx
   5ac3b:       8b 8e 94 6a 04 00       mov    0x46a94(%esi),%ecx
   5ac41:       89 54 24 08             mov    %edx,0x8(%esp)
   5ac45:       89 4c 24 04             mov    %ecx,0x4(%esp)
   5ac49:       89 04 24                mov    %eax,(%esp)
   5ac4c:       e8 7b 46 00 00          call   5f2cc <_objc_msgSend$stub>
   5ac51:       83 c4 10                add    $0x10,%esp
   5ac54:       5e                      pop    %esi
   5ac55:       5f                      pop    %edi
   5ac56:       5d                      pop    %ebp
   5ac57:       c3                      ret    
   5ac58:       0f 1f 84 00 00 00 00    nopl   0x0(%eax,%eax,1)
   5ac5f:       00

I won’t go into detail about how to read the disassembly, but hopefully you’ll be able to see that at 5abf4 we’re running a block of code on what seems to be the UIWebView’s dedicated web thread (_WebThreadRun$stub). The block itself seems to be making 3 consecutive Objective-C method calls (_objc_msgSend$stub) at 5ac20, 5ac32 & 5ac4c.

By running the previous snippet where we were linking against the private iAdCore framework and using ADWebView, we can set a symbolic breakpoint on ___29-[ADWebView setWebGLEnabled:]_block_invoke_0 and see what the values of the $eax & $ecx registers are prior to calling objc_msgSend. Look at the i386 ABI for objc_msgSend(), and you’ll note that these registers correspond to the receiver ($eax) of the message and the selector ($ecx) being called.

Stepping through, we see:

  • At 5ac20, $eax is an instance of ADWebView, and $ecx is _browserView.
  • At 5ac32, $eax is an instance of UIWebDocumentView and $ecx is webView.
  • At 5ac4c, $eax is an instance of WebView and $ecx is _setWebGLEnabled:.

From this, we can piece together our re-implementation of -[ADWebView setWebGLEnabled:]:

Re-implementation of -[ADWebView setWebGLEnabled:]
1
2
3
4
5
- (void)setWebGLEnabled:(BOOL)enableWebGL {
  UIWebDocumentView* webDocumentView = [self _browserView];
  WebView* backingWebView = [webDocumentView webView];
  [backingWebView _setWebGLEnabled:enableWebGL];
}

I’ve put together an Xcode project demoing the results of this:

Clone UIWebViewWebGL on Github

Summing Up

Apple are clearly working towards supporting WebGL in a more general sense, as can be seen in their support for it in iAds.

Instead of whinging about it only being available in iAds, I’ve demonstrated that it’s possible to take advantage of WebGL using standard embedded web views (albeit using private APIs).

This will let developers track the progress of Apple’s commitment to WebGL, and skate to where the puck is going to be.

I’m really looking forward to seeing what you all come up with!

Discuss on Hacker News »