With Zombie Track Meat (ZTM) fresh on the Chrome Web Store, we’d like to do what most developers, including ourselves, forget to do. The POSTMORTEM... What follows is a Tech Postmortem regarding Native Client from the perspective of an iOS Developer. I’ll save the more meta Game Design stuff for a future post.
History
Fuzzycube Software is made up of two people, Jeff Ruediger and Pete Parisi. Separately, we've been making games since 1995. We’ve worked together since 2001 at prior companies such as Ensemble Studios - Microsoft (Age of Mythology series, Age of Empires 3, Halo Wars), and Apple (REDACTED). This has allowed us to work with many platforms together such as Xbox360, PC, OSX, iOS, and now Chrome.
Why Native Client and the Google Stack
I’ve been asked this question several times recently, “Why go with Native Client?” Firstly Native Client (NaCl) is a technology that allows you to run C++ code in a webpage without the need for a user to download a plugin. Which serves two great purposes: Firstly, rather than being limited in users to the number of people who’ve bought specific hardware, you have the entire internet as your potential user base. For a small independent studio, it’s crucial for us to reach the maximum number of users possible. Secondly, there’s been lots of studies to show that plugins just don’t work on the web; they are a barrier to entry that often times scare users to moving on to other content. The fact that NaCl doesn’t require a plugin means one less barrier to play the game.
Since Fuzzycube started, we’ve made two of our three games for the iOS, Panda Picnic and iQuarterback 2, all written in Obj-C. Moving to NaCl required us to switch over to C++. With NaCl, we can now use the same C++ code base to ship our products to Desktop, Mobile, and now the Web. Without NaCl, there was no one language that we could use to move our products between platforms, as such, porting our products will take significantly more time to do. Obj-C is fantastic for what is does on iOS / OSX, but native code, in my opinion, isn’t going to go away anytime soon. Whether you view native code as the weed you can’t kill or as the midichlorians that power the net, it’s here to stay as long as the current generation of CS students are being taught C/C++.
Zombie Track Meat (ZTM) uses Cocos2D-x, a popular 2D rendering library. The server backend for Leaderboards, Player savegames, etc uses PHP running off of DreamHost. We didn’t set out to use much of the Google Stack, but once we started looking around, we were amazed about what technology Google had available for game development. We ended up using Google Analytics for tracking of our website visits, game stats etc; Google Wallet for IAP (which was easier to get started than we had thought), and Google+ for a social graph. Can anyone spot the common trend here? These products are all offered by Google FREE of charge to us as developers; No upfront cost, no per use cost, and as a small independent studio, that’s really important for us. The one service we ended up not using was Google App Engine, primarily due to hosting costs.
I’ve been asked this question several times recently, “Why go with Native Client?” Firstly Native Client (NaCl) is a technology that allows you to run C++ code in a webpage without the need for a user to download a plugin. Which serves two great purposes: Firstly, rather than being limited in users to the number of people who’ve bought specific hardware, you have the entire internet as your potential user base. For a small independent studio, it’s crucial for us to reach the maximum number of users possible. Secondly, there’s been lots of studies to show that plugins just don’t work on the web; they are a barrier to entry that often times scare users to moving on to other content. The fact that NaCl doesn’t require a plugin means one less barrier to play the game.
Since Fuzzycube started, we’ve made two of our three games for the iOS, Panda Picnic and iQuarterback 2, all written in Obj-C. Moving to NaCl required us to switch over to C++. With NaCl, we can now use the same C++ code base to ship our products to Desktop, Mobile, and now the Web. Without NaCl, there was no one language that we could use to move our products between platforms, as such, porting our products will take significantly more time to do. Obj-C is fantastic for what is does on iOS / OSX, but native code, in my opinion, isn’t going to go away anytime soon. Whether you view native code as the weed you can’t kill or as the midichlorians that power the net, it’s here to stay as long as the current generation of CS students are being taught C/C++.
Zombie Track Meat (ZTM) uses Cocos2D-x, a popular 2D rendering library. The server backend for Leaderboards, Player savegames, etc uses PHP running off of DreamHost. We didn’t set out to use much of the Google Stack, but once we started looking around, we were amazed about what technology Google had available for game development. We ended up using Google Analytics for tracking of our website visits, game stats etc; Google Wallet for IAP (which was easier to get started than we had thought), and Google+ for a social graph. Can anyone spot the common trend here? These products are all offered by Google FREE of charge to us as developers; No upfront cost, no per use cost, and as a small independent studio, that’s really important for us. The one service we ended up not using was Google App Engine, primarily due to hosting costs.
Before we dive into the post-mortem of the technical notes, it’s worth setting up a bit of a mental model of how the port went. Porting to NaCl is similar to porting to other platforms, but with a few nuances.
Firstly, since NaCl is a cross platform technology, we couldn’t rely on any OS specific APIs being available or working correctly. To make up for that, Chrome provides a set of APIs that grant access to the platform, called Pepper. NaCl has full support for these Pepper APIs, and you can use them to read files, play sounds, and draw 3D graphics. We added the NaCl platform to Cocos2D-x similar to how it supports iOS, Win32, etc.
Secondly, and the hardest part about the port, was that all Pepper calls are required to come from the main thread. Which, in itself, is not that big of an issue, but when you couple it against the fact that all Pepper APIs are asynchronous, now you get into problems. For example, the FileIO apis in Pepper are non-blocking, which meant we couldn’t do a batch-replace of fread calls to pepper::fread calls, because the logic flow didn’t work the same way.
Firstly, since NaCl is a cross platform technology, we couldn’t rely on any OS specific APIs being available or working correctly. To make up for that, Chrome provides a set of APIs that grant access to the platform, called Pepper. NaCl has full support for these Pepper APIs, and you can use them to read files, play sounds, and draw 3D graphics. We added the NaCl platform to Cocos2D-x similar to how it supports iOS, Win32, etc.
Secondly, and the hardest part about the port, was that all Pepper calls are required to come from the main thread. Which, in itself, is not that big of an issue, but when you couple it against the fact that all Pepper APIs are asynchronous, now you get into problems. For example, the FileIO apis in Pepper are non-blocking, which meant we couldn’t do a batch-replace of fread calls to pepper::fread calls, because the logic flow didn’t work the same way.
Thirdly, we couldn’t run any blocking functions on the main thread. This was where things got really tricky. As it turns out, the main thread is shared between NaCl and a whole bunch of other Chrome processing frameworks, (like V8, the chrome javascript engine). So this meant we couldn’t do any spinlooping, waiting, or blocking on the main thread, as it would stall out the page..
Having these limitations in mind, it’ll make more sense when we discuss the issues below. For more technical details, we recommend you watch this great Introductory video from GDC on how to port to native client.
What went wrong?
- Cocos2D-x as the engine choice.
Because our previous titles were built using pure Obj-C, we needed a new client rendering engine for our NaCl project. When we started we identified 3-4 possible engines that we could use (Unity, Moai, BlitzTech, Cocos2D-x, Ogre), and evaluated them against building our own. Our decision was made by stack ranking based on familiarity, prior output created by the engine, and maturity with respect to NaCl. Unity and Cocos2D-x won out our nearly every category. When we started, Moai, Blitztech, and Ogre were “too early” on their solutions for NaCl, while Unity hadn’t gone public with their 3.5 release with NaCl support. That pretty much left Cocos2D-x or build your own.
Rather than rolling our own engine, we choose Cocos2D-x and off we went. As it stands today, Moai is much more mature and Unity is public with their NaCl support. Unity wins hand down because of the tool chain they provide.
Day to day life in Cocos2D-x was familiar to use since we used the Obj-C version on Panda Picnic. It quickly became clear that the architecture of the engine is not well suited to the needs of NaCl. You can not use the result of an fread call on the next line. Cocos2D-x breaks in every place it does File I/O. Cocos2D-x is more a downstream engine. As in, we will load or do that work whenever you need it downstream versus a more console centric strategy that pre-allocates and doesn’t do File I/O except in very explicit cases. We solved this by doing a few things:
- Run all game logic on a worker thread. The Main thread is reserved for all NaCl calls.
- Spin wait the worker thread after a File I/O call is sent to the main thread.
- Preload blocks of files in certain places so we don’t spin wait during gameplay.
- No Debugger in an IDE.
Hello Printf, I have missed thee. I last saw you when I was using the Java for the first time (1997) and I ignored you during the Visual Studio and Xcode days. I’m sorry. Forgive me? One of the largest problems with our development of NaCl was the lack of a decent debugger, and secondly by lack of integration with our development IDEs. Writing a NaCl game without modern debuggers inside IDEs is painful at best. It’s a showstopper for many people. While creating ZTM, the best we got was a delicate dance of attaching gdb in a terminal window on a 64bit machine running Windows 7. In order to avoid this issue, we had to keep a windows build working at all times so we could do the bulk of our development there, and then do weekly checks against the NaCl build for performance and bugs. So although we were developing for NaCl directly, we had to treat it like a port due to lack of good debugging and IDE integration.
- No support for Native Profiling tools
The tagllne reads, “Take your existing native code and put it on the web.” There is no mention of getting to use your existing performance tools (Instruments, VTune, PIX, etc). Google’s solution is to put all tools into the Chrome browser. It would be really nice to be able to profile in your tool of choice instead of having to learn the intricacies of a new profiling tool. As a shim, we profiled in PIX on the PC by linking our Windows build against the ANGLE dlls. For reference, ANGLE is a software that converts GLES2.0 commands to DX9 commands. Chrome uses this technology on windows to get around the lack of good OpenGL driver support.
- Toolchain is not yet ready for prime time.
The NaCl SDK does not come with Visual Studio or Xcode projects that you can open/build/run. When we started the build process used Scons and today it has moved on to MAKE files. I don’t want to sound like a whiner, but..<checks year> its 2012. Our solution was to make a custom Xcode project that ran buildGame.sh which called Scons. This allowed for Xcode to catch and report errors so that clicking on the error took you to the file and line. There are some other similar type solutions for Visual Studio as well.
What went right?
- Cocos2D-x as the engine choice.
This is listed in both right and wrong. Here is why it was a good choice for us. Its a simple, easy to learn engine that comes with source. If you want to make a change, it’s easy to do. You aren’t stuck behind a product roadmap that you don’t control. Also, the developer community is strong which helps when you need reference material or need to hire someone. It allowed for cross platform development at the same, which leads us to the next point.
What went right?
- Cocos2D-x as the engine choice.
This is listed in both right and wrong. Here is why it was a good choice for us. Its a simple, easy to learn engine that comes with source. If you want to make a change, it’s easy to do. You aren’t stuck behind a product roadmap that you don’t control. Also, the developer community is strong which helps when you need reference material or need to hire someone. It allowed for cross platform development at the same, which leads us to the next point.
- Cross Platform Development
We developed ZTM on Native Client, OSX, Win32, and iOS at the same time. One programmer would be building Native Client, another would be building Win32, and another would be on iOS. What we found is that each platform will let certain bugs slip through unnoticed while others are unforgiving. For example, we were leaking memory on exit. iOS and Native Client don’t report it, but Visual Studio on win32 would constantly nag you to fix it. Accessing freed memory on iOS would routinely work(!), while Native Client and Win32 would crater. Ultimately, this led us to making a much better overall product than if we only developed for one platform. Having reference builds on Win32 and iOS to compare Native Client against was a huge win. Again, having C++ as a pivot language was crucial for us, without it, we wouldn’t be able to co-develop across all these platforms.
We developed ZTM on Native Client, OSX, Win32, and iOS at the same time. One programmer would be building Native Client, another would be building Win32, and another would be on iOS. What we found is that each platform will let certain bugs slip through unnoticed while others are unforgiving. For example, we were leaking memory on exit. iOS and Native Client don’t report it, but Visual Studio on win32 would constantly nag you to fix it. Accessing freed memory on iOS would routinely work(!), while Native Client and Win32 would crater. Ultimately, this led us to making a much better overall product than if we only developed for one platform. Having reference builds on Win32 and iOS to compare Native Client against was a huge win. Again, having C++ as a pivot language was crucial for us, without it, we wouldn’t be able to co-develop across all these platforms.
- Rapid Development
Our artist, Pete, works primarily on a PC while I thoroughly enjoy using the PC as foot rest and prefer my MacBook Air. Having a non-coder on a different platform wasn’t a hindrance at all. I could make a Native Client build, zip it up, throw it on Dropbox and in seconds Pete can be playing inside Chrome. While the game is running, we can hot swap loose textures, sounds, and data files by refreshing the browser tab like you would do with a web page. Its so seductively fast to iterate on content in Native Client.
- Crunching the Textures
Native Client supports DXT texture compression for formats DXT1, DXT3, DXT5. This is fantastic because the DXTn format has had the benefit of lots of work over the years. Since we are a web game, the size of our assets was crucial, we didn’t want users to wait any longer than they have to in order to play our game. Being a 2D game, texture footprint was our largest memory problem. Colt McAnlis, Developer Advocate at Google was really helpful on this one, and worked to make a custom texture compression format for our project (you can check out the results of his work here). As the video mentions, there was another great piece of software that landed in just enough time for us to use, called CRUNCH ; it was similar to the work Google had done, but with much better performance. Crunch ended up being crucial for our texture footprint. Standard DXT5 compression would take a 4kx4k texture from 8mb down to 1.2mb, Crunch however, will take that down further to ~400KB. At runtime, we load the crunch’d texture and transcode it into memory that we feed directly to OpenGL ES as DXTn. The transcode is super fast (~256 metatexels / second) so we get the benefit of having a smaller build plus fast texture loading.
Summation
Overall, we are very happy with the Native Client technology. The rapid development is VERY nice for fast iteration. The tech allows us to easily reach a market that was previously segmented in silos like Steam and the Mac App Store. Sure, the tech has its issues, but I firmly believe these are early adopter pain points that get solved. The people at Google have told us that they are working on addressing all of these issues.
Engine wise, if you don’t want to build your own and don’t need source access, I would recommend Unity’s solution. If you would like source, look at Moai then Cocos2D-x. Moai’s solution has been vetted by shipping titles on several platforms and is a valid player in the open source social / mobile engine space.
Our artist, Pete, works primarily on a PC while I thoroughly enjoy using the PC as foot rest and prefer my MacBook Air. Having a non-coder on a different platform wasn’t a hindrance at all. I could make a Native Client build, zip it up, throw it on Dropbox and in seconds Pete can be playing inside Chrome. While the game is running, we can hot swap loose textures, sounds, and data files by refreshing the browser tab like you would do with a web page. Its so seductively fast to iterate on content in Native Client.
- Crunching the Textures
Native Client supports DXT texture compression for formats DXT1, DXT3, DXT5. This is fantastic because the DXTn format has had the benefit of lots of work over the years. Since we are a web game, the size of our assets was crucial, we didn’t want users to wait any longer than they have to in order to play our game. Being a 2D game, texture footprint was our largest memory problem. Colt McAnlis, Developer Advocate at Google was really helpful on this one, and worked to make a custom texture compression format for our project (you can check out the results of his work here). As the video mentions, there was another great piece of software that landed in just enough time for us to use, called CRUNCH ; it was similar to the work Google had done, but with much better performance. Crunch ended up being crucial for our texture footprint. Standard DXT5 compression would take a 4kx4k texture from 8mb down to 1.2mb, Crunch however, will take that down further to ~400KB. At runtime, we load the crunch’d texture and transcode it into memory that we feed directly to OpenGL ES as DXTn. The transcode is super fast (~256 metatexels / second) so we get the benefit of having a smaller build plus fast texture loading.
Summation
Overall, we are very happy with the Native Client technology. The rapid development is VERY nice for fast iteration. The tech allows us to easily reach a market that was previously segmented in silos like Steam and the Mac App Store. Sure, the tech has its issues, but I firmly believe these are early adopter pain points that get solved. The people at Google have told us that they are working on addressing all of these issues.
Engine wise, if you don’t want to build your own and don’t need source access, I would recommend Unity’s solution. If you would like source, look at Moai then Cocos2D-x. Moai’s solution has been vetted by shipping titles on several platforms and is a valid player in the open source social / mobile engine space.
Go try out the game now on the Chrome Web Store
Related Links
- Zombie Track Meat
- fuzzycubesoftware.com
- gonacl.com
- mainroach.blogspot.com
- cocosx-2d
- crunch texture compression
- Moai
- Unity
Fuzzycube Software is an independent game studio based in Silicon Valley. Before starting Fuzzycube the founders previously worked at Apple, have presented at WWDC and helped develop AAA games such as Age of Mythology, Age of Empires 3, and Halo Wars at Ensemble Studios. Fuzzycube is dedicated to creating awesome, fun, and polished apps for the mobile platform. Inquiries can be made to Jeff Ruediger
Add a comment